瀏覽代碼

feat:1、实现空节点功能,当文档标记为空目录时不加载内容页无法编辑内容
2、实现HTTP接口方式登录,
3、优化markdown编辑器部分功能

lifei6671 6 年之前
父節點
當前提交
b510a45908

+ 1 - 1
controllers/AccountController.go

@@ -107,7 +107,7 @@ func (c *AccountController) Login() {
 		member, err := models.NewMember().Login(account, password)
 		if err == nil {
 			member.LastLoginTime = time.Now()
-			member.Update()
+			_ = member.Update("last_login_time")
 
 			c.SetMember(*member)
 

+ 1 - 8
controllers/BookController.go

@@ -753,14 +753,7 @@ func (c *BookController) Release() {
 		}
 		bookId = book.BookId
 	}
-	go func(identify string) {
-		models.NewBook().ReleaseContent(bookId)
-
-		//当文档发布后,需要删除已缓存的转换项目
-		outputPath := filepath.Join(conf.GetExportOutputPath(), strconv.Itoa(bookId))
-		_ = os.RemoveAll(outputPath)
-
-	}(identify)
+	go models.NewBook().ReleaseContent(bookId)
 
 	c.JsonResult(0, "发布任务已推送到任务队列,稍后将在后台执行。")
 }

+ 5 - 0
controllers/DocumentController.go

@@ -203,6 +203,7 @@ func (c *DocumentController) Edit() {
 				beego.Error("查询项目时出错 -> ", err)
 				c.ShowErrorPage(500, "查询项目时出错")
 			}
+			return
 		}
 		if bookResult.RoleId == conf.BookObserver {
 			c.JsonResult(6002, "项目不存在或权限不足")
@@ -318,6 +319,8 @@ func (c *DocumentController) Create() {
 
 	if isOpen == 1 {
 		document.IsOpen = 1
+	} else if isOpen == 2 {
+		document.IsOpen = 2
 	} else {
 		document.IsOpen = 0
 	}
@@ -742,6 +745,7 @@ func (c *DocumentController) Content() {
 
 		doc.Version = time.Now().Unix()
 		doc.Content = content
+		doc.ModifyAt = c.Member.MemberId
 
 		if err := doc.InsertOrUpdate(); err != nil {
 			beego.Error("InsertOrUpdate => ", err)
@@ -775,6 +779,7 @@ func (c *DocumentController) Content() {
 	doc, err := models.NewDocument().Find(docId)
 	if err != nil {
 		c.JsonResult(6003, "文档不存在")
+		return
 	}
 
 	attach, err := models.NewAttachment().FindListByDocumentId(doc.DocumentId)

+ 0 - 1
main.go

@@ -7,7 +7,6 @@ import (
 	_ "github.com/astaxie/beego/session/memcache"
 	_ "github.com/astaxie/beego/session/mysql"
 	_ "github.com/astaxie/beego/session/redis"
-	_ "github.com/go-sql-driver/mysql"
 	"github.com/kardianos/service"
 	"github.com/lifei6671/mindoc/commands"
 	"github.com/lifei6671/mindoc/commands/daemon"

+ 35 - 16
models/BookModel.go

@@ -11,28 +11,32 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 
+	"encoding/json"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/logs"
 	"github.com/astaxie/beego/orm"
 	"github.com/lifei6671/mindoc/conf"
+	"github.com/lifei6671/mindoc/utils"
 	"github.com/lifei6671/mindoc/utils/cryptil"
 	"github.com/lifei6671/mindoc/utils/filetil"
 	"github.com/lifei6671/mindoc/utils/requests"
 	"github.com/lifei6671/mindoc/utils/ziptil"
 	"gopkg.in/russross/blackfriday.v2"
-	"encoding/json"
-	"github.com/lifei6671/mindoc/utils"
 )
 
+var releaseQueue = make(chan int, 500)
+var once = sync.Once{}
+
 // Book struct .
 type Book struct {
 	BookId int `orm:"pk;auto;unique;column(book_id)" json:"book_id"`
 	// BookName 项目名称.
 	BookName string `orm:"column(book_name);size(500)" json:"book_name"`
 	//所属项目空间
-	ItemId   int    `orm:"column(item_id);type(int);default(1)" json:"item_id"`
+	ItemId int `orm:"column(item_id);type(int);default(1)" json:"item_id"`
 	// Identify 项目唯一标识.
 	Identify string `orm:"column(identify);size(100);unique" json:"identify"`
 	//是否是自动发布 0 否/1 是
@@ -375,7 +379,7 @@ ORDER BY book.order_index, book.book_id DESC limit ?,?`
 	}
 	sql := "SELECT m.account,doc.modify_time FROM md_documents AS doc LEFT JOIN md_members AS m ON doc.modify_at=m.member_id WHERE book_id = ? LIMIT 1 ORDER BY doc.modify_time DESC"
 
-	if err == nil && len(books) > 0 {
+	if len(books) > 0 {
 		for index, book := range books {
 			var text struct {
 				Account    string
@@ -580,22 +584,37 @@ WHERE (relationship_id > 0 OR book.privately_owned = 0 or team.team_member_id >
 	}
 }
 
-//发布文档
+// ReleaseContent 批量发布文档
 func (book *Book) ReleaseContent(bookId int) {
+	releaseQueue <- bookId
+	once.Do(func() {
+		go func() {
+			defer func() {
+				if err := recover(); err != nil {
+					beego.Error("协程崩溃 ->", err)
+				}
+			}()
+			for bookId := range releaseQueue {
+				o := orm.NewOrm()
 
-	o := orm.NewOrm()
+				var docs []*Document
+				_, err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id", bookId).All(&docs)
 
-	var docs []*Document
-	_, err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id", bookId).All(&docs)
+				if err != nil {
+					beego.Error("发布失败 =>", bookId, err)
+					continue
+				}
+				for _, item := range docs {
+					item.BookId = bookId
+					_ = item.ReleaseContent()
+				}
 
-	if err != nil {
-		beego.Error("发布失败 =>", bookId, err)
-		return
-	}
-	for _, item := range docs {
-		item.BookId = bookId
-		_ = item.ReleaseContent()
-	}
+				//当文档发布后,需要删除已缓存的转换项目
+				outputPath := filepath.Join(conf.GetExportOutputPath(), strconv.Itoa(bookId))
+				_ = os.RemoveAll(outputPath)
+			}
+		}()
+	})
 }
 
 //重置文档数量

+ 15 - 3
models/DocumentModel.go

@@ -38,7 +38,7 @@ type Document struct {
 	ModifyTime time.Time `orm:"column(modify_time);type(datetime);auto_now" json:"modify_time"`
 	ModifyAt   int       `orm:"column(modify_at);type(int)" json:"-"`
 	Version    int64     `orm:"column(version);type(bigint);" json:"version"`
-	//是否展开子目录:0 否/1 是
+	//是否展开子目录:0 否/1 是 /2 空间节点,单击时展开下一级
 	IsOpen     int           `orm:"column(is_open);type(int);default(0)" json:"is_open"`
 	AttachList []*Attachment `orm:"-" json:"attach"`
 }
@@ -301,7 +301,18 @@ func (item *Document) Processor() *Document {
 			if selector := docQuery.Find("div.wiki-bottom").First(); selector.Size() <= 0 && item.MemberId > 0 {
 				//处理文档结尾信息
 				docCreator, err := NewMember().Find(item.MemberId, "real_name", "account")
-				release := "<div class=\"wiki-bottom\">文档更新时间: " + item.ModifyTime.Local().Format("2006-01-02 15:04") + " &nbsp;&nbsp;作者:"
+				release := "<div class=\"wiki-bottom\">"
+				if item.ModifyAt > 0 {
+					docModify, err := NewMember().Find(item.ModifyAt, "real_name", "account")
+					if err == nil {
+						if docModify.RealName != "" {
+							release += "最后编辑: " + docModify.RealName + " &nbsp;"
+						} else {
+							release += "最后编辑: " + docModify.Account + " &nbsp;"
+						}
+					}
+				}
+				release += "文档更新时间: " + item.ModifyTime.Local().Format("2006-01-02 15:04") + " &nbsp;&nbsp;作者:"
 				if err == nil && docCreator != nil {
 					if docCreator.RealName != "" {
 						release += docCreator.RealName
@@ -324,7 +335,7 @@ func (item *Document) Processor() *Document {
 				if src, ok := selection.Attr("src"); ok {
 					src = strings.TrimSpace(strings.ToLower(src))
 					//过滤掉没有链接的图片标签
-					if src == "" || strings.HasPrefix(src,"data:text/html"){
+					if src == "" || strings.HasPrefix(src, "data:text/html") {
 						selection.Remove()
 						return
 					}
@@ -348,6 +359,7 @@ func (item *Document) Processor() *Document {
 					//移除危险脚本链接
 					if strings.HasPrefix(val, "data:text/html") ||
 						strings.HasPrefix(val, "vbscript:") ||
+						strings.HasPrefix(val, "&#106;avascript:") ||
 						strings.HasPrefix(val, "javascript:") {
 						selection.SetAttr("href", "#")
 					}

+ 32 - 20
models/DocumentTree.go

@@ -2,26 +2,27 @@ package models
 
 import (
 	"bytes"
-	"html/template"
-	"math"
+	"fmt"
 	"github.com/astaxie/beego/orm"
 	"github.com/lifei6671/mindoc/conf"
-	"fmt"
+	"html/template"
+	"math"
 )
 
 type DocumentTree struct {
-	DocumentId   int               `json:"id"`
-	DocumentName string            `json:"text"`
-	ParentId     interface{}       `json:"parent"`
-	Identify     string            `json:"identify"`
-	BookIdentify string            `json:"-"`
-	Version      int64             `json:"version"`
-	State        *DocumentSelected `json:"-"`
-	AAttrs		 map[string]interface{}		`json:"a_attr"`
+	DocumentId   int                    `json:"id"`
+	DocumentName string                 `json:"text"`
+	ParentId     interface{}            `json:"parent"`
+	Identify     string                 `json:"identify"`
+	BookIdentify string                 `json:"-"`
+	Version      int64                  `json:"version"`
+	State        *DocumentSelected      `json:"-"`
+	AAttrs       map[string]interface{} `json:"a_attr"`
 }
 type DocumentSelected struct {
 	Selected bool `json:"selected"`
 	Opened   bool `json:"opened"`
+	Disabled bool `json:"disabled"`
 }
 
 //获取项目的文档树状结构
@@ -32,7 +33,10 @@ func (item *Document) FindDocumentTree(bookId int) ([]*DocumentTree, error) {
 
 	var docs []*Document
 
-	count, err := o.QueryTable(item).Filter("book_id", bookId).OrderBy("order_sort", "document_id").Limit(math.MaxInt32).All(&docs, "document_id", "version", "document_name", "parent_id", "identify","is_open")
+	count, err := o.QueryTable(item).Filter("book_id", bookId).
+		OrderBy("order_sort", "document_id").
+		Limit(math.MaxInt32).
+		All(&docs, "document_id", "version", "document_name", "parent_id", "identify", "is_open")
 
 	if err != nil {
 		return trees, err
@@ -42,13 +46,19 @@ func (item *Document) FindDocumentTree(bookId int) ([]*DocumentTree, error) {
 	trees = make([]*DocumentTree, count)
 
 	for index, item := range docs {
-		tree := &DocumentTree{}
+		tree := &DocumentTree{
+			AAttrs: map[string]interface{}{"is_open": false, "opened": 0},
+		}
 		if index == 0 {
 			tree.State = &DocumentSelected{Selected: true, Opened: true}
-			tree.AAttrs = map[string]interface{}{ "is_open": true}
-		}else if item.IsOpen == 1 {
+			tree.AAttrs = map[string]interface{}{"is_open": true, "opened": 1}
+		} else if item.IsOpen == 1 {
 			tree.State = &DocumentSelected{Selected: false, Opened: true}
-			tree.AAttrs = map[string]interface{}{ "is_open": true}
+			tree.AAttrs = map[string]interface{}{"is_open": true, "opened": 1}
+		}
+		if item.IsOpen == 2 {
+			tree.State = &DocumentSelected{Selected: false, Opened: false, Disabled: true}
+			tree.AAttrs = map[string]interface{}{"disabled": true, "opened": 2}
 		}
 		tree.DocumentId = item.DocumentId
 		tree.Identify = item.Identify
@@ -115,7 +125,7 @@ func getDocumentTree(array []*DocumentTree, parentId int, selectedId int, select
 			if item.DocumentId == selectedParentId || (item.State != nil && item.State.Opened) {
 				selectedLi = ` class="jstree-open"`
 			}
-			buf.WriteString(fmt.Sprintf("<li id=\"%d\"%s><a href=\"",item.DocumentId,selectedLi))
+			buf.WriteString(fmt.Sprintf("<li id=\"%d\"%s><a href=\"", item.DocumentId, selectedLi))
 			if item.Identify != "" {
 				uri := conf.URLFor("DocumentController.Read", ":key", item.BookIdentify, ":id", item.Identify)
 				buf.WriteString(uri)
@@ -123,9 +133,11 @@ func getDocumentTree(array []*DocumentTree, parentId int, selectedId int, select
 				uri := conf.URLFor("DocumentController.Read", ":key", item.BookIdentify, ":id", item.DocumentId)
 				buf.WriteString(uri)
 			}
-			buf.WriteString(fmt.Sprintf("\" title=\"%s\"",template.HTMLEscapeString(item.DocumentName)))
-			buf.WriteString(fmt.Sprintf(" data-version=\"%d\"%s>%s</a>",item.Version,selected,template.HTMLEscapeString(item.DocumentName)))
-
+			buf.WriteString(fmt.Sprintf("\" title=\"%s\"", template.HTMLEscapeString(item.DocumentName)))
+			if item.State != nil && item.State.Disabled {
+				buf.WriteString(" disabled=\"true\"")
+			}
+			buf.WriteString(fmt.Sprintf(" data-version=\"%d\"%s>%s</a>", item.Version, selected, template.HTMLEscapeString(item.DocumentName)))
 
 			for _, sub := range array {
 				if p, ok := sub.ParentId.(int); ok && p == item.DocumentId {

+ 82 - 7
models/Member.go

@@ -2,8 +2,12 @@
 package models
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
 	"regexp"
 	"strings"
 	"time"
@@ -67,8 +71,11 @@ func (m *Member) Login(account string, password string) (*Member, error) {
 
 	if err != nil {
 		if beego.AppConfig.DefaultBool("ldap_enable", false) == true {
-			logs.Info("转入LDAP登陆")
+			logs.Info("转入LDAP登陆 ->", account)
 			return member.ldapLogin(account, password)
+		} else if beego.AppConfig.String("http_login_url") != "" {
+			logs.Info("转入 HTTP 接口登陆 ->", account)
+			return member.httpLogin(account, password)
 		} else {
 			logs.Error("用户登录 ->", err)
 			return member, ErrMemberNoExist
@@ -85,6 +92,8 @@ func (m *Member) Login(account string, password string) (*Member, error) {
 		}
 	case "ldap":
 		return member.ldapLogin(account, password)
+	case "http":
+		return member.httpLogin(account, password)
 	default:
 		return member, ErrMemberAuthMethodInvalid
 	}
@@ -131,7 +140,7 @@ func (m *Member) ldapLogin(account string, password string) (*Member, error) {
 		beego.Error("绑定 LDAP 用户失败 ->", err)
 		return m, ErrorMemberPasswordError
 	}
-	if m.Account == "" {
+	if m.MemberId <= 0 {
 		m.Account = account
 		m.Email = searchResult.Entries[0].GetAttributeValue("mail")
 		m.AuthMethod = "ldap"
@@ -149,6 +158,73 @@ func (m *Member) ldapLogin(account string, password string) (*Member, error) {
 	return m, nil
 }
 
+func (m *Member) httpLogin(account, password string) (*Member, error) {
+	urlStr := beego.AppConfig.String("http_login_url")
+	if urlStr == "" {
+		return nil, ErrMemberAuthMethodInvalid
+	}
+
+	val := url.Values{"": []string{""}}
+	resp, err := http.PostForm(urlStr, val)
+	if err != nil {
+		beego.Error("通过接口登录失败 -> ", urlStr, account, err)
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		beego.Error("读取接口返回值失败 -> ", urlStr, account, err)
+		return nil, err
+	}
+	beego.Info("HTTP 登录接口返回数据 ->", string(body))
+
+	var result map[string]interface{}
+
+	if err := json.Unmarshal(body, &result); err != nil {
+		beego.Error("解析接口返回值失败 -> ", urlStr, account, string(body))
+		return nil, errors.New("解析接口返回值失败")
+	}
+
+	if code, ok := result["errcode"]; !ok || code.(float64) != 200 {
+
+		if msg, ok := result["message"]; ok {
+			return nil, errors.New(msg.(string))
+		}
+		return nil, errors.New("接口返回值格式不正确")
+	}
+	if m.MemberId <= 0 {
+		member := NewMember()
+
+		if email, ok := result["email"]; !ok || email == "" {
+			return nil, errors.New("接口返回的数据缺少邮箱字段")
+		} else {
+			member.Email = email.(string)
+		}
+
+		if avatar, ok := result["avater"]; ok && avatar != "" {
+			member.Avatar = avatar.(string)
+		} else {
+			member.Avatar = conf.URLForWithCdnImage("/static/images/headimgurl.jpg")
+		}
+		if realName, ok := result["real_name"]; ok && realName != "" {
+			member.RealName = realName.(string)
+		}
+		member.Account = account
+		member.Password = password
+		member.AuthMethod = "http"
+		member.Role = conf.SystemRole(beego.AppConfig.DefaultInt("ldap_user_role", 2))
+		member.CreateTime = time.Now()
+		if err := member.Add(); err != nil {
+			beego.Error("自动注册用户错误", err)
+			return m, ErrorMemberPasswordError
+		}
+		member.ResolveRoleName()
+		*m = *member
+	}
+	return m, nil
+}
+
 // Add 添加一个用户.
 func (m *Member) Add() error {
 	o := orm.NewOrm()
@@ -219,7 +295,6 @@ func (m *Member) Find(id int, cols ...string) (*Member, error) {
 	return m, nil
 }
 
-
 func (m *Member) ResolveRoleName() {
 	if m.Role == conf.MemberSuperRole {
 		m.RoleName = "超级管理员"
@@ -283,11 +358,11 @@ func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member, int, error) {
 	return members, int(totalCount), nil
 }
 
-func (c *Member) IsAdministrator() bool {
-	if c == nil || c.MemberId <= 0 {
+func (m *Member) IsAdministrator() bool {
+	if m == nil || m.MemberId <= 0 {
 		return false
 	}
-	return c.Role == 0 || c.Role == 1
+	return m.Role == 0 || m.Role == 1
 }
 
 //根据指定字段查找用户.
@@ -425,7 +500,7 @@ func (m *Member) Delete(oldId int, newId int) error {
 		o.Rollback()
 		return err
 	}
-	_,err = o.QueryTable(NewTeamMember()).Filter("member_id",oldId).Delete()
+	_, err = o.QueryTable(NewTeamMember()).Filter("member_id", oldId).Delete()
 
 	if err != nil {
 		o.Rollback()

+ 176 - 69
static/editor.md/editormd.js

@@ -2984,102 +2984,170 @@
         h1 : function() {
             var cm        = this.cm;
             var cursor    = cm.getCursor();
-            var selection = cm.getSelection();
-
-            if (cursor.ch !== 0)
+            var selection = cm.getLine(cursor.line);
+            var patt1=new RegExp("^#{1}[ ]");//如果存在H1
+            var patt2=new RegExp("^#{1,6}[ ]");//如果存在H2-H6
+            //如果存在H1,取消H1的设置
+            if (patt1.test(selection)===true){
+                selection=selection.replace(/^#{1}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection(selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch -2);
+            }
+            //如果存在H1-H6,取消H2-H6,并替换为H1
+            else if(patt2.test(selection)===true){
+                selection=selection.replace(/^#{1,6}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("# "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +2);
+
+            }else
+            //设置为H1
             {
-                cm.setCursor(cursor.line, 0);
-                cm.replaceSelection("# " + selection);
-                cm.setCursor(cursor.line, cursor.ch + 2);
-            }
-            else
-            {
-                cm.replaceSelection("# " + selection);
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("# "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +2);
             }
         },
 
         h2 : function() {
             var cm        = this.cm;
             var cursor    = cm.getCursor();
-            var selection = cm.getSelection();
-
-            if (cursor.ch !== 0)
-            {
-                cm.setCursor(cursor.line, 0);
-                cm.replaceSelection("## " + selection);
-                cm.setCursor(cursor.line, cursor.ch + 3);
-            }
-            else
-            {
-                cm.replaceSelection("## " + selection);
+            var selection = cm.getLine(cursor.line);
+            var patt1=new RegExp("^#{2}[ ]");//如果存在H1
+            var patt2=new RegExp("^#{1,6}[ ]");//如果存在H1-H6
+            //如果存在H2,取消H2的设置
+            if (patt1.test(selection)===true){
+                selection=selection.replace(/^#{2}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection(selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch -3);
+            }else if(patt2.test(selection)===true){
+                selection=selection.replace(/^#{1,6}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("## "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +3);
+
+            }else{
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("## "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +3);
             }
         },
 
         h3 : function() {
             var cm        = this.cm;
             var cursor    = cm.getCursor();
-            var selection = cm.getSelection();
-
-            if (cursor.ch !== 0)
+            var selection = cm.getLine(cursor.line);
+            var patt1=new RegExp("^#{3}[ ]");//如果存在H3
+            var patt2=new RegExp("^#{1,6}[ ]");//如果存在H1-H6
+            //如果存在H3,取消H3的设置
+            if (patt1.test(selection)===true){
+                selection=selection.replace(/^#{3}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection(selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch -4);
+            }
+            //如果存在H1-H6,取消H1-H6,并替换为H3
+            else if(patt2.test(selection)===true){
+                selection=selection.replace(/^#{1,6}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +4);
+
+            }else
+            //设置为H3
             {
-                cm.setCursor(cursor.line, 0);
-                cm.replaceSelection("### " + selection);
-                cm.setCursor(cursor.line, cursor.ch + 4);
-            }
-            else
-            {
-                cm.replaceSelection("### " + selection);
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +4);
             }
         },
 
         h4 : function() {
             var cm        = this.cm;
             var cursor    = cm.getCursor();
-            var selection = cm.getSelection();
-
-            if (cursor.ch !== 0)
-            {
-                cm.setCursor(cursor.line, 0);
-                cm.replaceSelection("#### " + selection);
-                cm.setCursor(cursor.line, cursor.ch + 5);
-            }
-            else
+            var selection = cm.getLine(cursor.line);
+            var patt1=new RegExp("^#{4}[ ]");//如果存在H4
+            var patt2=new RegExp("^#{1,6}[ ]");//如果存在H1-H6
+            //如果存在H4,取消H4的设置
+            if (patt1.test(selection)===true){
+                selection=selection.replace(/^#{4}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection(selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch -5);
+            }
+            //如果存在H1-H6,取消H1-H6,并替换为H4
+            else if(patt2.test(selection)===true){
+                selection=selection.replace(/^#{1,6}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("#### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +5);
+
+            }else
+            //设置为H4
             {
-                cm.replaceSelection("#### " + selection);
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("#### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +5);
             }
         },
 
         h5 : function() {
             var cm        = this.cm;
             var cursor    = cm.getCursor();
-            var selection = cm.getSelection();
-
-            if (cursor.ch !== 0)
-            {
-                cm.setCursor(cursor.line, 0);
-                cm.replaceSelection("##### " + selection);
-                cm.setCursor(cursor.line, cursor.ch + 6);
-            }
-            else
+            var selection = cm.getLine(cursor.line);
+            var patt1=new RegExp("^#{5}[ ]");//如果存在H5
+            var patt2=new RegExp("^#{1,6}[ ]");//如果存在H1-H6
+            //如果存在H5,取消H5的设置
+            if (patt1.test(selection)===true){
+                selection=selection.replace(/^#{5}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection(selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch -6);
+            }
+            //如果存在H1-H6,取消H1-H6,并替换为H5
+            else if(patt2.test(selection)===true){
+                selection=selection.replace(/^#{1,6}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("##### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +6);
+
+            }else
+            //设置为H5
             {
-                cm.replaceSelection("##### " + selection);
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("##### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +6);
             }
         },
 
         h6 : function() {
             var cm        = this.cm;
             var cursor    = cm.getCursor();
-            var selection = cm.getSelection();
-
-            if (cursor.ch !== 0)
+            var selection = cm.getLine(cursor.line);
+            var patt1=new RegExp("^#{6}[ ]");//如果存在H6
+            var patt2=new RegExp("^#{1,6}[ ]");//如果存在H1-H6
+            //如果存在H6,取消H6的设置
+            if (patt1.test(selection)===true){
+                selection=selection.replace(/^#{6}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection(selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch -7);
+            }
+            //如果存在H1-H6,取消H1-H6,并替换为H6
+            else if(patt2.test(selection)===true){
+                selection=selection.replace(/^#{1,6}[ ]/,"");
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("###### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +7);
+
+            }else
+            //设置为H6
             {
-                cm.setCursor(cursor.line, 0);
-                cm.replaceSelection("###### " + selection);
-                cm.setCursor(cursor.line, cursor.ch + 7);
-            }
-            else
-            {
-                cm.replaceSelection("###### " + selection);
+                cm.setSelection({line:cursor.line, ch:0}, {line:cursor.line+1, ch:0 });
+                cm.replaceSelection("###### "+selection+"\n");
+                cm.setCursor(cursor.line, cursor.ch +7);
             }
         },
 
@@ -3087,20 +3155,36 @@
             var cm        = this.cm;
             var cursor    = cm.getCursor();
             var selection = cm.getSelection();
-
+            var patt1=new RegExp("^-{1}[ ]");
+            var cnt =0;
+            //如果未选择,将该行设置为无序列表
             if (selection === "")
             {
+                cm.setCursor(cursor.line, 0);
                 cm.replaceSelection("- " + selection);
+                cm.setCursor(cursor.line, cursor.ch + 2);
             }
             else
             {
                 var selectionText = selection.split("\n");
-
+                //判断取中内容是否已作无序列表标记
                 for (var i = 0, len = selectionText.length; i < len; i++)
                 {
-                    selectionText[i] = (selectionText[i] === "") ? "" : "- " + selectionText[i];
+                    if (patt1.test(selectionText[i])===true ){cnt++;}
                 }
+                //如果全作了无序列表标记,取消标记
+                if(cnt===selectionText.length){
+                    for (var i = 0, len = selectionText.length; i < len; i++){
+                        selectionText[i] = (selectionText[i] === "") ? "" : selectionText[i].replace(/^-{1}[ ]/,"");
+                    }
+                }else
+                //对未打上无序列表标记,打上标记
+                {
+                    for (var i = 0, len = selectionText.length; i < len; i++){
+                        selectionText[i] = (selectionText[i] === "") ? "" : "- "+selectionText[i].replace(/^-{1}[ ]/,"");
+                    }
 
+                }
                 cm.replaceSelection(selectionText.join("\n"));
             }
         },
@@ -3109,20 +3193,36 @@
             var cm        = this.cm;
             var cursor    = cm.getCursor();
             var selection = cm.getSelection();
-
-            if(selection === "")
+            var patt1=new RegExp("^[0-9]+[\.][ ]");
+            var cnt =0;
+            //如果未选择,将该行设置为有序列表
+            if (selection === "")
             {
+                cm.setCursor(cursor.line, 0);
                 cm.replaceSelection("1. " + selection);
+                cm.setCursor(cursor.line, cursor.ch + 3);
             }
             else
             {
                 var selectionText = selection.split("\n");
-
+                //判断取中内容是否已作有序列表标记
                 for (var i = 0, len = selectionText.length; i < len; i++)
                 {
-                    selectionText[i] = (selectionText[i] === "") ? "" : (i+1) + ". " + selectionText[i];
+                    if (patt1.test(selectionText[i])===true ){cnt++;}
                 }
+                //如果全作了有序列表标记,取消标记
+                if(cnt===selectionText.length){
+                    for (var i = 0, len = selectionText.length; i < len; i++){
+                        selectionText[i] = (selectionText[i] === "") ? "" : selectionText[i].replace(/^[0-9]+[\.][ ]/,"");
+                    }
+                }else
+                //对未打上有序列表标记,打上标记
+                {
+                    for (var i = 0, len = selectionText.length; i < len; i++){
+                        selectionText[i] = (selectionText[i] === "") ? "" : (i+1)+". "+selectionText[i].replace(/^[0-9]+[\.][ ]/,"");
+                    }
 
+                }
                 cm.replaceSelection(selectionText.join("\n"));
             }
         },
@@ -4765,7 +4865,14 @@
 
         return datefmt;
     };
-
+    /**
+     * 获取指定行的内容
+     * @param n
+     * @returns {*}
+     */
+    editormd.getLine = function(n) {
+        return this.cm.getLine(n);
+    };
     return editormd;
 
 }));

+ 95 - 90
static/js/editor.js

@@ -11,14 +11,14 @@ function openLastSelectedNode() {
         return false;
     }
     var $isSelected = false;
-    if(window.localStorage){
+    if (window.localStorage) {
         var $selectedNodeId = window.sessionStorage.getItem("MinDoc::LastLoadDocument:" + window.book.identify);
-        try{
-            if($selectedNodeId){
+        try {
+            if ($selectedNodeId) {
                 //遍历文档树判断是否存在节点
-                $.each(window.documentCategory,function (i, n) {
-                    if(n.id == $selectedNodeId && !$isSelected){
-                        var $node = {"id" : n.id};
+                $.each(window.documentCategory, function (i, n) {
+                    if (n.id == $selectedNodeId && !$isSelected) {
+                        var $node = {"id": n.id};
                         window.treeCatalog.deselect_all();
                         window.treeCatalog.select_node($node);
                         $isSelected = true;
@@ -26,16 +26,16 @@ function openLastSelectedNode() {
                 });
 
             }
-        }catch($ex){
+        } catch ($ex) {
             console.log($ex)
         }
     }
 
     //如果节点不存在,则默认选中第一个节点
-    if (!$isSelected && window.documentCategory.length > 0){
+    if (!$isSelected && window.documentCategory.length > 0) {
         var doc = window.documentCategory[0];
 
-        if(doc && doc.id > 0){
+        if (doc && doc.id > 0) {
             var node = {"id": doc.id};
             $("#sidebar").jstree(true).select_node(node);
             $isSelected = true;
@@ -49,7 +49,7 @@ function openLastSelectedNode() {
  * @param $node
  */
 function setLastSelectNode($node) {
-    if(window.localStorage) {
+    if (window.localStorage) {
         if (typeof $node === "undefined" || !$node) {
             window.sessionStorage.removeItem("MinDoc::LastLoadDocument:" + window.book.identify);
         } else {
@@ -83,14 +83,14 @@ function jstree_save(node, parent) {
     });
 
     $.ajax({
-        url : window.sortURL,
-        type :"post",
-        data : JSON.stringify(nodeData),
-        success : function (res) {
+        url: window.sortURL,
+        type: "post",
+        data: JSON.stringify(nodeData),
+        success: function (res) {
             layer.close(index);
-            if (res.errcode === 0){
+            if (res.errcode === 0) {
                 layer.msg("保存排序成功");
-            }else{
+            } else {
                 layer.msg(res.message);
             }
         }
@@ -101,7 +101,7 @@ function jstree_save(node, parent) {
  * 创建文档
  */
 function openCreateCatalogDialog($node) {
-    var $then =  $("#addDocumentModal");
+    var $then = $("#addDocumentModal");
 
     var doc_id = $node ? $node.id : 0;
 
@@ -117,16 +117,16 @@ function openCreateCatalogDialog($node) {
  * @param node
  * @returns {Array}
  */
-function getSiblingSort (node) {
+function getSiblingSort(node) {
     var data = [];
 
-    for(var key in node.children){
+    for (var key in node.children) {
         var index = data.length;
 
         data[index] = {
-            "id" : parseInt(node.children[key]),
-            "sort" : parseInt(key),
-            "parent" : Number(node.id) ? Number(node.id) : 0
+            "id": parseInt(node.children[key]),
+            "sort": parseInt(key),
+            "parent": Number(node.id) ? Number(node.id) : 0
         };
     }
     return data;
@@ -139,26 +139,26 @@ function getSiblingSort (node) {
  */
 function openDeleteDocumentDialog($node) {
     var index = layer.confirm('你确定要删除该文档吗?', {
-        btn: ['确定','取消'] //按钮
-    }, function(){
+        btn: ['确定', '取消'] //按钮
+    }, function () {
 
-        $.post(window.deleteURL,{"identify" : window.book.identify,"doc_id" : $node.id}).done(function (res) {
+        $.post(window.deleteURL, {"identify": window.book.identify, "doc_id": $node.id}).done(function (res) {
             layer.close(index);
-            if(res.errcode === 0){
+            if (res.errcode === 0) {
                 window.treeCatalog.delete_node($node);
                 window.documentCategory.remove(function (item) {
-                   return item.id == $node.id;
+                    return item.id == $node.id;
                 });
 
 
                 // console.log(window.documentCategory)
                 setLastSelectNode();
-            }else{
-                layer.msg("删除失败",{icon : 2})
+            } else {
+                layer.msg("删除失败", {icon: 2})
             }
         }).fail(function () {
             layer.close(index);
-            layer.msg("删除失败",{icon : 2})
+            layer.msg("删除失败", {icon: 2})
         });
 
     });
@@ -169,7 +169,7 @@ function openDeleteDocumentDialog($node) {
  * @param $node
  */
 function openEditCatalogDialog($node) {
-    var $then =  $("#addDocumentModal");
+    var $then = $("#addDocumentModal");
     var doc_id = parseInt($node ? $node.id : 0);
     var text = $node ? $node.text : '';
     var parentId = $node && $node.parent !== '#' ? $node.parent : 0;
@@ -179,21 +179,21 @@ function openEditCatalogDialog($node) {
     $then.find("input[name='parent_id']").val(parentId);
     $then.find("input[name='doc_name']").val(text);
 
-    if($node.a_attr && $node.a_attr.is_open){
-        $then.find("input[name='is_open'][value='1']").prop("checked","checked");
-    }else{
-        $then.find("input[name='is_open'][value='0']").prop("checked","checked");
-    }
+    var open = $node.a_attr && $node.a_attr.opened ? $node.a_attr.opened  : 0;
 
-    for (var index in window.documentCategory){
+
+    console.log($node)
+    $then.find("input[name='is_open'][value='" + open + "']").prop("checked", "checked");
+
+    for (var index in window.documentCategory) {
         var item = window.documentCategory[index];
-        if(item.id === doc_id){
+        if (item.id === doc_id) {
             $then.find("input[name='doc_identify']").val(item.identify);
             break;
         }
     }
 
-    $then.modal({ show : true });
+    $then.modal({show: true});
 }
 
 /**
@@ -201,9 +201,9 @@ function openEditCatalogDialog($node) {
  * @param $node
  */
 function pushDocumentCategory($node) {
-    for (var index in window.documentCategory){
+    for (var index in window.documentCategory) {
         var item = window.documentCategory[index];
-        if(item.id === $node.id){
+        if (item.id === $node.id) {
 
             window.documentCategory[index] = $node;
             return;
@@ -211,6 +211,7 @@ function pushDocumentCategory($node) {
     }
     window.documentCategory.push($node);
 }
+
 /**
  * 将数据重置到Vue列表中
  * @param $lists
@@ -218,7 +219,7 @@ function pushDocumentCategory($node) {
 function pushVueLists($lists) {
 
     window.vueApp.lists = [];
-    $.each($lists,function (i, item) {
+    $.each($lists, function (i, item) {
         window.vueApp.lists.push(item);
     });
 }
@@ -229,7 +230,7 @@ function pushVueLists($lists) {
 function releaseBook() {
     $.ajax({
         url: window.releaseURL,
-        data: { "identify": window.book.identify },
+        data: {"identify": window.book.identify},
         type: "post",
         dataType: "json",
         success: function (res) {
@@ -241,18 +242,19 @@ function releaseBook() {
         }
     });
 }
+
 //实现小提示
 $("[data-toggle='tooltip']").hover(function () {
     var title = $(this).attr('data-title');
     var direction = $(this).attr("data-direction");
     var tips = 3;
-    if(direction === "top"){
+    if (direction === "top") {
         tips = 1;
-    }else if(direction === "right"){
+    } else if (direction === "right") {
         tips = 2;
-    }else if(direction === "bottom"){
+    } else if (direction === "bottom") {
         tips = 3;
-    }else if(direction === "left"){
+    } else if (direction === "left") {
         tips = 4;
     }
     index = layer.tips(title, this, {
@@ -262,52 +264,54 @@ $("[data-toggle='tooltip']").hover(function () {
     layer.close(index);
 });
 //弹出创建文档的遮罩层
-$("#btnAddDocument").on("click",function () {
+$("#btnAddDocument").on("click", function () {
     $("#addDocumentModal").modal("show");
 });
 //用于还原创建文档的遮罩层
-$("#addDocumentModal").on("hidden.bs.modal",function () {
-     $(this).find("form").html(window.sessionStorage.getItem("MinDoc::addDocumentModal"));
-    var $then =  $("#addDocumentModal");
+$("#addDocumentModal").on("hidden.bs.modal", function () {
+    $(this).find("form").html(window.sessionStorage.getItem("MinDoc::addDocumentModal"));
+    var $then = $("#addDocumentModal");
 
     $then.find("input[name='parent_id']").val('');
     $then.find("input[name='doc_id']").val('');
     $then.find("input[name='doc_name']").val('');
-}).on("shown.bs.modal",function () {
+}).on("shown.bs.modal", function () {
     $(this).find("input[name='doc_name']").focus();
-}).on("show.bs.modal",function () {
-     window.sessionStorage.setItem("MinDoc::addDocumentModal",$(this).find("form").html())
+}).on("show.bs.modal", function () {
+    window.sessionStorage.setItem("MinDoc::addDocumentModal", $(this).find("form").html())
 });
 
-function showError($msg,$id) {
-    if(!$id){
+function showError($msg, $id) {
+    if (!$id) {
         $id = "#form-error-message"
     }
     $($id).addClass("error-message").removeClass("success-message").text($msg);
     return false;
 }
 
-function showSuccess($msg,$id) {
-    if(!$id){
+function showSuccess($msg, $id) {
+    if (!$id) {
         $id = "#form-error-message"
     }
     $($id).addClass("success-message").removeClass("error-message").text($msg);
     return true;
 }
 
-window.documentHistory = function() {
+window.documentHistory = function () {
     layer.open({
         type: 2,
         title: '历史版本',
         shadeClose: true,
         shade: 0.8,
-        area: ['700px','80%'],
+        area: ['700px', '80%'],
         content: window.historyURL + "?identify=" + window.book.identify + "&doc_id=" + window.selectNode.id,
-        end : function () {
-            if(window.SelectedId){
-                var selected = {node:{
-                    id : window.SelectedId
-                }};
+        end: function () {
+            if (window.SelectedId) {
+                var selected = {
+                    node: {
+                        id: window.SelectedId
+                    }
+                };
                 window.loadDocument(selected);
                 window.SelectedId = null;
             }
@@ -315,10 +319,10 @@ window.documentHistory = function() {
     });
 };
 
-function uploadImage($id,$callback) {
+function uploadImage($id, $callback) {
     /** 粘贴上传图片 **/
-    document.getElementById($id).addEventListener('paste', function(e) {
-        if(e.clipboardData && e.clipboardData.items) {
+    document.getElementById($id).addEventListener('paste', function (e) {
+        if (e.clipboardData && e.clipboardData.items) {
             var clipboard = e.clipboardData;
             for (var i = 0, len = clipboard.items.length; i < len; i++) {
                 if (clipboard.items[i].kind === 'file' || clipboard.items[i].type.indexOf('image') > -1) {
@@ -389,44 +393,45 @@ function initHighlighting() {
         hljs.highlightBlock(block);
     });
 }
+
 $(function () {
     window.vueApp = new Vue({
-        el : "#attachList",
-        data : {
-            lists : []
+        el: "#attachList",
+        data: {
+            lists: []
         },
-        delimiters : ['${','}'],
-        methods : {
-            removeAttach : function ($attach_id) {
+        delimiters: ['${', '}'],
+        methods: {
+            removeAttach: function ($attach_id) {
                 var $this = this;
                 var item = $this.lists.filter(function ($item) {
                     return $item.attachment_id == $attach_id;
                 });
 
-                if(item && item[0].hasOwnProperty("state")){
+                if (item && item[0].hasOwnProperty("state")) {
                     $this.lists = $this.lists.filter(function ($item) {
                         return $item.attachment_id != $attach_id;
                     });
                     return;
                 }
                 $.ajax({
-                    url : window.removeAttachURL,
-                    type : "post",
-                    data : { "attach_id" : $attach_id},
-                    success : function (res) {
-                        if(res.errcode === 0){
+                    url: window.removeAttachURL,
+                    type: "post",
+                    data: {"attach_id": $attach_id},
+                    success: function (res) {
+                        if (res.errcode === 0) {
                             $this.lists = $this.lists.filter(function ($item) {
                                 return $item.attachment_id != $attach_id;
                             });
-                        }else{
+                        } else {
                             layer.msg(res.message);
                         }
                     }
                 });
             }
         },
-        watch : {
-            lists : function ($lists) {
+        watch: {
+            lists: function ($lists) {
                 $("#attachInfo").text(" " + $lists.length + " 个附件")
             }
         }
@@ -434,21 +439,21 @@ $(function () {
     /**
      * 启动自动保存,默认30s自动保存一次
      */
-    if(window.book && window.book.auto_save){
+    if (window.book && window.book.auto_save) {
         setTimeout(function () {
             setInterval(function () {
-                var $then =  $("#markdown-save");
-                if(!window.saveing && $then.hasClass("change")){
+                var $then = $("#markdown-save");
+                if (!window.saveing && $then.hasClass("change")) {
                     $then.trigger("click");
                 }
-            },30000);
-        },30000);
+            }, 30000);
+        }, 30000);
     }
     /**
      * 当离开窗口时存在未保存的文档会提示保存
      */
-    $(window).on("beforeunload",function () {
-        if($("#markdown-save").hasClass("change")){
+    $(window).on("beforeunload", function () {
+        if ($("#markdown-save").hasClass("change")) {
             return '您输入的内容尚未保存,确定离开此页面吗?';
         }
     });

+ 6 - 1
static/js/kancloud.js

@@ -199,7 +199,12 @@ $(function () {
             "multiple" : false,
             'animation' : 0
         }
-    }).on('select_node.jstree', function (node, selected, event) {
+    }).on('select_node.jstree', function (node, selected) {
+        //如果是空目录则直接出发展开下一级功能
+        if (selected.node.a_attr && selected.node.a_attr.disabled) {
+            selected.instance.toggle_node(selected.node);
+            return false
+        }
         $(".m-manual").removeClass('manual-mobile-show-left');
         loadDocument(selected.node.a_attr.href, selected.node.id,selected.node.a_attr['data-version']);
     });

+ 14 - 2
static/js/markdown.js

@@ -125,9 +125,11 @@ $(function () {
            // 插入 GFM 任务列表
            var cm = window.editor.cm;
            var selection = cm.getSelection();
-
+           var cursor    = cm.getCursor();
            if (selection === "") {
+               cm.setCursor(cursor.line, 0);
                cm.replaceSelection("- [x] " + selection);
+               cm.setCursor(cursor.line, cursor.ch + 6);
            } else {
                var selectionText = selection.split("\n");
 
@@ -196,6 +198,10 @@ $(function () {
             layer.msg("获取当前文档信息失败");
             return;
         }
+        if (node.a_attr && node.a_attr.disabled) {
+            layer.msg("空节点不能添加内容");
+            return;
+        }
 
         var doc_id = parseInt(node.id);
 
@@ -381,7 +387,7 @@ $(function () {
 
         //如果没有选中节点则选中默认节点
         // openLastSelectedNode();
-    }).on('select_node.jstree', function (node, selected, event) {
+    }).on('select_node.jstree', function (node, selected) {
 
         if ($("#markdown-save").hasClass('change')) {
             if (confirm("编辑内容未保存,需要保存吗?")) {
@@ -391,6 +397,12 @@ $(function () {
                 return true;
             }
         }
+        //如果是空目录则直接出发展开下一级功能
+        if (selected.node.a_attr && selected.node.a_attr.disabled) {
+            selected.instance.toggle_node(selected.node);
+            return false
+        }
+
 
         loadDocument(selected);
     }).on("move_node.jstree", jstree_save).on("delete_node.jstree",function($node,$parent) {

+ 7 - 3
views/document/markdown_edit_template.tpl

@@ -167,17 +167,21 @@
 
                 </div>
                 <div class="form-group">
-                        <div class="col-lg-6">
+                        <div class="col-lg-4">
                             <label>
                                 <input type="radio" name="is_open" value="1"> 展开<span class="text">(在阅读时会自动展开节点)</span>
                             </label>
                         </div>
-                        <div class="col-lg-6">
+                        <div class="col-lg-4">
                             <label>
                                 <input type="radio" name="is_open" value="0" checked> 关闭<span class="text">(在阅读时会关闭节点)</span>
                             </label>
                         </div>
-
+                    <div class="col-lg-4">
+                        <label>
+                            <input type="radio" name="is_open" value="2"> 空目录<span class="text">(单击时会展开下级节点)</span>
+                        </label>
+                    </div>
                     <div class="clearfix"></div>
                 </div>
             </div>