kuaifan 5 years ago
parent
commit
3c07780af9

+ 38 - 0
app/Http/Controllers/Api/DocsController.php

@@ -293,4 +293,42 @@ class DocsController extends Controller
         //未完成,应该还要删除章节
         return Base::retSuccess('删除成功!');
     }
+
+    /**
+     * 获取章节内容
+     *
+     * @apiParam {Number} id                章节数据ID
+     */
+    public function section__content()
+    {
+        $id = intval(Request::input('id'));
+        $row = Base::DBC2A(DB::table('docs_section')->where('id', $id)->first());
+        if (empty($row)) {
+            return Base::retError('文档不存在或已被删除!');
+        }
+        $cRow = Base::DBC2A(DB::table('docs_content')->select(['content'])->where('sid', $id)->first());
+        if (empty($cRow)) {
+            $cRow = [ 'content' => '' ];
+        }
+        return Base::retSuccess('success', array_merge($row, $cRow));
+    }
+
+    /**
+     * 获取章节内容
+     *
+     * @apiParam {Number} id                章节数据ID
+     * @apiParam {Object} [D]               Request Payload 提交
+     * - content: 内容
+     */
+    public function section__save()
+    {
+        $id = intval(Request::input('id'));
+        $row = Base::DBC2A(DB::table('docs_section')->where('id', $id)->first());
+        if (empty($row)) {
+            return Base::retError('文档不存在或已被删除!');
+        }
+        $D = Base::getContentsParse('D');
+        DB::table('docs_content')->updateOrInsert(['sid' => $id], ['content' => $D['content']]);
+        return Base::retSuccess('保存成功!');
+    }
 }

+ 3 - 0
app/Http/Middleware/VerifyCsrfToken.php

@@ -20,5 +20,8 @@ class VerifyCsrfToken extends Middleware
 
         //汇报提交
         'api/report/template/',
+
+        //保存文档
+        'api/docs/section/save/',
     ];
 }

File diff suppressed because it is too large
+ 1453 - 240
package-lock.json


+ 3 - 1
package.json

@@ -34,6 +34,8 @@
         "tinymce": "^5.2.2",
         "view-design": "^4.2.0",
         "vuedraggable": "^2.23.2",
-        "vue-clipboard2": "^0.3.1"
+        "vue-clipboard2": "^0.3.1",
+        "vue-kityminder-gg": "^1.2.3",
+        "x-data-spreadsheet": "^1.1.2"
     }
 }

+ 3 - 0
resources/assets/js/common.js

@@ -536,6 +536,9 @@
          * @returns {string}
          */
         jsonStringify(json, defaultVal) {
+            if (typeof json !== 'object') {
+                return json;
+            }
             try{
                 return JSON.stringify(json);
             }catch (e) {

+ 1 - 1
resources/assets/js/main/components/docs/NestedDraggable.vue

@@ -10,7 +10,7 @@
                 <div class="dashed"></div>
                 <div class="header">
                     <div class="tip"><img :src="detail.icon"/></div>
-                    <div class="title">{{ detail.title }}</div>
+                    <div class="title" @click="handleClick('open', detail)">{{ detail.title }}</div>
                 </div>
                 <div class="info">
                     <Icon type="md-create" @click="handleClick('edit', detail)"/>

+ 20 - 0
resources/assets/js/main/components/docs/minder/index.js

@@ -0,0 +1,20 @@
+import 'vue-kityminder-gg/static/kity'
+import 'vue-kityminder-gg/static/kityminder-core'
+import "hotboxkit/less/hotbox.less"
+import Editor from './minder'
+
+const MindEditor = {
+    Minder: Editor
+};
+
+const install = function (Vue, opts = {}) {
+    Object.keys(MindEditor).forEach((key) => {
+        Vue.component(key, MindEditor[key]);
+    });
+};
+
+// auto install
+if (typeof window !== 'undefined' && window.Vue) {
+    install(window.Vue);
+}
+export default Object.assign(MindEditor, {install});

File diff suppressed because it is too large
+ 344 - 0
resources/assets/js/main/components/docs/minder/minder.vue


+ 49 - 0
resources/assets/js/main/components/docs/sheet/index.vue

@@ -0,0 +1,49 @@
+<template>
+    <div ref="xspreadsheet" class="xspreadsheet"></div>
+</template>
+
+<style lang="scss" scoped>
+    .xspreadsheet {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+    }
+</style>
+<script>
+    import Spreadsheet from 'x-data-spreadsheet';
+    import zhCN from 'x-data-spreadsheet/dist/locale/zh-cn';
+
+    export default {
+        name: "Sheet",
+        props: {
+            value: {
+                type: Object,
+                default: function () {
+                    return {}
+                }
+            },
+        },
+        data() {
+            return {
+                sheet: null,
+            }
+        },
+        mounted() {
+            Spreadsheet.locale('zh-cn', zhCN);
+            //
+            this.sheet = new Spreadsheet(this.$refs.xspreadsheet, {
+                view: {
+                    height: () => this.$refs.xspreadsheet.clientHeight,
+                    width: () => this.$refs.xspreadsheet.clientWidth,
+                },
+            }).loadData(this.value).change(data => {
+                this.$emit('input', data);
+            });
+            //
+            this.sheet.validate()
+        },
+    }
+</script>

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

@@ -159,6 +159,7 @@
                         .docs-h1 {
                             flex: 1;
                             font-size: 16px;
+                            white-space: nowrap;
                         }
                         .docs-setting {
                             display: flex;
@@ -485,6 +486,10 @@
 
             handleSection(act, detail) {
                 switch (act) {
+                    case 'open':
+                        this.goForward({name: 'docs-edit', params: {sid:detail.id, other:detail||{}}});
+                        break;
+
                     case 'edit':
                         this.addSectionId = detail.id;
                         this.addSectionShow = true

+ 276 - 0
resources/assets/js/main/pages/docs/edit.vue

@@ -0,0 +1,276 @@
+<template>
+    <div class="w-main docs-edit">
+
+        <v-title>{{$L('文档编辑')}}-{{$L('轻量级的团队在线协作')}}</v-title>
+
+        <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"><Icon type="md-share" /></div>
+                <div class="header-menu"><Icon type="md-eye" /></div>
+                <div class="header-menu"><Icon type="md-time" /></div>
+                <div class="header-title">{{docDetail.title}}</div>
+                <div v-if="docDetail.type=='mind'" class="header-hint">选中节点,按enter键添加子节点,tab键添加同级节点</div>
+                <Button :disabled="disabledBtn || loadIng > 0" class="header-button" size="small" type="primary" @click="handleClick('save')">保存</Button>
+            </div>
+            <div class="docs-body">
+                <t-editor v-if="docDetail.type=='document'" class="body-text" v-model="docContent.content" height="100%"></t-editor>
+                <minder v-else-if="docDetail.type=='mind'" class="body-mind" @exportData="exportMindData" :template="docContent.template" :theme="docContent.theme" :importData="docContent.root"></minder>
+                <sheet v-else-if="docDetail.type=='sheet'" class="body-sheet" v-model="docContent.content"></sheet>
+            </div>
+        </div>
+
+    </div>
+</template>
+
+
+<style lang="scss">
+    .docs-edit {
+        .body-text {
+            .teditor-loadedstyle {
+                .tox-tinymce {
+                    border: 0;
+                    border-radius: 0;
+                }
+                .tox-mbtn {
+                    height: 28px;
+                }
+                .tox-menubar,
+                .tox-toolbar-overlord {
+                    padding: 0 12%;
+                    background: #f9f9f9;
+                }
+                .tox-toolbar__primary {
+                    background: none;
+                    border-top: 1px solid #eaeaea;
+                }
+                .tox-toolbar-overlord {
+                    border-bottom: 1px solid #E9E9E9;
+                }
+                .tox-toolbar__group:not(:last-of-type) {
+                    border-right: 1px solid #eaeaea;
+                }
+                .tox-sidebar-wrap {
+                    margin: 22px 12%;
+                    border: 1px solid #e8e8e8;
+                    border-radius: 2px;
+                    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.08);
+                    .tox-edit-area {
+                        border-top: 0;
+                    }
+                }
+                .tox-statusbar {
+                    border-top: 1px solid #E9E9E9;
+                    .tox-statusbar__resize-handle {
+                        display: none;
+                    }
+                }
+            }
+        }
+        .body-sheet {
+            box-sizing: content-box;
+            * {
+                box-sizing: content-box;
+            }
+        }
+    }
+</style>
+<style lang="scss" scoped>
+    .docs-edit {
+        .edit-box {
+            display: flex;
+            flex-direction: column;
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            .edit-header {
+                display: flex;
+                flex-direction: row;
+                align-items: center;
+                width: 100%;
+                height: 38px;
+                background-color: #ffffff;
+                box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.1);
+                position: relative;
+                z-index: 9;
+                .header-menu {
+                    width: 50px;
+                    height: 100%;
+                    text-align: center;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    margin-right: 3px;
+                    cursor: pointer;
+                    color: #777777;
+                    position: relative;
+                    .ivu-icon {
+                        font-size: 16px;
+                    }
+                    &:hover,
+                    &.active {
+                        color: #fff;
+                        background: #059DFD;
+                    }
+                }
+                .header-title {
+                    flex: 1;
+                    color: #333333;
+                    border-left: 1px solid #ddd;
+                    margin-left: 5px;
+                    padding-left: 24px;
+                    padding-right: 24px;
+                    font-size: 16px;
+                    white-space: nowrap;
+                }
+                .header-hint {
+                    padding-right: 22px;
+                    font-size: 12px;
+                    color: #666;
+                    white-space: nowrap;
+                }
+                .header-button {
+                    font-size: 12px;
+                    margin-right: 12px;
+                }
+            }
+            .docs-body {
+                flex: 1;
+                width: 100%;
+                position: relative;
+                .body-text {
+                    display: flex;
+                    width: 100%;
+                    height: 100%;
+                    .teditor-loadedstyle {
+                        height: 100%;
+                    }
+                }
+            }
+        }
+    }
+</style>
+<script>
+    import Vue from 'vue'
+    import minder from '../../components/docs/minder'
+    import TEditor from "../../components/TEditor";
+    import Sheet from "../../components/docs/sheet/index";
+
+    Vue.use(minder)
+
+    export default {
+        components: {Sheet, TEditor},
+        data () {
+            return {
+                loadIng: 0,
+
+                sid: 0,
+
+                docDetail: { },
+                docContent: { },
+                bakContent: null,
+            }
+        },
+        mounted() {
+
+        },
+        activated() {
+            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'))
+            }
+        },
+        deactivated() {
+            if ($A.getToken() === false) {
+                this.sid = 0;
+            }
+        },
+        watch: {
+            sid(val) {
+                if ($A.runNum(val) <= 0) {
+                    this.goBack();
+                    return;
+                }
+                this.docDetail = { };
+                this.docContent = { };
+                this.bakContent = null;
+                this.getDetail();
+            }
+        },
+        computed: {
+            disabledBtn() {
+                let tmpContent = $A.jsonStringify(this.docContent);
+                return this.bakContent == tmpContent;
+            }
+        },
+        methods: {
+            getDetail() {
+                this.loadIng++;
+                $A.aAjax({
+                    url: 'docs/section/content',
+                    data: {
+                        id: this.sid,
+                    },
+                    complete: () => {
+                        this.loadIng--;
+                    },
+                    error: () => {
+                        this.goBack();
+                        alert(this.$L('网络繁忙,请稍后再试!'));
+                    },
+                    success: (res) => {
+                        if (res.ret === 1) {
+                            this.docDetail = res.data;
+                            this.docContent = $A.jsonParse(res.data.content);
+                            this.bakContent = $A.jsonStringify(this.docContent);
+                        } else {
+                            this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
+                            this.goBack();
+                        }
+                    }
+                });
+            },
+
+            exportMindData(json) {
+                this.docContent = json;
+            },
+
+            handleClick(act) {
+                switch (act) {
+                    case "back":
+                    case "save":
+                        let tmpContent = $A.jsonStringify(this.docContent);
+                        if (this.bakContent != tmpContent) {
+                            this.bakContent = tmpContent;
+                            $A.aAjax({
+                                url: 'docs/section/save?id=' + this.sid,
+                                method: 'post',
+                                data: {
+                                    D: Object.assign(this.docDetail, {content: tmpContent})
+                                },
+                                error: () => {
+                                    alert(this.$L('网络繁忙,保存失败!'));
+                                },
+                                success: (res) => {
+                                    if (res.ret === 1) {
+                                        this.$Message.success(res.msg);
+                                    } else {
+                                        this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
+                                    }
+                                }
+                            });
+                        }
+                        if (act == 'back') {
+                            this.goBack();
+                        }
+                        break;
+
+                    case "menu":
+                        break;
+
+                }
+            }
+        },
+    }
+</script>

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

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