浏览代码

no message

kuaifan 5 年之前
父节点
当前提交
0d74eb8b15
共有 36 个文件被更改,包括 1937 次插入210 次删除
  1. 263 42
      app/Http/Controllers/Api/DocsController.php
  2. 3 0
      app/Http/Controllers/Api/ProjectController.php
  3. 8 3
      app/Http/Controllers/Api/UsersController.php
  4. 99 0
      app/Module/Docs.php
  5. 14 0
      app/Module/Users.php
  6. 34 0
      database/migrations/2020_07_13_104810_create_pre_docs_users_table.php
  7. 45 0
      database/migrations/2020_07_13_105212_alter_pre_docs_book_table.php
  8. 34 0
      database/migrations/2020_07_13_110335_alter_pre_docs_section_table.php
  9. 4 3
      package.json
  10. 9 0
      resources/assets/js/main/components/UserInput.vue
  11. 5 5
      resources/assets/js/main/components/WHeader.vue
  12. 36 2
      resources/assets/js/main/components/docs/flow/index.vue
  13. 38 0
      resources/assets/js/main/components/docs/minder/editor.js
  14. 61 38
      resources/assets/js/main/components/docs/minder/minder.vue
  15. 122 0
      resources/assets/js/main/components/docs/setting.vue
  16. 57 4
      resources/assets/js/main/components/docs/sheet/index.vue
  17. 283 0
      resources/assets/js/main/components/docs/users.vue
  18. 2 2
      resources/assets/js/main/components/project/archived.vue
  19. 2 2
      resources/assets/js/main/components/project/header/archived.vue
  20. 2 2
      resources/assets/js/main/components/project/header/create.vue
  21. 2 2
      resources/assets/js/main/components/project/my/favor.vue
  22. 2 2
      resources/assets/js/main/components/project/my/join.vue
  23. 2 2
      resources/assets/js/main/components/project/my/manage.vue
  24. 2 2
      resources/assets/js/main/components/project/todo/attention.vue
  25. 2 2
      resources/assets/js/main/components/project/todo/complete.vue
  26. 2 2
      resources/assets/js/main/components/project/users.vue
  27. 43 3
      resources/assets/js/main/pages/docs.vue
  28. 218 29
      resources/assets/js/main/pages/docs/edit.vue
  29. 362 0
      resources/assets/js/main/pages/docs/view.vue
  30. 11 1
      resources/assets/js/main/pages/index.vue
  31. 5 0
      resources/assets/js/main/routes.js
  32. 1 1
      resources/assets/statics/public/css/iview.css
  33. 54 1
      resources/assets/statics/public/js/grapheditor/index.html
  34. 81 58
      resources/assets/statics/public/js/grapheditor/viewer.html
  35. 23 2
      resources/lang/en/general.js
  36. 6 0
      resources/lang/en/general.php

+ 263 - 42
app/Http/Controllers/Api/DocsController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
 
 use App\Http\Controllers\Controller;
 use App\Module\Base;
+use App\Module\Docs;
 use App\Module\Users;
 use App\Tasks\PushTask;
 use Cache;
@@ -43,6 +44,18 @@ class DocsController extends Controller
         }
         //
         $lists = DB::table('docs_book')
+            ->where('role_edit', 'reg')
+            ->orWhere(function ($query) use ($user) {
+                $query->where('role_edit', 'private')->where('username', $user['username']);
+            })
+            ->orWhere(function ($query) use ($user) {
+                $query->where('role_edit', 'member')->whereIn('id', function ($query2) use ($user) {
+                    $query2->select('bookid')
+                        ->from('docs_users')
+                        ->where('username', $user['username'])
+                        ->whereRaw(env('DB_PREFIX') . 'docs_book.id = bookid');
+                });
+            })
             ->orderByDesc('id')
             ->paginate(Min(Max(Base::nullShow(Request::input('pagesize'), 10), 1), 100));
         $lists = Base::getPageList($lists);
@@ -55,6 +68,7 @@ class DocsController extends Controller
     /**
      * 添加/修改知识库
      *
+     * @apiParam {Number} id                知识库数据ID
      * @apiParam {String} title             知识库名称
      */
     public function book__add()
@@ -68,6 +82,10 @@ class DocsController extends Controller
         //
         $id = intval(Request::input('id'));
         $title = trim(Request::input('title'));
+        $role = Docs::checkRole($id, 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
         if (mb_strlen($title) < 2 || mb_strlen($title) > 100) {
             return Base::retError('标题限制2-100个字!');
         }
@@ -77,9 +95,6 @@ class DocsController extends Controller
             if (empty($row)) {
                 return Base::retError('知识库不存在或已被删除!');
             }
-            if ($row['username'] != $user['username']) {
-                return Base::retError('此操作仅限知识库负责人!');
-            }
             $data = [
                 'title' => $title,
             ];
@@ -102,6 +117,48 @@ class DocsController extends Controller
     }
 
     /**
+     * 设置知识库
+     *
+     * @apiParam {Number} id                知识库数据ID
+     * @apiParam {String} role_edit
+     * @apiParam {String} role_view
+     */
+    public function book__setting()
+    {
+        $user = Users::authE();
+        if (Base::isError($user)) {
+            return $user;
+        } else {
+            $user = $user['data'];
+        }
+        //
+        $id = intval(Request::input('id'));
+        $role = Docs::checkRole($id, 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
+        $row = Base::DBC2A(DB::table('docs_book')->where('id', $id)->first());
+        if (empty($row)) {
+            return Base::retError('知识库不存在或已被删除!');
+        }
+        $setting = Base::string2array($row['setting']);
+        $type = trim(Request::input('type'));
+        if ($type == 'save') {
+            foreach (Request::input() AS $key => $value) {
+                if (in_array($key, ['role_edit', 'role_view'])) {
+                    $setting[$key] = $value;
+                }
+            }
+            DB::table('docs_book')->where('id', $id)->update([
+                'role_edit' => $setting['role_edit'],
+                'role_view' => $setting['role_view'],
+                'setting' => Base::array2string($setting),
+            ]);
+        }
+        return Base::retSuccess($type == 'save' ? '修改成功!' : 'success', $setting ?: json_decode('{}'));
+    }
+
+    /**
      * 删除知识库
      *
      * @apiParam {Number} id                知识库数据ID
@@ -130,11 +187,13 @@ class DocsController extends Controller
     }
 
     /**
-     * 章节列表
+     * 成员-列表
      *
-     * @apiParam {Number} bookid                知识库数据ID
+     * @apiParam {Number} id            知识库数据ID
+     * @apiParam {Number} [page]        当前页,默认:1
+     * @apiParam {Number} [pagesize]    每页显示数量,默认:20,最大:100
      */
-    public function section__lists()
+    public function users__lists()
     {
         $user = Users::authE();
         if (Base::isError($user)) {
@@ -143,8 +202,120 @@ class DocsController extends Controller
             $user = $user['data'];
         }
         //
+        $id = intval(Request::input('id'));
+        $role = Docs::checkRole($id, 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
+        $row = Base::DBC2A(DB::table('docs_book')->where('id', $id)->first());
+        if (empty($row)) {
+            return Base::retError('知识库不存在或已被删除!');
+        }
+        //
+        $lists = DB::table('docs_book')
+            ->join('docs_users', 'docs_book.id', '=', 'docs_users.bookid')
+            ->select(['docs_book.title', 'docs_users.*'])
+            ->where([
+                ['docs_book.id', $id],
+            ])
+            ->orderByDesc('docs_users.id')->paginate(Min(Max(Base::nullShow(Request::input('pagesize'), 10), 1), 100));
+        $lists = Base::getPageList($lists);
+        if ($lists['total'] == 0) {
+            return Base::retError('未找到任何相关的成员');
+        }
+        foreach ($lists['lists'] AS $key => $item) {
+            $userInfo = Users::username2basic($item['username']);
+            $lists['lists'][$key]['userimg'] = $userInfo['userimg'];
+            $lists['lists'][$key]['nickname'] = $userInfo['nickname'];
+            $lists['lists'][$key]['profession'] = $userInfo['profession'];
+        }
+        return Base::retSuccess('success', $lists);
+    }
+
+    /**
+     * 成员-添加、删除
+     *
+     * @apiParam {String} act
+     * - delete: 删除成员
+     * - else: 添加成员
+     * @apiParam {Number} id                    知识库数据ID
+     * @apiParam {Array|String} username        用户名(或用户名组)
+     */
+    public function users__join()
+    {
+        $user = Users::authE();
+        if (Base::isError($user)) {
+            return $user;
+        } else {
+            $user = $user['data'];
+        }
+        //
+        $id = intval(Request::input('id'));
+        $role = Docs::checkRole($id, 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
+        $row = Base::DBC2A(DB::table('docs_book')->where('id', $id)->first());
+        if (empty($row)) {
+            return Base::retError('知识库不存在或已被删除!');
+        }
+        //
+        $usernames = Request::input('username');
+        if (empty($usernames)) {
+            return Base::retError('参数错误!');
+        }
+        if (!is_array($usernames)) {
+            if (Base::strExists($usernames, ',')) {
+                $usernames = explode(',', $usernames);
+            } else {
+                $usernames = [$usernames];
+            }
+        }
+        //
+        foreach ($usernames AS $username) {
+            $inRow = Base::DBC2A(DB::table('docs_users')->where(['bookid' => $id, 'username' => $username])->first());
+            switch (Request::input('act')) {
+                case 'delete': {
+                    if ($inRow) {
+                        DB::table('docs_users')->where([
+                            'bookid' => $id,
+                            'username' => $username
+                        ])->delete();
+                    }
+                    break;
+                }
+                default: {
+                    if (!$inRow && $username != $user['username']) {
+                        DB::table('docs_users')->insert([
+                            'bookid' => $id,
+                            'username' => $username,
+                            'indate' => Base::time()
+                        ]);
+                    }
+                    break;
+                }
+            }
+        }
+        return Base::retSuccess('操作完成!');
+    }
+
+    /**
+     * 章节列表
+     *
+     * @apiParam {String} act                   请求方式,用于判断权限
+     * - edit: 管理页请求
+     * - view: 阅读页请求
+     * @apiParam {Number} bookid                知识库数据ID
+     */
+    public function section__lists()
+    {
+        $bookid = intval(Request::input('bookid'));
+        $role = Docs::checkRole($bookid, Request::input('act'));
+        if (Base::isError($role)) {
+            return $role;
+        }
         $lists = Base::DBC2A(DB::table('docs_section')
-            ->where('bookid', intval(Request::input('bookid')))
+            ->where('bookid', $bookid)
             ->orderByDesc('inorder')
             ->orderByDesc('id')
             ->take(500)
@@ -155,7 +326,11 @@ class DocsController extends Controller
         foreach ($lists AS $key => $item) {
             $lists[$key]['icon'] = Base::fillUrl('images/files/' . $item['type'] . '.png');
         }
-        return Base::retSuccess('success', Base::list2Tree($lists, 'id', 'parentid'));
+        $bookDetail = Base::DBC2A(DB::table('docs_book')->select(['title'])->where('id', $bookid)->first());
+        return Base::retSuccess('success', [
+            'book' => $bookDetail ?: json_decode('{}'),
+            'tree' => Base::list2Tree($lists, 'id', 'parentid')
+        ]);
     }
 
     /**
@@ -175,6 +350,10 @@ class DocsController extends Controller
         }
         //
         $bookid = intval(Request::input('bookid'));
+        $role = Docs::checkRole($bookid, 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
         $bookRow = Base::DBC2A(DB::table('docs_book')->where('id', $bookid)->first());
         if (empty($bookRow)) {
             return Base::retError('知识库不存在或已被删除!');
@@ -232,7 +411,7 @@ class DocsController extends Controller
     }
 
     /**
-     * 排序任务
+     * 排序章节
      *
      * @apiParam {Number} bookid                知识库数据ID
      * @apiParam {String} oldsort               旧排序数据
@@ -248,6 +427,10 @@ class DocsController extends Controller
         }
         //
         $bookid = intval(Request::input('bookid'));
+        $role = Docs::checkRole($bookid, 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
         $bookRow = Base::DBC2A(DB::table('docs_book')->where('id', $bookid)->first());
         if (empty($bookRow)) {
             return Base::retError('知识库不存在或已被删除!');
@@ -291,6 +474,10 @@ class DocsController extends Controller
         if (empty($row)) {
             return Base::retError('文档不存在或已被删除!');
         }
+        $role = Docs::checkRole($row['bookid'], 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
         DB::table('docs_section')->where('parentid', $id)->update([ 'parentid' => $row['parentid'] ]);
         DB::table('docs_section')->where('id', $id)->delete();
         DB::table('docs_content')->where('sid', $id)->delete();
@@ -300,7 +487,10 @@ class DocsController extends Controller
     /**
      * 获取章节内容
      *
-     * @apiParam {Number|String} id                章节数据ID(或:章节数据ID-历史数据ID)
+     * @apiParam {String} act                   请求方式,用于判断权限
+     * - edit: 管理页请求
+     * - view: 阅读页请求
+     * @apiParam {Number|String} id             章节数据ID(或:章节数据ID-历史数据ID)
      */
     public function section__content()
     {
@@ -315,6 +505,10 @@ class DocsController extends Controller
         if (empty($row)) {
             return Base::retError('文档不存在或已被删除!');
         }
+        $role = Docs::checkRole($row['bookid'], Request::input('act'));
+        if (Base::isError($role)) {
+            return $role;
+        }
         $whereArray = [];
         if ($hid > 0) {
             $whereArray[] = ['id', '=', $hid];
@@ -346,6 +540,10 @@ class DocsController extends Controller
         if (empty($row)) {
             return Base::retError('文档不存在或已被删除!');
         }
+        $role = Docs::checkRole($row['bookid'], 'view');
+        if (Base::isError($role)) {
+            return $role;
+        }
         //
         $lists = Base::DBC2A(DB::table('docs_content')
             ->where('sid', $id)
@@ -379,6 +577,13 @@ class DocsController extends Controller
         if (empty($row)) {
             return Base::retError('文档不存在或已被删除!');
         }
+        $role = Docs::checkRole($row['bookid'], 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
+        if ($row['lockdate'] + 60 > Base::time() && $row['lockname'] != $user['username']) {
+            return Base::retError(['已被会员【%】锁定!', Users::nickname($row['lockname'])]);
+        }
         $D = Base::getContentsParse('D');
         DB::table('docs_content')->insert([
             'bookid' => $row['bookid'],
@@ -387,40 +592,56 @@ class DocsController extends Controller
             'username' => $user['username'],
             'indate' => Base::time()
         ]);
-        //通知正在编辑的成员
-        $sid = $id;
-        $array = Base::json2array(Cache::get("docs::" . $sid));
-        if ($array) {
-            foreach ($array as $uname => $vbody) {
-                if (intval($vbody['indate']) + 20 < time()) {
-                    unset($array[$uname]);
-                }
-            }
+        Docs::notice($id, [ 'type' => 'update' ]);
+        //
+        return Base::retSuccess('保存成功!');
+    }
+
+    /**
+     * 保存章节内容
+     *
+     * @apiParam {String} act
+     * - lock: 锁定
+     * - unlock: 解锁
+     * @apiParam {Number} id                章节数据ID
+     */
+    public function section__lock()
+    {
+        $user = Users::authE();
+        if (Base::isError($user)) {
+            return $user;
+        } else {
+            $user = $user['data'];
         }
-        $pushLists = [];
-        foreach ($array AS $tuser) {
-            $uLists = Base::DBC2A(DB::table('ws')->select(['fd', 'username', 'channel'])->where('username', $tuser['username'])->get());
-            foreach ($uLists AS $item) {
-                if ($item['username'] == $user['username']) {
-                    continue;
-                }
-                $pushLists[] = [
-                    'fd' => $item['fd'],
-                    'msg' => [
-                        'messageType' => 'docs',
-                        'body' => [
-                            'type' => 'update',
-                            'sid' => $sid,
-                            'nickname' => $user['nickname'] ?: $user['username'],
-                            'time' => time(),
-                        ]
-                    ]
-                ];
-            }
+        //
+        $id = intval(Request::input('id'));
+        $act = trim(Request::input('act'));
+        $row = Base::DBC2A(DB::table('docs_section')->where('id', $id)->first());
+        if (empty($row)) {
+            return Base::retError('文档不存在或已被删除!');
         }
-        $pushTask = new PushTask($pushLists);
-        Task::deliver($pushTask);
+        $role = Docs::checkRole($row['bookid'], 'edit');
+        if (Base::isError($role)) {
+            return $role;
+        }
+        if ($row['lockdate'] + 60 > Base::time() && $row['lockname'] != $user['username']) {
+            return Base::retError(['已被会员【%】锁定!', Users::nickname($row['lockname'])]);
+        }
+        if ($act == 'lock') {
+            $upArray = [
+                'lockname' => $user['username'],
+                'lockdate' => Base::time(),
+            ];
+        } else {
+            $upArray = [
+                'lockname' => '',
+                'lockdate' => 0,
+            ];
+        }
+        DB::table('docs_section')->where('id', $id)->update($upArray);
+        $upArray['type'] = $act;
+        Docs::notice($id, $upArray);
         //
-        return Base::retSuccess('保存成功!');
+        return Base::retSuccess($act == 'lock' ? '锁定成功' : '已解除锁定', $upArray);
     }
 }

+ 3 - 0
app/Http/Controllers/Api/ProjectController.php

@@ -825,6 +825,9 @@ class ProjectController extends Controller
                 }
             }
         }
+        if ($logArray) {
+            DB::table('project_log')->insert($logArray);
+        }
         return Base::retSuccess('操作完成!');
     }
 

+ 8 - 3
app/Http/Controllers/Api/UsersController.php

@@ -141,12 +141,13 @@ class UsersController extends Controller
      *
      * @apiParam {Object} where            搜索条件
      * - where.usernameequal
-     * - where.username
      * - where.nousername
-     * - where.identity
+     * - where.username
      * - where.noidentity
-     * - where.projectid
+     * - where.identity
      * - where.noprojectid
+     * - where.projectid
+     * - where.nobookid
      * @apiParam {Number} [take]           获取数量,10-100
      */
     public function searchinfo()
@@ -182,6 +183,10 @@ class UsersController extends Controller
             $whereRaw.= $whereRaw ? ' AND ' : '';
             $whereRaw.= "`username` NOT IN (SELECT username FROM `" . env('DB_PREFIX') . "project_users` WHERE `type`='成员' AND `projectid`=" . intval($keys['noprojectid']) .")";
         }
+        if (intval($keys['nobookid']) > 0) {
+            $whereRaw.= $whereRaw ? ' AND ' : '';
+            $whereRaw.= "`username` NOT IN (SELECT username FROM `" . env('DB_PREFIX') . "docs_users` WHERE `bookid`=" . intval($keys['nobookid']) .")";
+        }
         //
         $lists = DBCache::table('users')->select(['id', 'username', 'nickname', 'userimg', 'profession'])
             ->where($whereArr)

+ 99 - 0
app/Module/Docs.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace App\Module;
+
+use App\Tasks\PushTask;
+use Cache;
+use DB;
+use Hhxsv5\LaravelS\Swoole\Task\Task;
+
+/**
+ * Class Docs
+ * @package App\Module
+ */
+class Docs
+{
+    /**
+     * 检验是否有阅读或修改权限
+     * @param $bookid
+     * @param string $checkType     edit|view
+     * @return array|mixed
+     */
+    public static function checkRole($bookid, $checkType = 'edit')
+    {
+        $row = Base::DBC2A(DB::table('docs_book')->where('id', $bookid)->first());
+        if (empty($row)) {
+            return Base::retError('知识库不存在或已被删除!');
+        }
+        $userE = Users::authE();
+        if (Base::isError($userE)) {
+            $user = [];
+        } else {
+            $user = $userE['data'];
+        }
+        $checkType = $checkType == 'edit' ? 'edit' : 'view';
+        if ($checkType == 'edit') {
+            if (empty($user)) {
+                return $userE;
+            }
+        } else {
+            if ($row['role_view'] != 'all') {
+                if (empty($user)) {
+                    return Base::retError('知识库仅对会员开放,请登录后再试!', -1001);
+                }
+            }
+        }
+        if ($row['role_' . $checkType] == 'member') {
+            if (!DB::table('docs_users')->where('bookid', $bookid)->where('username', $user['username'])->exists()) {
+                return Base::retError('知识库仅对成员开放!', -1002);
+            }
+        } elseif ($row['role_' . $checkType] == 'private') {
+            if ($row['username'] != $user['username']) {
+                return Base::retError('知识库仅对作者开放!', -1003);
+            }
+        }
+        //
+        return Base::retSuccess('success');
+    }
+
+    /**
+     * 通知正在编辑的成员
+     *
+     * @param integer $sid      章节ID
+     * @param array $bodyArray  body参数
+     */
+    public static function notice($sid, $bodyArray = [])
+    {
+        $user = Users::auth();
+        $array = Base::json2array(Cache::get("docs::" . $sid));
+        if ($array) {
+            foreach ($array as $uname => $vbody) {
+                if (intval($vbody['indate']) + 20 < time()) {
+                    unset($array[$uname]);
+                }
+            }
+        }
+        $pushLists = [];
+        foreach ($array AS $tuser) {
+            $uLists = Base::DBC2A(DB::table('ws')->select(['fd', 'username', 'channel'])->where('username', $tuser['username'])->get());
+            foreach ($uLists AS $item) {
+                if ($item['username'] == $user['username']) {
+                    continue;
+                }
+                $pushLists[] = [
+                    'fd' => $item['fd'],
+                    'msg' => [
+                        'messageType' => 'docs',
+                        'body' => array_merge([
+                            'sid' => $sid,
+                            'nickname' => $user['nickname'] ?: $user['username'],
+                            'time' => time(),
+                        ], $bodyArray)
+                    ]
+                ];
+            }
+        }
+        $pushTask = new PushTask($pushLists);
+        Task::deliver($pushTask);
+    }
+}

+ 14 - 0
app/Module/Users.php

@@ -291,6 +291,20 @@ class Users
     }
 
     /**
+     * 获取会员昵称
+     * @param $username
+     * @return mixed
+     */
+    public static function nickname($username)
+    {
+        $info = self::username2basic($username);
+        if (empty($info)) {
+            return $username;
+        }
+        return $info['nickname'] ?: $info['username'];
+    }
+
+    /**
      * 用户头像,不存在时返回默认
      * @param string $var 头像地址 或 会员用户名
      * @return \Illuminate\Contracts\Routing\UrlGenerator|string

+ 34 - 0
database/migrations/2020_07_13_104810_create_pre_docs_users_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreatePreDocsUsersTable extends Migration
+{
+	/**
+	 * Run the migrations.
+	 *
+	 * @return void
+	 */
+	public function up()
+	{
+		Schema::create('docs_users', function(Blueprint $table)
+		{
+			$table->increments('id');
+			$table->integer('bookid')->nullable()->default(0)->index('IDEX_bookid')->comment('项目ID');
+			$table->string('username', 100)->nullable()->default('')->index('IDEX_username')->comment('关系用户名');
+			$table->bigInteger('indate')->nullable()->default(0)->comment('添加时间');
+		});
+	}
+
+	/**
+	 * Reverse the migrations.
+	 *
+	 * @return void
+	 */
+	public function down()
+	{
+		Schema::drop('docs_users');
+	}
+}

+ 45 - 0
database/migrations/2020_07_13_105212_alter_pre_docs_book_table.php

@@ -0,0 +1,45 @@
+W<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AlterPreDocsBookTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('docs_book', function (Blueprint $table) {
+            //
+            $table->string('role_edit', 20)->after('title')->nullable()->default('reg')->index('IDEX_role_edit');
+            $table->string('role_view', 20)->after('role_edit')->nullable()->default('all')->index('IDEX_role_view');
+            $table->text('setting')->after('role_view')->nullable();
+        });
+        //
+        $upArray = [
+            'role_edit' => 'reg',
+            'role_view' => 'all'
+        ];
+        DB::table('docs_book')->update(array_merge($upArray, [
+            'setting' => \App\Module\Base::array2string($upArray)
+        ]));
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('docs_book', function (Blueprint $table) {
+            $table->dropColumn('role_edit');
+            $table->dropColumn('role_view');
+            $table->dropColumn('setting');
+        });
+    }
+}

+ 34 - 0
database/migrations/2020_07_13_110335_alter_pre_docs_section_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AlterPreDocsSectionTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('docs_section', function (Blueprint $table) {
+            $table->string('lockname', 100)->after('type')->nullable()->default('')->comment('锁定会员');
+            $table->bigInteger('lockdate')->after('lockname')->nullable()->default(0)->comment('锁定时间');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('docs_section', function (Blueprint $table) {
+            $table->dropColumn('lockname');
+            $table->dropColumn('lockdate');
+        });
+    }
+}

+ 4 - 3
package.json

@@ -26,12 +26,13 @@
         "vue-template-compiler": "^2.6.11"
     },
     "dependencies": {
-        "tinymce": "^5.3.0",
-        "view-design": "^4.2.0",
+        "tinymce": "^5.4.1",
+        "view-design": "^4.3.1",
         "vuedraggable": "^2.23.2",
         "vue-clipboard2": "^0.3.1",
         "vue-emoji-picker": "^1.0.1",
         "vue-kityminder-gg": "^1.3.1",
-        "x-data-spreadsheet": "^1.1.2"
+        "x-data-spreadsheet": "^1.1.4",
+        "xlsx": "^0.16.3"
     }
 }

+ 9 - 0
resources/assets/js/main/components/UserInput.vue

@@ -134,6 +134,9 @@
             projectid: {
                 default: ''
             },
+            nobookid: {
+                default: ''
+            },
             placeholder: {
                 default: ''
             },
@@ -239,6 +242,9 @@
                         if (this.projectid) {
                             where['projectid'] = this.projectid;
                         }
+                        if (this.nobookid) {
+                            where['nobookid'] = this.nobookid;
+                        }
                         this.noDataText = this.$L("数据加载中.....");
                         $A.aAjax({
                             url: window.location.origin + '/api/users/searchinfo',
@@ -375,6 +381,9 @@
                 if (this.projectid) {
                     where['projectid'] = this.projectid;
                 }
+                if (this.nobookid) {
+                    where['nobookid'] = this.nobookid;
+                }
                 this.noDataText = this.$L("数据加载中.....");
                 $A.aAjax({
                     url: window.location.origin + '/api/users/searchinfo',

+ 5 - 5
resources/assets/js/main/components/WHeader.vue

@@ -44,29 +44,29 @@
         </div>
         <WDrawer v-model="systemDrawerShow" maxWidth="640" :title="$L('系统设置')">
             <Form ref="formSystem" :model="formSystem" :label-width="120">
-                <FormItem :label="$L('首页Logo')" prop="userimg">
+                <FormItem :label="$L('首页Logo')">
                     <ImgUpload v-model="formSystem.logo" :num="1"></ImgUpload>
                     <span style="color:#777">{{$L('建议尺寸:%', '300x52')}}</span>
                 </FormItem>
-                <FormItem :label="$L('Github图标')" prop="userimg">
+                <FormItem :label="$L('Github图标')">
                     <RadioGroup v-model="formSystem.github">
                         <Radio label="show">{{$L('显示')}}</Radio>
                         <Radio label="hidden">{{$L('隐藏')}}</Radio>
                     </RadioGroup>
                 </FormItem>
-                <FormItem :label="$L('允许注册')" prop="userimg">
+                <FormItem :label="$L('允许注册')">
                     <RadioGroup v-model="formSystem.reg">
                         <Radio label="open">{{$L('允许')}}</Radio>
                         <Radio label="close">{{$L('禁止')}}</Radio>
                     </RadioGroup>
                 </FormItem>
-                <FormItem :label="$L('音视频通话')" prop="callav">
+                <FormItem :label="$L('音视频通话')">
                     <RadioGroup v-model="formSystem.callav">
                         <Radio label="open">{{$L('开启')}}</Radio>
                         <Radio label="close">{{$L('关闭')}}</Radio>
                     </RadioGroup>
                 </FormItem>
-                <FormItem :label="$L('完成自动归档')" prop="autoArchived">
+                <FormItem :label="$L('完成自动归档')">
                     <RadioGroup :value="formSystem.autoArchived" @on-change="formArchived">
                         <Radio label="open">{{$L('开启')}}</Radio>
                         <Radio label="close">{{$L('关闭')}}</Radio>

+ 36 - 2
resources/assets/js/main/components/docs/flow/index.vue

@@ -35,6 +35,7 @@
     }
 </style>
 <script>
+    import JSPDF from "jspdf";
 
     export default {
         name: "Flow",
@@ -42,13 +43,17 @@
             value: {
                 type: ''
             },
+            readOnly: {
+                type: Boolean,
+                default: false
+            },
         },
         data() {
             return {
                 loadIng: true,
 
                 flow: null,
-                url: window.location.origin + '/js/grapheditor/index.html',
+                url: window.location.origin + '/js/grapheditor/' + (this.readOnly ? 'viewer' : 'index') + '.html',
             }
         },
         mounted() {
@@ -61,7 +66,6 @@
         },
         methods: {
             handleMessage (event) {
-                // 根据上面制定的结构来解析iframe内部发回来的数据
                 const data = event.data;
                 switch (data.act) {
                     case 'ready':
@@ -77,7 +81,37 @@
                     case 'change':
                         this.$emit('input', data.params.xml);
                         break
+
+                    case 'imageContent':
+                        let pdf = new JSPDF({
+                            format: [data.params.width, data.params.height]
+                        });
+                        pdf.addImage(data.params.content, 'PNG', 0, 0, 0, 0);
+                        pdf.save(`${data.params.name}.pdf`);
+                        break
                 }
+            },
+
+            exportPNG(name, scale = 10) {
+                this.flow.postMessage({
+                    act: 'exportPNG',
+                    params: {
+                        name: name || this.$L('无标题'),
+                        scale: scale,
+                        type: 'png',
+                    }
+                }, '*')
+            },
+
+            exportPDF(name, scale = 10) {
+                this.flow.postMessage({
+                    act: 'exportPNG',
+                    params: {
+                        name: name || this.$L('无标题'),
+                        scale: scale,
+                        type: 'imageContent',
+                    }
+                }, '*')
             }
         },
     }

+ 38 - 0
resources/assets/js/main/components/docs/minder/editor.js

@@ -0,0 +1,38 @@
+define(function(require, exports, module) {
+
+    /**
+     * 运行时
+     */
+    var runtimes = [];
+
+    function assemble(runtime) {
+        runtimes.push(runtime);
+    }
+
+    function KMEditor(selector) {
+        this.selector = selector;
+        for (var i = 0; i < runtimes.length; i++) {
+            if (typeof runtimes[i] == 'function') {
+                runtimes[i].call(this, this);
+            }
+        }
+    }
+    KMEditor.assemble = assemble;
+    assemble(require('vue-kityminder-gg/src/runtime/container'));
+    assemble(require('vue-kityminder-gg/src/runtime/fsm'));
+    assemble(require('vue-kityminder-gg/src/runtime/minder'));
+    assemble(require('vue-kityminder-gg/src/runtime/receiver'));
+    assemble(require('vue-kityminder-gg/src/runtime/hotbox'));
+    assemble(require('vue-kityminder-gg/src/runtime/input'));
+    assemble(require('vue-kityminder-gg/src/runtime/clipboard-mimetype'));
+    assemble(require('vue-kityminder-gg/src/runtime/clipboard'));
+    assemble(require('vue-kityminder-gg/src/runtime/drag'));
+    if (window.__minderReadOnly !== true) {
+        assemble(require('vue-kityminder-gg/src/runtime/node'));
+        assemble(require('vue-kityminder-gg/src/runtime/history'));
+        assemble(require('vue-kityminder-gg/src/runtime/jumping'));
+        assemble(require('vue-kityminder-gg/src/runtime/priority'));
+        assemble(require('vue-kityminder-gg/src/runtime/progress'));
+    }
+    return module.exports = KMEditor;
+});

+ 61 - 38
resources/assets/js/main/components/docs/minder/minder.vue

@@ -5,40 +5,40 @@
                 <i class="ft icon" :title="$L('缩放')">&#xE7B3;</i>
                 <div slot="content">
                     <ul class="quickul">
-                        <li @click="minder.execCommand('Zoom', 200)">200%</li>
-                        <li @click="minder.execCommand('Zoom', 150)">150%</li>
-                        <li @click="minder.execCommand('Zoom', 100)">100%</li>
-                        <li @click="minder.execCommand('Zoom', 50)">50%</li>
-                        <li @click="minder.execCommand('Zoom', 25)">25%</li>
+                        <li @click="execCommand('Zoom', 200)">200%</li>
+                        <li @click="execCommand('Zoom', 150)">150%</li>
+                        <li @click="execCommand('Zoom', 100)">100%</li>
+                        <li @click="execCommand('Zoom', 50)">50%</li>
+                        <li @click="execCommand('Zoom', 25)">25%</li>
                     </ul>
                 </div>
             </Tooltip>
-            <Tooltip placement="top" theme="light">
+            <Tooltip v-if="readOnly!==true" placement="top" theme="light">
                 <i class="ft icon" :title="$L('图形')">&#xE621;</i>
                 <div slot="content">
                     <ul class="quickul mold">
-                        <li @click="minder.execCommand('template', 'default')"><span class="default"></span></li>
-                        <li @click="minder.execCommand('template', 'structure')"><span class="structure"></span></li>
-                        <li @click="minder.execCommand('template', 'filetree')"><span class="filetree"></span></li>
-                        <li @click="minder.execCommand('template', 'right')"><span class="right"></span></li>
-                        <li @click="minder.execCommand('template', 'fish-bone')"><span class="fish-bone"></span></li>
-                        <li @click="minder.execCommand('template', 'tianpan')"><span class="tianpan"></span></li>
+                        <li @click="execCommand('template', 'default')"><span class="default"></span></li>
+                        <li @click="execCommand('template', 'structure')"><span class="structure"></span></li>
+                        <li @click="execCommand('template', 'filetree')"><span class="filetree"></span></li>
+                        <li @click="execCommand('template', 'right')"><span class="right"></span></li>
+                        <li @click="execCommand('template', 'fish-bone')"><span class="fish-bone"></span></li>
+                        <li @click="execCommand('template', 'tianpan')"><span class="tianpan"></span></li>
                     </ul>
                 </div>
             </Tooltip>
-            <Tooltip placement="top" theme="light">
+            <Tooltip v-if="readOnly!==true" placement="top" theme="light">
                 <i class="ft icon" :title="$L('样式')">&#xE678;</i>
                 <div slot="content">
                     <ul class="quickul">
-                        <li @click="minder.execCommand('theme', 'fresh-blue')">{{$L('天空蓝')}}</li>
-                        <li @click="minder.execCommand('theme', 'wire')">{{$L('线框')}}</li>
-                        <li @click="minder.execCommand('theme', 'fish')">{{$L('鱼骨图')}}</li>
-                        <li @click="minder.execCommand('theme', 'classic')">{{$L('脑图经典')}}</li>
-                        <li @click="minder.execCommand('theme', 'classic-compact')">{{$L('紧凑经典')}}</li>
-                        <li @click="minder.execCommand('theme', 'snow')">{{$L('温柔冷光')}}</li>
-                        <li @click="minder.execCommand('theme', 'snow-compact')">{{$L('紧凑冷光')}}</li>
-                        <li @click="minder.execCommand('theme', 'tianpan')">{{$L('经典天盘')}}</li>
-                        <li @click="minder.execCommand('theme', 'tianpan-compact')">{{$L('紧凑天盘')}}</li>
+                        <li @click="execCommand('theme', 'fresh-blue')">{{$L('天空蓝')}}</li>
+                        <li @click="execCommand('theme', 'wire')">{{$L('线框')}}</li>
+                        <li @click="execCommand('theme', 'fish')">{{$L('鱼骨图')}}</li>
+                        <li @click="execCommand('theme', 'classic')">{{$L('脑图经典')}}</li>
+                        <li @click="execCommand('theme', 'classic-compact')">{{$L('紧凑经典')}}</li>
+                        <li @click="execCommand('theme', 'snow')">{{$L('温柔冷光')}}</li>
+                        <li @click="execCommand('theme', 'snow-compact')">{{$L('紧凑冷光')}}</li>
+                        <li @click="execCommand('theme', 'tianpan')">{{$L('经典天盘')}}</li>
+                        <li @click="execCommand('theme', 'tianpan-compact')">{{$L('紧凑天盘')}}</li>
                     </ul>
                 </div>
             </Tooltip>
@@ -46,12 +46,12 @@
                 <i class="ft icon" :title="$L('折叠')">&#xE779;</i>
                 <div slot="content">
                     <ul class="quickul">
-                        <li @click="minder.execCommand('ExpandToLevel', 1)">{{$L('展开到一级节点')}}</li>
-                        <li @click="minder.execCommand('ExpandToLevel', 2)">{{$L('展开到二级节点')}}</li>
-                        <li @click="minder.execCommand('ExpandToLevel', 3)">{{$L('展开到三级节点')}}</li>
-                        <li @click="minder.execCommand('ExpandToLevel', 4)">{{$L('展开到四级节点')}}</li>
-                        <li @click="minder.execCommand('ExpandToLevel', 5)">{{$L('展开到五级节点')}}</li>
-                        <li @click="minder.execCommand('ExpandToLevel', 99)">{{$L('展开全部节点')}}</li>
+                        <li @click="execCommand('ExpandToLevel', 1)">{{$L('展开到一级节点')}}</li>
+                        <li @click="execCommand('ExpandToLevel', 2)">{{$L('展开到二级节点')}}</li>
+                        <li @click="execCommand('ExpandToLevel', 3)">{{$L('展开到三级节点')}}</li>
+                        <li @click="execCommand('ExpandToLevel', 4)">{{$L('展开到四级节点')}}</li>
+                        <li @click="execCommand('ExpandToLevel', 5)">{{$L('展开到五级节点')}}</li>
+                        <li @click="execCommand('ExpandToLevel', 99)">{{$L('展开全部节点')}}</li>
                     </ul>
                 </div>
             </Tooltip>
@@ -61,9 +61,6 @@
             <Tooltip placement="top" :content="$L('移动')">
                 <div @click="[minder.execCommand('Hand'),isHand=!isHand]"><i class="ft icon" :class="{active:isHand}">&#xE6CF;</i></div>
             </Tooltip>
-            <Tooltip placement="top" :content="$L('导出PNG图片')">
-                <div @click="exportHandle(0)"><Icon type="md-photos"/></div>
-            </Tooltip>
         </div>
         <div :id="id"></div>
     </div>
@@ -169,7 +166,6 @@
     }
 </style>
 <script>
-    const Editor = require('vue-kityminder-gg/src/editor');
     import {generateMixed} from 'vue-kityminder-gg/src/utils/index.js';
     import 'vue-kityminder-gg/examples/styles/minder.css';
     import JSPDF from 'jspdf';
@@ -207,6 +203,10 @@
                 type: Boolean,
                 default: true
             },
+            readOnly: {
+                type: Boolean,
+                default: false
+            },
             id: {
                 type: String,
                 default: 'minder-component-' + generateMixed(12)
@@ -219,23 +219,39 @@
             };
         },
         methods: {
-            exportHandle(n) {
-                if (n === 0) {
+            execCommand(var1, var2) {
+                if (this.readOnly === true) {
+                    this.minder.enable();
+                    this.$nextTick(() => {
+                        this.minder.execCommand(var1, var2);
+                        this.$nextTick(() => {
+                            this.minder.disable();
+                            if (this.isHand) {
+                                this.minder.execCommand('Hand');
+                            }
+                        });
+                    });
+                } else {
+                    this.minder.execCommand(var1, var2);
+                }
+            },
+            exportHandle(n, filename) {
+                filename = filename || (this.value.root.data.text || this.$L('无标题'));
+                if (n === 0 || n === 'png') {
                     this.minder.exportData('png').then((content) => {
                         let element = document.createElement('a');
                         element.setAttribute('href', content);
-                        let filename = this.value.root.data.text || this.$L('无标题');
                         element.setAttribute('download', filename);
                         element.style.display = 'none';
                         document.body.appendChild(element);
                         element.click();
                         document.body.removeChild(element);
                     });
-                } else if (n === 1) {
+                } else if (n === 1 || n === 'pdf') {
                     this.minder.exportData('png').then((content) => {
-                        var doc = new JSPDF();
+                        let doc = new JSPDF();
                         doc.addImage(content, 'PNG', 0, 0, 0, 0);
-                        doc.save(`${this.value.root.data.text || this.$L('无标题')}.pdf`);
+                        doc.save(`${filename}.pdf`);
                     });
                 }
             },
@@ -245,8 +261,15 @@
                         if (this.minder !== null) {
                             return;
                         }
+                        window.__minderReadOnly = this.readOnly;
+                        const Editor = require('./editor');
                         this.minder = window.editor = new Editor(document.getElementById(this.id)).minder;
                         this.minder.importJson(this.value);
+                        if (this.readOnly === true) {
+                            this.minder.disable();
+                            this.minder.execCommand('Hand');
+                            this.isHand = true;
+                        }
                         this.$emit('minderHandle', this.minder);
                         this.minder.on('contentchange', val => {
                             this.$emit('input', this.minder.exportJson());

+ 122 - 0
resources/assets/js/main/components/docs/setting.vue

@@ -0,0 +1,122 @@
+<template>
+    <drawer-tabs-container>
+        <div class="book-setting">
+            <Form ref="formSystem" :model="formSystem" :label-width="80">
+                <FormItem :label="$L('修改权限')">
+                    <RadioGroup v-model="formSystem.role_edit">
+                        <Radio label="private">{{$L('私有文库')}}</Radio>
+                        <Radio label="member">{{$L('成员开放')}}</Radio>
+                        <Radio label="reg">{{$L('注册会员')}}</Radio>
+                    </RadioGroup>
+                </FormItem>
+                <FormItem :label="$L('阅读权限')">
+                    <RadioGroup v-model="formSystem.role_view">
+                        <Radio label="private">{{$L('私有文库')}}</Radio>
+                        <Radio label="member">{{$L('成员开放')}}</Radio>
+                        <Radio label="reg">{{$L('注册会员')}}</Radio>
+                        <Radio label="all">{{$L('完全开放')}}</Radio>
+                    </RadioGroup>
+                </FormItem>
+                <FormItem>
+                    <Button :loading="loadIng > 0" type="primary" @click="handleSubmit('formSystem')">{{$L('提交')}}</Button>
+                    <Button :loading="loadIng > 0" @click="handleReset('formSystem')" style="margin-left: 8px">{{$L('重置')}}</Button>
+                </FormItem>
+            </Form>
+        </div>
+    </drawer-tabs-container>
+</template>
+
+<style lang="scss" scoped>
+    .book-setting {
+        padding: 0 12px;
+    }
+</style>
+<script>
+    import DrawerTabsContainer from "../DrawerTabsContainer";
+    export default {
+        name: 'BookSetting',
+        components: {DrawerTabsContainer},
+        props: {
+            id: {
+                default: 0
+            },
+            canload: {
+                type: Boolean,
+                default: true
+            },
+        },
+        data () {
+            return {
+                loadYet: false,
+
+                loadIng: 0,
+
+                formSystem: {},
+            }
+        },
+        mounted() {
+            if (this.canload) {
+                this.loadYet = true;
+                this.getSetting();
+            }
+        },
+
+        watch: {
+            id() {
+                if (this.loadYet) {
+                    this.getSetting();
+                }
+            },
+            canload(val) {
+                if (val && !this.loadYet) {
+                    this.loadYet = true;
+                    this.getSetting();
+                }
+            }
+        },
+
+        methods: {
+            getSetting(save) {
+                this.loadIng++;
+                $A.aAjax({
+                    url: 'docs/book/setting?type=' + (save ? 'save' : 'get'),
+                    data: Object.assign(this.formSystem, {
+                        id: this.id
+                    }),
+                    complete: () => {
+                        this.loadIng--;
+                    },
+                    success: (res) => {
+                        if (res.ret === 1) {
+                            this.formSystem = res.data;
+                            this.formSystem.role_edit = this.formSystem.role_edit || 'reg';
+                            this.formSystem.role_view = this.formSystem.role_view || 'all';
+                            if (save) {
+                                this.$Message.success(this.$L('修改成功'));
+                            }
+                        } else {
+                            if (save) {
+                                this.$Modal.error({title: this.$L('温馨提示'), content: res.msg });
+                            }
+                        }
+                    }
+                });
+            },
+            handleSubmit(name) {
+                this.$refs[name].validate((valid) => {
+                    if (valid) {
+                        switch (name) {
+                            case "formSystem": {
+                                this.getSetting(true);
+                                break;
+                            }
+                        }
+                    }
+                })
+            },
+            handleReset(name) {
+                this.$refs[name].resetFields();
+            },
+        }
+    }
+</script>

+ 57 - 4
resources/assets/js/main/components/docs/sheet/index.vue

@@ -1,7 +1,18 @@
 <template>
-    <div ref="xspreadsheet" class="xspreadsheet"></div>
+    <div ref="xspreadsheet" class="xspreadsheet" :class="{'xspreadsheet-readonly':readOnly}"></div>
 </template>
 
+<style lang="scss">
+    .xspreadsheet-readonly {
+        .x-spreadsheet-menu {
+            > li:first-child {
+              > div.x-spreadsheet-icon {
+                  display: none;
+              }
+            }
+        }
+    }
+</style>
 <style lang="scss" scoped>
     .xspreadsheet {
         position: absolute;
@@ -15,6 +26,7 @@
 <script>
     import Spreadsheet from 'x-data-spreadsheet';
     import zhCN from 'x-data-spreadsheet/dist/locale/zh-cn';
+    import XLSX from 'xlsx';
 
     export default {
         name: "Sheet",
@@ -25,6 +37,10 @@
                     return {}
                 }
             },
+            readOnly: {
+                type: Boolean,
+                default: false
+            },
         },
         data() {
             return {
@@ -36,7 +52,7 @@
         mounted() {
             Spreadsheet.locale('zh-cn', zhCN);
             //
-            this.sheet = new Spreadsheet(this.$refs.xspreadsheet, {
+            let options = {
                 view: {
                     height: () => {
                         try {
@@ -53,11 +69,48 @@
                         }
                     },
                 },
-            }).loadData(this.value).change(data => {
-                this.$emit('input', data);
+            };
+            if (this.readOnly) {
+                options.mode = 'read'
+                options.showToolbar = false
+                options.showContextmenu = false;
+            }
+            this.sheet = new Spreadsheet(this.$refs.xspreadsheet, options).loadData(this.value).change(data => {
+                if (!this.readOnly) {
+                    this.$emit('input', data);
+                }
             });
             //
             this.sheet.validate()
         },
+        methods: {
+            exportExcel(name, bookType){
+                var new_wb = this.xtos(this.sheet.getData());
+                XLSX.writeFile(new_wb, name + "." + (bookType == 'xlml' ? 'xls' : bookType), {
+                    bookType: bookType || "xlsx"
+                });
+            },
+
+            xtos(sdata) {
+                var out = XLSX.utils.book_new();
+                sdata.forEach(function(xws) {
+                    var aoa = [[]];
+                    var rowobj = xws.rows;
+                    for(var ri = 0; ri < rowobj.len; ++ri) {
+                        var row = rowobj[ri];
+                        if(!row) continue;
+                        aoa[ri] = [];
+                        Object.keys(row.cells).forEach(function(k) {
+                            var idx = +k;
+                            if(isNaN(idx)) return;
+                            aoa[ri][idx] = row.cells[k].text;
+                        });
+                    }
+                    var ws = XLSX.utils.aoa_to_sheet(aoa);
+                    XLSX.utils.book_append_sheet(out, ws, xws.name);
+                });
+                return out;
+            },
+        }
     }
 </script>

+ 283 - 0
resources/assets/js/main/components/docs/users.vue

@@ -0,0 +1,283 @@
+<template>
+    <drawer-tabs-container>
+        <div class="book-users">
+            <!-- 按钮 -->
+            <Button :loading="loadIng > 0" type="primary" icon="md-add" @click="addUser">{{$L('添加成员')}}</Button>
+            <!-- 列表 -->
+            <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
+            <!-- 分页 -->
+            <Page class="pageBox" :total="listTotal" :current="listPage" :disabled="loadIng > 0" @on-change="setPage" @on-page-size-change="setPageSize" :page-size-opts="[10,20,30,50,100]" placement="top" show-elevator show-sizer show-total transfer></Page>
+        </div>
+    </drawer-tabs-container>
+</template>
+
+<style lang="scss" scoped>
+    .book-users {
+        padding: 0 12px;
+        .tableFill {
+            margin: 12px 0 20px;
+        }
+    }
+</style>
+<script>
+    import DrawerTabsContainer from "../DrawerTabsContainer";
+    export default {
+        name: 'BookUsers',
+        components: {DrawerTabsContainer},
+        props: {
+            id: {
+                default: 0
+            },
+            canload: {
+                type: Boolean,
+                default: true
+            },
+        },
+        data () {
+            return {
+                loadYet: false,
+
+                loadIng: 0,
+
+                columns: [],
+
+                lists: [],
+                listPage: 1,
+                listTotal: 0,
+                noDataText: "",
+            }
+        },
+        created() {
+            this.noDataText = this.$L("数据加载中.....");
+            this.columns = [{
+                "title": this.$L("头像"),
+                "minWidth": 60,
+                "maxWidth": 100,
+                render: (h, params) => {
+                    return h('img', {
+                        style: {
+                            width: "32px",
+                            height: "32px",
+                            verticalAlign: "middle",
+                            objectFit: "cover",
+                            borderRadius: "50%"
+                        },
+                        attrs: {
+                            src: params.row.userimg
+                        },
+                    });
+                }
+            }, {
+                "title": this.$L("用户名"),
+                "key": 'username',
+                "minWidth": 80,
+                "ellipsis": true,
+            }, {
+                "title": this.$L("昵称"),
+                "minWidth": 80,
+                "ellipsis": true,
+                render: (h, params) => {
+                    return h('span', params.row.nickname || '-');
+                }
+            }, {
+                "title": this.$L("职位/职称"),
+                "minWidth": 100,
+                "ellipsis": true,
+                render: (h, params) => {
+                    return h('span', params.row.profession || '-');
+                }
+            }, {
+                "title": this.$L("加入时间"),
+                "width": 160,
+                render: (h, params) => {
+                    return h('span', $A.formatDate("Y-m-d H:i:s", params.row.indate));
+                }
+            }, {
+                "title": this.$L("操作"),
+                "key": 'action',
+                "width": 80,
+                "align": 'center',
+                render: (h, params) => {
+                    return h('Button', {
+                        props: {
+                            type: 'primary',
+                            size: 'small'
+                        },
+                        style: {
+                            fontSize: '12px'
+                        },
+                        on: {
+                            click: () => {
+                                this.$Modal.confirm({
+                                    title: this.$L('移出成员'),
+                                    content: this.$L('你确定要将此成员移出项目吗?'),
+                                    loading: true,
+                                    onOk: () => {
+                                        $A.aAjax({
+                                            url: 'docs/users/join',
+                                            data: {
+                                                act: 'delete',
+                                                id: params.row.id,
+                                                username: params.row.username,
+                                            },
+                                            error: () => {
+                                                this.$Modal.remove();
+                                                alert(this.$L('网络繁忙,请稍后再试!'));
+                                            },
+                                            success: (res) => {
+                                                this.$Modal.remove();
+                                                this.getLists();
+                                                setTimeout(() => {
+                                                    if (res.ret === 1) {
+                                                        this.$Message.success(res.msg);
+                                                    }else{
+                                                        this.$Modal.error({title: this.$L('温馨提示'), content: res.msg });
+                                                    }
+                                                }, 350);
+                                            }
+                                        });
+                                    }
+                                });
+                            }
+                        }
+                    }, this.$L('删除'));
+                }
+            }];
+        },
+        mounted() {
+            if (this.canload) {
+                this.loadYet = true;
+                this.getLists(true);
+            }
+        },
+
+        watch: {
+            id() {
+                if (this.loadYet) {
+                    this.getLists(true);
+                }
+            },
+            canload(val) {
+                if (val && !this.loadYet) {
+                    this.loadYet = true;
+                    this.getLists(true);
+                }
+            }
+        },
+
+        methods: {
+            setPage(page) {
+                this.listPage = page;
+                this.getLists();
+            },
+
+            setPageSize(size) {
+                if (Math.max($A.runNum(this.listPageSize), 10) != size) {
+                    this.listPageSize = size;
+                    this.getLists();
+                }
+            },
+
+            getLists(resetLoad) {
+                if (resetLoad === true) {
+                    this.listPage = 1;
+                }
+                if (this.id == 0) {
+                    this.lists = [];
+                    this.listTotal = 0;
+                    this.noDataText = this.$L("没有相关的数据");
+                    return;
+                }
+                this.loadIng++;
+                this.noDataText = this.$L("数据加载中.....");
+                $A.aAjax({
+                    url: 'docs/users/lists',
+                    data: {
+                        page: Math.max(this.listPage, 1),
+                        pagesize: Math.max($A.runNum(this.listPageSize), 10),
+                        id: this.id,
+                    },
+                    complete: () => {
+                        this.loadIng--;
+                    },
+                    error: () => {
+                        this.noDataText = this.$L("数据加载失败!");
+                    },
+                    success: (res) => {
+                        if (res.ret === 1) {
+                            this.lists = res.data.lists;
+                            this.listTotal = res.data.total;
+                            this.noDataText = this.$L("没有相关的数据");
+                        } else {
+                            this.lists = [];
+                            this.listTotal = 0;
+                            this.noDataText = res.msg;
+                        }
+                    }
+                });
+            },
+
+            addUser() {
+                this.userValue = "";
+                this.$Modal.confirm({
+                    render: (h) => {
+                        return h('div', [
+                            h('div', {
+                                style: {
+                                    fontSize: '16px',
+                                    fontWeight: '500',
+                                    marginBottom: '20px',
+                                }
+                            }, this.$L('添加成员')),
+                            h('UserInput', {
+                                props: {
+                                    value: this.userValue,
+                                    multiple: true,
+                                    nousername: $A.getUserName(),
+                                    nobookid: this.id,
+                                    placeholder: this.$L('请输入昵称/用户名搜索')
+                                },
+                                on: {
+                                    input: (val) => {
+                                        this.userValue = val;
+                                    }
+                                }
+                            })
+                        ])
+                    },
+                    loading: true,
+                    onOk: () => {
+                        if (this.userValue) {
+                            let username = this.userValue;
+                            $A.aAjax({
+                                url: 'docs/users/join',
+                                data: {
+                                    act: 'join',
+                                    id: this.id,
+                                    username: username,
+                                },
+                                error: () => {
+                                    this.$Modal.remove();
+                                    alert(this.$L('网络繁忙,请稍后再试!'));
+                                },
+                                success: (res) => {
+                                    this.$Modal.remove();
+                                    this.getLists();
+                                    setTimeout(() => {
+                                        if (res.ret === 1) {
+                                            this.$Message.success(res.msg);
+                                        } else {
+                                            this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
+                                        }
+                                    }, 350);
+                                }
+                            });
+                        } else {
+                            this.$Modal.remove();
+                        }
+                    },
+                });
+            }
+        }
+    }
+</script>

+ 2 - 2
resources/assets/js/main/components/project/archived.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-archived">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-archived {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/header/archived.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-header-archived">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-header-archived {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/header/create.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-header-create">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-header-create {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/my/favor.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-my-favor">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-my-favor {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/my/join.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-my-join">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-my-join {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/my/manage.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-my-manage">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-my-manage {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/todo/attention.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-todo-attention">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-todo-attention {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/todo/complete.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-todo-complete">
             <!-- 列表 -->
             <Table class="tableFill" ref="tableRef" :columns="columns" :data="lists" :loading="loadIng > 0" :no-data-text="noDataText" stripe></Table>
             <!-- 分页 -->
@@ -10,7 +10,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-todo-complete {
         .tableFill {
             margin: 12px 12px 20px;
         }

+ 2 - 2
resources/assets/js/main/components/project/users.vue

@@ -1,6 +1,6 @@
 <template>
     <drawer-tabs-container>
-        <div class="project-complete">
+        <div class="project-users">
             <!-- 按钮 -->
             <Button :loading="loadIng > 0" type="primary" icon="md-add" @click="addUser">{{$L('添加成员')}}</Button>
             <!-- 列表 -->
@@ -12,7 +12,7 @@
 </template>
 
 <style lang="scss" scoped>
-    .project-complete {
+    .project-users {
         padding: 0 12px;
         .tableFill {
             margin: 12px 0 20px;

+ 43 - 3
resources/assets/js/main/pages/docs.vue

@@ -36,7 +36,8 @@
                                 <div class="docs-setting">
                                     <Button @click="[addSectionId=0,addSectionShow=true]">{{$L('新增章节')}}</Button>
                                     <Button @click="[addBookId=selectBookData.id,addBookShow=true]">{{$L('修改标题')}}</Button>
-                                    <!--<Button>{{$L('权限设置')}}</Button>-->
+                                    <Button @click="showShare">{{$L('分享')}}</Button>
+                                    <Button @click="[settingDrawerShow=true,settingDrawerTab='setting']">{{$L('设置')}}</Button>
                                     <Button type="warning" ghost @click="onBookDelete(selectBookData.id)">{{$L('删除')}}</Button>
                                 </div>
                             </div>
@@ -90,6 +91,16 @@
             </div>
         </Modal>
 
+        <WDrawer v-model="settingDrawerShow" maxWidth="750">
+            <Tabs v-if="settingDrawerShow" v-model="settingDrawerTab">
+                <TabPane :label="$L('文档设置')" name="setting">
+                    <book-setting :canload="settingDrawerShow && settingDrawerTab == 'setting'" :id="selectBookData.id"></book-setting>
+                </TabPane>
+                <TabPane :label="$L('文档成员')" name="member">
+                    <book-users :canload="settingDrawerShow && settingDrawerTab == 'member'" :id="selectBookData.id"></book-users>
+                </TabPane>
+            </Tabs>
+        </WDrawer>
     </div>
 </template>
 
@@ -200,8 +211,11 @@
 <script>
     import WContent from "../components/WContent";
     import NestedDraggable from "../components/docs/NestedDraggable";
+    import BookSetting from "../components/docs/setting";
+    import BookUsers from "../components/docs/users";
+    import WDrawer from "../components/iview/WDrawer";
     export default {
-        components: {NestedDraggable, WContent},
+        components: {WDrawer, BookUsers, BookSetting, NestedDraggable, WContent},
         data () {
             return {
                 loadIng: 0,
@@ -234,6 +248,9 @@
                 sectionTypeLists: [],
 
                 sortDisabled: false,
+
+                settingDrawerShow: false,
+                settingDrawerTab: 'setting',
             }
         },
 
@@ -441,6 +458,7 @@
                 $A.aAjax({
                     url: 'docs/section/lists',
                     data: {
+                        act: 'edit',
                         bookid: bookid
                     },
                     complete: () => {
@@ -457,7 +475,7 @@
                             return;
                         }
                         if (res.ret === 1) {
-                            this.sectionLists = res.data;
+                            this.sectionLists = res.data.tree;
                             this.sectionNoDataText = this.$L("没有相关的数据");
                         }else{
                             this.sectionLists = [];
@@ -578,6 +596,28 @@
                         });
                         break;
                 }
+            },
+
+            showShare() {
+                this.$Modal.confirm({
+                    render: (h) => {
+                        return h('div', [
+                            h('div', {
+                                style: {
+                                    fontSize: '16px',
+                                    fontWeight: '500',
+                                    marginBottom: '20px',
+                                }
+                            }, this.$L('文档链接')),
+                            h('Input', {
+                                props: {
+                                    value: $A.fillUrl('#/docs/view/b' + this.selectBookData.id),
+                                    readonly: true,
+                                },
+                            })
+                        ])
+                    },
+                });
             }
         },
     }

+ 218 - 29
resources/assets/js/main/pages/docs/edit.vue

@@ -6,12 +6,20 @@
         <div class="edit-box">
             <div class="edit-header">
                 <div class="header-menu active" @click="handleClick('back')"><Icon type="md-arrow-back" /></div>
-                <div class="header-menu" @click="handleClick('menu')"><Icon type="md-menu" /></div>
-                <!--<div class="header-menu" @click="handleClick('share')"><Icon type="md-share" /></div>
-                <div class="header-menu" @click="handleClick('view')"><Icon type="md-eye" /></div>-->
-                <div class="header-menu" @click="handleClick('history')"><Icon type="md-time" /></div>
+                <Tooltip class="header-menu" :content="$L('知识库目录')">
+                    <div class="menu-container" @click="handleClick('menu')"><Icon type="md-menu" /></div>
+                </Tooltip>
+                <Tooltip class="header-menu" :content="$L('分享文档')">
+                    <div class="menu-container" @click="handleClick('share')"><Icon type="md-share" /></div>
+                </Tooltip>
+                <Tooltip class="header-menu" :content="$L('浏览文档')">
+                    <a class="menu-container" target="_blank" :href="handleClick('view')"><Icon type="md-eye" /></a>
+                </Tooltip>
+                <Tooltip class="header-menu" :content="$L('历史版本')">
+                    <div class="menu-container" @click="handleClick('history')"><Icon type="md-time" /></div>
+                </Tooltip>
                 <Poptip class="header-menu synch">
-                    <div class="synch-container">
+                    <div class="menu-container">
                         <Icon type="md-contacts" :title="$L('正在协作会员')"/><em v-if="synchUsers.length > 0">{{synchUsers.length}}</em>
                     </div>
                     <ul class="synch-lists" slot="content">
@@ -23,6 +31,13 @@
                         </li>
                     </ul>
                 </Poptip>
+                <Tooltip class="header-menu" :class="{lock:isLock}" max-width="500">
+                    <div slot="content" style="white-space:nowrap">
+                        <span v-if="isLock&&docDetail.lockname!=userInfo.username">【<UserView :username="docDetail.lockname"/>】{{$L('已锁定')}}</span>
+                        <span v-else>{{$L('锁定后其他会员将无法修改保存文档。')}}</span>
+                    </div>
+                    <div class="menu-container" @click="handleClick(isLock?'unlock':'lock')"><Icon :type="isLock?'md-lock':'md-unlock'" /></div>
+                </Tooltip>
                 <div class="header-title">{{docDetail.title}}</div>
                 <div v-if="docDetail.type=='document'" class="header-hint">
                     <ButtonGroup size="small" shape="circle">
@@ -30,7 +45,28 @@
                         <Button :type="`${docContent.type=='md'?'primary':'default'}`" @click="$set(docContent, 'type', 'md')">{{$L('MD编辑器')}}</Button>
                     </ButtonGroup>
                 </div>
-                <div v-else-if="docDetail.type=='mind'" class="header-hint">{{$L('选中节点,按enter键添加子节点,tab键添加同级节点')}}</div>
+                <div v-if="docDetail.type=='mind'" class="header-hint">
+                    {{$L('选中节点,按enter键添加同级节点,tab键添加子节点')}}
+                </div>
+                <Dropdown v-if="docDetail.type=='mind' || docDetail.type=='flow' || docDetail.type=='sheet'"
+                          trigger="click"
+                          class="header-hint"
+                          @on-click="exportMenu">
+                    <a href="javascript:void(0)">
+                        {{$L('导出')}}
+                        <Icon type="ios-arrow-down"></Icon>
+                    </a>
+                    <DropdownMenu v-if="docDetail.type=='sheet'" slot="list">
+                        <DropdownItem name="xlsx">{{$L('导出XLSX')}}</DropdownItem>
+                        <DropdownItem name="xlml">{{$L('导出XLS')}}</DropdownItem>
+                        <DropdownItem name="csv">{{$L('导出CSV')}}</DropdownItem>
+                        <DropdownItem name="txt">{{$L('导出TXT')}}</DropdownItem>
+                    </DropdownMenu>
+                    <DropdownMenu v-else slot="list">
+                        <DropdownItem name="png">{{$L('导出PNG图片')}}</DropdownItem>
+                        <DropdownItem name="pdf">{{$L('导出PDF文件')}}</DropdownItem>
+                    </DropdownMenu>
+                </Dropdown>
                 <Button :disabled="(disabledBtn || loadIng > 0) && hid == 0" class="header-button" size="small" type="primary" @click="handleClick('save')">{{$L('保存')}}</Button>
             </div>
             <div class="docs-body">
@@ -38,9 +74,9 @@
                     <MDEditor v-if="docContent.type=='md'" class="body-text" v-model="docContent.content" height="100%"></MDEditor>
                     <TEditor v-else class="body-text" v-model="docContent.content" height="100%"></TEditor>
                 </template>
-                <minder v-else-if="docDetail.type=='mind'" class="body-mind" v-model="docContent"></minder>
-                <sheet v-else-if="docDetail.type=='sheet'" class="body-sheet" v-model="docContent.content"></sheet>
-                <flow v-else-if="docDetail.type=='flow'" class="body-flow" v-model="docContent.content"></flow>
+                <minder v-else-if="docDetail.type=='mind'" ref="myMind" class="body-mind" v-model="docContent"></minder>
+                <sheet v-else-if="docDetail.type=='sheet'" ref="mySheet" class="body-sheet" v-model="docContent.content"></sheet>
+                <flow v-else-if="docDetail.type=='flow'" ref="myFlow" class="body-flow" v-model="docContent.content"></flow>
             </div>
         </div>
 
@@ -145,7 +181,7 @@
                 background-color: #ffffff;
                 box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.1);
                 position: relative;
-                z-index: 9;
+                z-index: 99;
                 .header-menu {
                     width: 50px;
                     height: 100%;
@@ -155,25 +191,37 @@
                     justify-content: center;
                     margin-right: 3px;
                     cursor: pointer;
-                    color: #777777;
                     position: relative;
+                    .menu-container {
+                        display: inline-block;
+                        width: 50px;
+                        height: 38px;
+                        line-height: 38px;
+                        color: #777777;
+                        transition: color .2s ease;
+                    }
                     .ivu-icon {
                         font-size: 16px;
                     }
                     &.synch {
-                        .synch-container {
-                            width: 50px;
-                            height: 38px;
-                            line-height: 38px;
+                        .menu-container {
                             em {
                                 padding-left: 2px;
                             }
                         }
                     }
+                    &.lock {
+                        .menu-container {
+                            color: #059DFD;
+                        }
+                    }
                     &:hover,
                     &.active {
                         color: #fff;
                         background: #059DFD;
+                        .menu-container {
+                            color: #fff;
+                        }
                     }
                     .synch-lists {
                         max-height: 500px;
@@ -231,6 +279,9 @@
                         font-size: 12px;
                         padding: 0 10px;
                     }
+                    .ivu-dropdown-item {
+                        font-size: 12px !important;
+                    }
                 }
                 .header-button {
                     font-size: 12px;
@@ -293,6 +344,8 @@
                 routeName: '',
                 synergyNum: 0,
                 synchUsers: [],
+
+                timeValue: Math.round(new Date().getTime() / 1000),
             }
         },
         created() {
@@ -358,6 +411,10 @@
                 }
             }, false);
             //
+            setInterval(() => {
+                this.timeValue = Math.round(new Date().getTime() / 1000);
+            });
+            //
             $A.WSOB.setOnMsgListener("chat/index", ['docs'], (msgDetail) => {
                 if (this.routeName !== this.$route.name) {
                     return;
@@ -366,17 +423,53 @@
                 if (body.sid != this.sid) {
                     return;
                 }
-                if (body.type === 'users') {
-                    this.synchUsers = body.lists;
-                    this.synchUsers.splice(this.synchUsers.length);
-                } else if (body.type === 'update') {
-                    this.$Modal.confirm({
-                        title: this.$L("更新提示"),
-                        content: this.$L('团队成员(%)更新了内容,<br/>更新时间:%。<br/><br/>点击【确定】加载最新内容。', body.nickname, $A.formatDate("Y-m-d H:i:s", body.time)),
-                        onOk: () => {
-                            this.refreshDetail();
+                switch (body.type) {
+                    case 'users':
+                        this.synchUsers = body.lists;
+                        this.synchUsers.splice(this.synchUsers.length);
+                        break;
+
+                    case 'update':
+                        this.$Modal.confirm({
+                            title: this.$L("更新提示"),
+                            content: this.$L('团队成员(%)更新了内容,<br/>更新时间:%。<br/><br/>点击【确定】加载最新内容。', body.nickname, $A.formatDate("Y-m-d H:i:s", body.time)),
+                            onOk: () => {
+                                this.refreshDetail();
+                            }
+                        });
+                        break;
+
+                    case 'lock':
+                    case 'unlock':
+                        if (this.docDetail.lockname == body.lockname) {
+                            return;
                         }
-                    });
+                        this.$set(this.docDetail, 'lockname', body.lockname);
+                        this.$set(this.docDetail, 'lockdate', body.lockdate);
+                        this.$Notice.close('docs-lock')
+                        this.$Notice[body.type=='lock'?'warning':'info']({
+                            name: 'docs-lock',
+                            duration: 0,
+                            render: h => {
+                                return h('div', {
+                                    style: {
+                                        lineHeight: '18px'
+                                    }
+                                }, [
+                                    h('span', {
+                                        style: {
+                                            fontWeight: 500
+                                        }
+                                    }, body.nickname + ':'),
+                                    h('span', {
+                                        style: {
+                                            paddingLeft: '6px'
+                                        }
+                                    }, this.$L(body.type == 'lock' ? '锁定文档' : '解锁文档'))
+                                ])
+                            }
+                        });
+                        break;
                 }
             });
         },
@@ -385,6 +478,12 @@
             this.synergy(true);
         },
         deactivated() {
+            if (this.isLock && this.docDetail.lockname == this.userInfo.username) {
+                this.docDetail.lockname = '';
+                this.handleClick('unlock');
+            }
+            this.$Notice.close('docs-lock');
+            //
             this.synergy(false);
             this.docDrawerShow = false;
             if ($A.getToken() === false) {
@@ -410,6 +509,7 @@
                             $A.aAjax({
                                 url: 'docs/section/lists',
                                 data: {
+                                    act: 'edit',
                                     bookid: bookid
                                 },
                                 error: () => {
@@ -423,7 +523,7 @@
                                         return;
                                     }
                                     if (res.ret === 1) {
-                                        this.sectionLists = res.data;
+                                        this.sectionLists = res.data.tree;
                                         this.sectionNoDataText = this.$L("没有相关的数据");
                                     }else{
                                         this.sectionLists = [];
@@ -473,10 +573,12 @@
                 return this.bakContent == $A.jsonStringify(this.docContent);
             },
             synchUsersS() {
-                let temp = Math.round(new Date().getTime() / 1000);
                 return this.synchUsers.filter(item => {
-                    return item.indate + 10 > temp;
+                    return item.indate + 10 > this.timeValue;
                 });
+            },
+            isLock() {
+                return !!(this.docDetail.lockname && this.docDetail.lockdate > this.timeValue - 60);
             }
         },
         methods: {
@@ -540,6 +642,7 @@
                 $A.aAjax({
                     url: 'docs/section/content',
                     data: {
+                        act: 'edit',
                         id: this.sid,
                     },
                     complete: () => {
@@ -554,6 +657,7 @@
                             this.docDetail = res.data;
                             this.docContent = $A.jsonParse(res.data.content);
                             this.bakContent = $A.jsonStringify(this.docContent);
+                            this.continueLock(1000);
                         } else {
                             this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
                             this.goBack();
@@ -641,6 +745,7 @@
                                     this.historyNoDataText = '';
                                 } else {
                                     this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
+                                    this.bakContent = '';
                                 }
                             }
                         });
@@ -653,10 +758,94 @@
                         break;
 
                     case "share":
+                        this.$Modal.confirm({
+                            render: (h) => {
+                                return h('div', [
+                                    h('div', {
+                                        style: {
+                                            fontSize: '16px',
+                                            fontWeight: '500',
+                                            marginBottom: '20px',
+                                        }
+                                    }, this.$L('文档链接')),
+                                    h('Input', {
+                                        props: {
+                                            value: this.handleClick('view'),
+                                            readonly: true,
+                                        },
+                                    })
+                                ])
+                            },
+                        });
+                        break;
+
+                    case "lock":
+                    case "unlock":
+                        $A.aAjax({
+                            url: 'docs/section/lock?id=' + this.getSid(),
+                            data: {
+                                act: act,
+                            },
+                            error: () => {
+                                alert(this.$L('网络繁忙,请稍后再试!'));
+                            },
+                            success: (res) => {
+                                if (res.ret === 1) {
+                                    if (this.docDetail.lockname != res.data.lockname) {
+                                        this.$Message.success(res.msg);
+                                    }
+                                    this.$set(this.docDetail, 'lockname', res.data.lockname);
+                                    this.$set(this.docDetail, 'lockdate', res.data.lockdate);
+                                    this.continueLock(20000);
+                                } else {
+                                    this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
+                                }
+                            }
+                        });
+                        break;
+
                     case "view":
-                        this.$Message.info(this.$L("敬请期待!"));
+                        return $A.fillUrl('#/docs/view/' + this.docDetail.id);
+
+                }
+            },
+
+            continueLock(time) {
+                if (!this.isLock) {
+                    return;
+                }
+                if (this.docDetail.lockname != this.userInfo.username) {
+                    return;
+                }
+                this.__continueLock = $A.randomString(6);
+                let tempString = this.__continueLock;
+                setTimeout(() => {
+                    if (tempString != this.__continueLock) {
+                        return;
+                    }
+                    if (!this.isLock) {
+                        return;
+                    }
+                    if (this.docDetail.lockname != this.userInfo.username) {
+                        return;
+                    }
+                    this.handleClick('lock');
+                }, time);
+            },
+
+            exportMenu(act) {
+                switch (this.docDetail.type) {
+                    case 'mind':
+                        this.$refs.myMind.exportHandle(act == 'pdf' ? 1 : 0, this.docDetail.title);
                         break;
 
+                    case 'flow':
+                        this.$refs.myFlow[act == 'pdf' ? 'exportPDF' : 'exportPNG'](this.docDetail.title, 1);
+                        break;
+
+                    case 'sheet':
+                        this.$refs.mySheet.exportExcel(this.docDetail.title, act);
+                        break;
                 }
             }
         },

+ 362 - 0
resources/assets/js/main/pages/docs/view.vue

@@ -0,0 +1,362 @@
+<template>
+    <div class="w-main docs-view">
+
+        <v-title>{{$L('文档浏览')}}-{{$L('轻量级的团队在线协作')}}</v-title>
+
+        <div class="view-box">
+            <div class="view-head">
+                <div class="header-title">
+                    <span v-if="bookDetail.title">{{bookDetail.title}}</span>
+                    <em v-if="bookDetail.title && docDetail.title">-</em>
+                    {{docDetail.title}}
+                </div>
+                <Button class="header-button" size="small" type="primary" ghost @click="toggleFullscreen">{{$L(isFullscreen ? '退出全屏' : '全屏')}}</Button>
+            </div>
+            <div class="view-main" :class="{'view-book':isBook}">
+                <div class="view-menu">
+                    <div class="view-menu-list">
+                        <nested-draggable :lists="sectionLists" :readonly="true" :activeid="sid" @change="handleSection"></nested-draggable>
+                    </div>
+                </div>
+                <div class="view-body">
+                    <div class="view-body-content">
+                        <template v-if="docDetail.type=='document'">
+                            <MarkdownPreview v-if="docContent.type=='md'" :initialValue="docContent.content"></MarkdownPreview>
+                            <ReportContent v-else :content="docContent.content"></ReportContent>
+                        </template>
+                        <minder v-else-if="docDetail.type=='mind'" ref="myMind" class="body-mind" v-model="docContent" :readOnly="true"></minder>
+                        <sheet v-else-if="docDetail.type=='sheet'" ref="mySheet" class="body-sheet" v-model="docContent.content" :readOnly="true"></sheet>
+                        <flow v-else-if="docDetail.type=='flow'" ref="myFlow" class="body-flow" v-model="docContent.content" :readOnly="true"></flow>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+
+<style lang="scss">
+    .view-body {
+        .view-body-content {
+            .markdown-preview,
+            .report-content {
+                margin: 0 !important;
+                padding: 0 !important;
+            }
+            .minder-editor-container {
+                transform: translateZ(0);
+            }
+            .body-sheet {
+                box-sizing: content-box;
+                * {
+                    box-sizing: content-box;
+                }
+            }
+        }
+    }
+</style>
+<style lang="scss" scoped>
+    .docs-view {
+        background-color: #ffffff;
+        .view-box {
+            display: flex;
+            flex-direction: column;
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            .view-head {
+                display: flex;
+                flex-direction: row;
+                align-items: center;
+                width: 100%;
+                height: 38px;
+                box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.1);
+                position: relative;
+                z-index: 99;
+                .header-title {
+                    flex: 1;
+                    color: #333333;
+                    padding-left: 12px;
+                    padding-right: 12px;
+                    font-size: 16px;
+                    font-weight: 500;
+                    white-space: nowrap;
+                    em {
+                        padding: 0 3px;
+                        font-weight: normal;
+                    }
+                }
+                .header-hint {
+                    padding-right: 22px;
+                    font-size: 12px;
+                    color: #666;
+                    white-space: nowrap;
+                    .ivu-btn {
+                        font-size: 12px;
+                        padding: 0 10px;
+                    }
+                    .ivu-dropdown-item {
+                        font-size: 12px !important;
+                    }
+                }
+                .header-button {
+                    font-size: 12px;
+                    margin-right: 12px;
+                }
+            }
+            .view-main {
+                flex: 1;
+                width: 100%;
+                display: flex;
+                flex-direction: row;
+                align-items: flex-start;
+                justify-content: flex-start;
+                &.view-book {
+                    .view-menu {
+                        border-right: 0;
+                        width: 100%;
+                        .view-menu-list {
+                            padding: 18px 8%;
+                        }
+                    }
+                    .view-body {
+                        display: none;
+                    }
+                }
+                .view-menu {
+                    position: relative;
+                    height: 100%;
+                    width: 280px;
+                    border-right: 1px solid #E6ECF1;
+                    .view-menu-list {
+                        position: absolute;
+                        left: 0;
+                        top: 0;
+                        right: 0;
+                        bottom: 0;
+                        padding: 18px 12px;
+                        overflow: auto;
+                    }
+                }
+                .view-body {
+                    flex: 1;
+                    height: 100%;
+                    position: relative;
+                    .view-body-content {
+                        position: absolute;
+                        left: 0;
+                        top: 0;
+                        right: 0;
+                        bottom: 0;
+                        padding: 18px;
+                        overflow: auto;
+                    }
+                }
+            }
+        }
+    }
+</style>
+<script>
+    import Vue from 'vue'
+    import minder from '../../components/docs/minder'
+    Vue.use(minder)
+
+
+    const Sheet = resolve => require(['../../components/docs/sheet/index'], resolve);
+    const Flow = resolve => require(['../../components/docs/flow/index'], resolve);
+    const NestedDraggable = resolve => require(['../../components/docs/NestedDraggable'], resolve);
+    const MarkdownPreview = resolve => require(['../../components/MDEditor/components/preview/index'], resolve);
+    const ReportContent = resolve => require(['../../components/report/content'], resolve);
+
+    export default {
+        components: {Sheet, Flow, ReportContent, MarkdownPreview, NestedDraggable},
+        data () {
+            return {
+                loadIng: 0,
+
+                sid: 0,
+
+                docDetail: { },
+                docContent: { },
+
+                bookDetail: {},
+
+                sectionLists: [],
+                sectionNoDataText: "",
+
+                userInfo: {},
+
+                routeName: '',
+
+                isBook: false,
+                isFullscreen: false,
+            }
+        },
+        mounted() {
+            this.routeName = this.$route.name;
+            this.userInfo = $A.getUserInfo((res, isLogin) => {
+                if (this.userInfo.id != res.id) {
+                    this.userInfo = res;
+                }
+            }, false);
+            //
+            document.addEventListener("fullscreenchange", () => {
+                this.isFullscreen = !!document.fullscreenElement;
+            });
+        },
+        activated() {
+            this.refreshSid();
+        },
+        deactivated() {
+            if ($A.getToken() === false) {
+                this.sid = 0;
+            }
+        },
+        watch: {
+            sid(val) {
+                if (!val) {
+                    return;
+                }
+                val += "";
+                if (val.substring(0, 1) == 'b') {
+                    this.isBook = true;
+                    this.docDetail.bookid = val.substring(1);
+                    this.getSectionMenu();
+                } else {
+                    this.isBook = false;
+                    this.refreshDetail();
+                }
+            },
+            '$route' (To) {
+                this.sid = To.params.sid;
+            },
+        },
+        methods: {
+            refreshSid() {
+                this.sid = this.$route.params.sid;
+                if (typeof this.$route.params.other === "object") {
+                    this.$set(this.docDetail, 'title', $A.getObject(this.$route.params.other, 'title'))
+                }
+            },
+
+            refreshDetail() {
+                this.docDetail = { };
+                this.docContent = { };
+                this.getDetail();
+            },
+
+            getDetail() {
+                this.loadIng++;
+                $A.aAjax({
+                    url: 'docs/section/content',
+                    data: {
+                        act: 'view',
+                        id: this.sid,
+                    },
+                    complete: () => {
+                        this.loadIng--;
+                    },
+                    error: () => {
+                        alert(this.$L('网络繁忙,请稍后再试!'));
+                    },
+                    success: (res) => {
+                        if (res.ret === 1) {
+                            this.docDetail = res.data;
+                            this.docContent = $A.jsonParse(res.data.content);
+                            this.getSectionMenu();
+                        } else {
+                            this.$Modal.error({
+                                title: this.$L('温馨提示'),
+                                content: res.msg,
+                                onOk: () => {
+                                    if (res.data == '-1001') {
+                                        this.goForward({path: '/', query:{from:encodeURIComponent(window.location.href)}}, true);
+                                    }
+                                }
+                            });
+                        }
+                    }
+                });
+            },
+
+            getSectionMenu() {
+                this.sectionNoDataText = this.$L("数据加载中.....");
+                let bookid = this.docDetail.bookid;
+                $A.aAjax({
+                    url: 'docs/section/lists',
+                    data: {
+                        act: 'view',
+                        bookid: bookid
+                    },
+                    error: () => {
+                        if (bookid != this.docDetail.bookid) {
+                            return;
+                        }
+                        this.sectionNoDataText = this.$L("数据加载失败!");
+                    },
+                    success: (res) => {
+                        if (bookid != this.docDetail.bookid) {
+                            return;
+                        }
+                        if (res.ret === 1) {
+                            this.bookDetail = res.data.book;
+                            this.sectionLists = res.data.tree;
+                            this.sectionNoDataText = this.$L("没有相关的数据");
+                        } else {
+                            this.sectionLists = [];
+                            this.sectionNoDataText = res.msg;
+                            this.$Modal.error({
+                                title: this.$L('温馨提示'),
+                                content: res.msg,
+                                onOk: () => {
+                                    if (res.data == '-1001') {
+                                        this.goForward({path: '/', query:{from:encodeURIComponent(window.location.href)}}, true);
+                                    }
+                                }
+                            });
+                        }
+                    }
+                });
+            },
+
+            handleSection(act, detail) {
+                if (act === 'open') {
+                    this.goForward({name: 'docs-view', params: {sid: detail.id, other: detail || {}}});
+                    this.refreshSid();
+                }
+            },
+
+            toggleFullscreen() {
+                if (this.isFullscreen) {
+                    this.exitFullscreen();
+                } else {
+                    this.launchFullscreen(this.$el);
+                }
+            },
+
+            launchFullscreen(element) {
+                if (element.requestFullscreen) {
+                    element.requestFullscreen();
+                } else if (element.mozRequestFullScreen) {
+                    element.mozRequestFullScreen();
+                } else if (element.msRequestFullscreen) {
+                    element.msRequestFullscreen();
+                } else if (element.webkitRequestFullscreen) {
+                    element.webkitRequestFullScreen();
+                }
+            },
+
+            exitFullscreen() {
+                if (document.exitFullscreen) {
+                    document.exitFullscreen();
+                } else if (document.msExitFullscreen) {
+                    document.msExitFullscreen();
+                } else if (document.mozCancelFullScreen) {
+                    document.mozCancelFullScreen();
+                } else if (document.webkitExitFullscreen) {
+                    document.webkitExitFullscreen();
+                }
+            }
+        },
+    }
+</script>

+ 11 - 1
resources/assets/js/main/pages/index.vue

@@ -370,6 +370,8 @@
                     github: '',
                     reg: '',
                 }),
+
+                fromUrl: '',
             }
         },
         created() {
@@ -401,6 +403,10 @@
         },
         mounted() {
             this.getSetting();
+            this.fromUrl = decodeURIComponent($A.getObject(this.$route.query, 'from'));
+            if (this.fromUrl) {
+                this.loginChack();
+            }
         },
         deactivated() {
             this.loginShow = false;
@@ -461,7 +467,11 @@
                                     this.loginShow = false;
                                     this.$refs.login.resetFields();
                                     this.$Message.success(this.$L('登录成功'));
-                                    this.goForward({path: '/todo'}, true);
+                                    if (this.fromUrl) {
+                                        window.location.replace(this.fromUrl);
+                                    } else {
+                                        this.goForward({path: '/todo'}, true);
+                                    }
                                 } else {
                                     this.$Modal.error({
                                         title: this.$L("温馨提示"),

+ 5 - 0
resources/assets/js/main/routes.js

@@ -29,6 +29,11 @@ export default [
         meta: { slide: false },
         component: resolve => require(['./pages/docs/edit.vue'], resolve)
     }, {
+        path: '/docs/view/:sid',
+        name: 'docs-view',
+        meta: { slide: false },
+        component: resolve => require(['./pages/docs/view.vue'], resolve)
+    }, {
         path: '/team',
         name: 'team',
         meta: { slide: false, tabActive: 'team' },

文件差异内容过多而无法显示
+ 1 - 1
resources/assets/statics/public/css/iview.css


+ 54 - 1
resources/assets/statics/public/js/grapheditor/index.html

@@ -74,11 +74,19 @@
             var editorUi = new EditorUi(new Editor(urlParams['chrome'] == '0', themes));
             var graph = editorUi.editor.graph;
 
+            var backupXml;
+            var newXml;
+
             graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(editorUi, function() {
+                newXml = mxUtils.getPrettyXml(editorUi.editor.getGraphXml());
+                if (backupXml.replace(/^<mxGraphModel(.*?)>/, '') == newXml.replace(/^<mxGraphModel(.*?)>/, '')) {
+                    return;
+                }
+                backupXml = newXml;
                 window.parent.postMessage({
                     act: 'change',
                     params: {
-                        xml: mxUtils.getPrettyXml(editorUi.editor.getGraphXml())
+                        xml: newXml
                     }
                 }, '*');
             }));
@@ -88,12 +96,57 @@
                 switch (data.act) {
                     case 'setXml':
                         try {
+                            backupXml = data.params.xml;
                             var defaultXml = mxUtils.parseXml(data.params.xml);
                             editorUi.editor.setGraphXml(defaultXml.documentElement);
                         } catch (e) {
 
                         }
                         break;
+
+                    case 'exportPNG':
+                        try {
+                            (function (name, scale, type) {
+                                name = name || '未命名';
+                                type = type || 'png';
+                                scale = scale || 1;
+                                var graph = editorUi.editor.graph;
+                                var bounds = graph.getGraphBounds();
+                                var width = Math.ceil(bounds.width / graph.view.scale * scale);
+                                var height = Math.ceil(bounds.height / graph.view.scale * scale);
+                                var source = '<?xml version="1.0" standalone="no"?>\r\n' + mxUtils.getXml(graph.getSvg(null, scale, 0))
+                                var image = new Image()
+                                image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source)
+                                var canvas = document.createElement('canvas')
+                                canvas.width = width
+                                canvas.height = height
+                                var context = canvas.getContext('2d')
+                                context.fillStyle = '#fff'
+                                context.fillRect(0, 0, 10000, 10000)
+                                image.onload = function () {
+                                    context.drawImage(image, 0, 0)
+                                    if (type == 'imageContent') {
+                                        window.parent.postMessage({
+                                            act: 'imageContent',
+                                            params: {
+                                                name: name,
+                                                width: width,
+                                                height: height,
+                                                content: canvas.toDataURL(`image/${type}`)
+                                            }
+                                        }, '*');
+                                    } else {
+                                        var a = document.createElement('a')
+                                        a.download = `${name}.${type}`
+                                        a.href = canvas.toDataURL(`image/${type}`)
+                                        a.click()
+                                    }
+                                }
+                            })(data.params.name, data.params.scale, data.params.type);
+                        } catch (e) {
+
+                        }
+                        break;
                 }
             });
 

+ 81 - 58
resources/assets/statics/public/js/grapheditor/viewer.html

@@ -1,71 +1,94 @@
-<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->
+<!--[if IE]>
+<meta http-equiv="X-UA-Compatible" content="IE=5,IE=9"><![endif]-->
 <!DOCTYPE html>
 <html>
 <head>
     <title>Grapheditor viewer</title>
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
-	<script type="text/javascript">
-		var STENCIL_PATH = 'stencils';
-		var IMAGE_PATH = 'images';
-		var STYLE_PATH = 'styles';
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <style>
+        #graph {
+            position: absolute;
+            cursor: move;
+            left: 0;
+            right: 0;
+            top: 0;
+            bottom: 0
+        }
+    </style>
+    <script type="text/javascript">
+        var STENCIL_PATH = 'stencils';
+        var IMAGE_PATH = 'images';
+        var STYLE_PATH = 'styles';
 
-		var urlParams = (function(url)
-		{
-			var result = new Object();
-			var idx = url.lastIndexOf('?');
+        var urlParams = (function (url) {
+            var result = {};
+            var idx = url.lastIndexOf('?');
+            if (idx > 0) {
+                var params = url.substring(idx + 1).split('&');
+                for (var i = 0; i < params.length; i++) {
+                    idx = params[i].indexOf('=');
+                    if (idx > 0) {
+                        result[params[i].substring(0, idx)] = params[i].substring(idx + 1);
+                    }
+                }
+            }
+            return result;
+        })(window.location.href);
 
-			if (idx > 0)
-			{
-				var params = url.substring(idx + 1).split('&');
+        var mxLoadResources = false;
+        var mxBasePath = './src';
+    </script>
+    <script type="text/javascript" src="sanitizer/sanitizer.min.js"></script>
+    <script type="text/javascript" src="js/mxClient.js"></script>
+    <script type="text/javascript" src="js/Graph.js"></script>
+    <script type="text/javascript" src="js/Shapes.js"></script>
+</head>
+<body class="geEditor">
+<div id="graph"></div>
+<script type="text/javascript">
+    (function (win) {
+        var graph = new Graph(document.getElementById('graph'));
 
-				for (var i = 0; i < params.length; i++)
-				{
-					idx = params[i].indexOf('=');
+        graph.setTooltips(false);
+        graph.setEnabled(false);
+        graph.setPanning(true);
 
-					if (idx > 0)
-					{
-						result[params[i].substring(0, idx)] = params[i].substring(idx + 1);
-					}
-				}
-			}
+        var style = graph.getStylesheet().getDefaultVertexStyle();
+        style[mxConstants.STYLE_FONTCOLOR] = "#000000";
+        style[mxConstants.STYLE_STROKECOLOR] = "#777777";
+        style[mxConstants.STYLE_FILLCOLOR] = "#ffffff";
+        (style = graph.getStylesheet().getDefaultEdgeStyle())[mxConstants.STYLE_STROKECOLOR] = "#777777";
+        style[mxConstants.STYLE_FILLCOLOR] = "#ffffff";
 
-			return result;
-		})(window.location.href);
+        graph.panningHandler.useLeftButtonForPanning = true;
+        graph.panningHandler.ignoreCell = true;
+        graph.container.style.cursor = "move";
+        graph.resizeContainer = false;
+        graph.centerZoom = true;
+        graph.border = 0;
 
-		// Sets the base path, the UI language via URL param and configures the
-		// supported languages to avoid 404s. The loading of all core language
-		// resources is disabled as all required resources are in grapheditor.
-		// properties. Note that in this example the loading of two resource
-		// files (the special bundle and the default bundle) is disabled to
-		// save a GET request. This requires that all resources be present in
-		// each properties file since only one file is loaded.
-		var mxLoadResources = false;
-		var mxBasePath = '../../../src';
-	</script>
-	<script type="text/javascript" src="sanitizer/sanitizer.min.js"></script>
-	<script type="text/javascript" src="../../../src/js/mxClient.js"></script>
-	<script type="text/javascript" src="js/Graph.js"></script>
-	<script type="text/javascript" src="js/Shapes.js"></script>
-</head>
-<body class="geEditor">
-	Input:
-	<br />
-	<textarea rows="24" cols="100" id="textarea"></textarea>
-	<br />
-	<button onclick="show(document.getElementById('textarea').value);return false;">Show</button>
-	<div id="graph"></div>
-	<script type="text/javascript">
-		var graph = new Graph(document.getElementById('graph'));
-		graph.resizeContainer = true;
-		graph.setEnabled(false);
 
-		function show(data)
-		{
-			var xmlDoc = mxUtils.parseXml(data);
-			var codec = new mxCodec(xmlDoc);
-			codec.decode(xmlDoc.documentElement, graph.getModel());
-		};
-	</script>
+        win.addEventListener("message", function (event) {
+            var data = event.data;
+            switch (data.act) {
+                case 'setXml':
+                    try {
+                        var xmlDoc = mxUtils.parseXml(data.params.xml);
+                        var codec = new mxCodec(xmlDoc);
+                        codec.decode(xmlDoc.documentElement, graph.getModel());
+                    } catch (e) {
+
+                    }
+                    break;
+            }
+        });
+
+        win.parent.postMessage({
+            act: 'ready',
+            params: {}
+        }, '*');
+    })(window);
+</script>
 </body>
 </html>

+ 23 - 2
resources/lang/en/general.js

@@ -83,7 +83,6 @@ export default {
     "展开全部节点": "Expand all nodes",
     "居中": "Center",
     "移动": "Mobile",
-    "导出PNG图片": "Export PNG image",
     "无标题": "Untitled",
     "默认节点": "The default node",
     "任务名称": "Mission name",
@@ -228,7 +227,7 @@ export default {
     "你确定要删除此项目吗?": "Are you sure you want to delete this item?",
     "文档编辑": "Document editing",
     "轻量级的团队在线协作": "Lightweight team online collaboration",
-    "选中节点,按enter键添加子节点,tab键添加同级节点": "Select the node, press enter to add a child node, tab key to add sibling nodes",
+    "选中节点,按enter键添加同级节点,tab键添加子节点": "Select the node, press Enter to add siblings, and TAB to add children",
     "知识库目录": "Knowledge directory",
     "文档历史版本": "Document version history",
     "存档日期": "Archive Date",
@@ -427,4 +426,26 @@ export default {
     "完成自动归档": "Auto archiving",
     "天": "Day",
     "任务完成 % 天后自动归档。": "Tasks are automatically archived % days after completion.",
+    "修改权限": "Modify permissions",
+    "阅读权限": "Read permissions",
+    "私有文库": "Private",
+    "成员开放": "Members",
+    "注册会员": "Registered",
+    "完全开放": "Open",
+    "已锁定": "Locked",
+    "锁定后其他会员将无法修改保存文档。": "After locking, other members will not be able to modify the saved document.",
+    "导出": "Export",
+    "导出XLSX": "Export XLSX",
+    "导出XLS": "Export XLS",
+    "导出CSV": "Export CSV",
+    "导出TXT": "Export TXT",
+    "导出JPG图片": "Export JPG",
+    "导出PNG图片": "Export PNG",
+    "导出PDF文件": "Export PDF",
+    "锁定文档": "Lock",
+    "解锁文档": "Unlock",
+    "文档链接": "Links",
+    "分享": "Share",
+    "文档设置": "Setting",
+    "文档成员": "Members",
 }

+ 6 - 0
resources/lang/en/general.php

@@ -123,4 +123,10 @@ return [
     "发送内容长度已超出最大限制!" => "The content length has exceeded the maximum limit!",
     "自动归档时间不可小于%天!" => "Automatic filing time can not be less than % days!",
     "自动归档时间不可大于%天!" => "The automatic filing time cannot be more than % days!",
+    "已被会员【%】锁定!" => "Has been locked by the member [%]!",
+    "锁定成功" => "Locked",
+    "已解除锁定" => "Unlocked",
+    "知识库仅对会员开放,请登录后再试!" => "Knowledge base is open to members only, please login and try again!",
+    "知识库仅对成员开放!" => "Knowledge base is open to members only!",
+    "知识库仅对作者开放!" => "Knowledge base for authors only!",
 ];