优化体验

This commit is contained in:
Minho 2017-05-26 14:19:27 +08:00
parent a562499b83
commit bb9ba8c4b6
23 changed files with 794 additions and 275 deletions

View File

@ -40,6 +40,12 @@ const (
BookObserver = 3
)
const (
//本地账户校验
AuthMethodLocal = "local"
//LDAP用户校验
AuthMethodLDAP = "ldap"
)
var (
VERSION string
BUILD_TIME string

View File

@ -172,7 +172,9 @@ func (c *AccountController) FindPassword() {
if member.Status != 0 {
c.JsonResult(6007,"账号已被禁用")
}
if member.AuthMethod == conf.AuthMethodLDAP {
c.JsonResult(6011,"当前用户不支持找回密码")
}
count,err := models.NewMemberToken().FindSendCount(email,time.Now().Add(-1*time.Hour),time.Now())

View File

@ -508,7 +508,7 @@ func (c *BookController) Release() {
book_id := 0
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator() {
book,err := models.NewBook().FindByFieldFirst("identify",identify)
if err != nil {
@ -550,21 +550,32 @@ func (c *BookController) SaveSort() {
c.Abort("404")
}
bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
book_id := 0
if c.Member.IsAdministrator() {
book,err := models.NewBook().FindByFieldFirst("identify",identify)
if err != nil {
if err != nil {
beego.Error("DocumentController.Edit => ",err)
}
book_id = book.BookId
}else{
bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
if err != nil {
beego.Error("DocumentController.Edit => ",err)
c.Abort("403")
}
if bookResult.RoleId == conf.BookObserver {
c.JsonResult(6002,"项目不存在或权限不足")
c.Abort("403")
}
if bookResult.RoleId == conf.BookObserver {
c.JsonResult(6002,"项目不存在或权限不足")
}
book_id = bookResult.BookId
}
content := c.Ctx.Input.RequestBody
var docs []map[string]interface{}
err = json.Unmarshal(content,&docs)
err := json.Unmarshal(content,&docs)
if err != nil {
beego.Error(err)
@ -578,7 +589,7 @@ func (c *BookController) SaveSort() {
beego.Error(err)
continue;
}
if doc.BookId != bookResult.BookId {
if doc.BookId != book_id {
logs.Info("%s","权限错误")
continue;
}
@ -593,7 +604,7 @@ func (c *BookController) SaveSort() {
continue
}
if parent_id > 0 {
if parent,err := models.NewDocument().Find(int(parent_id)); err != nil || parent.BookId != bookResult.BookId {
if parent,err := models.NewDocument().Find(int(parent_id)); err != nil || parent.BookId != book_id {
continue
}
}

View File

@ -37,7 +37,7 @@ func isReadable(identify, token string, c *DocumentController) *models.BookResul
beego.Error(err)
c.Abort("500")
}
if c.Member != nil && c.Member.Role == conf.MemberSuperRole {
if c.Member != nil && c.Member.IsAdministrator(){
bookResult := book.ToBookResult()
return bookResult
}
@ -208,7 +208,7 @@ func (c *DocumentController) Edit() {
bookResult := models.NewBookResult()
var err error
//如果是超级管理者,则不判断权限
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator(){
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
c.JsonResult(6002, "项目不存在或权限不足")
@ -289,7 +289,7 @@ func (c *DocumentController) Create() {
}
book_id := 0
//如果是超级管理员则不判断权限
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator(){
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
beego.Error(err)
@ -370,7 +370,7 @@ func (c *DocumentController) Upload() {
}
book_id := 0
//如果是超级管理员,则不判断权限
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator(){
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
@ -584,7 +584,7 @@ func (c *DocumentController) Delete() {
book_id := 0
//如果是超级管理员则忽略权限判断
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator(){
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
beego.Error("FindByIdentify => ", err)
@ -637,7 +637,7 @@ func (c *DocumentController) Content() {
}
book_id := 0
//如果是超级管理员,则忽略权限
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator() {
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
c.JsonResult(6002, "项目不存在或权限不足")
@ -740,7 +740,7 @@ func (c *DocumentController) Export() {
return
}
bookResult := models.NewBookResult()
if c.Member != nil && c.Member.Role == conf.MemberSuperRole {
if c.Member != nil && c.Member.IsAdministrator(){
book, err := models.NewBook().FindByIdentify(identify)
if err != nil {
beego.Error(err)
@ -899,7 +899,7 @@ func (c *DocumentController) History() {
book_id := 0
//如果是超级管理员则忽略权限判断
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator(){
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
beego.Error("FindByIdentify => ", err)
@ -970,7 +970,7 @@ func (c *DocumentController) DeleteHistory() {
}
book_id := 0
//如果是超级管理员则忽略权限判断
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator(){
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
beego.Error("FindByIdentify => ", err)
@ -1022,7 +1022,7 @@ func (c *DocumentController) RestoreHistory() {
}
book_id := 0
//如果是超级管理员则忽略权限判断
if c.Member.Role == conf.MemberSuperRole {
if c.Member.IsAdministrator(){
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
beego.Error("FindByIdentify => ", err)

View File

@ -28,9 +28,6 @@ func (c *ManagerController) Prepare (){
func (c *ManagerController) Index() {
c.TplName = "manager/index.tpl"
if !c.Member.IsAdministrator() {
c.Abort("403")
}
c.Data["Model"] = models.NewDashboard().Query()
}
@ -40,10 +37,6 @@ func (c *ManagerController) Users() {
c.Prepare()
c.TplName = "manager/users.tpl"
if !c.Member.IsAdministrator() {
c.Abort("403")
}
pageIndex, _ := c.GetInt("page", 0)
members, totalCount, err := models.NewMember().FindToPager(pageIndex, 15)
@ -73,9 +66,6 @@ func (c *ManagerController) Users() {
// 添加用户.
func (c *ManagerController) CreateMember() {
c.Prepare()
if !c.Member.IsAdministrator() {
c.Abort("403")
}
account := strings.TrimSpace(c.GetString("account"))
password1 := strings.TrimSpace(c.GetString("password1"))
@ -131,10 +121,6 @@ func (c *ManagerController) CreateMember() {
func (c *ManagerController) UpdateMemberStatus() {
c.Prepare()
if !c.Member.IsAdministrator() {
c.Abort("403")
}
member_id, _ := c.GetInt("member_id", 0)
status, _ := c.GetInt("status", 0)
@ -168,10 +154,6 @@ func (c *ManagerController) UpdateMemberStatus() {
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 {
@ -204,9 +186,7 @@ func (c *ManagerController) ChangeMemberRole() {
func (c *ManagerController) EditMember() {
c.Prepare()
c.TplName = "manager/edit_users.tpl"
if !c.Member.IsAdministrator() {
c.Abort("403")
}
member_id,_ := c.GetInt(":id",0)
if member_id <= 0 {
@ -231,7 +211,7 @@ func (c *ManagerController) EditMember() {
if password1 != "" && password2 != password1 {
c.JsonResult(6001,"确认密码不正确")
}
if password1 != "" {
if password1 != "" && member.AuthMethod != conf.AuthMethodLDAP{
member.Password = password1
}
if err := member.Valid(password1 == "");err != nil {
@ -280,10 +260,10 @@ func (c *ManagerController) Books() {
//编辑项目
func (c *ManagerController) EditBook() {
c.Prepare()
c.TplName = "manager/edit_book.tpl"
if !c.Member.IsAdministrator() {
c.Abort("403")
}
identify := c.GetString(":key")
if identify == "" {
@ -334,9 +314,6 @@ func (c *ManagerController) EditBook() {
// 删除项目.
func (c *ManagerController) DeleteBook() {
c.Prepare()
if !c.Member.IsAdministrator() {
c.Abort("403")
}
book_id, _ := c.GetInt("book_id", 0)
@ -359,7 +336,7 @@ func (c *ManagerController) DeleteBook() {
// CreateToken 创建访问来令牌.
func (c *ManagerController) CreateToken() {
c.Prepare()
action := c.GetString("action")
identify := c.GetString("identify")
@ -396,10 +373,6 @@ func (c *ManagerController) Setting() {
c.Prepare()
c.TplName = "manager/setting.tpl"
if !c.Member.IsAdministrator() {
c.Abort("403")
}
options, err := models.NewOption().All()
if c.Ctx.Input.IsPost() {
@ -480,9 +453,7 @@ func (c *ManagerController) Comments() {
//DeleteComment 标记评论为已删除
func (c *ManagerController) DeleteComment() {
c.Prepare()
if !c.Member.IsAdministrator() {
c.Abort("403")
}
comment_id, _ := c.GetInt("comment_id", 0)
if comment_id <= 0 {
@ -505,7 +476,7 @@ func (c *ManagerController) DeleteComment() {
//设置项目私有状态.
func (c *ManagerController) PrivatelyOwned() {
c.Prepare()
status := c.GetString("status")
identify := c.GetString("identify")

View File

@ -12,6 +12,7 @@ import (
"github.com/lifei6671/godoc/models"
"github.com/lifei6671/godoc/utils"
"github.com/lifei6671/godoc/graphics"
"github.com/lifei6671/godoc/conf"
)
type SettingController struct {
@ -45,6 +46,9 @@ func (c *SettingController) Password() {
c.TplName = "setting/password.tpl"
if c.Ctx.Input.IsPost() {
if c.Member.AuthMethod == conf.AuthMethodLDAP {
c.JsonResult(6009,"当前用户不支持修改密码")
}
password1 := c.GetString("password1")
password2 := c.GetString("password2")
password3 := c.GetString("password3")

View File

@ -14,6 +14,7 @@ var (
ErrMemberEmailFormatError = errors.New("邮箱格式不正确")
ErrMemberPasswordFormatError = errors.New("密码必须在6-50个字符之间")
ErrMemberAccountFormatError = errors.New("账号只能由英文字母数字组成且在3-50个字符")
ErrMemberRoleError = errors.New("用户权限不正确")
// ErrorMemberPasswordError 密码错误.
ErrorMemberPasswordError = errors.New("用户密码错误")
//ErrorMemberAuthMethodInvalid 不支持此认证方式

View File

@ -277,6 +277,12 @@ func (m *Member) Valid(is_hash_password bool) error {
if strings.Count(m.Description,"") > 500 {
return ErrMemberDescriptionTooLong
}
if m.Role != conf.MemberGeneralRole && m.Role != conf.MemberSuperRole && m.Role != conf.MemberAdminRole {
return ErrMemberRoleError
}
if m.Status != 0 && m.Status != 1 {
m.Status = 0
}
//邮箱格式校验
if ok,err := regexp.MatchString(conf.RegexpEmail,m.Email); !ok || err != nil || m.Email == "" {
return ErrMemberEmailFormatError
@ -296,6 +302,7 @@ func (m *Member) Valid(is_hash_password bool) error {
return ErrMemberEmailExist
}
}
if m.MemberId > 0{
//校验用户是否存在
if _,err := NewMember().Find(m.MemberId);err != nil {
@ -312,6 +319,7 @@ func (m *Member) Valid(is_hash_password bool) error {
}
}
return nil
}

View File

@ -492,6 +492,7 @@ textarea{
background-color: #f9f9f9;
border: 1px solid #fff;
cursor: pointer;
color: #333;
}
.manager .dashboard-item:hover{
background-color: #563D7C;

View File

@ -215,6 +215,7 @@ body{
margin-left: 10px;
cursor: pointer;
}
/***************附件管理的样式*******************/
.attach-drop-panel{
display: block;
position: relative;
@ -286,20 +287,80 @@ body{
.attach-list .attach-item .close:hover {
color: #333;
}
/***********选择模板时的样式**************/
.template-list .container{
margin-top: 60px;
margin-bottom: 40px;
padding: 0 15px;
box-sizing: border-box;
}
.template-list .container .section{
position: relative;
margin: 0 15px;
padding-top: 60px;
float: left;
width: 150px;
height: 236px;
background: #fdfefe;
border: 1px solid #ddddd9;
text-align: center
}
.template-list .container .section>h3 a{
font-size: 20px;
font-weight: 200;
text-decoration: none;
color: #5d606b
}
.template-list .container .section>a {
display: inline-block;
position: absolute;
left: 50%;
top: -28px;
width: 56px;
height: 56px;
margin-left: -28px
}
.template-list .container .section>a .fa {
display: inline-block;
width: 56px;
height: 56px;
background-color: #fbfefe;
border: 1px solid #ddddd9;
border-radius: 50%;
line-height: 54px;
font-size: 24px;
color: #ddddd9
}
.template-list .container .section:hover {
border-color: #44b035
}
.template-list .container .section:hover>a {
background-color: #44b035;
padding: 5px;
border-radius: 50%;
width: 66px;
height: 66px;
margin-left: -33px;
top: -33px
}
.template-list .container .section:hover>a .fa {
background-color: #78c56d;
color: #fff;
border: 0;
line-height: 54px
}
.template-list .container .section ul {
margin-top: 35px;
padding-left: 0;
list-style: none
}
.template-list .container .section ul li {
margin-bottom: 10px;
padding: 0 10px;
line-height: 1.2;
color: #8e8d8d
}

View File

@ -0,0 +1,33 @@
/*************表格样式****************/
.editormd-preview-container table thead tr{
background-color: #0088CC;
color: #ffffff;
}
.editormd-preview-container table tr:nth-child(2n) {
background-color: #f8f8f8
}
.editormd-preview-container-body blockquote,.editormd-preview-container blockquote p {
font-size: 14px;
color: #999
}
/***********代码样式*****************/
.markdown-body .highlight pre, .markdown-body pre{
padding: 0;
font-size: 12px;
border-radius:0;
line-height: 1.4em;
}
.editormd-preview-container pre.prettyprint, .editormd-html-preview pre.prettyprint{
padding: 0;
}
.editormd-preview-container ol.linenums, .editormd-html-preview ol.linenums{
color: #999;
}
.editormd-preview-container ol.linenums>li:first-child,.editormd-html-preview ol.linenums>li:first-child{
padding-top: 10px;
}
.editormd-preview-container ol.linenums>li:last-child ,.editormd-html-preview ol.linenums>li:last-child{
padding-bottom: 10px;
}

View File

@ -0,0 +1,27 @@
jQuery.fn.highlight = function(pat) {
function innerHighlight(node, pat) {
var skip = 0;
if (node.nodeType === 3) {
var pos = node.data.toUpperCase().indexOf(pat);
if (pos >= 0) {
var spannode = document.createElement('em');
spannode.className = 'search-highlight';
var middlebit = node.splitText(pos);
var endbit = middlebit.splitText(pat.length);
var middleclone = middlebit.cloneNode(true);
spannode.appendChild(middleclone);
middlebit.parentNode.replaceChild(spannode, middlebit);
skip = 1;
}
}
else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
for (var i = 0; i < node.childNodes.length; ++i) {
i += innerHighlight(node.childNodes[i], pat);
}
}
return skip;
}
return this.each(function() {
innerHighlight(this, pat.toUpperCase());
});
};

207
static/js/kancloud.js Normal file
View File

@ -0,0 +1,207 @@
/***
* 加载文档到阅读区
* @param $url
* @param $id
* @param $callback
*/
function loadDocument($url,$id,$callback) {
$.ajax({
url : $url,
type : "GET",
beforeSend :function (xhr) {
var body = events.data('body_' + $id);
var title = events.data('title_' + $id);
var doc_title = events.data('doc_title_' + $id);
if(body && title && doc_title){
if (typeof $callback === "function") {
body = $callback(body);
}
$("#page-content").html(body);
$("title").text(title);
$("#article-title").text(doc_title);
events.trigger('article.open',{ $url : $url, $init : false , $id : $id });
return false;
}
NProgress.start();
},
success : function (res) {
if(res.errcode === 0){
var body = res.data.body;
var doc_title = res.data.doc_title;
var title = res.data.title;
$body = body;
if (typeof $callback === "function" ){
$body = $callback(body);
}
$("#page-content").html($body);
$("title").text(title);
$("#article-title").text(doc_title);
events.data('body_' + $id,body);
events.data('title_' + $id,title);
events.data('doc_title_' + $id,doc_title);
events.trigger('article.open',{ $url : $url, $init : true, $id : $id });
}else{
layer.msg("加载失败");
}
},
complete : function () {
NProgress.done();
}
});
}
function initHighlighting() {
$('pre code').each(function (i, block) {
hljs.highlightBlock(block);
});
hljs.initLineNumbersOnLoad();
}
var events = $("body");
$(function () {
$(".view-backtop").on("click", function () {
$('.manual-right').animate({ scrollTop: '0px' }, 200);
});
$(".manual-right").scroll(function () {
var top = $(".manual-right").scrollTop();
if(top > 100){
$(".view-backtop").addClass("active");
}else{
$(".view-backtop").removeClass("active");
}
});
window.isFullScreen = false;
initHighlighting();
window.jsTree = $("#sidebar").jstree({
'plugins':["wholerow","types"],
"types": {
"default" : {
"icon" : false // 删除默认图标
}
},
'core' : {
'check_callback' : true,
"multiple" : false ,
'animation' : 0
}
}).on('select_node.jstree',function (node,selected,event) {
$(".m-manual").removeClass('manual-mobile-show-left');
var url = selected.node.a_attr.href;
if(url === window.location.href){
return false;
}
loadDocument(url,selected.node.id);
});
$("#slidebar").on("click",function () {
$(".m-manual").addClass('manual-mobile-show-left');
});
$(".manual-mask").on("click",function () {
$(".m-manual").removeClass('manual-mobile-show-left');
});
/**
* 关闭侧边栏
*/
$(".manual-fullscreen-switch").on("click",function () {
isFullScreen = !isFullScreen;
if (isFullScreen) {
$(".m-manual").addClass('manual-fullscreen-active');
} else {
$(".m-manual").removeClass('manual-fullscreen-active');
}
});
//处理打开事件
events.on('article.open', function (event, $param) {
if ('pushState' in history) {
if ($param.$init === false) {
window.history.replaceState($param , $param.$id , $param.$url);
} else {
window.history.pushState($param, $param.$id , $param.$url);
}
} else {
window.location.hash = $param.$url;
}
initHighlighting();
$(".manual-right").scrollTop(0);
});
$(".navg-item[data-mode]").on("click",function () {
var mode = $(this).data('mode');
$(this).siblings().removeClass('active').end().addClass('active');
$(".m-manual").removeClass("manual-mode-view manual-mode-collect manual-mode-search").addClass("manual-mode-" + mode);
});
/**
* 项目内搜索
*/
$("#searchForm").ajaxForm({
beforeSubmit : function () {
var keyword = $.trim($("#searchForm").find("input[name='keyword']").val());
if(keyword === ""){
$(".search-empty").show();
$("#searchList").html("");
return false;
}
$("#btnSearch").attr("disabled","disabled").find("i").removeClass("fa-search").addClass("loading");
window.keyword = keyword;
},
success :function (res) {
var html = "";
if(res.errcode === 0){
for(var i in res.data){
var item = res.data[i];
html += '<li><a href="javascript:;" title="'+ item.doc_name +'" data-id="'+ item.doc_id+'"> '+ item.doc_name +' </a></li>';
}
}
if(html !== ""){
$(".search-empty").hide();
}else{
$(".search-empty").show();
}
$("#searchList").html(html);
},
complete : function () {
$("#btnSearch").removeAttr("disabled").find("i").removeClass("loading").addClass("fa-search");
}
});
window.onpopstate = function (e) {
var $param = e.state;
console.log($param);
if($param.hasOwnProperty("$url")) {
window.jsTree.jstree().deselect_all();
window.jsTree.jstree().select_node({ id : $param.$id });
$param.$init = false;
//events.trigger('article.open', $param );
}else{
console.log($param);
}
};
// var $node = window.jsTree.jstree().get_selected();
//
// if(typeof $node === "object") {
// $node = window.jsTree.jstree().get_node({ id : $node[0] });
// events.trigger('article.open',{ $url : $node.a_attr.href , $init : false, $id : $node.a_attr.id });
// }
});

View File

@ -64,7 +64,8 @@ $(function () {
window.documentHistory();
}else if(name === "save"){
saveDocument(false);
}else if(name === "template"){
$("#documentTemplateModal").modal("show");
}else if(name === "sidebar"){
$("#manualCategory").toggle(0,"swing",function () {
@ -151,7 +152,7 @@ $(function () {
layer.close(index);
layer.msg("文档加载失败");
});
}
};
/**
* 保存文档到服务器
@ -341,4 +342,17 @@ $(function () {
loadDocument(selected);
}).on("move_node.jstree",jstree_save);
$("#documentTemplateModal").on("click",".section>a[data-type]",function () {
var $this = $(this).attr("data-type");
var body = $("#template-" + $this).html();
if (body) {
window.isLoad = true;
window.editor.clear();
window.editor.insertValue(body);
window.editor.setCursor({line: 0, ch: 0});
resetEditorChanged(true);
}
$("#documentTemplateModal").modal('hide');
});
});

View File

@ -0,0 +1,2 @@
/*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */
.prettyprint{background:#22221b;font-family:Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Consolas,monospace;border:0!important}.pln{color:#f4f3ec}ol.linenums{margin-top:0;margin-bottom:0;color:#6c6b5a}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{padding-left:1em;background-color:#22221b;list-style-type:decimal}@media screen{.str{color:#7d9726}.kwd{color:#5f9182}.com{color:#6c6b5a}.typ{color:#36a166}.lit{color:#ae7313}.pun{color:#f4f3ec}.opn{color:#f4f3ec}.clo{color:#f4f3ec}.tag{color:#ba6236}.atn{color:#ae7313}.atv{color:#5b9d48}.dec{color:#ae7313}.var{color:#ba6236}.fun{color:#36a166}}

View File

@ -17,6 +17,8 @@
<link href="/static/css/jstree.css" rel="stylesheet">
{{if eq .Model.Editor "markdown"}}
<link href="{{cdncss "/static/editor.md/css/editormd.preview.css"}}" rel="stylesheet">
<link href="{{cdncss "/static/prettify/themes/atelier-estuary-dark.min.css"}}" rel="stylesheet">
<link href="/static/css/markdown.preview.css" rel="stylesheet">
{{else}}
<link href="{{cdncss "/static/highlight/styles/zenburn.css"}}" rel="stylesheet">
{{end}}
@ -226,196 +228,20 @@
<script type="text/javascript" src="{{cdnjs "/static/nprogress/nprogress.js"}}"></script>
<script type="text/javascript" src="{{cdnjs "/static/highlight/highlight.js"}}"></script>
<script type="text/javascript" src="{{cdnjs "/static/highlight/highlightjs-line-numbers.min.js"}}"></script>
<script type="text/javascript" src="/static/js/jquery.highlight.js"></script>
<script type="text/javascript" src="/static/js/kancloud.js"></script>
<script type="text/javascript">
var events = $("body");
var catalog = null;
function initHighlighting() {
$('pre code').each(function (i, block) {
hljs.highlightBlock(block);
});
hljs.initLineNumbersOnLoad();
}
function loadDocument($url,$id,$callback) {
$.ajax({
url : $url,
type : "GET",
beforeSend :function (xhr) {
var body = events.data('body_' + $id);
var title = events.data('title_' + $id);
var doc_title = events.data('doc_title_' + $id);
if(body && title && doc_title){
if (typeof $callback === "function") {
body = $callback(body);
}
$("#page-content").html(body);
$("title").text(title);
$("#article-title").text(doc_title);
events.trigger('article.open',$url,true);
return false;
}
NProgress.start();
},
success : function (res) {
if(res.errcode === 0){
var body = res.data.body;
var doc_title = res.data.doc_title;
var title = res.data.title;
$body = body;
if (typeof $callback === "function" ){
$body = $callback(body);
}
$("#page-content").html($body);
$("title").text(title);
$("#article-title").text(doc_title);
events.data('body_' + $id,body);
events.data('title_' + $id,title);
events.data('doc_title_' + $id,doc_title);
events.trigger('article.open',$url,false);
}else{
layer.msg("加载失败");
}
},
complete : function () {
NProgress.done();
}
});
}
$(function () {
$(".view-backtop").on("click", function () {
$('.manual-right').animate({ scrollTop: '0px' }, 200);
});
$(".manual-right").scroll(function () {
var top = $(".manual-right").scrollTop();
if(top > 100){
$(".view-backtop").addClass("active");
}else{
$(".view-backtop").removeClass("active");
}
});
window.isFullScreen = false;
initHighlighting();
$("#sidebar").jstree({
'plugins':["wholerow","types"],
"types": {
"default" : {
"icon" : false // 删除默认图标
}
},
'core' : {
'check_callback' : true,
"multiple" : false ,
'animation' : 0
}
}).on('select_node.jstree',function (node,selected,event) {
$(".m-manual").removeClass('manual-mobile-show-left');
var url = selected.node.a_attr.href;
if(url === window.location.href){
return false;
}
loadDocument(url,selected.node.id);
});
$("#slidebar").on("click",function () {
$(".m-manual").addClass('manual-mobile-show-left');
});
$(".manual-mask").on("click",function () {
$(".m-manual").removeClass('manual-mobile-show-left');
});
$(".manual-fullscreen-switch").on("click",function () {
isFullScreen = !isFullScreen;
if (isFullScreen) {
$(".m-manual").addClass('manual-fullscreen-active');
} else {
$(".m-manual").removeClass('manual-fullscreen-active');
}
});
events.on('article.open', function (event, url,init) {
if ('pushState' in history) {
if (init == false) {
history.replaceState({ }, '', url);
init = true;
} else {
history.pushState({ }, '', url);
}
} else {
location.hash = url;
}
initHighlighting();
});
$(".navg-item[data-mode]").on("click",function () {
var mode = $(this).data('mode');
$(this).siblings().removeClass('active').end().addClass('active');
$(".m-manual").removeClass("manual-mode-view manual-mode-collect manual-mode-search").addClass("manual-mode-" + mode);
});
/**
* 项目内搜索
*/
$("#searchForm").ajaxForm({
beforeSubmit : function () {
var keyword = $.trim($("#searchForm").find("input[name='keyword']").val());
if(keyword === ""){
$(".search-empty").show();
$("#searchList").html("");
return false;
}
$("#btnSearch").attr("disabled","disabled").find("i").removeClass("fa-search").addClass("loading");
window.keyword = keyword;
},
success :function (res) {
var html = "";
if(res.errcode === 0){
for(var i in res.data){
var item = res.data[i];
html += '<li><a href="javascript:;" title="'+ item.doc_name +'" data-id="'+ item.doc_id+'"> '+ item.doc_name +' </a></li>';
}
}
if(html !== ""){
$(".search-empty").hide();
}else{
$(".search-empty").show();
}
$("#searchList").html(html);
},
complete : function () {
$("#btnSearch").removeAttr("disabled").find("i").removeClass("loading").addClass("fa-search");
}
});
$("#searchList").on("click","a",function () {
var id = $(this).attr("data-id");
var url = "{{urlfor "DocumentController.Read" ":key" .Model.Identify ":id" ""}}/" + id;
$(this).parent("li").siblings().find("a").removeClass("active");
$(this).addClass("active");
loadDocument(url,id,function (body) {
if(body !== ""){
console.log(window.keyword);
body = body.replace(new RegExp(window.keyword,"g"),'<em class="search-highlight">' + window.keyword + '</em>');
}
return body;
});
$(function () {
$("#searchList").on("click","a",function () {
var id = $(this).attr("data-id");
var url = "{{urlfor "DocumentController.Read" ":key" .Model.Identify ":id" ""}}/" + id;
$(this).parent("li").siblings().find("a").removeClass("active");
$(this).addClass("active");
loadDocument(url,id,function (body) {
return $(body).highlight(window.keyword);
});
});
});
</script>
</body>
</html>

View File

@ -29,6 +29,8 @@
<link href="{{cdncss "/static/highlight/styles/zenburn.css"}}" rel="stylesheet">
<link href="{{cdncss "/static/webuploader/webuploader.css"}}" rel="stylesheet">
<link href="/static/css/markdown.css" rel="stylesheet">
<link href="{{cdncss "/static/prettify/themes/atelier-estuary-dark.min.css"}}" rel="stylesheet">
<link href="/static/css/markdown.preview.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
@ -77,7 +79,9 @@
<a href="javascript:;" data-toggle="tooltip" data-title="添加表格"><i class="fa fa-table item" name="table" unselectable="on"></i></a>
<a href="javascript:;" data-toggle="tooltip" data-title="引用"><i class="fa fa-quote-right item" name="quote" unselectable="on"></i></a>
<a href="javascript:;" data-toggle="tooltip" data-title="GFM 任务列表"><i class="fa fa-tasks item" name="tasks" aria-hidden="true"></i></a>
<a href="javascript:;" data-toggle="tooltip" data-title="附件"><i class="fa fa-paperclip last" aria-hidden="true" name="attachment"></i></a>
<a href="javascript:;" data-toggle="tooltip" data-title="附件"><i class="fa fa-paperclip item" aria-hidden="true" name="attachment"></i></a>
<a href="javascript:;" data-toggle="tooltip" data-title="模板"><i class="fa fa-tachometer last" name="template"></i></a>
</div>
<div class="editormd-group pull-right">
@ -225,6 +229,57 @@
</div>
</div>
<div class="modal fade" id="documentTemplateModal" tabindex="-1" role="dialog" aria-labelledby="请选择模板类型" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="modal-title">请选择模板类型</h4>
</div>
<div class="modal-body template-list">
<div class="container">
<div class="section">
<a data-type="normal" href="javascript:;"><i class="fa fa-file-o"></i></a>
<h3><a data-type="normal" href="javascript:;">普通文档</a></h3>
<ul>
<li>默认类型</li>
<li>简单的文本文档</li>
</ul>
</div>
<div class="section">
<a data-type="api" href="javascript:;"><i class="fa fa-file-code-o"></i></a>
<h3><a data-type="api" href="javascript:;">API文档</a></h3>
<ul>
<li>用于API文档速写</li>
<li>支持代码高亮</li>
</ul>
</div>
<div class="section">
<a data-type="code" href="javascript:;"><i class="fa fa-book"></i></a>
<h3><a data-type="code" href="javascript:;">数据字典</a></h3>
<ul>
<li>用于数据字典显示</li>
<li>表格支持</li>
</ul>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
</div>
</div>
</div>
</div>
<template id="template-normal">
{{template "document/template_normal.tpl"}}
</template>
<template id="template-api">
{{template "document/template_api.tpl"}}
</template>
<template id="template-code">
{{template "document/template_code.tpl"}}
</template>
<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
<script src="{{cdnjs "/static/vuejs/vue.min.js"}}" type="text/javascript"></script>
<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
@ -233,10 +288,6 @@
<script src="{{cdnjs "/static/editor.md/editormd.js"}}" type="text/javascript"></script>
<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
<script type="text/javascript">
</script>
<script src="/static/js/editor.js" type="text/javascript"></script>
<script src="/static/js/markdown.js" type="text/javascript"></script>
<script type="text/javascript">

View File

@ -0,0 +1,71 @@
#### 简要描述:
- 用户登录接口
#### 接口版本:
|版本号|制定人|制定日期|修订日期|
|:---- |:---|:----- |----- |
|2.1.0 |秦亮 |2017-03-20 | xxxx-xx-xx |
#### 请求URL:
- http://xx.com/api/login
#### 请求方式:
- GET
- POST
#### 请求头:
|参数名|是否必须|类型|说明|
|:---- |:---|:----- |----- |
|Content-Type |是 |string |请求类型: application/json |
|Content-MD5 |是 |string | 请求内容签名 |
#### 请求参数:
|参数名|是否必须|类型|说明|
|:---- |:---|:----- |----- |
|username |是 |string |用户名 |
|password |是 |string | 密码 |
#### 返回示例:
**正确时返回:**
```
{
"errcode": 0,
"data": {
"uid": "1",
"account": "admin",
"nickname": "Minho",
"group_level": 0 ,
"create_time": "1436864169",
"last_login_time": "0",
}
}
```
**错误时返回:**
```
{
"errcode": 500,
"errmsg": "invalid appid"
}
```
#### 返回参数说明:
|参数名|类型|说明|
|:----- |:-----|----- |
|group_level |int |用户组id1超级管理员2普通用户 |
#### 备注:
- 更多返回错误代码请看首页的错误代码描述

View File

@ -0,0 +1,13 @@
### 数据库字典
#### 用户表,储存用户信息
|字段|类型|空|默认|注释|
|:---- |:------- |:--- |-- -|------ |
|uid |int(10) |否 | | |
|username |varchar(20) |否 | | 用户名 |
|password |varchar(50) |否 | | 密码 |
|name |varchar(15) |是 | | 昵称 |
|reg_time |int(11) |否 | 0 | 注册时间 |
#### 备注:无

View File

@ -0,0 +1,208 @@
# MinDoc 简介
[![Build Status](https://travis-ci.org/lifei6671/godoc.svg?branch=master)](https://travis-ci.org/lifei6671/godoc)
MinDoc 是一款针对IT团队开发的简单好用的文档管理系统。
MinDoc 的前身是 SmartWiki 文档系统。SmartWiki 是基于 PHP 框架 laravel 开发的一款文档管理系统。因 PHP 的部署对普通用户来说太复杂,所以改用 Golang 开发。可以方便用户部署和实用。
开发缘起是公司IT部门需要一款简单实用的项目接口文档管理和分享的系统。其功能和界面源于 kancloud 。
可以用来储存日常接口文档,数据库字典,手册说明等文档。内置项目管理,用户管理,权限管理等功能,能够满足大部分中小团队的文档管理需求。
演示站点: [http://doc.iminho.me](http://doc.iminho.me)
# 安装与使用
**如果你的服务器上没有安装golang程序请手动设置一个环境变量如下键名为 ZONEINFO值为MinDoc跟目录下的/lib/time/zoneinfo.zip 。**
**Windows 教程:** [https://github.com/lifei6671/godoc/blob/master/README_WIN.md](docs/README_WIN.md)
**Linux 教程:**  [https://github.com/lifei6671/godoc/blob/master/README_LINUX.md](docs/README_LINUX.md)
**PDF 导出配置教程**  [https://github.com/lifei6671/godoc/blob/master/docs/README_LINUX.md](docs/WKHTMLTOPDF.md)
对于没有Golang使用经验的用户可以从 [https://github.com/lifei6671/godoc/releases](https://github.com/lifei6671/godoc/releases) 这里下载编译完的程序。
如果有Golang开发经验建议通过编译安装。
```bash
git clone https://github.com/lifei6671/godoc.git
go get -d ./...
go build -ldflags "-w"
```
MinDoc 使用MySQL储存数据且编码必须是`utf8mb4_general_ci`。请在安装前,把数据库配置填充到项目目录下的 conf/app.conf 中。
如果conf目录下不存在 app.conf 请重命名 app.conf.example 为 app.conf。
如果 MinDoc 根目录下存在 install.lock 文件表示已经初始化过数据库,想要重新初始化数据库,只需要删除该文件重新启动程序即可。
**默认程序会自动创建表同时初始化一个超级管理员用户admin 密码123456 。请登录后重新设置密码。**
## Linux 下后台运行
在 Linux 如果想让程序后台运行可以执行如下命令:
```bash
#使程序后台运行
nohup ./godoc &
```
该命令会使程序后台执行,但是服务器重启后不会自动启动服务。
使用 supervisor 做服务,可以使服务器重启后自动重启 MinDoc。
## Windows 下后台运行
Windows 下后台运行需要借助 CMD 命令行命令:
```bash
#在MinDoc跟目录下新建一个slave.vbs文件
Set ws = CreateObject("Wscript.Shell")
ws.run "cmd /c start.bat",vbhide
#再建一个start.bat文件
@echo off
godoc_windows_amd64.exe
```
启动时双击slave.vbs即可等待程序初始化完数据库会在该目录下创建一个install.lock文件标识已安装成功。
如果是自己编译可以用以下命令即可编译出不依赖cmd命令的后台运行的程序
```bash
go build -ldflags "-H=windowsgui"
```
通过该命令编译的Golang程序在Windows上默认后台运行。
请将将 MinDoc 加入开机启动列表,使程序开机启动。
## 密码找回功能
密码找回功能依赖邮件服务,因此,需要配置邮件服务才能使用该功能,该配置位于 `conf/app.conf` 中:
```bash
#邮件配置
#是否启用邮件
enable_mail=true
#smtp服务器的账号
smtp_user_name=admin@iminho.me
#smtp服务器的地址
smtp_host=smtp.ym.163.com
#密码
smtp_password=1q2w3e__ABC
#端口号
smtp_port=25
#邮件发送人的地址
form_user_name=admin@iminho.me
#邮件有效期30分钟
mail_expired=30
```
# 使用Docker部署
如果是Docker用户可参考项目内置的Dockerfile文件编译镜像。
在启动镜像时需要提供如下的环境变量:
```ini
MYSQL_PORT_3306_TCP_ADDR MySQL地址
MYSQL_PORT_3306_TCP_PORT MySQL端口号
MYSQL_INSTANCE_NAME MySQL数据库名称
MYSQL_USERNAME MySQL账号
MYSQL_PASSWORD MySQL密码
HTTP_PORT 程序监听的端口号
```
举个栗子
```bash
docker run -p 8181:8181 -e MYSQL_PORT_3306_TCP_ADDR=127.0.0.1 -e MYSQL_PORT_3306_TCP_PORT=3306 -e MYSQL_INSTANCE_NAME=mindoc_db -e MYSQL_USERNAME=root -e MYSQL_PASSWORD=123456 -e httpport=8181 -d daocloud.io/lifei6671/godoc:latest
```
# 项目截图
**创建项目**
![创建项目](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501204438.png)
**项目列表**
![项目列表](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501203542.png)
**项目概述**
![项目概述](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501203619.png)
**项目成员**
![项目成员](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501203637.png)
**项目设置**
![项目设置](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501203656.png)
**基于Editor.md开发的Markdown编辑器**
![基于Editor.md开发的Markdown编辑器](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501203854.png)
**基于wangEditor开发的富文本编辑器**
![基于wangEditor开发的富文本编辑器](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501204651.png)
**项目预览**
![项目预览](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501204609.png)
**超级管理员后台**
![超级管理员后台](https://raw.githubusercontent.com/lifei6671/godoc/master/uploads/20170501204710.png)
# 使用的技术
- beego 1.8.1
- mysql 5.6
- editor.md
- bootstrap 3.2
- jquery 库
- layer 弹出层框架
- webuploader 文件上传框架
- Nprogress 库
- jstree 树状结构库
- font awesome 字体库
- cropper 图片剪裁库
- layer 弹出层框架
- highlight 代码高亮库
- to-markdown HTML转Markdown库
- wangEditor 富文本编辑器
# 主要功能
- 项目管理,可以对项目进行编辑更改,成员添加等。
- 文档管理,添加和删除文档等。
- 评论管理,可以管理文档评论和自己发布的评论。
- 用户管理,添加和禁用用户,个人资料更改等。
- 用户权限管理 实现用户角色的变更。
- 项目加密可以设置项目公开状态私有项目需要通过Token访问。
- 站点配置,可开启匿名访问、验证码等。
# 参与开发
我们欢迎您在 MinDoc 项目的 GitHub 上报告 issue 或者 pull request。
如果您还不熟悉GitHub的Fork and Pull开发模式您可以阅读GitHub的文档https://help.github.com/articles/using-pull-requests 获得更多的信息。
# 关于作者
一个不纯粹的PHPer一个不自由的 gopher 。

View File

@ -47,7 +47,7 @@
<div class="form-group">
<label>用户密码</label>
<input type="password" class="form-control" name="password1" placeholder="用户密码" maxlength="50">
<p style="color: #999;font-size: 12px;">不修改密码请留空</p>
<p style="color: #999;font-size: 12px;">不修改密码请留空,只支持本地用户修改密码</p>
</div>
<div class="form-group">
<label>确认密码</label>

View File

@ -40,21 +40,21 @@
</div>
</div>
<div class="box-body manager">
<div class="dashboard-item">
<a href="{{urlfor "ManagerController.Books"}}" 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>
</a>
<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>
<a href="{{urlfor "ManagerController.Users"}}" class="dashboard-item">
<span class="fa fa-users" aria-hidden="true"></span>
<span class="fa-class">会员数量</span>
<span class="fa-class">{{.Model.MemberNumber}}</span>
</a>
<!--
{{/*
<div class="dashboard-item">

View File

@ -29,7 +29,9 @@
<div class="page-left">
<ul class="menu">
<li class="active"><a href="{{urlfor "SettingController.Index"}}" class="item"><i class="fa fa-sitemap" aria-hidden="true"></i> 基本信息</a> </li>
{{if ne .Member.AuthMethod "ldap"}}
<li><a href="{{urlfor "SettingController.Password"}}" class="item"><i class="fa fa-user" aria-hidden="true"></i> 修改密码</a> </li>
{{end}}
</ul>
</div>
<div class="page-right">