15724580513 há 5 anos atrás
pai
commit
2cbf23de80
87 ficheiros alterados com 14959 adições e 0 exclusões
  1. 72 0
      src/components/client/CommentComp/MtaComment.vue
  2. 115 0
      src/components/client/CommentComp/MtaCommentBase.vue
  3. 27 0
      src/components/client/CommentComp/MtaCommentLayout.vue
  4. 84 0
      src/components/client/CommentComp/MtaCommentReply.vue
  5. 123 0
      src/components/client/MtaRate/MtaRate.vue
  6. 40 0
      src/components/client/MtaRate/MtaRateBase.vue
  7. 39 0
      src/components/client/MtaRate/MtaRateReadOnly.vue
  8. 170 0
      src/components/client/Questions/GapFilling.vue
  9. 172 0
      src/components/client/Questions/MultipleChoice.vue
  10. 221 0
      src/components/client/Questions/ReadTheTopic.vue
  11. 239 0
      src/components/client/Questions/ShortAnswerQuestion.vue
  12. 163 0
      src/components/client/Questions/SingleChoice.vue
  13. 216 0
      src/components/client/Questions/TrueOrFalse.vue
  14. 320 0
      src/components/client/Questions/upLoadFileForJianda.vue
  15. 41 0
      src/components/client/QuestionsForCuoti/AnswerArea.vue
  16. 40 0
      src/components/client/QuestionsForCuoti/AnswerAreaTianKong.vue
  17. 335 0
      src/components/client/QuestionsForCuoti/GapFilling.vue
  18. 282 0
      src/components/client/QuestionsForCuoti/MultipleChoice.vue
  19. 194 0
      src/components/client/QuestionsForCuoti/ReadTheTopic.vue
  20. 312 0
      src/components/client/QuestionsForCuoti/ShortAnswerQuestion.vue
  21. 230 0
      src/components/client/QuestionsForCuoti/SingleChoice.vue
  22. 62 0
      src/components/client/QuestionsForCuoti/TianKongItem.vue
  23. 205 0
      src/components/client/QuestionsForCuoti/TrueOrFalse.vue
  24. 44 0
      src/components/client/common/CardComp.vue
  25. 255 0
      src/components/client/common/MtaBreadcrumb.vue
  26. 86 0
      src/components/client/common/StClock.vue
  27. 49 0
      src/components/client/common/footer.vue
  28. 640 0
      src/components/client/common/header.vue
  29. 192 0
      src/components/client/common/mtaDialog.vue
  30. 94 0
      src/components/client/kechengDetaileTab/biji.vue
  31. 90 0
      src/components/client/kechengDetaileTab/bijiDialog.vue
  32. 40 0
      src/components/client/kechengDetaileTab/bijiSlot.vue
  33. 53 0
      src/components/client/test/TestComponent.vue
  34. 231 0
      src/components/custom/MtaBusAudioPlayer.vue
  35. 262 0
      src/components/custom/MtaBusCoursePlayer.vue
  36. 243 0
      src/components/custom/MtaBusPdfPlayer.vue
  37. 180 0
      src/components/custom/MtaBusVideoPlayer.vue
  38. 151 0
      src/components/custom/MtaBusVideoPlayer_bk.vue
  39. 75 0
      src/components/custom/StDialog.vue
  40. 149 0
      src/components/custom/StFormulaDialog.vue
  41. 503 0
      src/components/custom/VideoPlayer.vue
  42. 392 0
      src/components/custom/VideoPlayerBk.vue
  43. 320 0
      src/components/management/AliUploadVideo.vue
  44. 333 0
      src/components/management/Layout/AliPlay/AliPlay.vue
  45. 226 0
      src/components/management/Layout/EditableTree/EditableTree.vue
  46. 125 0
      src/components/management/Layout/EditableTree/EditableTreeItem.vue
  47. 213 0
      src/components/management/Layout/EditableTreeOrg/EditableTree.vue
  48. 123 0
      src/components/management/Layout/EditableTreeOrg/EditableTreeItem.vue
  49. 22 0
      src/components/management/Layout/Footer.vue
  50. 138 0
      src/components/management/Layout/FormFieldset/FormFieldset.vue
  51. 482 0
      src/components/management/Layout/Header.vue
  52. 116 0
      src/components/management/Layout/ImageDownloadPreview/ImageDownloadPreview.vue
  53. 18 0
      src/components/management/Layout/ImageDownloadPreview/ImageDownloadPreviewSlot.vue
  54. 94 0
      src/components/management/Layout/ImageDownloadPreview/ImgageDownloadPreviewBase.vue
  55. 272 0
      src/components/management/Layout/InfiniteScrollInput/InfiniteScrollInput3.vue
  56. 185 0
      src/components/management/Layout/PaperPreview/PaperPreview.vue
  57. 45 0
      src/components/management/Layout/PaperPreview/shitiStructure.vue
  58. 268 0
      src/components/management/Layout/SelectInput/SelectInputTree.vue
  59. 275 0
      src/components/management/Layout/SelectInput/StSelectInputTree.vue
  60. 23 0
      src/components/management/Layout/SelectInput/StSelectInputTreeSlot.vue
  61. 206 0
      src/components/management/Layout/SelectInput/clientSelectInputTree.vue
  62. 60 0
      src/components/management/Layout/Sidebar/SidebarItem.vue
  63. 113 0
      src/components/management/Layout/SlidingBlock/SlidingBlock.js
  64. 243 0
      src/components/management/Layout/SystemConfig/SystemThemeConfig.vue
  65. 73 0
      src/components/management/Layout/UploadAlCloud/UploadAlCloud.vue
  66. 152 0
      src/components/management/Layout/UploadAlCloud/uploadFile.vue
  67. 73 0
      src/components/management/Layout/eChartTableExpand/eChartTableExpand.vue
  68. 21 0
      src/components/management/Layout/pageLayout/pageLayout.vue
  69. 136 0
      src/components/management/Layout/popoverLogin/popoverLogin.vue
  70. 39 0
      src/components/management/Layout/popoverLogin/popoverWrap.vue
  71. 517 0
      src/components/management/Layout/templateImport/AddUserImport.vue
  72. 620 0
      src/components/management/Layout/templateImport/import.vue
  73. 107 0
      src/components/management/Layout/videoBanner/dialogGuideContent.vue
  74. 200 0
      src/components/management/Layout/videoBanner/index.vue
  75. 111 0
      src/components/management/Layout/welcomeInfo/QuickEntry.vue
  76. 139 0
      src/components/management/Layout/welcomeInfo/RecentTest.vue
  77. 175 0
      src/components/management/Layout/welcomeInfo/StatisticalAnalysis.vue
  78. 523 0
      src/components/management/QuillEditor.vue
  79. 25 0
      src/components/management/QuillEditorStBlockEmbed.js
  80. 20 0
      src/components/management/Transform.vue
  81. 34 0
      src/components/management/UEditor.vue
  82. 196 0
      src/components/management/common/MtaBreadcrumb.vue
  83. 3 0
      src/components/management/globalUploader/bus.js
  84. 464 0
      src/components/management/globalUploader/globalUploader.vue
  85. BIN
      src/components/management/globalUploader/images/image-icon.png
  86. BIN
      src/components/management/globalUploader/images/text-icon.png
  87. BIN
      src/components/management/globalUploader/images/video-icon.png

+ 72 - 0
src/components/client/CommentComp/MtaComment.vue

@@ -0,0 +1,72 @@
+<template>
+    <!--    <div class="c-mta-comment">-->
+    <!--<MtaRate code="readOnly" :myData="myData"></MtaRate>
+    <MtaRate code="editor" @change="handleRateChange"></MtaRate>-->
+    <!--    评论    -->
+    <MtaCommentBase
+            :my-data="myData"
+            @comment-list="handleCommentList"
+            @delete="handleDelete"
+            @reply="handleReply"
+            @reply-delete="handleReplyDelete"
+            v-if="code !== 'reply'"
+    ></MtaCommentBase>
+    <MtaCommentReply
+            :my-data="myData"
+            @delete="handleDelete"
+            v-else
+    ></MtaCommentReply>
+    <!--    </div>-->
+</template>
+
+<script>
+    import MtaRate        from '../MtaRate/MtaRate';
+    import MtaCommentBase from './MtaCommentBase';
+    import MtaCommentReply from './MtaCommentReply';
+
+
+    export default {
+        name:       'MtaComment',
+        components: {
+            MtaRate,
+            MtaCommentBase,
+            MtaCommentReply,
+        },
+        props:      {
+            myData: {
+                type:     Object,
+                required: true,
+            },
+            code: {
+                type: String,
+                default: ''
+            }
+        },
+        data() {
+            return {};
+        },
+        methods:    {
+           /* handleRateChange(data) {
+                console.log('评分:', data);
+            },*/
+            handleDelete(data) {
+                this.$emit('delete', data);
+            },
+            handleReply(data) {
+                this.$emit('reply', data);
+            },
+            handleReplyDelete(data) {
+                this.$emit('reply-delete', data);
+            },
+            handleCommentList(data) {
+                this.$emit('comment-list', data);
+            },
+        },
+    };
+</script>
+
+<style lang="scss" scoped>
+    .c-mta-comment {
+
+    }
+</style>

+ 115 - 0
src/components/client/CommentComp/MtaCommentBase.vue

@@ -0,0 +1,115 @@
+<template>
+    <MtaCommentLayout>
+        <template slot="avatar">
+            <el-avatar :src="myData.userPic || circleUrl" class="c-c-comment-author" size="large"></el-avatar>
+        </template>
+        <template slot="nameAndRate">
+            <span>{{myData.userName}}</span>
+            <MtaRate :my-data="myData" code="readOnly"></MtaRate>
+        </template>
+        <template slot="content">
+            <div :class="{ 'NotAll': isAllContent}" v-if="isAllContent">{{myData.content}}</div>
+            <div :class="{ 'NotAll': !isAllContent}" v-else>{{breviary}}</div>
+        </template>
+        <template slot="btnAndDate">
+            <div>
+                <el-button @click="handleToggleContent" type="text"
+                           v-if="myData.content && myData.content.length > strLength"
+                           v-text="!isAllContent ? '全文' : '收起'"></el-button>
+                <el-button @click="handleReply" type="text">回复</el-button>
+
+                <el-button @click="openCommentListDialog" type="text" v-if="myData.infoCount>1">
+                    更多
+                    <!--                    <span>({{myData.infoCount}})</span>-->
+                </el-button>
+
+                <el-button @click="handleDelete" type="text" v-if="myData.delFlag">删除</el-button>
+            </div>
+            <div v-if="myData.updateTime">
+                {{myData.updateTime}}
+            </div>
+        </template>
+        <template slot="Reply" v-if="myData.info && myData.info.length">
+            <MtaCommentReply :my-data="myData.info[0]" @delete="handleReplyDelete"></MtaCommentReply>
+        </template>
+    </MtaCommentLayout>
+</template>
+
+<script>
+    import MtaCommentLayout from './MtaCommentLayout';
+    import MtaRate          from '../MtaRate/MtaRate';
+    import MtaCommentReply  from './MtaCommentReply';
+
+    export default {
+        name:       'MtaCommentBase',
+        components: {
+            MtaRate,
+            MtaCommentLayout,
+            MtaCommentReply,
+        },
+        props:      {
+            myData: {
+                type:      Object,
+                required:  true,
+                validator: function (value) {
+                    return value.hasOwnProperty('score')
+                        && value.hasOwnProperty('content')
+                        && value.hasOwnProperty('userName')
+                        && value.hasOwnProperty('userPic')
+                        && value.hasOwnProperty('delFlag')
+                        && value.hasOwnProperty('info')
+                        && value.hasOwnProperty('updateTime');
+
+                },
+            },
+        },
+        data() {
+            return {
+                circleUrl:    'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+                isAllContent: false,
+                replyDialog:  false,
+                strLength:    220,
+            };
+        },
+        computed:   {
+            breviary() {
+                if (this.myData.content.length > this.strLength) {
+                    return this.myData.content.substring(0, this.strLength) + '...';
+                } else {
+                    return this.myData.content;
+                }
+            },
+        },
+        methods:    {
+            handleToggleContent() {
+                this.isAllContent = !this.isAllContent;
+            },
+            handleReply() {
+                const opt = JSON.parse(JSON.stringify(this.myData));
+                this.$emit('reply', opt);
+            },
+            handleDelete() {
+                const opt = JSON.parse(JSON.stringify(this.myData));
+                this.$emit('delete', opt);
+            },
+            handleReplyDelete(data) {
+                const myData = JSON.parse(JSON.stringify(this.myData));
+                const opt = {
+                    parent:  myData,
+                    current: data,
+                };
+                this.$emit('reply-delete', opt);
+            },
+            openCommentListDialog() {
+                const myData = JSON.parse(JSON.stringify(this.myData));
+                this.$emit('comment-list', myData);
+            },
+        },
+    };
+</script>
+
+<style lang="scss">
+    .c-Mta-Comment-Base {
+
+    }
+</style>

+ 27 - 0
src/components/client/CommentComp/MtaCommentLayout.vue

@@ -0,0 +1,27 @@
+<template>
+    <div class="c-mta-comment-layout">
+        <div class="head-portrait-box">
+            <slot name="avatar"></slot>
+        </div>
+        <div class="c-comment-content">
+            <div class="c-first-line">
+               <slot name="nameAndRate"></slot>
+            </div>
+            <div class="c-two-line">
+                <slot name="content"></slot>
+            </div>
+            <div class="c-three-line">
+                <slot name="btnAndDate"></slot>
+            </div>
+            <div class="c-four-line">
+                <slot name="Reply"></slot>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: 'MtaCommentLayout',
+    };
+</script>

+ 84 - 0
src/components/client/CommentComp/MtaCommentReply.vue

@@ -0,0 +1,84 @@
+<template>
+    <MtaCommentLayout>
+        <template slot="avatar">
+            <el-avatar :src="myData.userPic || circleUrl" class="c-c-comment-author" size="large"></el-avatar>
+        </template>
+        <template slot="nameAndRate">
+            <span>{{myData.userName}}</span>
+        </template>
+        <template slot="content">
+<!--            <div :class="{ 'NotAll': !isAllContent}">{{myData.result}}</div>-->
+            <div :class="{ 'NotAll': isAllContent}" v-if="isAllContent">{{myData.result}}</div>
+            <div :class="{ 'NotAll': !isAllContent}" v-else>{{breviary}}</div>
+        </template>
+        <template slot="btnAndDate">
+            <div class="c-btnAndDate">
+                <div>
+                    <el-button @click="handleToggleContent" type="text"
+                               v-if="myData.result && myData.result.length > strLength" v-text="!isAllContent ? '全文' : '收起'"></el-button>
+                    <el-button @click="handleDelete" type="text" v-if="myData.delFlag">删除</el-button>
+<!--                    <el-button type="text" @click="handleDelete">删除</el-button>-->
+                </div>
+                <div v-if="myData.createTime">
+                    {{myData.createTime}}
+                </div>
+            </div>
+
+        </template>
+    </MtaCommentLayout>
+</template>
+
+<script>
+    import MtaCommentLayout from './MtaCommentLayout';
+
+    export default {
+        name:       'MtaCommentReply',
+        components: {
+            MtaCommentLayout,
+        },
+        props:      {
+            myData: {
+                type:      Object,
+                required:  true,
+                validator: function (value) {
+                    return value.hasOwnProperty('userPic')
+                        && value.hasOwnProperty('result')
+                        && value.hasOwnProperty('userName')
+                        && value.hasOwnProperty('delFlag')
+                        && value.hasOwnProperty('createTime');
+                },
+            },
+        },
+        computed:   {
+            breviary() {
+                if (this.myData.result.length > this.strLength) {
+                    return this.myData.result.substring(0, this.strLength) + '...';
+                } else {
+                    return this.myData.result
+                }
+            },
+        },
+        data() {
+            return {
+                circleUrl:    'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+                isAllContent: false,
+                strLength: 200,
+            };
+        },
+        methods:    {
+            handleToggleContent() {
+                this.isAllContent = !this.isAllContent;
+            },
+            handleDelete() {
+                const data = JSON.parse(JSON.stringify(this.myData));
+                this.$emit('delete', data);
+            },
+        },
+    };
+</script>
+
+<style>
+    .c-btnAndDate {
+
+    }
+</style>

+ 123 - 0
src/components/client/MtaRate/MtaRate.vue

@@ -0,0 +1,123 @@
+<template>
+    <MtaRateReadOnly
+            :score="MtaRateReadOnlyObj.defaultText"
+            :value="MtaRateReadOnlyObj.value"
+            v-if="readOnly"></MtaRateReadOnly>
+    <MtaRateBase
+            :default-value="defaultScore"
+            @change="handleEditorRateChange"
+            ref="MtaRateBase"
+            v-else></MtaRateBase>
+</template>
+
+<script>
+    import MtaRateBase     from './MtaRateBase';
+    import MtaRateReadOnly from './MtaRateReadOnly';
+
+    export default {
+        name:       'MtaRate',
+        props:      {
+            /**
+             * readOnly 只读无法编辑
+             * editor 可以编辑评分
+             */
+            code:    {
+                type:      String,
+                required:  true,
+                validator: function (value) {
+                    return ['readOnly', 'editor'].indexOf(value) !== -1;
+                },
+            },
+            mapping: {
+                type:    Object,
+                default: () => {
+                    return {};
+                },
+            },
+            myData:  {
+                type:     Object,
+                required: true,
+            },
+            defaultValue: {
+                type: Number,
+                validator: function (value) {
+                    return value>=0 && value<=10;
+                },
+            },
+        },
+        components: {
+            MtaRateBase,
+            MtaRateReadOnly,
+        },
+        computed: {
+            defaultScore() {
+                return this.defaultValue/2
+            }
+        },
+        watch:      {
+            code:   {
+                handler(a, b) {
+                    this.readOnly = (a === 'readOnly');
+                },
+                immediate: true,
+            },
+            myData: {
+                handler(a, b) {
+                    if (typeof a === 'object') {
+                        if (this.code === 'editor') {
+                            this.MtaRateEditor.value = this.getMathScore(Number(a[this.mapping.score || 'score'])) || 0;
+
+                            // 非default时 具有dom 可重置数据
+                            if (this.$refs['MtaRateBase']) {
+                                this.$refs['MtaRateBase'].reventData(this.MtaRateEditor.value);
+                            }
+
+                        } else if (this.code === 'readOnly') {
+                            this.MtaRateReadOnlyObj.value = this.getMathScore(Number(a[this.mapping.score || 'score'])) || 0;
+                            this.MtaRateReadOnlyObj.defaultText = `${a[this.mapping.score || 'score'] || 0}分`;
+                        }
+
+                    } else {
+                        if (this.code === 'editor') {
+                            this.MtaRateEditor.value = 0;
+                        } else if (this.code === 'readOnly') {
+                            this.MtaRateReadOnlyObj.value = 0;
+                            this.MtaRateReadOnlyObj.defaultText = `0分`;
+                        }
+
+                    }
+                },
+                immediate: true,
+                deep:      true,
+
+            },
+        },
+        data() {
+            return {
+                readOnly:           false,
+                MtaRateReadOnlyObj: {
+                    defaultText: '0分',
+                    value:       0,
+                },
+                MtaRateEditor:      {
+                    value: 0,
+                },
+            };
+        },
+        methods:    {
+            getMathScore(data) {
+                if (typeof data !== 'number') {
+                    throw new Error(`MtaRate组件: ${this.mapping.score || 'score'}需要Number类型`);
+                }
+                return Math.ceil(data / 2);
+            },
+            handleEditorRateChange(data) {
+                this.$emit('change', data * 2);
+            },
+        },
+    };
+</script>
+
+<style scoped>
+
+</style>

+ 40 - 0
src/components/client/MtaRate/MtaRateBase.vue

@@ -0,0 +1,40 @@
+<template>
+    <el-rate
+            :show-text="showText"
+            :texts="texts"
+            @change="changeRateValue"
+            v-model="value"
+    >
+    </el-rate>
+</template>
+
+<script>
+    export default {
+        name:    'MtaRateBase',
+        props:   {
+            defaultValue: {
+                type:    Number,
+                default: 0,
+            },
+        },
+        data() {
+            return {
+                texts:    ['2分', '4分', '6分', '8分', '10分'],
+                showText: false,
+                value:    this.defaultValue,
+            };
+        },
+        methods: {
+            changeRateValue(data) {
+                this.$emit('change', data);
+            },
+            reventData(data) {
+                this.value = data;
+            }
+        },
+    };
+</script>
+
+<style scoped>
+
+</style>

+ 39 - 0
src/components/client/MtaRate/MtaRateReadOnly.vue

@@ -0,0 +1,39 @@
+<template>
+    <div class="c-mta-rate_read-only">
+        <el-rate
+                disabled
+                v-model="value"
+        />
+        <span class="c-rate-score_text">{{score}}</span>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: 'MtaRateReadOnly',
+        props: {
+            score: {
+                type: String,
+                required: true
+            },
+            value: {
+                type: Number,
+                default: 0
+            }
+        },
+    };
+</script>
+
+<style lang="scss" scoped>
+.c-mta-rate_read-only {
+    display: flex;
+    justify-content: flex-start;
+    line-height: 25px;
+
+    .c-rate-score_text {
+        margin-left: 5px;
+        font-size: 16px;
+        color: #333;
+    }
+}
+</style>

+ 170 - 0
src/components/client/Questions/GapFilling.vue

@@ -0,0 +1,170 @@
+<template>
+    <div class="mta-gap-filling" v-if="questionData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiNum">
+                    <span v-if="isYueDu">({{shitiNum}})</span>
+                    <span v-else>{{shitiNum}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="questionData.name"></div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{questionData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player
+                    :options="audioOptions"
+                    @updAudioPlaytimes="updAudioPlaytimes"
+            >
+            </mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <div class="mta-inputTK-row" v-for="item in questionTKCount">
+                    <span class="mta-num">填空{{item}}:</span>
+                    <el-input v-model="tianKongResult[item-1]" @change="inputChange"
+                              :placeholder="`请输入填空${item}答案`"></el-input>
+                </div>
+
+            </el-col>
+        </el-row>
+        <div class="mta-modal" v-if="getShowModel"></div>
+    </div>
+</template>
+
+<script>
+    import { mapGetters }    from 'vuex';
+    import MtaBusAudioPlayer from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name:       'GapFilling',
+        props:      {
+            questionData: { // 单选题数据
+                require: true,
+                type:    Object,
+            },
+            shitiIndex:   { // 试题序号
+                type: String,
+            },
+            isYueDu:      {
+                type:    Boolean,
+                default: false,
+            },
+            yuduIndex:    {
+                type: Number,
+            },
+        },
+        components: {
+            MtaBusAudioPlayer,
+        },
+        computed:   {
+            questionTKCount() {
+                if (this.questionData.count) {
+                    return this.questionData.count;
+                }
+            },
+            ...mapGetters(['getShowScore', 'getShowModel']),
+        },
+        watch:      {
+            questionData: {
+                handler:   function (newVal, oldVal) {
+                    this.tianKongResult = [];
+                    this.newTiankongData = newVal.reply;
+                    if (this.newTiankongData) {
+                        this.newTiankongData.forEach(item => {
+                            // if (item && item.length) {
+                                this.tianKongResult.push(item);
+                            // }
+                        });
+                    }
+
+                    if (!this.tianKongResult.length) {
+                        for (let i = 0; i < newVal.count; i++) {
+                            this.tianKongResult.push('');
+                        }
+                    }
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+                                this.audioOptions.curPlaytimes = newVal.playtimes;
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+
+                },
+                immediate: true,
+                deep:      true,
+            },
+            shitiIndex:   {
+                handler(newVal, oldVal) {
+                    if (newVal) {
+                        let c = newVal.split('-');
+                        this.paragraph = c[0];
+                        this.shitiNum = c[1];
+                    }
+                },
+                immediate: true,
+            },
+        },
+        data() {
+            return {
+                newTiankongData: this.questionData.reply === null ? '' : this.questionData.reply,
+                tianKongResult:  [],
+                otherResult:     [],
+                // 段落
+                paragraph:       null,
+                shitiNum:        0,
+                audioOptions:    null,
+            };
+        },
+        methods:    {
+            inputChange() {
+                const myData = [];
+                this.tianKongResult.forEach(item => {
+                    myData.push(item.trim());
+                });
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        type:      'tiankong',
+                        stId:      this.questionData.stId,
+                        data:      myData,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        stId:      this.questionData.stId,
+                        data:      myData,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+
+            },
+            updAudioPlaytimes(playtimes) {
+                if (this.questionData.stId === 0) { // 阅读题
+                    this.$emit('updAudioPlaytimesSub', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                } else { // 其他题
+                    this.$emit('updAudioPlaytimes', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                }
+            },
+        },
+    };
+</script>
+

+ 172 - 0
src/components/client/Questions/MultipleChoice.vue

@@ -0,0 +1,172 @@
+<template>
+    <div class="mta-multiple-choice" v-if="questionData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiNum">
+                    <span v-if="isYueDu">({{shitiNum}})</span>
+                    <span v-else>{{shitiNum}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="questionData.name">
+                </div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{questionData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player
+                    :options="audioOptions"
+                    @updAudioPlaytimes="updAudioPlaytimes"
+            >
+            </mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <el-checkbox-group v-model="newCheckListData" @change="selectChange">
+
+                    <div class="mta-checkbox-row" v-for="item in questionDataContent">
+
+<!--                        <el-checkbox :label="item.index"><span style="display: flex;justify-content: flex-start;flex-direction: row"><span>{{item.num}}、</span><span v-html="item.label"></span></span></el-checkbox>-->
+                        <el-checkbox :label="`${item.index}`">
+                            <slot>
+                                <span style="vertical-align: top;display: inline-block;width: auto;" class="wordOption">{{item.num}}、</span>
+                                <span v-html="item.label" class="wordBox"></span>
+                            </slot>
+                        </el-checkbox>
+                    </div>
+                </el-checkbox-group>
+
+            </el-col>
+        </el-row>
+        <div class="mta-modal" v-if="getShowModel"></div>
+    </div>
+</template>
+
+<script>
+    import {getLetterByIndex} from '@/utils/common'
+    import {mapGetters} from 'vuex'
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name: "MultipleChoice",
+        props: {
+            questionData: { // 单选题数据
+                require: true,
+                type: Object,
+            },
+            shitiIndex: {
+                type: String,
+            },
+            isYueDu: {
+                type: Boolean,
+                default: false
+            },
+            yuduIndex: {
+                type: Number,
+            }
+        },
+        components: {
+            MtaBusAudioPlayer,
+        },
+        computed: {
+            questionDataContent() {
+                if (this.questionData.content && this.questionData.content.length) {
+                    return this.questionData.content.map((item, index) => {
+                        return {
+                            label: item,
+                            num: getLetterByIndex(index),
+                            index: index
+                        }
+                    });
+                }
+            },
+            ...mapGetters(['getShowScore', 'getShowModel']),
+        },
+        watch: {
+            questionData: {
+                handler(newVal, oldVal) {
+                    if (newVal.reply && newVal.reply.length) {
+                        newVal.reply.sort().forEach(item => {
+                            this.newCheckListData.push(item);
+                        })
+                    } else {
+                        this.newCheckListData = [];
+                    }
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+                                this.audioOptions.curPlaytimes = newVal.playtimes;
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+            },
+            shitiIndex: {
+                handler(newVal, oldVal) {
+                    if (newVal) {
+                        let c = newVal.split('-');
+                        this.paragraph = c[0];
+                        this.shitiNum = c[1];
+                    }
+                },
+                immediate: true,
+            },
+        },
+        data() {
+            return {
+                newCheckListData: [],
+                // 段落
+                paragraph: null,
+                shitiNum: 0,
+                audioOptions: null,
+            }
+        },
+        methods: {
+            selectChange(val) {
+                // this.$emit('reply', [this.questionData.paragraph ,this.questionData.stId,this.newCheckListData]);
+                // this.$emit('reply', [1 ,this.questionData.stId, this.newCheckListData]);
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        type: 'duoxuan',
+                        stId: this.questionData.stId,
+                        data: this.newCheckListData.sort(),
+                        number: this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        stId: this.questionData.stId,
+                        data: this.newCheckListData.sort(),
+                        number: this.shitiIndex - 1,
+                    });
+                }
+
+
+            },
+            updAudioPlaytimes(playtimes) {
+                if (this.questionData.stId === 0) { // 阅读题
+                    this.$emit('updAudioPlaytimesSub', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                } else { // 其他题
+                    this.$emit('updAudioPlaytimes', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                }
+            },
+        },
+    }
+</script>

+ 221 - 0
src/components/client/Questions/ReadTheTopic.vue

@@ -0,0 +1,221 @@
+<template>
+    <div class="mta-read-the-topic">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+
+                <!--                <span>阅读以下文字并回答问题</span>-->
+                <!--                <span class="mta-SC-score" v-if="getShowScore">({{questionData.score}}分)</span>-->
+            </el-col>
+        </el-row>
+
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <span style="margin-right: 10px;" v-if="!!shitiNum">{{shitiNum}}.</span>
+                <!-- 试题题干区域 -->
+                <div style="display: flex;justify-content: space-between;flex-direction: row;">
+
+                    <span class="mta-SC-title" v-html="questionData.name"></span>
+                    <span class="mta-SC-score" v-if="getShowScore">({{questionData.score}}分)</span>
+                </div>
+            </el-col>
+        </el-row>
+
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player
+                    :options="audioOptions"
+                    @updAudioPlaytimes="updAudioPlaytimes"
+            >
+            </mta-bus-audio-player>
+        </el-row>
+
+        <el-row class="mta-three-row" type="flex">
+            <el-col>
+                <!-- 试题区域 -->
+                <!-- 单选题 -->
+                <div v-for="(item,index) in danxuanData" :key="`danxuan${index}`" v-if="danxuanData.length > 0">
+                    <mta-single-choice
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :shitiIndex="`${paragraph}-${num+index}`"
+                            @reply="reply"
+                            @updAudioPlaytimesSub="e=> updAudioPlaytimesSub('danxuan', index, e)"
+                    ></mta-single-choice>
+                </div>
+                <!-- 多选题 -->
+                <div v-for="(item,index)  in duoxuanData" :key="`duoxuan${index}`" v-if="duoxuanData.length > 0">
+                    <mta-multiple-choice
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :shitiIndex="`${paragraph}-${num+index+danxuanData.length}`"
+                            @reply="reply"
+                            @updAudioPlaytimesSub="e=> updAudioPlaytimesSub('duoxuan', index, e)"
+                    ></mta-multiple-choice>
+                </div>
+                <!-- 判断题 -->
+                <div v-for="(item,index)  in panduanData" :key="`panduan${index}`" v-if="panduanData.length > 0">
+                    <mta-true-or-false
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :shitiIndex="`${paragraph}-${num+index+panduanData.length+duoxuanData.length}`"
+                            @reply="reply"
+                            @updAudioPlaytimesSub="e=> updAudioPlaytimesSub('panduan', index, e)"
+                    ></mta-true-or-false>
+                </div>
+                <!-- 填空题 -->
+                <div v-for="(item,index)  in tiankongData" :key="`tiankong${index}`" v-if="tiankongData.length > 0">
+                    <mta-gap-filling
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :shitiIndex="`${paragraph}-${num+index+danxuanData.length+panduanData.length+panduanData.length}`"
+                            @reply="reply"
+                            @updAudioPlaytimesSub="e=> updAudioPlaytimesSub('tiankong', index, e)"
+                    ></mta-gap-filling>
+                </div>
+                <!-- 简答题 -->
+                <div v-for="(item,index)  in jiandaData" :key="`jianda${index}`" v-if="jiandaData.length > 0">
+                    <mta-short-answer-question
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            @clickUploadFile="clickUploadFile"
+                            @richText="richText"
+                            :shitiIndex="`${paragraph}-${num+index+danxuanData.length+panduanData.length+panduanData.length+tiankongData.length}`"
+                            @reply="reply"
+                            :option="option"
+                            @updAudioPlaytimesSub="e=> updAudioPlaytimesSub('jianda', index, e)"
+                    ></mta-short-answer-question>
+                </div>
+            </el-col>
+        </el-row>
+        <div class="mta-modal" v-if="getShowModel"></div>
+    </div>
+</template>
+
+<script>
+    import MtaSingleChoice        from '@/components/client/Questions/SingleChoice.vue';
+    import MtaTrueOrFalse         from '@/components/client/Questions/TrueOrFalse.vue';
+    import MtaMultipleChoice      from '@/components/client/Questions/MultipleChoice.vue';
+    import MtaGapFilling          from '@/components/client/Questions/GapFilling.vue';
+    import MtaShortAnswerQuestion from '@/components/client/Questions/ShortAnswerQuestion.vue';
+    import { mapGetters }         from 'vuex';
+    import MtaBusAudioPlayer      from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name:       'ReadTheTopic',
+        components: {
+            MtaSingleChoice,
+            MtaMultipleChoice,
+            MtaTrueOrFalse,
+            MtaGapFilling,
+            MtaShortAnswerQuestion,
+            MtaBusAudioPlayer,
+        },
+        props:      {
+            questionData: { // 简答题数据
+                require: true,
+                type:    Object,
+            },
+            shitiIndex:   {
+                type: String,
+            },
+            option:       {
+                type:    Object,
+                default: () => ({}),
+            },
+        },
+        computed:   {
+            danxuanData() {
+                return this.questionData.danxuan;
+            },
+            duoxuanData() {
+                return this.questionData.duoxuan;
+            },
+            panduanData() {
+                return this.questionData.panduan;
+            },
+            jiandaData() {
+                return this.questionData.jianda;
+            },
+            tiankongData() {
+                return this.questionData.tiankong;
+            },
+            ...mapGetters(['getShowScore', 'getShowModel']),
+        },
+        data() {
+            return {
+                num:          1,
+                // 段落
+                paragraph:    null,
+                shitiNum:     0,
+                audioOptions: null,
+            };
+        },
+        watch:      {
+            shitiIndex:   {
+                handler(newVal, oldVal) {
+                    if (newVal) {
+                        let c = newVal.split('-');
+                        this.paragraph = c[0];
+                        this.shitiNum = c[1];
+                    }
+                },
+                immediate: true,
+            },
+            questionData: {
+                handler(newVal, oldVal) {
+                    // console.log(newVal);
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+                                this.audioOptions.curPlaytimes = newVal.playtimes;
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+            },
+        },
+        methods:    {
+            clickUploadFile(data){
+                this.$emit('clickUploadFile', data);
+            },
+            richText(){
+                this.$emit('richText','richtext');
+            },
+            reply(data) {
+                const newData = Object.assign(data, {
+                    stId:      this.questionData.stId,
+                    paragraph: this.paragraph,
+                });
+                this.$emit('reply', newData);
+            },
+            updAudioPlaytimes(playtimes) {
+                this.$emit('updAudioPlaytimes', {
+                    stId:      this.questionData.stId,
+                    playtimes: playtimes,
+                });
+            },
+            updAudioPlaytimesSub(type, index, payload) {
+                this.$emit('updAudioPlaytimes', {
+                    stId:      this.questionData.stId,
+                    type:      type,
+                    index:     index,
+                    playtimes: payload.playtimes,
+                });
+            },
+        },
+    };
+</script>
+

+ 239 - 0
src/components/client/Questions/ShortAnswerQuestion.vue

@@ -0,0 +1,239 @@
+<template>
+    <div class="mta-short-answer-question" v-if="questionData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiNum">
+                    <span v-if="isYueDu">({{shitiNum}})</span>
+                    <span v-else>{{shitiNum}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="questionData.name">
+                </div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{questionData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player
+                    :options="audioOptions"
+                    @updAudioPlaytimes="updAudioPlaytimes"
+            >
+            </mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <!--<el-input
+                        @change="inputChange"
+                        type="textarea"
+                        :rows="2"
+                        placeholder="请输入简答题答案"
+                        v-model="textarea">
+                </el-input>-->
+
+                <mta-st-quill-editor
+                        ref="editorQuill"
+                        :value="textarea"
+                        :quillEditorRef="quillEditorRef"
+                        flg="1"
+                        @richText="richText"
+                        @clickUploadFile="clickUploadFile"
+                        @syncValue="syncValue">
+                </mta-st-quill-editor>
+            </el-col>
+        </el-row>
+        <div class="mta-modal" v-if="getShowModel"></div>
+        <up-load-file-for-jianda
+                :option="option"
+                :myfileLists="questionData.files"
+                @getFileLists="handleGetFileLists"
+        />
+    </div>
+</template>
+
+<script>
+    import { mapGetters }      from 'vuex';
+    import { getStringByHtml } from '@/utils/common.js';
+    import MtaStQuillEditor    from '@/components/management/QuillEditor.vue';
+    import MtaBusAudioPlayer   from '@/components/custom/MtaBusAudioPlayer.vue';
+    import upLoadFileForJianda from '@/components/client/Questions/upLoadFileForJianda.vue';
+
+    export default {
+        name:       'ShortAnswerQuestion',
+        components: {
+            MtaStQuillEditor,
+            MtaBusAudioPlayer,
+            upLoadFileForJianda,
+        },
+        props:      {
+            questionData: { // 简答题数据
+                require: true,
+                type:    Object,
+            },
+            shitiIndex:   {
+                type: String,
+            },
+            isYueDu:      {
+                type:    Boolean,
+                default: false,
+            },
+            yuduIndex:    {
+                type: Number,
+            },
+            option:       {
+                type:    Object,
+                default: () => ({}),
+            },
+        },
+        watch:      {
+            questionData: {
+                handler:   function (newVal, oldVal) {
+                    this.textarea = newVal.reply;
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+                                this.audioOptions.curPlaytimes = newVal.playtimes;
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+            },
+            shitiIndex:   {
+                handler(newVal, oldVal) {
+                    if (newVal) {
+                        let c = newVal.split('-');
+                        this.paragraph = c[0];
+                        this.shitiNum = c[1];
+                    }
+                },
+                immediate: true,
+            },
+            textarea:     {
+                handler:   function (newVal, oldVal) {
+                    this.debounce(this.changeStr, 1000);
+                },
+                immediate: true,
+            },
+        },
+        computed:   {
+            ...mapGetters(['getShowScore', 'getShowModel']),
+        },
+        data() {
+            return {
+                textarea:       this.questionData.reply === null ? '' : this.questionData.reply,
+                // 段落
+                paragraph:      null,
+                shitiNum:       0,
+                quillEditorRef: '',
+                audioOptions:   null,
+            };
+        },
+        created() {
+            this.quillEditorRef = 'quillEditorRef' + Math.floor(Math.random() * 100000);
+        },
+        methods:    {
+
+            clickUploadFile(data){
+                this.$emit('clickUploadFile', data);
+            },
+            richText(){
+                this.$emit('richText','richtext');
+            },
+            handleGetFileLists(data) {
+                this.questionData.files = JSON.stringify(data);
+                this.changeStr();
+            },
+            syncValue(flg, content) {
+                this.content = content;
+                this.textarea = content;
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        type:      'jianda',
+                        stId:      this.questionData.stId,
+                        files:     this.questionData.files,
+                        data:      content,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        stId:      this.questionData.stId,
+                        files:     this.questionData.files,
+                        data:      content,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+
+
+            },
+            /*inputChange(val) {
+                // this.$emit('reply', [1, this.questionData.stId, val]);
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        type:      'jianda',
+                        stId:      this.questionData.stId,
+                        data:      val,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        stId:      this.questionData.stId,
+                        data:      val,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+
+            },*/
+            debounce:  function (fn, wait) {
+                if (this.fun !== null) {
+                    clearTimeout(this.fun);
+                }
+                this.fun = setTimeout(fn, wait);
+            },
+            changeStr: function () {
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        type:      'jianda',
+                        stId:      this.questionData.stId,
+                        files:     this.questionData.files,
+                        data:      this.textarea,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        stId:      this.questionData.stId,
+                        files:     this.questionData.files,
+                        data:      this.textarea,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+            },
+            updAudioPlaytimes(playtimes) {
+                if (this.questionData.stId === 0) { // 阅读题
+                    this.$emit('updAudioPlaytimesSub', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                } else { // 其他题
+                    this.$emit('updAudioPlaytimes', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                }
+            },
+        },
+    };
+</script>

+ 163 - 0
src/components/client/Questions/SingleChoice.vue

@@ -0,0 +1,163 @@
+<template>
+    <div class="mta-single-choice" v-if="questionData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+            <span class="wrap-shitiIndex" v-if="!!shitiNum">
+                <span v-if="isYueDu">({{shitiNum}})</span>
+                <span v-else>{{shitiNum}}.</span>
+            </span>
+                <!--  <span>【单选题】</span>-->
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="questionData.name"></div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{questionData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player
+                    :options="audioOptions"
+                    @updAudioPlaytimes="updAudioPlaytimes"
+            >
+            </mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <el-radio-group v-model="newRadioData" @change="selectChange">
+                    <div class="mta-radio-row" v-for="(item, index) in questionDataContent">
+                        <el-radio :label="`${index}`">
+                            <slot>
+                                <span style="vertical-align: top;display: inline-block;width: auto;" class="wordOption">{{item.num}}、</span>
+                                <span v-html="item.label" class="wordBox"></span>
+                            </slot>
+                        </el-radio>
+                    </div>
+                </el-radio-group>
+            </el-col>
+        </el-row>
+        <div class="mta-modal" v-if="getShowModel"></div>
+    </div>
+
+</template>
+
+<script>
+    import { getLetterByIndex } from '@/utils/common';
+    import { mapGetters }       from 'vuex';
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name:       'SingleChoice',
+        props:      {
+            questionData: { // 单选题数据
+                require: true,
+                type:    Object,
+            },
+            shitiIndex:   { // 题序号
+                type: String,
+            },
+            isYueDu:      {
+                type:    Boolean,
+                default: false,
+            },
+            yuduIndex:    {
+                type: Number,
+            },
+        },
+        components: {
+            MtaBusAudioPlayer,
+        },
+        data() {
+            return {
+                // 答案
+                newRadioData: this.questionData.reply,
+                // 段落
+                paragraph:    null,
+                shitiNum:     0,
+                audioOptions: null,
+            };
+        },
+        methods:    {
+            selectChange(val) {
+                this.questionData.reply = val;
+                // this.$emit('reply', [this.questionData.paragraph ,this.questionData.stId, this.newRadioData]);
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        type:      'danxuan',
+                        stId:      this.questionData.stId,
+                        data:      this.newRadioData,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        stId:      this.questionData.stId,
+                        data:      this.newRadioData,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+            },
+            updAudioPlaytimes(playtimes) {
+                if (this.questionData.stId === 0) { // 阅读题
+                    this.$emit('updAudioPlaytimesSub', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                } else { // 其他题
+                    this.$emit('updAudioPlaytimes', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                }
+            },
+        },
+        watch:      {
+
+            questionData: {
+                handler(newVal, oldVal) {
+                    // console.log(newVal);
+                    this.newRadioData = newVal.reply;
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+                                this.audioOptions.curPlaytimes = newVal.playtimes;
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+            },
+            shitiIndex:   {
+                handler(newVal, oldVal) {
+                    if (newVal) {
+                        let c = newVal.split('-');
+                        this.paragraph = c[0];
+                        this.shitiNum = c[1];
+                    }
+                },
+                immediate: true,
+            },
+        },
+        computed:   {
+            questionDataContent() {
+                if (this.questionData.content && this.questionData.content.length) {
+                    return this.questionData.content.map((item, index) => {
+                        return {
+                            label: item,
+                            num:   getLetterByIndex(index),
+                        };
+                    });
+                }
+            },
+            ...mapGetters(['getShowScore', 'getShowModel']),
+        },
+    };
+</script>

+ 216 - 0
src/components/client/Questions/TrueOrFalse.vue

@@ -0,0 +1,216 @@
+<template>
+    <div class="mta-true-or-false"  v-if="questionData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiNum">
+                    <span v-if="isYueDu">({{shitiNum}})</span>
+                    <span v-else>{{shitiNum}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="questionData.name"></div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{questionData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player
+                    :options="audioOptions"
+                    @updAudioPlaytimes="updAudioPlaytimes"
+            >
+            </mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <el-radio-group v-model="newRadioData" @change="selectChange">
+                    <div class="mta-panduan-row">
+                        <el-radio label="1">正确</el-radio>
+                    </div>
+                    <div class="mta-panduan-row">
+                        <el-radio label="0">错误</el-radio>
+                    </div>
+                </el-radio-group>
+
+            </el-col>
+        </el-row>
+        <div class="mta-modal" v-if="getShowModel"></div>
+    </div>
+</template>
+
+<script>
+    import {mapGetters} from 'vuex'
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name: "TrueOrFalse",
+        props: {
+            questionData: { // 单选题数据
+                require: true,
+                type: Object,
+            },
+            shitiIndex: { // 试题序号
+                type: String,
+            },
+            isYueDu: {
+                type: Boolean,
+                default: false
+            },
+            yuduIndex: {
+                type: Number,
+            }
+        },
+        components: {
+            MtaBusAudioPlayer,
+        },
+        data() {
+            return {
+                newRadioData: this.questionData.reply,
+                // 段落
+                paragraph: null,
+                shitiNum: 0,
+                audioOptions: null,
+            }
+        },
+        watch: {
+            questionData: {
+                handler(newVal, oldVal) {
+                    // console.log(newVal);
+                    this.newRadioData = newVal.reply;
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+                                this.audioOptions.curPlaytimes = newVal.playtimes;
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+            },
+            shitiIndex: {
+                handler(newVal, oldVal) {
+                    if (newVal) {
+                        let c = newVal.split('-');
+                        this.paragraph = c[0];
+                        this.shitiNum = c[1];
+                    }
+                },
+                immediate: true,
+            },
+        },
+        computed: {
+            ...mapGetters(['getShowScore', 'getShowModel']),
+        },
+        methods: {
+            selectChange(val) {
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        type: 'panduan',
+                        stId: this.questionData.stId,
+                        data: this.newRadioData,
+                        number: this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.paragraph,
+                        stId: this.questionData.stId,
+                        data: this.newRadioData,
+                        number: this.shitiIndex-1,
+                    });
+                }
+
+            },
+            updAudioPlaytimes(playtimes) {
+                if (this.questionData.stId === 0) { // 阅读题
+                    this.$emit('updAudioPlaytimesSub', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                } else { // 其他题
+                    this.$emit('updAudioPlaytimes', {
+                        stId:      this.questionData.stId,
+                        playtimes: playtimes,
+                    });
+                }
+            },
+        },
+    }
+</script>
+
+<style lang="scss" scoped>
+    .mta-true-or-false {
+        position: relative;
+        font-size: 14px;
+
+        .mta-first-row {
+            .el-col {
+                display: flex;
+                justify-content: flex-start;
+                flex-direction: row;
+
+                .mta-SC-title {
+                    color: #333;
+                    line-height: 24px;
+                }
+
+                .mta-SC-score {
+                    color: #999;
+                    min-width: 100px;
+                    line-height: 24px
+                }
+            }
+
+            .wrap-shitiIndex {
+                font-size: 13px;
+                line-height: 24px;
+                color: #333;
+                margin-right: 10px;
+                font-weight: bold;
+            }
+        }
+
+        .mta-two-row {
+            margin-bottom: 12px;
+
+            .el-col {
+
+                .mta-panduan-row {
+                    margin: 10px 0;
+                }
+            }
+        }
+
+        .mta-modal {
+            position: absolute;
+            left: 0;
+            top: 0;
+            bottom: 0;
+            right: 0;
+            z-index: 999;
+        }
+    }
+</style>
+<style lang="scss">
+    .mta-true-or-false {
+
+        .mta-SC-title {
+            p {
+                margin: 0;
+                padding: 0;
+            }
+        }
+
+        .el-radio {
+            color: #666;
+        }
+    }
+
+</style>

+ 320 - 0
src/components/client/Questions/upLoadFileForJianda.vue

@@ -0,0 +1,320 @@
+<template>
+    <div class="up-load-file-for-jianda">
+        <div class="my-up-load-file-main">
+            <!--    enter button    -->
+            <el-tooltip content="注:最多可以上传两个文件"
+                        placement="top"
+                        popper-class="c-aliUpload-tooltip"><i
+                    class="aliUpload-note-word"></i></el-tooltip>
+            <el-upload
+                    class="upload-demo"
+                    action=""
+                    :show-file-list="false"
+                    :http-request="importFun"
+            >
+                <el-button size="small" @click="beforeUpload('input')" type="text">上传文件</el-button>
+            </el-upload>
+            <el-button @click="handleOpenDialog" type="text">扫码上传</el-button>
+            <!--    file lists    -->
+            <template v-if="fileLists.length">
+                <span
+                        class="file-lists_span fsize-m6"
+                        v-for="(item, index) in fileLists"
+                        :key="item.url">
+                    <span v-if="!item.isImage">附件{{index+1}}</span>
+                    <span v-if="item.isImage">
+                        <img :src="item.url" width="20" height="20">
+                    </span>
+
+                    <span style="cursor: pointer;" @click="ConfirmDelete(item)">
+                        <i class="el-icon-close"></i>
+                    </span>
+                </span>
+            </template>
+        </div>
+        <!--    dialog    -->
+        <el-dialog
+                title="扫码上传图片"
+                :visible.sync="dialogImageVisible"
+                width="30%"
+                center
+                :close-on-click-modal="false"
+                @close="handleImageClose">
+            <div class="dialog-content">
+                <div class="my-box">
+                    <!--        倒计时            -->
+                    <p>请在1分钟内手机扫码上传图片:{{countdown}}秒</p>
+                    <p>
+                        <vue-qrcode :value=QRCodeUrl :options="{ color: { dark: '#0275D8' }  }"></vue-qrcode>
+                    </p>
+                </div>
+            </div>
+            <span slot="footer" class="dialog-footer"></span>
+        </el-dialog>
+
+        <MtaStDialog
+                  :dVisible.sync="mtaDeleteVisible"
+                  dType="warning"
+                  :modalAppendToBody="false"
+                  dMessage="选中的数据将彻底删除"
+                  @comfirmCallback="doDelete"
+        ></MtaStDialog>
+
+    </div>
+</template>
+
+<script>
+    import { getUploadImg }  from '@/api/AlCloud.js';
+    import axios             from 'axios';
+    import { getOpenSearch } from '@/api/base64.js';
+    import VueQrcode         from '@chenfengyuan/vue-qrcode';
+
+    export default {
+        name:       'upLoadFileForJianda',
+        components: {
+            VueQrcode,
+        },
+        data() {
+            return {
+                dialogImageVisible: false,
+                mtaDeleteVisible:   false,
+                fileLists:          [],
+                mySelectDelete:     null,
+                QRCodeUrl:          '',
+                key:                '',
+                link:               '',
+                countdown:          120, // 60秒倒计时
+                timer1:             null,
+                timer2:             null,
+            };
+        },
+        props:      {
+            option:      {
+                type:    Object,
+                default: () => ({}),
+            },
+            myfileLists: {
+                type:    String | null,
+            },
+        },
+        watch:      {
+            myfileLists: {
+                handler(a, b) {
+                    let c = a || [];
+
+                    if (typeof c === 'string') {
+                        c = JSON.parse(c);
+                    }
+
+                    c.forEach(item => {
+                        const isImage = this.checkIsImg(item);
+                        this.fileLists.push({
+                                                isImage: isImage,
+                                                url:     item,
+                                            });
+                    });
+                },
+                immediate: true,
+                deep:      true,
+            },
+        },
+        methods:    {
+
+            beforeUpload(data){
+                window.onblur = null;
+                this.$emit('clickUploadFile', data);
+            },
+            handleGetImageDataFun() {
+                this.timer2 = setInterval(() => {
+                    // 获取手机上传文件
+                    // const loading = this.loading();
+                    getOpenSearch({
+                                      key: this.key,
+                                  })
+                    .then(res => {
+                        const data = res.data.urlList || [];
+
+                        if (!data.length) {
+                            return;
+                        }
+
+                        this.handleComplateData2(data);
+                        // this.$emit('accessory', this.fileLists);
+                        // this.fileLists = [];
+                        this.dialogImageVisible = false;
+                        this.clearTimeFun();
+                        // loading.close();
+                    })
+                    .catch(err => {
+                        // this.$message.error('手机上传文件获取异常');
+                        // loading.close();
+                        console.log(err);
+                    });
+                }, 5000);
+            },
+            // 倒计时60秒
+            setTime() {
+                if (this.countdown === 0) {
+                    this.dialogImageVisible = false;
+                    this.countdown = 120;
+                    this.clearTimeFun();
+                } else {
+                    this.countdown--;
+                    this.timer1 = setTimeout(() => {
+                        this.setTime();
+                    }, 1000);
+                }
+            },
+            clearTimeFun() {
+                clearTimeout(this.timer1);
+                clearInterval(this.timer2);
+                this.timer1 = null;
+                this.timer2 = null;
+                this.countdown = 120;
+            },
+            doDelete() {
+                this.handleDelete(this.mySelectDelete);
+            },
+            ConfirmDelete(data) {
+                this.mySelectDelete = data;
+                this.mtaDeleteVisible = true;
+            },
+            // 文件上传 函数
+            importFun(params) {
+                if(this.fileLists.length > 2 || this.fileLists.length === 2){
+                    this.$message.error('最多可以上传两个文件');
+                    return;
+                }
+                if (!params) {
+                    return false;
+                }
+                const suffixList = params.file.name.split('.');
+                const options = {
+                    prefix: 'temp/',
+                    suffix: suffixList[suffixList.length-1],
+                };
+                getUploadImg(options).then((res) => {
+                    if (res.code === 0) {
+                        // 二进制文件通过forData对象进行传递
+                        const FormDataForAl = new FormData();
+                        const multipartParams = Object.assign({}, res.data, {
+                            Filename:              `images/${params.filename}`,
+                            success_action_status: '200',
+                        });
+                        // 参数数据
+                        FormDataForAl.append('key', multipartParams.key);
+                        FormDataForAl.append('policy', multipartParams.policy);
+                        FormDataForAl.append('signature', multipartParams.signature);
+                        FormDataForAl.append('OSSAccessKeyId', multipartParams.accessid);
+                        FormDataForAl.append('success_action_status', multipartParams.success_action_status);
+                        // OSS要求, file放到最后
+                        FormDataForAl.append('file', params.file);
+                        const loading = this.loading();
+                        axios.post(multipartParams.uploadUrl, FormDataForAl)
+                             .then(alRes => {
+                                 if (alRes.status === 200) {
+                                     const fileUrl = `${multipartParams.downloadUrl}/${multipartParams.key}`;
+                                     if (this.checkFileListslength()) {
+                                         this.$message.success('附件上传成功');
+                                         this.handleComplateData(fileUrl);
+                                     } else {
+                                         this.$message.error('附件最多上传2个');
+                                     }
+                                 }
+                                 loading.close();
+                             })
+                             .catch(err => {
+                                 loading.close();
+                                 console.log(err);
+                             });
+                    }
+
+                });
+            },
+            // 手机上传 图片处理
+            handleComplateData2(fileUrls) {
+                fileUrls.forEach(item => {
+                    const isImage = this.checkIsImg(item);
+
+                    this.fileLists.push({
+                                            isImage: isImage,
+                                            url:     item,
+                                        });
+                });
+
+                this.backData();
+            },
+            // Pc文件上传
+            handleComplateData(fileUrl) {
+                const isImage = this.checkIsImg(fileUrl);
+
+                this.fileLists.push({
+                                        isImage: isImage,
+                                        url:     fileUrl,
+                                    });
+                this.backData();
+                // this.$emit('accessory', this.fileLists);
+                // this.fileLists = [];
+            },
+            backData() {
+                const c = this.fileLists.map(item => {
+                    return item.url;
+                });
+                this.$emit('getFileLists', c);
+                this.fileLists = [];
+            },
+            // 校验是否是图片
+            checkIsImg(data) {
+                if (typeof data !== 'string') {
+                    throw new Error('校验是否是否图片参数错误');
+                }
+                const strArr = data.split('.');
+
+                if (!strArr.length) {
+                    throw new Error('文件地址数据缺少关键字: .');
+                }
+
+                const whiteList = ['jpg', 'png', 'jpeg', 'bmp'];
+                return whiteList.some(item => item === strArr[strArr.length - 1]);
+            },
+            // 校验 file lists 数组是否大于2
+            checkFileListslength() {
+                return true;
+            },
+            handleDelete(data) {
+                this.fileLists = this.fileLists.filter(item => item.url !== data.url);
+                this.mySelectDelete = null;
+                this.backData();
+            },
+            handleImageClose() {
+                this.clearTimeFun();
+            },
+            handleOpenDialog() {
+                this.dialogImageVisible = true;
+                this.setTime();
+                this.handleGetImageDataFun();
+            },
+            getQRCodeUrl(data) {
+                if (data.length === 0) {
+                    throw new Error('二维码唯一识别参数异常');
+                }
+
+                // console.log('queryArr:', data);
+                // console.log('全局变量:', process.env.VUE_APP_BASE_API_TARGET);
+                this.key = '';
+                data.forEach(item => {
+                    this.key += item;
+                });
+                // H5 接口地址
+                //const myH5Url = process.env.VUE_APP_BASE_API_TARGET;
+                const myH5Url = window.location.protocol + '//' + `${document.location.host}/` + process.env.VUE_APP_DIST_NAME + `/#/phoneFile`;
+                this.QRCodeUrl = `${myH5Url}?key=${this.key}`;
+
+            },
+        },
+        created() {
+            const queryArr = Object.values(this.option);
+            this.getQRCodeUrl(queryArr);
+        },
+    };
+</script>

+ 41 - 0
src/components/client/QuestionsForCuoti/AnswerArea.vue

@@ -0,0 +1,41 @@
+<template>
+    <el-row class="mta-answer-area" type="flex">
+        <el-col>
+            <!-- 试题内容区域 -->
+            <div class="mta-three-row-div">
+                <span class="three-span">正确答案:</span>
+                <span class="answer-vhtml-div" style="flex:1" v-html="showAnswer"></span>
+            </div>
+            <div class="mta-three-row-div">
+                <span class="three-span">答案解析:</span>
+<!--                <el-input type="textarea" :readonly="true" v-model="analysis" :rows="3"></el-input>-->
+                <div style="flex:1" v-html="analysis" class="answer-vhtml-div"></div>
+            </div>
+        </el-col>
+    </el-row>
+</template>
+
+<script>
+    import {getStringByHtml2} from '@/utils/common.js'
+
+    export default {
+        name: "AnswerArea",
+        props: {
+            data: {
+                type: Object,
+                require: true
+            }
+        },
+        computed: {
+            showAnswer() {
+                // return getStringByHtml(this.data.analysis);
+                return this.data.showAnswer
+            },
+            analysis() {
+                // return getStringByHtml(this.data.analysis);
+                return this.data.analysis
+            }
+        }
+    }
+</script>
+

+ 40 - 0
src/components/client/QuestionsForCuoti/AnswerAreaTianKong.vue

@@ -0,0 +1,40 @@
+<template>
+    <el-row class="mta-answer-area" type="flex">
+        <el-col>
+            <!-- 试题内容区域 -->
+            <div class="mta-three-row-div">
+                <span class="three-span">正确答案:</span>
+                <span style="flex:1" class="mta-answer-list">
+                    <span v-for="item of data.showAnswer">
+                        <span>{{item.label}}</span>
+                        <span>({{item.answer}})</span>
+                    </span>
+                </span>
+            </div>
+            <div class="mta-three-row-div">
+                <span class="three-span">答案解析:</span>
+<!--                <el-input type="textarea" :readonly="true" v-model="analysis" :rows="3"></el-input>-->
+                <div style="flex:1" v-html="analysis" class="answer-vhtml-div"></div>
+            </div>
+        </el-col>
+    </el-row>
+</template>
+
+<script>
+    import {getStringByHtml} from '@/utils/common.js'
+
+    export default {
+        name: "AnswerArea",
+        props: {
+            data: {
+                type: Object,
+                require: true
+            }
+        },
+        computed: {
+            analysis() {
+                return this.data.analysis
+            }
+        }
+    }
+</script>

+ 335 - 0
src/components/client/QuestionsForCuoti/GapFilling.vue

@@ -0,0 +1,335 @@
+<template>
+    <div class="mta-gap-filling" v-if="pageData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiIndex">
+                    <span v-if="isYueDu">({{shitiIndex}})</span>
+                    <span v-else>{{shitiIndex}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="pageData.name"></div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{pageData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player :checktimes="false" :options="audioOptions"></mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <div class="mta-inputTK-row" v-for="(item,index) in questionTKCount" :key="index">
+                    <!--<span class="mta-num">填空题{{item}}:</span>
+                    <el-input v-model="tianKongResult[item-1]"
+                              @change="inputChange"
+                              :placeholder="`请输入填空题${item}答案`"></el-input>-->
+                    <mta-tian-kong-item
+                            :data="{
+                            num: item,
+                            typeCase: typeCase,
+                            answer: tianKongResult[item-1],
+                            result: pageData.result
+                        }"
+                            @change="inputChange"></mta-tian-kong-item>
+                </div>
+                <!--                <div class="mta-modal" v-if="getShowModel"></div> -->
+            </el-col>
+        </el-row>
+        <mta-answer-area-tian-kong
+                v-if="typeCase === 'geren' || typeCase === 'cuoti2' || typeCase === 'cuoti3' || typeCase === 'yulan'"
+                :data="{
+                    showAnswer:showAnswer,
+                    analysis: pageData.answer
+                }"
+        ></mta-answer-area-tian-kong>
+
+    </div>
+</template>
+
+<script>
+    import {mapGetters} from 'vuex'
+    import MtaAnswerAreaTianKong from '@/components/client/QuestionsForCuoti/AnswerAreaTianKong.vue'
+    import MtaTianKongItem from '@/components/client/QuestionsForCuoti/TianKongItem.vue'
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name: "GapFilling",
+        props: {
+            questionData: { // 单选题数据
+                require: true,
+                type: Object,
+            },
+            shitiIndex: { // 试题序号
+                type: Number,
+                default: 0,
+            },
+            isYueDu: {
+                type: Boolean,
+                default: false
+            },
+            yuduIndex: {
+                type: Number,
+            },
+            // 组件接环状态, kaoshi,geren,yulan,cuoti2
+            typeCase: {
+                type: String,
+                default: 'kaoshi'
+            },
+            getShowModel: {
+                type: Boolean,
+                default: true
+            }
+        },
+        components: {
+            MtaAnswerAreaTianKong,
+            MtaTianKongItem,
+            MtaBusAudioPlayer,
+        },
+        data() {
+            return {
+                // 页面渲染必须数据
+                pageData: null,
+
+                // 答案
+                newTiankongData: this.questionData.reply === null ? '' : this.questionData.reply,
+                tianKongResult: [],
+                // 展示答案
+                showAnswer: null,
+                // 错误
+                showError: false,
+                // 解析
+                analysis: null,
+                audioOptions: null,
+            }
+        },
+        methods: {
+            getShowAnswer(data) {
+                let str = [];
+                if (data === undefined) {
+                    return;
+                }
+                data.forEach((item, index) => {
+                    str.push({
+                        answer: item.join(','),
+                        label: `填空${index + 1}`
+                    })
+                });
+                return str;
+            },
+            checkAnswer(result, reply) {
+                let innerStatus = false;
+
+                if (reply === null) {
+                    return true
+                }
+
+                reply.forEach((item, index) => {
+                    if (result[index].indexOf(item) === -1) {
+                        innerStatus = true;
+                    }
+                });
+
+                return innerStatus
+
+            },
+            inputChange(val) {
+                // this.$emit('reply', [this.questionData.paragraph ,this.questionData.stId,resultOpt]);
+                // this.$emit('reply', [1, this.questionData.stId, resultOpt]);
+
+                this.tianKongResult[val.num - 1] = val.data.trim();
+
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        type: 'tiankong',
+                        stId: this.questionData.stId,
+                        data: this.tianKongResult,
+                        number: this.yuduIndex
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        stId: this.questionData.stId,
+                        data: this.tianKongResult,
+                        number: this.shitiIndex - 1
+                    });
+                }
+            }
+        },
+        computed: {
+            questionTKCount() {
+                if (this.questionData.count) {
+                    return this.questionData.count;
+                }
+            },
+            ...mapGetters(['getShowScore']),
+        },
+        watch: {
+            questionData: {
+                handler: function (newVal, oldVal) {
+
+                    switch (this.typeCase) {
+                        case `kaoshi`:
+                            this.tianKongResult = [];
+                            this.pageData = newVal;
+                            this.newTiankongData = newVal.reply;
+                            if (this.newTiankongData) {
+                                this.newTiankongData.forEach(item => {
+                                    // if (item && item.length) {
+                                        this.tianKongResult.push(item);
+                                    // }
+                                });
+                            }
+
+                            if (!this.tianKongResult.length) {
+                                for (let i = 0; i < newVal.count; i++) {
+                                    this.tianKongResult.push('')
+                                }
+                            }
+                            break;
+                        case `geren`:
+                            this.tianKongResult = [];
+                            this.pageData = newVal;
+                            // 答案
+                            this.newTiankongData = newVal.reply;
+                            if (this.newTiankongData) {
+                                this.newTiankongData.forEach(item => {
+                                    if (item && item.length) {
+                                        this.tianKongResult.push(item);
+                                    }
+                                });
+                            } else {
+                                return;
+                            }
+
+                            if (!this.tianKongResult.length) {
+                                for (let i = 0; i < newVal.count; i++) {
+                                    const opt = [];
+                                    this.tianKongResult.push(opt)
+                                }
+                            }
+
+                            // 解析答案
+                            this.showAnswer = this.getShowAnswer(newVal.result);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            // 错误是否显示
+                            this.showError = this.checkAnswer(newVal.result, newVal.reply);
+                            break;
+                        case `cuoti`:
+                            this.pageData = newVal;
+                            this.newTiankongData = newVal.reply;
+                            this.tianKongResult = [];
+                            if (this.newTiankongData) {
+                                this.newTiankongData.forEach(item => {
+                                    if (item && item.length) {
+                                        this.tianKongResult.push(item);
+                                    }
+                                });
+                            }
+
+                            if (!this.tianKongResult.length) {
+                                for (let i = 0; i < newVal.count; i++) {
+                                    this.tianKongResult.push('')
+                                }
+                            }
+                            break;
+                        case `yulan`:
+                            this.pageData = newVal;
+                            this.newTiankongData = newVal.result;
+                            this.tianKongResult = [];
+
+                            if (this.newTiankongData) {
+                                this.newTiankongData.forEach(item => {
+                                    if (item && item.length) {
+                                        this.tianKongResult.push(item);
+                                    }
+                                });
+                            }
+
+                            if (!this.tianKongResult.length) {
+                                for (let i = 0; i < newVal.count; i++) {
+                                    this.tianKongResult.push('')
+                                }
+                            }
+                            // 解析答案
+                            this.showAnswer = this.getShowAnswer(this.tianKongResult);
+                            // 解析答案
+                            this.showError = this.checkAnswer(newVal.result, newVal.reply);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti2`:
+                            this.tianKongResult = [];
+                            this.pageData = newVal;
+                            // 单选题答案
+                            this.newTiankongData = newVal.reply;
+                            if (this.newTiankongData) {
+                                this.newTiankongData.forEach(item => {
+                                    if (item && item.length) {
+                                        this.tianKongResult.push(item);
+                                    }
+                                });
+                            }
+
+                            if (!this.tianKongResult.length) {
+                                for (let i = 0; i < newVal.count; i++) {
+                                    this.tianKongResult.push('')
+                                }
+                            }
+                            // 解析答案
+                            this.showAnswer = this.getShowAnswer(newVal.result);
+                            // 解析答案
+                            this.showError = this.checkAnswer(newVal.result, newVal.reply);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti3`:
+                            this.tianKongResult = [];
+                            this.pageData = newVal;
+                            // 单选题答案
+                            this.newTiankongData = newVal.reply;
+                            if (this.newTiankongData) {
+                                this.newTiankongData.forEach(item => {
+                                    if (item && item.length) {
+                                        this.tianKongResult.push(item);
+                                    }
+                                });
+                            }
+
+                            if (!this.tianKongResult.length) {
+                                for (let i = 0; i < newVal.count; i++) {
+                                    this.tianKongResult.push('')
+                                }
+                            }
+                            // 解析答案
+                            this.showAnswer = this.getShowAnswer(newVal.result);
+                            // 解析答案
+                            this.showError = this.checkAnswer(newVal.result, newVal.reply);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                    }
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+                deep: true
+            },
+        },
+    }
+</script>
+

+ 282 - 0
src/components/client/QuestionsForCuoti/MultipleChoice.vue

@@ -0,0 +1,282 @@
+<template>
+    <div class="mta-multiple-choice" v-if="pageData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiIndex">
+                    <span v-if="isYueDu">({{shitiIndex}})</span>
+                    <span v-else>{{shitiIndex}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="pageData.name">
+                </div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{pageData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player :checktimes="false" :options="audioOptions"></mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <el-checkbox-group v-model="newCheckListData" @change="selectChange">
+
+                    <div class="mta-checkbox-row" v-for="(item,index) in questionDataContent"
+                         :class="{'showError': showError && typeCase === 'geren'}">
+
+                        <el-checkbox :label="`${item.index}`">
+                            <slot>
+                                <span style="vertical-align: top;display: inline-block;width: auto;" class="wordOption">{{item.num}}、</span>
+                                <span v-html="item.label" class="wordBox"></span>
+                            </slot>
+                        </el-checkbox>
+                    </div>
+                </el-checkbox-group>
+                <div class="mta-modal" v-if="getShowModel"></div>
+            </el-col>
+        </el-row>
+        <mta-answer-area
+                v-if="typeCase === 'geren' || typeCase === 'cuoti2' || typeCase === 'cuoti3' || typeCase === 'yulan'"
+                :data="{
+                    showAnswer:showAnswer,
+                    analysis: pageData.answer
+                }"
+        ></mta-answer-area>
+
+    </div>
+</template>
+
+<script>
+    import {getLetterByIndex} from '@/utils/common'
+    import {mapGetters} from 'vuex'
+    import MtaAnswerArea from '@/components/client/QuestionsForCuoti/AnswerArea.vue'
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name: "MultipleChoice",
+        props: {
+            questionData: { // 单选题数据
+                require: true,
+                type: Object,
+            },
+            shitiIndex: {
+                type: Number,
+                default: 0,
+            },
+            isYueDu: {
+                type: Boolean,
+                default: false
+            },
+            yuduIndex: {
+                type: Number,
+            },
+            // 组件接环状态, kaoshi,geren,yulan,cuoti2
+            typeCase: {
+                type: String,
+                default: 'kaoshi'
+            },
+            getShowModel: {
+                type: Boolean,
+                default: true
+            }
+        },
+        components: {
+            MtaAnswerArea,
+            MtaBusAudioPlayer,
+        },
+        data() {
+            return {
+                // 页面渲染必须数据
+                pageData: null,
+                // 答案
+                newCheckListData: [],
+                showAnswer: null,
+                // 错误
+                showError: false,
+                // 解析
+                analysis: null,
+
+                audioOptions: null,
+            }
+        },
+        methods: {
+            checkAnswer(result, reply) {
+                let innerStatus = false;
+                if (reply === null) {
+                    return true;
+                }
+                if (result.length !== reply.length) {
+                    return true;
+                }
+                result.forEach(item => {
+                    if (reply.indexOf(item) === -1) {
+                        innerStatus = true;
+                    }
+                });
+
+
+
+                return innerStatus;
+            },
+            getShowAnswer(data) {
+                const opt = [];
+                if (data === undefined) {
+                    return;
+                }
+                // 答案排序
+                const newData  = data.sort(function sortNumber(a,b){return a - b})
+
+                newData.forEach(item => {
+                    opt.push(getLetterByIndex(Number(item)));
+                });
+                return opt.join(',');
+            },
+            selectChange(val) {
+                // this.$emit('reply', [this.questionData.paragraph ,this.questionData.stId,this.newCheckListData]);
+                // this.$emit('reply', [1 ,this.questionData.stId, this.newCheckListData]);
+                // this.questionData.reply = val;
+
+                this.pageData.reply = val;
+                this.showError = this.checkAnswer(this.pageData.result, val);
+
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        type: 'duoxuan',
+                        stId: this.questionData.stId,
+                        data: this.newCheckListData.sort(),
+                        number: this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        stId: this.questionData.stId,
+                        data: this.newCheckListData.sort(),
+                        number: this.shitiIndex - 1,
+                    });
+                }
+            }
+        },
+        computed: {
+            questionDataContent() {
+                if (this.pageData.content && this.pageData.content.length) {
+                    return this.pageData.content.map((item, index) => {
+                        return {
+                            label: item,
+                            num: getLetterByIndex(index),
+                            index: index
+                        }
+                    });
+                }
+            },
+            ...mapGetters(['getShowScore']),
+        },
+        watch: {
+            questionData: {
+                handler(newVal, oldVal) {
+                    switch (this.typeCase) {
+                        case `kaoshi`:
+                            this.pageData = newVal;
+                            if (newVal.reply && newVal.reply.length) {
+                                newVal.reply.sort().forEach(item => {
+                                    this.newCheckListData.push(item);
+                                })
+                            } else {
+                                this.newCheckListData = [];
+                            }
+                            break;
+                        case `geren`:
+                            this.pageData = newVal;
+                            // 答案
+                            if (newVal.reply && newVal.reply.length) {
+                                newVal.reply.sort().forEach(item => {
+                                    this.newCheckListData.push(item);
+                                })
+                            } else {
+                                this.newCheckListData = [];
+                            }
+                            // 解析答案
+                            // this.showAnswer = getLetterByIndex(Number(newVal.result));
+                            this.showAnswer = this.getShowAnswer(newVal.result);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            // 错误是否显示
+                            this.showError = this.checkAnswer(newVal.result, newVal.reply);
+                            break;
+                        case `cuoti`:
+                            this.pageData = newVal;
+                            if (newVal.reply && newVal.reply.length) {
+                                newVal.reply.sort().forEach(item => {
+                                    this.newCheckListData.push(item);
+                                })
+                            } else {
+                                this.newCheckListData = [];
+                            }
+                            break;
+                        case `yulan`:
+                            this.pageData = newVal;
+                            /*if (newVal.result && newVal.result.length) {
+                                newVal.result.forEach(item => {
+                                    this.newCheckListData.push(item);
+                                })
+                            } else {
+                                this.newCheckListData = [];
+                            }*/
+                            this.newCheckListData = [];
+                            // 解析答案
+                            this.showAnswer = this.getShowAnswer(newVal.result);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti2`:
+                            this.pageData = newVal;
+                            // 单选题答案
+                            if (newVal.reply && newVal.reply.length) {
+                                newVal.reply.sort().forEach(item => {
+                                    this.newCheckListData.push(item);
+                                })
+                            } else {
+                                this.newCheckListData = [];
+                            }
+                            // 解析答案
+                            this.showAnswer = this.getShowAnswer(newVal.result);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti3`:
+                            this.pageData = newVal;
+                            // 单选题答案
+                            if (newVal.reply && newVal.reply.length) {
+                                newVal.reply.sort().forEach(item => {
+                                    this.newCheckListData.push(item);
+                                })
+                            } else {
+                                this.newCheckListData = [];
+                            }
+                            // 解析答案
+                            this.showAnswer = this.getShowAnswer(newVal.result);
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                    }
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+            }
+        },
+    }
+</script>

+ 194 - 0
src/components/client/QuestionsForCuoti/ReadTheTopic.vue

@@ -0,0 +1,194 @@
+<template>
+    <div class="mta-read-the-topic">
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题题干区域 -->
+                <span class="wrap-shitiIndex" v-if="!!shitiIndex">{{shitiIndex}}.</span>
+                <div style="display: flex;justify-content: space-between;flex-direction: row;">
+                    <span class="mta-SC-title" v-html="questionData.name"></span>
+                    <span class="mta-SC-score" v-if="getShowScore">({{questionData.score}}分)</span>
+                </div>
+
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions' >
+            <mta-bus-audio-player :checktimes="false" :options="audioOptions"></mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-three-row" type="flex">
+            <el-col>
+                <!-- 试题区域 -->
+                <!-- 单选题 -->
+                <div v-for="(item,index) in danxuanData" :key="`danxuan${index}`" v-if="danxuanData.length > 0">
+                    <mta-single-choice
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :typeCase="typeCase"
+                            :yuduIndex="index"
+                            :getShowModel="getShowModel"
+                            :shitiIndex="num+index"
+                            @reply="reply"
+                    ></mta-single-choice>
+                </div>
+                <!-- 多选题 -->
+                <div v-for="(item,index)  in duoxuanData" :key="`duoxuan${index}`" v-if="duoxuanData.length > 0">
+                    <mta-multiple-choice
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :typeCase="typeCase"
+                            :getShowModel="getShowModel"
+                            :shitiIndex="num+index+danxuanData.length"
+                            @reply="reply"
+                    ></mta-multiple-choice>
+                </div>
+                <!-- 判断题 -->
+                <div v-for="(item,index)  in panduanData" :key="`panduan${index}`" v-if="panduanData.length > 0">
+                    <mta-true-or-false
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :typeCase="typeCase"
+                            :getShowModel="getShowModel"
+                            :shitiIndex="num+index+duoxuanData.length+danxuanData.length"
+                            @reply="reply"
+                    ></mta-true-or-false>
+                </div>
+                <!-- 填空题 -->
+                <div v-for="(item,index)  in tiankongData" :key="`tiankong${index}`" v-if="tiankongData.length > 0">
+                    <mta-gap-filling
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :getShowModel="getShowModel"
+                            :typeCase="typeCase"
+                            :shitiIndex="num+index+danxuanData.length+duoxuanData.length+panduanData.length"
+                            @reply="reply"
+                    ></mta-gap-filling>
+                </div>
+                <!-- 简答题 -->
+                <div v-for="(item,index)  in jiandaData" :key="`jianda${index}`" v-if="jiandaData.length > 0">
+                    <mta-short-answer-question
+                            :questionData="item"
+                            :is-yue-du="true"
+                            :yuduIndex="index"
+                            :getShowModel="getShowModel"
+                            :typeCase="typeCase"
+                            :shitiIndex="num+index+danxuanData.length+duoxuanData.length+panduanData.length+tiankongData.length"
+                            @reply="reply"
+                    ></mta-short-answer-question>
+                </div>
+            </el-col>
+        </el-row>
+        <!--<div class="mta-modal" v-if="getShowModel"></div>-->
+    </div>
+</template>
+
+<script>
+    import MtaSingleChoice from '@/components/client/QuestionsForCuoti/SingleChoice.vue';
+    import MtaTrueOrFalse from '@/components/client/QuestionsForCuoti/TrueOrFalse.vue';
+    import MtaMultipleChoice from '@/components/client/QuestionsForCuoti/MultipleChoice.vue';
+    import MtaGapFilling from '@/components/client/QuestionsForCuoti/GapFilling.vue';
+    import MtaShortAnswerQuestion from '@/components/client/QuestionsForCuoti/ShortAnswerQuestion.vue';
+    import {mapGetters} from 'vuex'
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name: "ReadTheTopic",
+        components: {
+            MtaSingleChoice,
+            MtaMultipleChoice,
+            MtaTrueOrFalse,
+            MtaGapFilling,
+            MtaShortAnswerQuestion,
+            MtaBusAudioPlayer,
+        },
+        props: {
+            questionData: { // 简答题数据
+                require: true,
+                type: Object,
+            },
+            shitiIndex: {
+                type: Number,
+                default: 0,
+            },
+            // 组件接环状态, kaoshi,geren,yulan,cuoti2
+            typeCase: {
+                type: String,
+                default: 'kaoshi'
+            },
+            getShowModel: {
+                type: Boolean,
+                default: true
+            }
+        },
+        created() {
+            /* this.danxuanData = this.questionData.shitiQueryDanxuanResponseVos ? this.questionData.shitiQueryDanxuanResponseVos :this.questionData.danxuanList;
+             this.duoxuanData = this.questionData.shitiQueryDuoxuanResponseVos ? this.questionData.shitiQueryDuoxuanResponseVos :this.questionData.duoxuanList;
+             this.panduanData = this.questionData.shitiQueryPanduanResponseVos ? this.questionData.shitiQueryPanduanResponseVos :this.questionData.panduanList;
+             this.jiandaData = this.questionData.shitiQueryJiandaResponseVos ? this.questionData.shitiQueryJiandaResponseVos :this.questionData.jiandaList;
+             this.tiankongData = this.questionData.shitiQueryTiankongResponseVos ? this.questionData.shitiQueryTiankongResponseVos :this.questionData.tiankongList;*/
+        },
+        computed: {
+            danxuanData() {
+                return this.questionData.danxuan
+            },
+            duoxuanData() {
+
+               return this.questionData.duoxuan
+
+                // return this.questionData.shitiQueryDuoxuanResponseVos ? this.questionData.shitiQueryDuoxuanResponseVos :this.questionData.duoxuanList
+            },
+            panduanData() {
+                 return this.questionData.panduan
+                // return this.questionData.shitiQueryPanduanResponseVos ? this.questionData.shitiQueryPanduanResponseVos :this.questionData.panduanList
+            },
+            jiandaData() {
+
+                return this.questionData.jianda
+
+                // return this.questionData.shitiQueryJiandaResponseVos ? this.questionData.shitiQueryJiandaResponseVos :this.questionData.jiandaList
+            },
+            tiankongData() {
+                 return this.questionData.tiankong
+                // return this.questionData.shitiQueryTiankongResponseVos ? this.questionData.shitiQueryTiankongResponseVos :this.questionData.tiankongList
+            },
+            ...mapGetters(['getShowScore']),
+        },
+        data() {
+            return {
+                num: 1,
+                audioOptions: null,
+            }
+        },
+        methods:  {
+            reply(data) {
+                const newData = Object.assign(data, {
+                    stId: this.questionData.stId,
+                    paragraph:  this.questionData.paragraph,
+                });
+                this.$emit('reply', newData)
+            }
+        },
+        watch:    {
+            questionData: {
+                handler(newVal, oldVal) {
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+            }
+        },
+    }
+</script>

+ 312 - 0
src/components/client/QuestionsForCuoti/ShortAnswerQuestion.vue

@@ -0,0 +1,312 @@
+<template>
+    <div class="mta-short-answer-question" v-if="pageData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiIndex">
+                    <span v-if="isYueDu">({{shitiIndex}})</span>
+                    <span v-else>{{shitiIndex}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="pageData.name">
+                </div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{pageData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player :options="audioOptions"
+                                  :checktimes="false"></mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <!--<el-input
+                        @change="inputChange"
+                        type="textarea"
+                        :rows="2"
+                        :readonly="typeCase=== 'yulan' || typeCase=== 'geren'"
+                        placeholder="请输入简答题答案"
+                        v-model="textarea">
+                </el-input>-->
+
+                <mta-st-quill-editor
+                        ref="editorQuill"
+                        :value="textarea"
+                        :quillEditorRef="quillEditorRef"
+                        flg="1"
+                        @syncValue="syncValue">
+                </mta-st-quill-editor>
+            </el-col>
+        </el-row>
+        <!--答案-->
+        <mta-answer-area
+                v-if="typeCase === 'geren' || typeCase === 'cuoti2' || typeCase === 'cuoti3' || typeCase === 'yulan'"
+                :data="{
+                    showAnswer:showAnswer,
+                    analysis: pageData.answer
+                }"
+        ></mta-answer-area>
+        <el-row class="mta-three-row ShortAnswer-btn-box" type="flex">
+            <el-col>
+                <el-button type="primary" v-if="filesArray&&filesArray.length>0" @click="downloadDialog=true"
+                           class="response-btn">
+                    下载附件
+                </el-button>
+                <el-button @click="yulanDialog=true" class="ShortAnswer-yl-btn"  type="primary" v-if="imgArray&&imgArray.length>0">
+                    预览图片
+                   <!-- <el-image :src="imgArray[0]" :preview-src-list="imgArray" class="ShortAnswer-image-btn"></el-image>-->
+                </el-button>
+            </el-col>
+
+        </el-row>
+        <el-dialog title="下载附件" :append-to-body="true" :visible.sync="downloadDialog" class="ShortAnswer-fj-dialog"
+                   width="30%">
+            <div v-for="(item,index) in filesArray" class="fj-box">
+                <div @click="downloadFile(item)">附件{{index+1}}</div>
+            </div>
+        </el-dialog>
+        <el-dialog title="图片"
+                   :visible.sync="yulanDialog"
+                   width="50%"
+                   center
+                   class="yulanDl"
+                   :close-on-click-modal="false"
+                   :append-to-body="true"
+                   @close="yulanDialogClose"
+                   @open="yulanDialogOpen"
+        >
+            <Perfect-scrollbar>
+                <div v-if="imgArray&&imgArray.length>0"
+                     style="display: flex;justify-content: flex-start; flex-wrap: wrap; height: 300px" >
+                    <el-image
+                            v-for="(item,index) in imgArray"
+                            :key="index"
+                            style="width: 100px; height: 100px; margin-left: 10px; margin-bottom: 6px"
+                            :src="item"
+                            :preview-src-list="[...item]">
+                        <div slot="error" class="image-slot">
+                            <!--  图片请求异常时的异常处理   -->
+                            <i class="el-icon-picture-outline"></i>
+                        </div>
+                    </el-image>
+                </div>
+            </Perfect-scrollbar>
+            <div slot="footer" class="dialog-footer">
+                <el-button @click="yulanDialog = false">关 闭</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    import { mapGetters }       from 'vuex';
+    import { getStringByHtml2 } from '@/utils/common.js';
+    import MtaAnswerArea        from '@/components/client/QuestionsForCuoti/AnswerArea.vue';
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+    import MtaStQuillEditor     from '@/components/management/QuillEditor.vue';
+    import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
+    export default {
+        name:       'ShortAnswerQuestion',
+        props:      {
+            questionData: { // 简答题数据
+                require: true,
+                type:    Object,
+            },
+            shitiIndex:   {
+                type:    Number,
+                default: 0,
+            },
+            isYueDu:      {
+                type:    Boolean,
+                default: false,
+            },
+            yuduIndex:    {
+                type: Number,
+            },
+            // 组件接环状态, kaoshi,geren,yulan,cuoti2
+            typeCase:     {
+                type:    String,
+                default: 'kaoshi',
+            },
+            getShowModel: {
+                type:    Boolean,
+                default: true,
+            },
+        },
+        components: {
+            MtaAnswerArea,
+            MtaBusAudioPlayer,
+            MtaStQuillEditor,
+            PerfectScrollbar
+        },
+        watch:      {
+            questionData: {
+                handler(newVal, oldVal) {
+                    switch (this.typeCase) {
+                        case `kaoshi`:
+                            this.pageData = newVal;
+                            this.textarea = newVal.reply === `null` ? '' : newVal.reply;
+                            break;
+                        case `geren`:
+                            this.pageData = newVal;
+                            // 答案
+                            this.textarea = newVal.reply === `null` ? '' : newVal.reply;
+                            // 解析答案
+                            this.showAnswer = getStringByHtml2(newVal.result);
+                            // 解析
+                            this.analysis = getStringByHtml2(newVal.answer);
+                            break;
+                        case `cuoti`:
+                            this.pageData = newVal;
+                            // 答案
+                            this.textarea = newVal.reply === `null` ? '' : newVal.reply;
+                            break;
+                        case `yulan`:
+                            this.pageData = newVal;
+                            // 答案
+                            // this.textarea = getStringByHtml(newVal.result === `null` ? '' : newVal.result);
+                            this.textarea = '';
+                            // 解析答案
+                            this.showAnswer = getStringByHtml2(newVal.result);
+                            // 解析
+                            this.analysis = getStringByHtml2(newVal.answer);
+
+                            break;
+                        case `cuoti2`:
+                            this.pageData = newVal;
+                            // 答案
+                            this.textarea = newVal.reply === `null` ? '' : newVal.reply;
+                            // 解析答案
+                            this.showAnswer = getStringByHtml2(newVal.result);
+                            // 解析
+                            this.analysis = getStringByHtml2(newVal.answer);
+                            break;
+                        case `cuoti3`:
+                            this.pageData = newVal;
+                            // 答案
+                            this.textarea = newVal.reply === `null` ? '' : newVal.reply;
+                            // 解析答案
+                            this.showAnswer = getStringByHtml2(newVal.result);
+                            // 解析
+                            this.analysis = getStringByHtml2(newVal.answer);
+                            break;
+                    }
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                    if (data.files) {
+                        let allFiles = JSON.parse(data.files);
+                        let imgArray = [];
+                        let filesArray = [];
+                        let imageType = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff'];
+                        for (let name of allFiles) {
+                            var index1 = name.lastIndexOf('.');
+                            var index2 = name.length;
+                            var suffix = name.substring(index1 + 1, index2);
+                            if (imageType.indexOf(suffix) != -1) {
+                                imgArray.push(name);  // 如果为图片
+                            } else {
+                                filesArray.push(name);
+                            }
+                        }
+                        this.imgArray = imgArray;
+                        this.filesArray = filesArray;
+                    }
+                },
+                immediate: true,
+                deep:      true,
+            },
+        },
+        computed:   {
+            ...mapGetters(['getShowScore']),
+        },
+        data() {
+            return {
+                // 页面渲染必须数据
+                pageData:       null,
+                // 答案
+                textarea:       null,
+                // 显示答案
+                showAnswer:     null,
+                // 解析
+                analysis:       null,
+                audioOptions:   '',
+                imgArray:       [],
+                filesArray:     [],
+                downloadDialog: false,
+                quillEditorRef: '',
+                yulanDialog: false,
+            };
+        },
+        methods:    {
+            yulanDialogClose(){
+
+            },
+            yulanDialogOpen(){
+
+            },
+            downloadFile(data) {
+                window.location.href = data;
+            },
+            inputChange(val) {
+                // this.$emit('reply', [1, this.questionData.stId, val]);
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        type:      'jianda',
+                        stId:      this.questionData.stId,
+                        data:      val,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        stId:      this.questionData.stId,
+                        data:      val,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+
+            },
+            syncValue(flg, content) {
+                this.content = content;
+                this.textarea = content;
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        type:      'jianda',
+                        stId:      this.questionData.stId,
+                        files:     this.questionData.files,
+                        data:      content,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        stId:      this.questionData.stId,
+                        files:     this.questionData.files,
+                        data:      content,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+
+
+            },
+        },
+        created() {
+            this.quillEditorRef = 'quillEditorRef' + Math.floor(Math.random() * 100000);
+        },
+    };
+</script>

+ 230 - 0
src/components/client/QuestionsForCuoti/SingleChoice.vue

@@ -0,0 +1,230 @@
+<template>
+    <div class="mta-single-choice" v-if="pageData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+            <span class="wrap-shitiIndex" v-if="!!shitiIndex">
+                <span v-if="isYueDu">({{shitiIndex}})</span>
+                <span v-else>{{shitiIndex}}.</span>
+            </span>
+                <!--  <span>【单选题】</span>-->
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="pageData.name"></div>
+
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{pageData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions'>
+            <mta-bus-audio-player :checktimes="false" :options="audioOptions"></mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <el-radio-group v-model="newRadioData" @change="selectChange">
+                    <div class="mta-radio-row" v-for="(item, index) in questionDataContent"
+                         :class="{'showError': showError && typeCase === 'geren'}">
+
+                        <el-radio :label="`${index}`">
+                            <span style="vertical-align: top;" class="wordOption">{{item.num}}、</span>
+                            <span class="wordBox" v-html="item.label"></span></el-radio>
+                    </div>
+                </el-radio-group>
+                <div class="mta-modal" v-if="getShowModel"></div>
+            </el-col>
+        </el-row>
+        <mta-answer-area
+                v-if="typeCase === 'geren' || typeCase === 'cuoti2' || typeCase === 'cuoti3'|| typeCase === 'yulan'"
+                :data="{
+                    showAnswer:showAnswer,
+                    analysis: pageData.answer
+                }"
+        ></mta-answer-area>
+        <!--<el-row class="mta-three-row" type="flex" v-if = "typeCase === 'geren' || typeCase === 'cuoti2'">
+            <el-col>
+                &lt;!&ndash; 试题内容区域 &ndash;&gt;
+                <div class="mta-three-row-div">
+                    <span class="three-span">正确答案:</span>
+                    <span>{{showAnswer}}</span>
+                </div>
+                <div class="mta-three-row-div">
+                    <span class="three-span">答案解析:</span>
+                    <el-input type="textarea" :readonly="true" v-model="pageData.answer"></el-input>
+                </div>
+            </el-col>
+        </el-row>-->
+
+    </div>
+
+</template>
+
+<script>
+    import { getLetterByIndex } from '@/utils/common';
+    import { mapGetters }       from 'vuex';
+    import MtaAnswerArea        from '@/components/client/QuestionsForCuoti/AnswerArea.vue';
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name:       'SingleChoice',
+        props:      {
+            questionData: { // 单选题数据
+                require: true,
+                type:    Object,
+            },
+            shitiIndex:   { // 题序号
+                type:    Number,
+                default: 0,
+            },
+            isYueDu:      {
+                type:    Boolean,
+                default: false,
+            },
+            yuduIndex:    {
+                type: Number,
+            },
+            // 组件接环状态, kaoshi,geren,yulan,cuoti2
+            typeCase:     {
+                type:    String,
+                default: 'kaoshi',
+            },
+            getShowModel: {
+                type:    Boolean,
+                default: true,
+            },
+        },
+        data() {
+            return {
+                // 页面渲染必须数据
+                pageData:     null,
+                // 答案
+                newRadioData: null,
+                showAnswer:   null,
+                // 错误
+                showError:    false,
+                // 解析
+                analysis:     null,
+                audioOptions: null,
+            };
+        },
+        methods:    {
+            checkAnswer(result, reply) {
+                return result !== reply;
+            },
+            selectChange(val) {
+                this.questionData.reply = val;
+
+                this.pageData.reply = val;
+                this.showError = this.checkAnswer(this.pageData.result, val);
+
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        type:      'danxuan',
+                        stId:      this.pageData.stId,
+                        data:      this.newRadioData,
+                        number:    this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph: this.questionData.paragraph,
+                        stId:      this.questionData.stId,
+                        data:      this.newRadioData,
+                        number:    this.shitiIndex - 1,
+                    });
+                }
+            },
+        },
+        components: {
+            MtaAnswerArea,
+            MtaBusAudioPlayer,
+        },
+        watch:      {
+            questionData: {
+                handler(newVal, oldVal) {
+                    switch (this.typeCase) {
+                        case `kaoshi`:
+                            this.pageData = newVal;
+                            this.newRadioData = newVal.reply;
+                            break;
+                        case `geren`:
+                            this.pageData = newVal;
+                            // 答案
+                            this.newRadioData = newVal.reply;
+                            // 解析答案
+                            this.showAnswer = getLetterByIndex(Number(newVal.result));
+                            // 解析
+                            this.analysis = newVal.answer;
+                            // 错误是否显示
+                            this.showError = this.checkAnswer(newVal.result, newVal.reply);
+                            break;
+                        case `cuoti`:
+                            this.pageData = newVal;
+                            this.newRadioData = newVal.reply;
+                            break;
+                        case `yulan`:
+                            this.pageData = newVal;
+                            // this.newRadioData = newVal.result;
+                            this.newRadioData = null;
+                            // 解析答案
+                            this.showAnswer = getLetterByIndex(Number(newVal.result));
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti2`:
+                            this.pageData = newVal;
+                            // 单选题答案
+                            this.newRadioData = newVal.reply;
+                            // 解析答案
+                            this.showAnswer = getLetterByIndex(Number(newVal.result));
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti3`:
+                            this.pageData = newVal;
+                            // 单选题答案
+                            this.newRadioData = newVal.reply;
+                            // 解析答案
+                            this.showAnswer = getLetterByIndex(Number(newVal.result));
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                    }
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+                deep:      true,
+            },
+
+        },
+        computed:   {
+            questionDataContent() {
+                if (this.questionData.content && this.questionData.content.length) {
+                    return this.questionData.content.map((item, index) => {
+                        return {
+                            label: item,
+                            num:   getLetterByIndex(index),
+                        };
+                    });
+                }
+            },
+            ...mapGetters(['getShowScore']),
+        },
+        created() {
+
+        },
+    };
+</script>
+

+ 62 - 0
src/components/client/QuestionsForCuoti/TianKongItem.vue

@@ -0,0 +1,62 @@
+<template>
+    <div class="mta-tiankong-item">
+        <span class="mta-num">填空{{num}}:</span>
+        <el-input v-model="inputText"
+                  :class="{'showError': showError && data.typeCase === 'geren', 'showSuccess': !showError && data.typeCase === 'geren' || data.typeCase === 'cuoti2'}"
+                  @change="inputChange"
+                  :readonly="data.typeCase=== 'yulan' || data.typeCase=== 'geren'"
+                  :placeholder="`请输入填空${num}答案`"></el-input>
+    </div>
+</template>
+
+<script>
+    import {getStringByHtml} from '@/utils/common.js'
+
+    export default {
+        name: "TianKongItem",
+        props: {
+            data: {
+                type: Object,
+                require: true
+            },
+        },
+        data() {
+            return {
+                num: this.data.num,
+                inputText: undefined,
+
+            }
+        },
+        methods: {
+            inputChange(val) {
+                this.$emit('change', {
+                    num: this.num,
+                    data: val
+                });
+                this.$emit('update:result', val);
+            }
+        },
+        watch: {
+            data: {
+                handler(newVal) {
+                    if (this.data.typeCase === 'yulan') {
+                        // this.inputText = newVal.answer.join(',');
+                        this.inputText = '';
+                    } else {
+                        this.inputText = newVal.answer
+                    }
+
+                },
+                immediate: true,
+                deep: true
+            }
+        },
+        computed: {
+            showError() {
+                let status = this.data.result[this.num - 1].indexOf(this.inputText) === -1;
+                return status
+            }
+
+        }
+    }
+</script>

+ 205 - 0
src/components/client/QuestionsForCuoti/TrueOrFalse.vue

@@ -0,0 +1,205 @@
+<template>
+    <div class="mta-true-or-false" v-if="pageData.name">
+        <el-row class="mta-first-row" type="flex">
+            <el-col>
+                <span class="wrap-shitiIndex" v-if="!!shitiIndex">
+                    <span v-if="isYueDu">({{shitiIndex}})</span>
+                    <span v-else>{{shitiIndex}}.</span>
+                </span>
+                <!-- 试题题目 -->
+                <div class="mta-SC-title" v-html="pageData.name"></div>
+                <span class="mta-SC-score" v-if="getShowScore && !isYueDu">({{pageData.score}}分)</span>
+            </el-col>
+        </el-row>
+        <el-row class="mta-two-row" type="flex" v-if='audioOptions' >
+            <mta-bus-audio-player :checktimes="false" :options="audioOptions"></mta-bus-audio-player>
+        </el-row>
+        <el-row class="mta-two-row" type="flex">
+            <el-col>
+                <!-- 试题内容区域 -->
+                <el-radio-group v-model="newRadioData" @change="selectChange"
+                                :class="{'showError': showError && typeCase === 'geren'}">
+                    <div class="mta-panduan-row">
+                        <el-radio label="1">正确</el-radio>
+                    </div>
+                    <div class="mta-panduan-row">
+                        <el-radio label="0">错误</el-radio>
+                    </div>
+                </el-radio-group>
+                <div class="mta-modal" v-if="getShowModel"></div>
+            </el-col>
+        </el-row>
+        <mta-answer-area
+                v-if="typeCase === 'geren' || typeCase === 'cuoti2' || typeCase === 'cuoti3' || typeCase === 'yulan'"
+                :data="{
+                    showAnswer:showAnswer,
+                    analysis: pageData.answer
+                }"
+        ></mta-answer-area>
+
+    </div>
+</template>
+
+<script>
+    import {mapGetters} from 'vuex'
+    import MtaAnswerArea from '@/components/client/QuestionsForCuoti/AnswerArea.vue'
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+
+    export default {
+        name: "TrueOrFalse",
+        props: {
+            questionData: { // 单选题数据
+                require: true,
+                type: Object,
+            },
+            shitiIndex: { // 试题序号
+                type: Number,
+                default: 0
+            },
+            isYueDu: {
+                type: Boolean,
+                default: false
+            },
+            yuduIndex: {
+                type: Number,
+            },
+            // 组件接环状态, kaoshi,geren,yulan,cuoti2
+            typeCase: {
+                type: String,
+                default: 'kaoshi'
+            },
+            getShowModel: {
+                type: Boolean,
+                default: true
+            }
+        },
+        components: {
+            MtaAnswerArea,
+            MtaBusAudioPlayer,
+        },
+        data() {
+            return {
+                // 页面渲染必须数据
+                pageData: null,
+                // 答案
+                newRadioData: this.questionData.reply,
+                showAnswer: null,
+                // 错误
+                showError: false,
+                // 解析
+                analysis: null,
+                audioOptions: null,
+            }
+        },
+        watch: {
+
+            questionData: {
+                handler(newVal, oldVal) {
+                    switch (this.typeCase) {
+                        case `kaoshi`:
+                            this.pageData = newVal;
+                            this.newRadioData = newVal.reply;
+                            break;
+                        case `geren`:
+                            this.pageData = newVal;
+                            // 答案
+                            this.newRadioData = newVal.reply;
+                            // 解析答案
+                            this.showAnswer = newVal.result === '0' ? '错误' : '正确';
+                            // 解析
+                            this.analysis = newVal.answer;
+                            // 错误是否显示
+                            this.showError = this.checkAnswer(newVal.result, newVal.reply);
+                            break;
+                        case `cuoti`:
+                            this.pageData = newVal;
+                            this.newRadioData = newVal.reply;
+                            break;
+                        case `yulan`:
+                            this.pageData = newVal;
+                            // this.newRadioData = newVal.result;
+                            this.newRadioData = null;
+                            // 解析答案
+                            this.showAnswer = newVal.result === '0' ? '错误' : '正确';
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti2`:
+                            this.pageData = newVal;
+                            // 单选题答案
+                            this.newRadioData = newVal.reply;
+                            // 解析答案
+                            this.showAnswer = newVal.reply === '0' ? '错误' : '正确';
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                        case `cuoti3`:
+                            this.pageData = newVal;
+                            // 单选题答案
+                            this.newRadioData = newVal.reply;
+                            // 解析答案
+                            this.showAnswer = newVal.reply === '0' ? '错误' : '正确';
+                            // 解析
+                            this.analysis = newVal.answer;
+                            break;
+                    }
+
+                    let data = this.questionData;
+                    if (data.adjunct) {
+                        let arrItem = JSON.parse(data.adjunct);
+                        for (const item of arrItem) {
+                            if (item.type === 'audio') {
+                                this.audioOptions = {};
+                                this.audioOptions.src = item.src;
+                                this.audioOptions.title = item.oriname.split('.')[0];
+                                this.audioOptions.autoplay = false;
+                                this.audioOptions.dragable = item.dragable;
+
+                                this.audioOptions.playtimes = item.playtimes;
+                            }
+                        }
+                    }
+                },
+                immediate: true,
+                deep: true
+            },
+
+            /* questionData: function (newVal, oldVal) {
+                 this.newRadioData = newVal.reply;
+             },*/
+        },
+        computed: {
+            ...mapGetters(['getShowScore']),
+        },
+        methods: {
+            checkAnswer(result, reply) {
+                return result !== reply
+            },
+            selectChange(val) {
+                this.questionData.reply = val;
+
+                this.pageData.reply = val;
+                this.showError = this.checkAnswer(this.pageData.result, val);
+
+                if (this.isYueDu) {
+                    this.$emit('reply', {
+                        paragraph:  this.questionData.paragraph,
+                        type: 'panduan',
+                        stId: this.questionData.stId,
+                        data: this.newRadioData,
+                        number: this.yuduIndex,
+                    });
+                } else {
+                    this.$emit('reply', {
+                        paragraph:  this.questionData.paragraph,
+                        stId: this.questionData.stId,
+                        data: this.newRadioData,
+                        number: this.shitiIndex - 1,
+                    });
+                }
+
+            }
+        },
+    }
+</script>
+

+ 44 - 0
src/components/client/common/CardComp.vue

@@ -0,0 +1,44 @@
+<template>
+    <el-card :body-style="{ padding: '0px' }" class="card-comp">
+        <slot name="shadow"></slot>
+
+        <div @click="handleCardClick" class="image-box">
+            <div class="image-box-div">
+                <img :src="img" class="image"/>
+            </div>
+            <span :style="TagStyle" class="my-tag" v-if="MyTag">{{MyTag}}</span>
+        </div>
+        <div class="card-word-box">
+            <slot/>
+        </div>
+    </el-card>
+</template>
+
+<script>
+    export default {
+        name:    'CardComp',
+        props:   {
+            // 图片数据
+            img:      {
+                type:     String,
+                required: true,
+            },
+            // 传入数据
+            MyOption: [String, Number, Boolean, Array, Object, Date, Function, Symbol],
+            // 标签
+            MyTag:    String,
+            // 标签样式
+            TagStyle: {
+                type:    Object,
+                default: () => ({}),
+            },
+
+        },
+        methods: {
+            handleCardClick() {
+
+                this.$emit('card-click', this.MyOption || 'click');
+            },
+        },
+    };
+</script>

+ 255 - 0
src/components/client/common/MtaBreadcrumb.vue

@@ -0,0 +1,255 @@
+<template>
+    <div class="c-breadcrumb-box">
+        <div class="st-breadcrumb">
+            <i class="breadcrumb-icon-box"></i>
+            <router-link :to="item.path" :class="!item.last ? 'st-breadcrumb-item' : 'st-breadcrumb-item-last'"
+                         v-for="item in nodes"
+                         :key="item.path">
+                <div v-text="item.display" class="breadcrumb-div"></div>
+            </router-link>
+        </div>
+        <div style="clear: both;"></div>
+    </div>
+</template>
+
+<script>
+    import * as commonUtils from '@/utils/common';
+
+    export default {
+        name:    'MtaBreadcrumb',
+        props:   {
+            options: {
+                type: Object,
+                default() {
+                    return {};
+                },
+            },
+        },
+        data() {
+            return {
+                nodes: [
+                    /*{
+                        path:    '/c/kecheng/lists',
+                        display: '课程列表',
+                        last:    false,
+                    },
+                    {
+                        path:    '/c/kecheng/details',
+                        display: '课程大纲',
+                        last:    false,
+                    },
+                    {
+                        path:    '/c/kecheng/courseInfo',
+                        display: '第一章 第一节',
+                        last:    true,
+                    },*/
+                ],
+            };
+        },
+        methods: {
+            getParentRoute(route) {
+
+                let parentPath = route.meta.parentPath;
+                if (parentPath && parentPath !== 'croot') {
+                    if (typeof parentPath === 'string') {
+                        return this.$router.match(parentPath, this.current);
+                    } else if (typeof parentPath === 'object' && Array.isArray(parentPath)) {
+                        if (parentPath.length < 2) {
+                            throw new Error(' 业务逻辑错误 parentPath 作为数组长度不能小于2');
+                        }
+                        let arrPaths = commonUtils.getCache('Breadcrumb', 'arrPaths');
+                        let maxIndex = -1;
+                        let retJ = 0;
+                        for (let j = 0; j < parentPath.length; j++) {
+                            const pathRoute = parentPath[j];
+                            for (let i = arrPaths.length - 1; i > -1; i--) {
+                                const pathCache = arrPaths[i];
+                                if (pathRoute === pathCache) {
+                                    if (maxIndex < i) {
+                                        maxIndex = i;
+                                        retJ = j;
+                                    }
+                                }
+                            }
+                        }
+                        return this.$router.match(parentPath[retJ], this.current);
+                    } else {
+                        console.log(route);
+                        throw new Error('Route meta.parentPath 异常');
+                    }
+                } else {
+                    return null;
+                }
+
+            },
+            getDisplayByRoute(route) {
+                let chaptersection = commonUtils.getCache('Breadcrumb', 'chaptersection');
+                let breadcrumbDisplayParamsObj = {};
+                if (chaptersection) {
+                    breadcrumbDisplayParamsObj.courseInfo = {
+                        p1: chaptersection.chapter,
+                        p2: chaptersection.section,
+                    }
+                }
+
+                let retDisplay = '';
+                // key value
+                let p1 = '';
+                let p2 = '';
+                for (var key in breadcrumbDisplayParamsObj) {
+                    if (route.path.indexOf(key) > -1) {
+                        p1 = breadcrumbDisplayParamsObj[key].p1;
+                        p2 = breadcrumbDisplayParamsObj[key].p2;
+                        break;
+                    }
+                }
+
+                if (!route.meta.breadcrumb) {
+                    throw new Error('路由:' + route.path + '  未标记breadcrumb');
+                }
+                // console.log(route, route.meta.breadcrumb, p1, p2);
+
+                if (p2 !== '') {
+                    retDisplay = route.meta.breadcrumb.display.stformat(p1, p2);
+                } else if (p1 !== '') {
+                    retDisplay = route.meta.breadcrumb.display.stformat(p1);
+                } else {
+                    retDisplay = route.meta.breadcrumb.display;
+                }
+                return retDisplay;
+            },
+            /**
+             * 通过路由构筑单节点
+             * @param nodesReverse
+             * @param route 路由
+             */
+            getNodeByRoute(nodesReverse, route) {
+                let node = {};
+                /*{
+                        path:    '/c/kecheng/courseInfo',
+                        display: '第一章 第一节',
+                        last:    true,
+                    }*/
+                node.path = route.path;
+                node.display = this.getDisplayByRoute(route);
+
+                if (node) {
+                    nodesReverse.push(node);
+                }
+
+                const parentRoute = this.getParentRoute(route);
+                if (parentRoute) {
+                    this.getNodeByRoute(nodesReverse, parentRoute);
+                }
+            },
+        },
+        created() {
+            /*// console.log('this.$route');
+            console.dir(this.$route);
+            // console.log('this.$router');
+            console.dir(this.$router);*/
+
+
+            let nodesReverse = [];
+            // 通过路由构筑单节点
+            this.getNodeByRoute(nodesReverse, this.$route);
+
+            // 添加首页
+            let tenantFlag_key = window.localStorage.getItem(`tenantFlag_key`)
+            if (tenantFlag_key === '1') {
+                nodesReverse.push({
+                                      path:    '/c/Index',
+                                      display: '首页',
+                                  });
+            } else if (tenantFlag_key === '2') {
+                nodesReverse.push({
+                                      path:    '/c/IndexPeixun',
+                                      display: '首页',
+                                  });
+            }
+
+
+            for (let i = nodesReverse.length - 1; i > -1; i--) {
+                const nodeReverse = nodesReverse[i];
+                if (i !== 0) {
+                    nodeReverse.last = false;
+                } else {
+                    nodeReverse.last = true;
+                }
+                this.nodes.push(nodeReverse);
+            }
+        },
+        mounted() {
+        },
+        watch:   {},
+    };
+</script>
+<style lang="scss">
+    .c-breadcrumb-box {
+        font-size: 14px;
+        margin-bottom: 32px;
+        margin-left: 30px;
+        .st-breadcrumb{
+            display: flex;
+            align-items: baseline;
+            a:last-child{
+                .breadcrumb-div::after{
+                    display: none;
+                }
+            }
+        }
+        .breadcrumb-icon-box{
+            width: 14px;
+            height: 14px;
+            background-image: url("../../../assets/images/icons/breadcrumb-icon-home.svg");
+            margin-right: 8px;
+        }
+        .st-breadcrumb-item {
+            font-size: 14px;
+            float: left;
+            color: #666;
+            text-decoration: none;
+        }
+        .breadcrumb-div{
+            display: flex;
+        }
+        .breadcrumb-div::after {
+            content: ' ';
+            width: 7px;
+            height: 16px;
+            display: inline-block;
+            background-image: url("../../../assets/images/icons/breadcrumb-icon.svg");
+            padding: 0 8px;
+            background-repeat: no-repeat;
+            background-position: center;
+        }
+
+        .st-breadcrumb-item:hover {
+            color: #2C98F2;
+            cursor: pointer;
+        }
+
+        .st-breadcrumb-item-last {
+            float: left;
+            text-decoration: none;
+            color: #333;
+        }
+
+        @media screen and (max-width: 1600px) {
+            margin-bottom: 24px;
+            margin-left: 20px;
+        }
+        @media screen and (max-width: 1440px) {
+            font-size: 13px;
+            margin-bottom: 16px;
+            .st-breadcrumb-item {
+                font-size: 13px;
+            }
+            .breadcrumb-div::after {
+                height: 14px;
+            }
+        }
+    }
+
+
+</style>

+ 86 - 0
src/components/client/common/StClock.vue

@@ -0,0 +1,86 @@
+<template>
+    <div class="countdown">
+        <div id='tiles'>
+            <!--<span>{{days}}</span>-->
+            <span>{{hours}}</span>
+            <span>{{minutes}}</span>
+            <span>{{seconds}}</span>
+        </div>
+        <div class="labels">
+            <!--<li>Days</li>-->
+            <li>Hours</li>
+            <li>Mins</li>
+            <li>Secs</li>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        name:     'stClock',
+        props:    {
+            endSecond: {
+                type:    Number,
+                require: true,
+                default: '0',
+            },
+        },
+        data() {
+            return {
+                days:    '',
+                hours:   '',
+                minutes: '',
+                seconds: '',
+                timer:   null,
+                endSecondC: null,
+            };
+        },
+        computed: {
+        },
+        watch:    {
+            endSecond: function () {
+                this.endSecondC = this.endSecond;
+                let _this = this;
+                this.timer = setInterval(function () {
+                    if(!_this.getCountdown(_this.endSecondC)) {
+                        clearInterval(_this.timer);
+                        _this.$emit('clockTimeOut');
+                    }
+                    _this.endSecondC = _this.endSecondC - 1;
+                }, 1000);
+            }
+
+        },
+        methods:  {
+            pad(n) {
+                return (n < 10 ? '0' : '') + n;
+            },
+            getCountdown(endSecond) {
+
+                // find the amount of "seconds" between now and target
+                if (endSecond <= 0) {
+                    return false;
+                }
+                let seconds_left = endSecond;
+
+                this.days = this.pad(parseInt(seconds_left / 86400));
+                seconds_left = seconds_left % 86400;
+
+                this.hours = this.pad(parseInt(seconds_left / 3600));
+                seconds_left = seconds_left % 3600;
+
+                this.minutes = this.pad(parseInt(seconds_left / 60));
+                this.seconds = this.pad(parseInt(seconds_left % 60));
+                return true;
+            },
+        },
+        created() {
+        },
+        beforeDestroy() {
+            if (this.timer) {
+                clearInterval(this.timer);
+            }
+        },
+
+    };
+</script>

+ 49 - 0
src/components/client/common/footer.vue

@@ -0,0 +1,49 @@
+<template>
+    <div class="mta-client-footer">
+        <el-row type="flex">
+            <el-col class="footer-col-wrap">
+                <!--版权所有2019 大连栋科软件工程有限公司-->
+                {{footText}}
+            </el-col>
+        </el-row>
+    </div>
+</template>
+
+<script>
+    import { mapGetters } from 'vuex'
+    export default {
+        name:    'mtaFooter',
+        computed: {
+            footText() {
+                let str = 'Copyright © 2019 llisoft.com All rights reserved 大连栋科软件工程有限公司 版权所有 辽ICP备09022904号-19';
+                return this.getReplaceContext && this.getReplaceContext.footText ? this.getReplaceContext.footText : str;
+            },
+            ...mapGetters([
+                              'getReplaceContext'
+                          ])
+        },
+        props:   {
+            activeNav: { // 默认选中的 路径 与组件中的index相互对应
+                type:    String,
+                require: true,
+                default: '0',
+            },
+        },
+        data() {
+            return {
+            };
+        },
+        watch:   {
+        },
+        methods: {
+            /*handleSelect(key, keyPath) {
+                this.$emit('navSelectChange', {
+                    key: key,
+                    keyPath: keyPath
+                })
+            }*/
+        },
+        created() {
+        }
+    };
+</script>

+ 640 - 0
src/components/client/common/header.vue

@@ -0,0 +1,640 @@
+<template>
+    <div class="mta-client-header">
+        <el-row type="flex" class="header-wrap">
+            <el-col class="header-col-wrap">
+                <!-- logo -->
+                <div class="header-col-logo">
+                    <img :src="logoImg" alt="logo">
+                </div>
+                <!-- nav -->
+                <div class="header-col-nav">
+                    <el-menu
+                            :default-active="activeIndex"
+                            class="menu-client-header"
+                            :router="true"
+                            mode="horizontal"
+                            background-color="#1E1D22"
+                            text-color="#fff"
+                            :active-text-color="getThemeColor(0)">
+                        <template v-for="(item,index) in headerArr">
+                            <el-submenu :index="`${item.keyPath}`" v-if="item.children && item.children.length > 0"
+                                        popper-class="mta-menu-two">
+                                <template slot="title">{{item.content}}</template>
+                                <el-menu-item :index="`${child.keyPath}`" v-for="child of item.children"
+                                              :key="child.keyPath">{{child.content}}
+                                </el-menu-item>
+                            </el-submenu>
+                            <el-menu-item :index="`${item.keyPath}`" v-else>
+                                {{item.content}}
+                            </el-menu-item>
+                        </template>
+                    </el-menu>
+                    <nav>
+                        <ul>
+                            <li>
+                                <el-popover
+                                        popper-class="nav-popover"
+                                        placement="bottom"
+                                        width="160"
+                                        trigger="click"
+                                >
+                                    <ul class="userControl">
+                                        <li>
+                                            <el-button type="text" icon="el-icon-key" @click="changePassword">
+                                                修改密码
+                                            </el-button>
+                                        </li>
+                                        <li v-if="!(getTenantCode === 'admin')">
+                                            <el-button type="text" icon="el-icon-s-order" @click="changeInformation">
+                                                修改资料
+                                            </el-button>
+                                        </li>
+                                        <li>
+                                            <el-button type="text" icon="el-icon-setting" @click="exitSystem">退出系统
+                                            </el-button>
+                                        </li>
+                                    </ul>
+                                    <span class="h-right-img" slot="reference">
+                                  <img :src="headerImg" alt="头像">
+                                  <span></span>
+                                </span>
+                                </el-popover>
+                            </li>
+                        </ul>
+                    </nav>
+                </div>
+            </el-col>
+        </el-row>
+        <el-dialog
+                title="修改密码"
+                :visible.sync="headerPasswordDialog"
+                :close-on-click-modal="false"
+                class="response-small-dialog"
+                @close="closePassDl"
+                center>
+            <el-form :model="HeaderPassChangeDate" status-icon :rules="HeaderPassRules" ref="ruleHeaderForm"
+                     class="demo-ruleForm2" label-width="100px">
+                <el-form-item label="原始密码:" prop="orgPass">
+                    <el-input v-model="HeaderPassChangeDate.orgPass"></el-input>
+                </el-form-item>
+                <el-form-item label="新的密码:" prop="pass">
+                    <el-input type="password" v-model="HeaderPassChangeDate.pass" autocomplete="off"></el-input>
+                </el-form-item>
+                <el-form-item label="确认密码:" prop="checkPass">
+                    <el-input type="password" v-model="HeaderPassChangeDate.checkPass" autocomplete="off"></el-input>
+                </el-form-item>
+            </el-form>
+
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="headerPasswordDialog = false">取 消</el-button>
+                <el-button type="primary" @click="saveHeaderPass">确 定</el-button>
+            </span>
+        </el-dialog>
+        <el-dialog
+                title="资料编辑"
+                :visible.sync="editorMessage"
+                :close-on-click-modal="false"
+                @close="ClosePassDl2('editorUserInfo')"
+                class="response-big-dialog"
+                center>
+            <el-form ref="formUserInfor" :rules="formRules3" status-icon :model="userInfoData" label-width="85px">
+                <div class="editorUserWrap">
+                    <div>
+                        <el-form-item label="用户名:" prop="userName">
+                            <el-input v-model="userInfoData.userName"
+                                      @blur="formatDataFun(userInfoData.userName)"></el-input>
+                        </el-form-item>
+                        <el-form-item label="真实姓名:" prop="realName">
+                            <el-input v-model="userInfoData.realName"></el-input>
+                        </el-form-item>
+                        <el-form-item label="出生年月:" prop="birthday">
+                            <el-date-picker
+                                    v-model="userInfoData.birthday"
+                                    type="date"
+                                    :default-value="userInfoData.defaultBirthday"
+                                    format="yyyy-MM-dd"
+                                    value-format="yyyy-MM-dd"
+                                    placeholder="选择日期">
+                            </el-date-picker>
+                        </el-form-item>
+                        <el-form-item label="电话号码:" prop="tel">
+                            <el-input v-model="userInfoData.tel"></el-input>
+                        </el-form-item>
+                    </div>
+                    <div>
+                        <el-form-item label="性别:" prop="gender" label-width="90px">
+                            <el-radio-group v-model="userInfoData.gender">
+                                <el-radio border :label="1">男</el-radio>
+                                <el-radio border :label="2">女</el-radio>
+                            </el-radio-group>
+                        </el-form-item>
+                        <el-form-item label="邮箱:" prop="email" label-width="90px">
+                            <el-input v-model="userInfoData.email"></el-input>
+                        </el-form-item>
+                        <el-form-item label="身份证号:" prop="idcard" label-width="90px">
+                            <el-input v-model="userInfoData.idcard"></el-input>
+                        </el-form-item>
+
+                    </div>
+                    <div>
+                        <!--<mta-upload-al-cloud :imageUrl="imageUrl"
+                                             @uploadFileStart="uploadFileStart"></mta-upload-al-cloud>-->
+                        <upload-file :imageUrl="imageUrl" :showBtnFlag="false"
+                                     @getFileUrl="getImageUrl"></upload-file>
+                        <h5 class="picture-size-h5">(最佳尺寸为65*65像素)</h5>
+                    </div>
+                </div>
+            </el-form>
+            <span slot="footer" class="dialog-footer">
+        <el-button @click="cancelSaveUserInfo">取 消</el-button>
+        <el-button type="primary" @click="saveUserInfo">确 定</el-button>
+      </span>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    import { getUploadImg }        from '@/api/AlCloud';
+    import MtaUploadAlCloud
+                                   from '@/components/management/Layout/UploadAlCloud/UploadAlCloud';
+    import axios                   from 'axios';
+    import { mapGetters }          from 'vuex';
+    import { setAuth, removeAuth } from '@/utils/auth';
+    import {
+        getAdminUserMypass,
+        getClientUserMyUpdate,
+        getClentUserMyInfo,
+        getExitStatus,
+    }                              from '@/api/user';
+    import {
+        setUserIcon,
+        checkUrlIsNotHttpUrl,
+        getTrimData,
+    }                              from '@/utils/common';
+    import { initThemeColor }      from '@/utils/theme';
+    import uploadFile              from '@/components/management/Layout/UploadAlCloud/uploadFile';
+
+    export default {
+        name:       'mtaHeader',
+        props:      {
+            activeNav: { // 默认选中的 路径 与组件中的index相互对应
+                type:    String,
+                require: true,
+                default: '0',
+            },
+        },
+        components: {
+            MtaUploadAlCloud,
+            uploadFile,
+        },
+        data() {
+            const validatePass2 = (rule, value, callback) => {
+                if (value === '') {
+                    callback(new Error('请再次输入密码'));
+                } else if (value !== this.HeaderPassChangeDate.pass) {
+                    callback(new Error('两次输入密码不一致!'));
+                } else {
+                    callback();
+                }
+            };
+            return {
+                activeIndex:          this.activeNav,
+                headerArr:            [
+                    {
+                        content: '首页',
+                        keyPath: '/c/Index',
+                    },
+                    {
+                        content: '首页',
+                        keyPath: '/c/IndexPeixun',
+                    },
+                    {
+                        content:  '课程中心',
+                        keyPath:  '/c/kecheng/lists',
+                        children: [
+                            {
+                                content:  '课程列表',
+                                keyPath:  '/c/kecheng/lists',
+                                children: [
+                                    {
+                                        content: '课程',
+                                        keyPath: '/c/kecheng/do',
+                                    },
+                                ],
+                            },
+                        ],
+                    },
+                    {
+                        content:  '考试中心',
+                        keyPath:  '/c/exam/lists',
+                        children: [
+                            {
+                                content:  '考试列表',
+                                keyPath:  '/c/exam/lists',
+                                children: [
+                                    {
+                                        content: '考试',
+                                        keyPath: '/c/exam/do',
+                                    },
+                                ],
+                            },
+                        ],
+                    },
+                    {
+                        content:  '成绩中心',
+                        keyPath:  '/c/score/lists',
+                        children: [
+                            {
+                                content: '个人成绩',
+                                keyPath: '/c/score/lists',
+                            },
+                            {
+                                content: '课程成绩',
+                                keyPath: '/c/score/kechengLists',
+                            },
+                            {
+                                content: '学分统计',
+                                keyPath: '/c/score/credit',
+                            },
+                        ],
+                    },
+                    {
+                        content:  '自主练习',
+                        keyPath:  '/c/Practice/lists',
+                        children: [
+                            {
+                                content:  '练习列表',
+                                keyPath:  '/c/Practice/lists',
+                                children: [
+                                    {
+                                        content: '练习页面',
+                                        keyPath: '/c/Practice/do',
+                                    },
+                                ],
+                            },
+                        ],
+                    },
+                    /*{
+                        content:  '错题中心',
+                        keyPath:  '/c/cuotiCenter/lists',
+                        children: [
+                            {
+                                content:  '错题列表',
+                                keyPath:  '/c/cuotiCenter/lists',
+                                children: [
+                                    {
+                                        content: '错题页面',
+                                        keyPath: '/c/cuotiCenter/page',
+                                    },
+                                ],
+                            },
+                        ],
+                    },
+                    {
+                        content:  '证书管理',
+                        keyPath:  '/c/cert/lists',
+                        children: [
+                            {
+                                content: '个人证书',
+                                keyPath: '/c/cert/lists',
+                            },
+                        ],
+                    },*/
+                    {
+                        content:  '公告管理',
+                        keyPath:  '/c/notice/lists',
+                        children: [
+                            {
+                                content: '公告中心',
+                                keyPath: '/c/notice/lists',
+                            },
+                        ],
+                    },
+                    {
+                        content:  '资源库',
+                        keyPath:  '/c/resource/library',
+                        children: [],
+                    },
+                    {
+                        content:  '互动问答',
+                        keyPath:  '/c/QuestionAndAnswer/lists',
+                        children: [],
+                    },
+                    {
+                        content:  '个人中心',
+                        keyPath:  '/c/personalCenter/lists',
+                        children: [
+                            {
+                                content: '个人信息',
+                                keyPath: '/c/personalCenter/lists',
+                            },
+                            {
+                                content:  '错题信息',
+                                keyPath:  '/c/cuotiCenter/lists',
+                                children: [
+                                    {
+                                        content: '错题页面',
+                                        keyPath: '/c/cuotiCenter/page',
+                                    },
+                                ],
+                            },
+                            {
+                                content: '证书信息',
+                                keyPath: '/c/cert/lists',
+                            },
+                        ],
+                    },
+                ],
+                // 密码修改
+                headerPasswordDialog: false,
+                HeaderPassChangeDate: {
+                    orgPass:   '',
+                    pass:      '',
+                    checkPass: '',
+                },
+                HeaderPassRules:      {
+                    orgPass:   [
+                        { message: '请输入原始密码', trigger: 'blur', required: true },
+                    ],
+                    pass:      [
+                        { message: '请输入新密码', trigger: 'blur', required: true },
+                    ],
+                    checkPass: [
+                        { validator: validatePass2, trigger: 'blur', required: true },
+                    ],
+                },
+                /// 个人信息编辑
+                editorMessage:        false,
+                userInfoData:         {
+                    userName:        '',
+                    birthday:        '',
+                    defaultBirthday: '',
+                    email:           '',
+                    gender:          '',
+                    userId:          '',
+                    idcard:          '',
+                    realName:        '',
+                    tel:             '',
+                },
+                formRules3:           {
+                    userName: [
+                        { trigger: 'blur', required: true, message: '请输入用户名' },
+                    ],
+                },
+                imageUrl:             '',
+                headerimgUrl:         require('../../../assets/images/default/user-img.png'),
+                menuFlag:             '',
+            };
+        },
+        watch:      {
+            '$route.path': function (newVal, oldVal) {
+                this.activeIndexSync();
+            },
+        },
+        computed:   {
+            logoImg() {
+                if (this.getReplaceContext === null) {
+                    return;
+                }
+                return this.getReplaceContext.logoImg;
+            },
+            // 个人头像 待定
+            headerImg() {
+                return checkUrlIsNotHttpUrl(this.getReplaceIcon.headImg)
+                       ? this.headerimgUrl
+                       : this.getReplaceIcon.headImg;
+            },
+            // 个人头像 待定
+            ...mapGetters([
+                              'getReplaceContext',
+                              'getTenantCode',
+                              'getReplaceIcon',
+                              'getThemeColor',
+                          ]),
+        },
+        methods:    {
+            getImageUrl(data) {
+                this.imageUrl = data;
+            },
+            // ************* 关窗清理 *****************
+            formatDataFun(data) {
+                this.userInfoData.userName = getTrimData(data);
+            },
+            ClosePassDl2() {
+                this.$refs.formUserInfor.resetFields();
+            },
+            // **************  弹窗事件 修改密码 *****************
+            // 密码修改
+            changePassword() {
+                this.headerPasswordDialog = true;
+            },
+            // 确认修改
+            saveHeaderPass() {
+                this.$refs.ruleHeaderForm.validate((valid) => {
+                    if (valid) {
+                        // 请求后台
+                        const options = {
+                            passwordNew: this.HeaderPassChangeDate.pass,
+                            passwordOld: this.HeaderPassChangeDate.orgPass,
+                        };
+
+                        getAdminUserMypass(options)
+                        .then(res => {
+
+                            if (res.code === 0 && res.data) {
+                                this.headerPasswordDialog = false;
+                                this.$message.success('修改成功');
+                            } else {
+                                this.$message.error('修改失败');
+                            }
+                        });
+                    } else {
+                        this.$message.error('请确定验证是否通过');
+                        return false;
+                    }
+                });
+            },
+            // 关闭修改密码
+            closePassDl() {
+                this.$refs.ruleHeaderForm.resetFields();
+            },
+            // *************  弹窗事件 帮助中心 **************
+            openHelpCenter() {
+
+            },
+            // ************** 弹窗事件 编辑信息 ****************
+            // 保存用户信息
+            saveUserInfo() {
+                const option = {
+                    birthday: this.userInfoData.birthday,
+                    email:    this.userInfoData.email,
+                    gender:   this.userInfoData.gender,
+                    icon:     this.imageUrl === undefined ? '' : this.imageUrl,
+                    idcard:   this.userInfoData.idcard,
+                    realName: this.userInfoData.realName,
+                    userId:   this.userInfoData.userId,
+                    userName: this.userInfoData.userName,
+                    tel:      this.userInfoData.tel,
+                };
+                const loading = this.loading();
+                // 请求后台
+                getClientUserMyUpdate(option)
+                .then(res => {
+                    if (res.data) {
+                        this.editorMessage = false;
+                        // 个人头像 待定
+                        const opt = {
+                            headImg: option.icon,
+                        };
+                        setUserIcon(opt);
+                        this.$message.success('修改成功');
+                        loading.close();
+                    }
+                }).catch(err => {
+                    // this.$message.error('请确定验证是否通过');
+                    loading.close();
+                    console.error('错误', err);
+                });
+            },
+            // 取消
+            cancelSaveUserInfo() {
+                this.editorMessage = false;
+            },
+            changeInformation() {
+                getClentUserMyInfo()
+                .then(res => {
+                    const userInfoObj = res.data;
+                    this.userInfoData.userName = userInfoObj.userName;
+                    this.userInfoData.birthday = userInfoObj.birthday === null
+                                                 ? new Date('1991-05-01')
+                                                 : new Date(userInfoObj.birthday);
+                    this.userInfoData.defaultBirthday = new Date(userInfoObj.birthday);
+                    this.imageUrl = userInfoObj.icon;
+                    this.userInfoData.email = userInfoObj.email;
+                    this.userInfoData.gender = userInfoObj.gender;
+                    this.userInfoData.idcard = userInfoObj.idcard;
+                    this.userInfoData.realName = userInfoObj.realName;
+                    this.userInfoData.tel = userInfoObj.tel;
+                    this.userInfoData.userId = userInfoObj.userId;
+                    this.editorMessage = true;
+                }).catch(err => {
+                    console.error('获取用户信息', err);
+                });
+            },
+
+            // ************** 退出 系统 **************************
+            exitSystem() {
+                getExitStatus().then(res => {
+                    if (res.code === 0 && res.data) {
+
+                        this.$router.push({ path: '/c/login' });
+
+                        setAuth();
+                        this.$message.success('系统退出成功');
+                    } else {
+                        this.$message.error('系统退出失败');
+                    }
+                });
+
+            },
+            // ************** 导航状态 ****************************
+            activeIndexSync() {
+                const path = this.$route.path;
+                const idx = _.findIndex(this.headerArr, function (o) {
+                    return o.keyPath === path;
+                });
+                if (idx > -1) {
+                    this.activeIndex = this.headerArr[idx].keyPath;
+                } else {
+                    for (const header of this.headerArr) {
+                        if (header.children) {
+                            const idx2 = _.findIndex(header.children, function (o) {
+                                return o.keyPath === path;
+                            });
+                            if (idx2 > -1) {
+                                this.activeIndex = header.keyPath;
+                                return;
+                            }
+                        }
+                    }
+                }
+            },
+            checkArray() {
+                if (JSON.parse(this.menuFlag) == 1) {
+
+                    for (let i = this.headerArr.length - 1; i >= 0; i--) {
+                        if (this.headerArr[i].content == '课程中心') {
+                            this.headerArr.splice(i, 1);
+                        }
+                        if (this.headerArr[i].keyPath === '/c/IndexPeixun') {
+                            this.headerArr.splice(i, 1);
+                        }
+                        if (this.headerArr[i].children && this.headerArr[i].children.length >= 2 && this.headerArr[i].children[1].content == '课程成绩') {
+                            this.headerArr[i].children.splice(1, 1);
+                        }
+                        if (this.headerArr[i].keyPath === '/c/resource/library') {
+                            this.headerArr.splice(i, 1);
+                        }
+                        if (this.headerArr[i].keyPath === '/c/QuestionAndAnswer/lists') {
+                            this.headerArr.splice(i, 1);
+                        }
+                    }
+                } else {
+                    for (let i = this.headerArr.length - 1; i >= 0; i--) {
+                        if (this.headerArr[i].keyPath === '/c/Index') {
+                            this.headerArr.splice(i, 1);
+                        }
+                    }
+                }
+                this.activeIndexSync();
+            },
+            /**
+             * 上传文件
+             */
+            uploadFileStart(params) {
+                if (!params) {
+                    return;
+                }
+                const suffixList = params.file.name.split('.').pop();
+                const options = {
+                    prefix: 'resource/',
+                    suffix: suffixList,
+                };
+                getUploadImg(options).then((res) => {
+
+                    if (res.code === 0) {
+                        // 二进制文件通过forData对象进行传递
+                        const FormDataForAl = new FormData();
+                        const multipartParams = Object.assign({}, res.data, {
+                            Filename:              `images/${params.filename}`,
+                            success_action_status: '200',
+                        });
+                        // 参数数据
+                        FormDataForAl.append('key', multipartParams.key);
+                        FormDataForAl.append('policy', multipartParams.policy);
+                        FormDataForAl.append('signature', multipartParams.signature);
+                        FormDataForAl.append('OSSAccessKeyId', multipartParams.accessid);
+                        FormDataForAl.append('success_action_status', multipartParams.success_action_status);
+                        // OSS要求, file放到最后
+                        FormDataForAl.append('file', params.file);
+
+                        axios.post(multipartParams.uploadUrl, FormDataForAl).then(alRes => {
+                            if (alRes.status === 200) {
+                                this.imageUrl = `${multipartParams.downloadUrl}/${multipartParams.key}`;
+                            }
+                        }).catch(alerr => {
+                            console.error('阿里云错误', alerr);
+                        });
+                    }
+                });
+            },
+        },
+        created() {
+            this.menuFlag = window.localStorage.getItem(`tenantFlag_key`);
+            this.checkArray();
+            /* activeIndex sync */
+            //this.activeIndexSync();
+            initThemeColor();
+        },
+    };
+</script>

+ 192 - 0
src/components/client/common/mtaDialog.vue

@@ -0,0 +1,192 @@
+<template>
+    <el-dialog v-if="showDialogType!==2"
+               title="提示"
+               class="mta-dialog"
+               :visible.sync="showDialog"
+               :show-close="showBtnClose"
+               @close="dialogClose"
+               :close-on-click-modal="false"
+               center
+               :width="dlWidth">
+    <span slot="title">
+        <div v-if="type===1" class="dialog-img">
+            <div>
+                <img src="@/assets/images/icons/notice-bell.svg" alt="image">
+            </div>
+        </div>
+        <div v-if="type===2" class="dialog-img">
+            <div>
+                <img src="@/assets/images/icons/dengDl.svg" alt="image">
+            </div>
+        </div>
+        <div v-if="type===3" class="dialog-img">
+            <div>
+                <img src="@/assets/images/icons/answerDl.svg" alt="image">
+            </div>
+        </div>
+    </span>
+        <!--<p style="line-height: 20px;">
+            {{conent.conent}}
+        </p>-->
+
+        <perfect-scrollbar>
+            <div class="mta-dialog-content">
+                <slot></slot>
+            </div>
+        </perfect-scrollbar>
+        <span slot="footer" class="dialog-footer">
+        <slot name="footer"></slot>
+        <span >
+            <el-button @click="handleCancel" size="mini" v-if="cancelFlag===1">取 消</el-button>
+             <el-button size="mini" type="primary" @click="handleConfirm">确 定</el-button>
+        </span>
+    </span>
+    </el-dialog>
+    <el-dialog v-else-if="showDialogType===2"
+               :visible.sync="showDialog"
+               :show-close="true"
+               class="mta-dialog2"
+               :close-on-click-modal="false"
+               @close="dialogClose"
+               center
+               width="20%">
+        <span slot="title">
+        </span>
+        <div class="userInfo-box">
+            <div class="fsize-m2">
+                <span>{{conent.title}}</span>
+            </div>
+            <div class="fsize-m2 score-box">
+                <img src="@/assets/images/icons/gongxi.svg" alt="">
+                <div>
+                    <span>得分:</span>
+                    <span class="score-word">{{conent.score}}分</span>
+                </div>
+                <img src="@/assets/images/icons/gongxi.svg" alt="" class="xz-img">
+            </div>
+            <div class="line-div"></div>
+            <div class="dialogDiv">
+                <label class="dialogLabel">总分:</label>
+                <span class="dialogSpan">{{conent.totalPoints}}分</span>
+            </div>
+            <div class="dialogDiv">
+                <label class="dialogLabel">及格分:</label>
+                <span class="dialogSpan">{{conent.passPoints}}分</span>
+            </div>
+            <!--       <div class="dialogDiv">
+                       <label class="dialogLabel">得分</label>
+                       <span class="dialogSpan">{{conent.score}}分</span>
+                   </div>-->
+            <div class="dialogDiv">
+                <label class="dialogLabel">正确率:</label>
+                <span class="dialogSpan">{{conent.correct}}</span>
+            </div>
+        </div>
+        <!--  <slot></slot>-->
+        <span slot="footer" class="dialog-footer">
+            <slot name="footer"></slot>
+            <span>
+                 <el-button @click="goPage" size="medium" style="background: #FFA111;border-color: #FFA111;" type="primary">确 定</el-button>
+            </span>
+      </span>
+    </el-dialog>
+</template>
+
+<script>
+    import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
+
+    export default {
+        name:       'mtaDialog',
+        components: {
+            PerfectScrollbar,
+        },
+        props:      {
+            dialogVisible:  {
+                type:    Boolean,
+                require: true,
+            },
+            type:           {
+                // 1 是 铃 , 2 是 灯 , 3 是 文件 ==> 图片
+                type:    Number,
+                default: 1,
+            },
+            showBtnClose: {
+                type: Boolean,
+                default: true,
+            },
+            conent:         {
+                type: Object,
+            },
+            showBtn:        {
+                type:    Boolean,
+                require: true,
+                default: false,
+            },
+            showDialogType: {
+                type:    Number,
+                default: 1,
+            },
+            dlWidth:        {
+                type:    String,
+                default: '20%',
+            },
+            //取消按钮隐藏显示标志
+            cancelFlag:           {
+                // 0隐藏 1显示
+                type:    Number,
+                default: 0,
+            },
+        },
+        data() {
+            return {
+                showDialog: this.dialogVisible,
+                img:        '',
+                images:     [
+                    '../../../assets/images/icons/notice-bell.svg',
+                ],
+            };
+        },
+        watch:      {
+            dialogVisible: {
+                handler() {
+                    this.showDialog = this.dialogVisible;
+                },
+                immediate: true,
+            },
+        },
+        methods:    {
+            handleConfirm() {
+                this.$emit('update:dialogVisible', false);
+                this.$emit('confirmCallback');
+            },
+            //取消
+            handleCancel() {
+                this.$emit('cancleCallback');
+            },
+            handleClose(done) {
+                this.$confirm('确认关闭?')
+                    .then(_ => {
+                        done();
+                    })
+                    .catch(_ => {
+                    });
+            },
+            dialogClose() {
+                this.$emit('update:dialogVisible', false);
+                this.$emit('close');
+            },
+            goPage() {
+                this.$emit('update:dialogVisible', false);
+                if(this.conent.personFlag=='kaoshi'){
+                    this.$router.push({ path: '/c/personalCenter/lists' });
+                }else if(this.conent.personFlag=='kecheng'){
+                    this.$router.push({ name: 'kechengDetails' });
+                }else {
+                    this.$router.push({ path: '/c/exam/lists' });
+                }
+               // this.$router.push({ path: '/c/exam/lists' });
+            },
+        },
+    };
+</script>
+

+ 94 - 0
src/components/client/kechengDetaileTab/biji.vue

@@ -0,0 +1,94 @@
+<template>
+    <bijiSlot>
+        <template slot="leftPart">
+            <img :src="data.userPic  || defultImg" alt="">
+        </template>
+        <template slot="titlePart">
+            <span>{{data.userName}}</span>
+        </template>
+        <template slot="contentPart">
+            {{data.content}}
+        </template>
+        <template slot="btnPart">
+            <div>
+                <el-button  @click="checkAllFun(data.content)" type="text" v-if="showBtnFlag"
+                           v-text="!checkAllFlag ? '收起' : '全文'"></el-button>
+                <el-button @click="editBtnFun(data)" type="text">编辑</el-button>
+                <el-button @click="deleteBtnFun(data)" type="text">删除</el-button>
+            </div>
+            <div>2020-12-45</div>
+            <!--<div>{{data.updatetime}}</div>-->
+        </template>
+    </bijiSlot>
+</template>
+
+<script>
+    import bijiSlot   from '@/components/client/kechengDetaileTab/bijiSlot.vue';
+    export default {
+        name:       'biji',
+        components: {
+            bijiSlot,
+        },
+        props: {
+            data: {
+                type: Object,
+            },
+        },
+        data() {
+            return {
+                showBtnFlag:false, //按钮显隐
+                checkAllFlag:false,  //按钮切换
+                defultImg:    'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+                content:''
+
+            };
+        },
+        methods:    {
+            checkAllFun(){
+                if(this.checkAllFlag){
+                    this.data.content = this.content;
+                    this.checkAllFlag = false
+                }else {
+                    this.data.content = this.data.content.slice(0,200) + '...';
+                    this.checkAllFlag = true
+                }
+            },
+            editBtnFun(data){
+                this.$emit('editBtnFun',data)
+            },
+            deleteBtnFun(data){
+                this.$emit('deleteBtnFun',data)
+            },
+        },
+        watch: {
+            data(){
+                this.content=[];
+                if(this.data.content.length>220){
+                    this.showBtnFlag = true;
+                    this.checkAllFlag = true;
+                    this.content = this.data.content;
+                    this.data.content = this.data.content.slice(0,220) + '...';
+                }else {
+                    this.showBtnFlag = false;
+                    this.checkAllFlag = false;
+                }
+            }
+        },
+        created() {
+            this.content=[];
+            if(this.data.content.length>220){
+                this.showBtnFlag = true;
+                this.checkAllFlag = true;
+                this.content = this.data.content;
+                this.data.content = this.data.content.slice(0,220) + '...';
+            }else {
+                this.showBtnFlag = false;
+                this.checkAllFlag = false;
+            }
+        },
+    };
+</script>
+
+<style lang="scss">
+
+</style>

+ 90 - 0
src/components/client/kechengDetaileTab/bijiDialog.vue

@@ -0,0 +1,90 @@
+<template>
+    <div>
+        <el-dialog :class="type==='Delete'? 'response-tip-dialog' :'response-small-dialog'"
+                   :close-on-click-modal="false"
+                   :show="show"
+                   :title="title"
+                   :visible.sync="visible"
+                   center>
+            <div v-if="type==='Edit'">
+                <el-input :autosize="{ minRows: 6, maxRows: 10}" type="textarea" v-model="data.content"></el-input>
+            </div>
+            <div v-else-if="type==='Delete'">
+                是否删除此条笔记?
+            </div>
+            <div v-else-if="type==='Add'">
+                <el-input
+                        :autosize="{ minRows: 6, maxRows: 10}"
+                        placeholder="请输入内容"
+                        type="textarea"
+                        v-model="addDialogConent">
+                </el-input>
+            </div>
+            <span class="dialog-footer" slot="footer">
+                <el-button @click="closeFun">取 消</el-button>
+                <el-button :disabled="isDisable" @click="confirmFun" type="primary">确 定</el-button>
+            </span>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    export default {
+        name:       'biji',
+        props: {
+            title: {
+                type: String,
+                default: '提示',
+            },
+            data: {
+                type: Object,
+            },
+            type: {
+                type: String,
+                required: true,
+            },
+            show: {
+                type: Boolean,
+                default: false
+            }
+        },
+        data() {
+            return {
+                visible: this.show,
+                addDialogConent: '',
+                isDisable: false,
+                timer:               null,
+            };
+        },
+        watch: {
+            show () {
+                this.visible = this.show;
+            }
+        },
+        methods:    {
+            closeFun(){
+                console.log('show'+this.type);
+                this.$emit('update:show', false)
+            },
+            confirmFun(){
+                let opt = {
+                    conent:this.addDialogConent
+                }
+                this.isDisable = true
+                this.timer = setTimeout(() => {
+                    this.isDisable = false
+                }, 1000);
+                this.$emit('confirmBtn'+this.type, this.data|| opt)
+            },
+            clearTime(){
+                clearTimeout(this.timer)
+            },
+        },
+        created() {
+        },
+    };
+</script>
+
+<style lang="scss">
+
+</style>

+ 40 - 0
src/components/client/kechengDetaileTab/bijiSlot.vue

@@ -0,0 +1,40 @@
+<template>
+    <div class="c-mta-biji-layout">
+        <div class="c-biji-image-box">
+            <slot name="leftPart"></slot>
+        </div>
+        <div class="c-biji-content-box">
+            <div class="c-biji-first-line">
+                <slot name="titlePart"></slot>
+            </div>
+            <div class="c-biji-two-line">
+                <slot name="contentPart"></slot>
+            </div>
+            <div class="c-biji-three-line">
+                <slot name="btnPart"></slot>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        name:       'biji',
+
+        data() {
+            return {
+
+            };
+        },
+        methods:    {
+
+        },
+        created() {
+
+        },
+    };
+</script>
+
+<style lang="scss">
+
+</style>

+ 53 - 0
src/components/client/test/TestComponent.vue

@@ -0,0 +1,53 @@
+<template>
+    <div>
+        <el-button @click="aaa">aaa</el-button>
+        <el-button @click="bbb">bbb</el-button>
+    </div>
+</template>
+<script>
+    export default {
+        name:     'TestComponent',
+        data() {
+            return {
+
+            };
+        },
+        props:    {
+            stData: {
+                require: true,
+                type:    Object,
+
+                /*a: {              ===  user 对象
+                    b: '232'
+                    ,c: '6767'
+                },
+                e: [],
+                f: false,
+                g: 232323,
+                    h: []
+                */
+            },
+            stStr:  {
+                require: true,
+                type:    String,
+            },
+        },
+        computed: {
+            /*stStrComputed() {
+                return this.stStr;
+            },*/
+        },
+        methods:  {
+            aaa() {
+                this.stData.a.b = parseInt(this.stData.a.b) + 1;
+                this.stData.a.c = parseInt(this.stData.a.c) + 2;
+            },
+            bbb() {
+                // this.stStr = this.stStr + 1
+                this.$emit('update:stStr', this.stStr + 1);
+            },
+        },
+        created() {
+        },
+    };
+</script>

+ 231 - 0
src/components/custom/MtaBusAudioPlayer.vue

@@ -0,0 +1,231 @@
+<template>
+    <div style="width:100%;text-align: center;" class="aplayer-box">
+        <aplayer
+                v-if="aplayerExist"
+                class="st-aplayer"
+                :autoplay="musicOptions.autoplay"
+                :controls="true"
+                :music="musicOptions"
+                @timeupdate="timeupdate"
+                @play="play"
+                @playing="playing"
+                @ended="ended"
+                :ref="aplayerRef"
+        >
+        </aplayer>
+    </div>
+</template>
+
+<script>
+    import Aplayer     from 'vue-aplayer';
+
+    Aplayer.disableVersionBadge = true;
+    import * as kejian from '@/api/peixun/kejian';
+    import * as commonUtils from '@/utils/common';
+
+    export default {
+        name:       'MtaBusAudioPlayer',
+        props:      {
+            options: {
+                type:     Object,
+                required: true,
+            },
+            checktimes: {
+                type:     Boolean,
+                default() {
+                    return true;
+                }
+            }
+        },
+        components: {
+            Aplayer,
+        },
+        data() {
+            return {
+                aplayerExist:     false,
+                musicOptions:     null,
+                aplayerRef:       'aplayerRef',
+                currentTime:      0,
+                currentPlaytimes: 0,
+            };
+        },
+        methods:    {
+            timeupdate(e) {
+                if (!this.musicOptions.dragable && this.checktimes) {
+                    // console.log(this.currentPlaytimes, this.musicOptions.playtimes);
+                    if (this.currentPlaytimes > this.musicOptions.playtimes && this.$refs[this.aplayerRef].$refs.audio.currentTime !== 0) {
+                        this.debounceWarning(this);
+                        this.$refs[this.aplayerRef].$refs.audio.pause();
+                        this.$refs[this.aplayerRef].$refs.audio.currentTime = 0;
+                        return;
+                    }
+                    // console.log(this.$refs[this.aplayerRef].$refs.audio.currentTime, this.currentTime);
+                    // 正常播放
+                    if (this.$refs[this.aplayerRef].$refs.audio.currentTime - this.currentTime < 0.6
+                        && this.$refs[this.aplayerRef].$refs.audio.currentTime - this.currentTime >= 0) {
+                        this.currentTime = this.$refs[this.aplayerRef].$refs.audio.currentTime;
+                    } else {
+                        this.$refs[this.aplayerRef].$refs.audio.currentTime = this.currentTime;
+                    }
+                } else {
+                    this.currentTime = this.$refs[this.aplayerRef].$refs.audio.currentTime;
+                }
+            },
+            play(e) {
+                if (this.currentTime < 0.6) {
+                    this.currentPlaytimes++;
+                    if (this.currentPlaytimes > this.musicOptions.playtimes) {
+                        return;
+                    }
+                    // save cache  userid + src
+                    this.$emit('updAudioPlaytimes', this.currentPlaytimes);
+                }
+
+            },
+            playing(e) {
+            },
+            ended(e) {
+                this.currentTime = 0;
+                commonUtils.saveCourseProgress(100, 100);
+                commonUtils.updateSectionProgress();
+            },
+            debounceWarning: _.debounce((that) => {
+                that.$message.warning('已超过最大播放次数');
+            }, 200),
+            initOption() {
+                this.musicOptions = {};
+                let defaultOption = {
+                    kjId:      null,
+                    title:     '无标题',
+                    artist:    '某艺术家',
+                    src:       null,
+                    pic:       '@/assets/images/default/audioImage.jpg',
+                    autoplay:  false,
+                    dragable:  null,
+                    playtimes: null,
+                };
+                this.musicOptions = Object.assign(defaultOption, this.options);
+                this.currentPlaytimes = this.musicOptions.curPlaytimes ? this.musicOptions.curPlaytimes : 0;
+            },
+            getMusicOptions() {
+                if (this.options.kjId) {
+                    this.aplayerExist = false;
+                    kejian.getKejian({
+                                         kjId: this.options.kjId,
+                                     }).then(res => {
+                        if (res.code === 0) {
+                            this.musicOptions = Object.assign(this.musicOptions, {
+                                title: res.data.name,
+                                src:   res.data.newUrl,
+                            });
+                            // console.log(this.musicOptions);
+                            this.aplayerExist = true;
+                        }
+                    });
+                } else {
+                    /*this.musicOptions = {
+                        title:    this.options.title,
+                        artist:   this.options.artist,
+                        src:      this.options.src,
+                        pic:      this.options.pic,
+                        autoplay: this.options.autoplay,
+                    };*/
+                    this.aplayerExist = true;
+                }
+            }
+        },
+        created() {
+            this.aplayerRef = Math.floor(Math.random() * 1000000);
+            this.initOption();
+        },
+        mounted() {
+            this.getMusicOptions();
+
+        },
+        watch: {
+            'options.kjId': {
+                handler(value, oValue) {
+                    this.getMusicOptions();
+                },
+            },
+        },
+    };
+</script>
+<style lang="scss" scoped>
+    .el-button {
+        font-size: 14px;
+    }
+
+    .fl {
+        display: block;
+        float: left;
+    }
+
+    .ml-30 {
+        margin-left: 30px;
+    }
+
+    .st-aplayer {
+        width: 320px;
+        margin: 0 auto;
+    }
+
+
+</style>
+<style lang="scss">
+    //音频
+    .aplayer-box{
+        .aplayer.st-aplayer {
+            overflow: visible;
+            .aplayer-body{
+                overflow: visible;
+                .aplayer-info{
+                    height: 30px;
+                    padding: 0 7px;
+                    overflow: visible;
+                    //音频名称
+                    .aplayer-music{
+                        display: none;
+                    }
+                    //音频控制条
+                    .aplayer-controller{
+                        padding: 6px 0;
+                    }
+                }
+            }
+            //音频图片
+            .aplayer-pic{
+                width: 30px;
+                height: 30px;
+                background: #fff!important;
+                //播放按钮
+                .aplayer-play{
+                    width: 18px;
+                    height: 18px;
+                    margin: 0 -12px -10px 0;
+                    .aplayer-icon-play{
+                        width: 14px;
+                        height: 14px;
+                        top: 2px;
+                        left: 3px;
+                    }
+                }
+                //暂停按钮
+                .aplayer-pause{
+                    width: 18px;
+                    height: 18px;
+                    bottom: 50%;
+                    right: 50%;
+                    margin: 0 -12px -10px 0;
+                    .aplayer-icon-pause{
+                        width: 14px;
+                        height: 14px;
+                        top: 2px;
+                        left: 2px;
+                    }
+                }
+            }
+
+        }
+    }
+</style>

+ 262 - 0
src/components/custom/MtaBusCoursePlayer.vue

@@ -0,0 +1,262 @@
+<template>
+    <div class="c-px-player-box">
+
+        <div class="play-board">
+            <perfect-scrollbar>
+                <div>
+                    <MtaBusVideoPlayer class="video-player" v-if="playBoardObj.type === 1 || playBoardObj.type === 7"
+                                       :options="playBoardObj.videoOptions" ref="mbVideoPlayer"></MtaBusVideoPlayer>
+                    <div class="div-mta-bus-audio-player" v-else-if="playBoardObj.type === 2">
+                        <MtaBusAudioPlayer :options="playBoardObj.audioOptions"
+                                           :checktimes="false"></MtaBusAudioPlayer>
+                    </div>
+
+                    <MtaBusPdfPlayer
+                            v-else-if="playBoardObj.type === 3 || playBoardObj.type === 4 || playBoardObj.type === 5 || playBoardObj.type === 6"
+                            :options="playBoardObj.pdfOptions" :loaded.sync="pdfloaded"></MtaBusPdfPlayer>
+
+                    <iframe v-if="playBoardObj.type === 8 && playBoardObj.ibl !== null && playBoardObj.ibl === false"
+                            :src="playBoardObj.iframeOptions.oldUrl"
+                            allowfullscreen='true'></iframe>
+                    <div v-if="playBoardObj.type === 8 && playBoardObj.ibl !== null && playBoardObj.ibl === true">
+                        <!--<a :href="playBoardObj.iframeOptions.oldUrl" target="_blank">请点击打开</a>-->
+                        <div style="margin-left: 300px;margin-top: 200px">三方视频未显示,请手动点击右侧菜单</div>
+
+                    </div>
+                </div>
+            </perfect-scrollbar>
+        </div>
+        <div class="menu-list">
+            <perfect-scrollbar class="perfect-box">
+                <!--<ul class="menu-list-ul">
+                    <li :class="{'active':item.active}" v-for="item in playList" :key="item.kjId"
+                        @click="genPlayboard(item)">
+                        <span class="courseware-title" :title="item.name"><i></i>{{ item.name }}</span>
+                        <span class="courseware-data">{{ formatSecondsToCnhms(item.duration, true) }}</span>
+                    </li>
+                </ul>-->
+                <div v-for="chapter in kecheng" :key="chapter.index">
+                    <div class="menu-chapter">{{chapter.value}}</div>
+                    <div v-for="section in chapter.children" :key="section.index">
+                        <div class="menu-section">&nbsp;&nbsp;{{section.value}}</div>
+                        <ul class="menu-list-ul">
+                            <li :class="{'active':kejian.active}" v-for="kejian in section.kejianArr" :key="kejian.kjId"
+                                @click="genPlayboard(kejian)">
+                                <span class="courseware-title" :title="kejian.name"><i></i>{{ kejian.name }}</span>
+                                <span class="courseware-data">{{ formatSecondsToCnhms(kejian.duration, true) }}</span>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </perfect-scrollbar>
+        </div>
+        <div class="menu-icon" @click="iconClivk">
+            <i :class="{ 'el-icon-tickets': !menuFlag}"></i>
+        </div>
+    </div>
+</template>
+
+<script>
+    import MtaBusVideoPlayer    from '@/components/custom/MtaBusVideoPlayer.vue';
+    import MtaBusAudioPlayer    from '@/components/custom/MtaBusAudioPlayer.vue';
+    import MtaBusPdfPlayer      from '@/components/custom/MtaBusPdfPlayer.vue';
+    import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
+    import * as commonUtils     from '@/utils/common';
+    import * as kechengApi      from '@/api/client/peixun/kecheng';
+
+    export default {
+        name:       'MtaBusSectionPlayer',
+        props:      {
+            kecheng: Array,
+        },
+        components: {
+            MtaBusVideoPlayer,
+            MtaBusAudioPlayer,
+            MtaBusPdfPlayer,
+            PerfectScrollbar,
+        },
+        data() {
+            return {
+                playBoardObj: {
+                    type:            null,
+                    pdfOptions:      null,
+                    tempKjId:        null,
+                    tempKjtype:      null,
+                    iframeBlackList: ['youku.com'],
+                    ibl:             null,
+                },
+                menuFlag:     true,
+                scrollCnt:    0,
+                pdfloaded:    true,
+            };
+        },
+        methods:    {
+            formatSecondsToCnhms: commonUtils.formatSecondsToCnhms,
+            genPlayboard(item) {
+                if (this.tempKjId === item.kjId) {
+                    return;
+                }
+                if (this.pdfloaded !== true) {
+                    this.$message.warning('pdf 加载中');
+                    return;
+                }
+
+                // set active & scrollCnt
+                let scrollCnt = 0;
+                let iscnt = true;
+                for (const chapter of this.kecheng) {
+                    iscnt ? scrollCnt++ : scrollCnt;
+                    for (const section of chapter.children) {
+                        iscnt ? scrollCnt++ : scrollCnt;
+                        for (const kejian of section.kejianArr) {
+                            if (kejian.kjId === item.kjId) {
+                                kejian.active = true;
+                                iscnt = false;
+                            } else {
+                                kejian.active = false;
+                            }
+                            iscnt ? scrollCnt++ : scrollCnt;
+                        }
+                    }
+                }
+                this.scrollCnt = scrollCnt;
+
+                let sectionPage = commonUtils.getCache('CourseInfo', 'sectionPage');
+                if (sectionPage) {
+                    sectionPage.kjId = item.kjId;
+                } else {
+                    sectionPage = {
+                        kjId: item.kjId,
+                    };
+                }
+                commonUtils.saveCache('CourseInfo', 'sectionPage', sectionPage);
+
+                // 上一个是视频
+                if (this.tempKjtype === 1 || this.tempKjtype === 7) {
+                    // hide dialog
+                    this.$refs.mbVideoPlayer.hidDialog();
+                }
+
+                this.playBoardObj.type = item.type;
+                // console.log(item);
+
+                this.$set(this.playBoardObj, 'videoOptions', {
+                    type:             2,
+                    progressFlag:     item.progressFlag,
+                    kjId:             item.kjId,
+                    questionDoneShow: true,
+                });
+                this.$set(this.playBoardObj, 'audioOptions', {
+                    kjId: item.kjId,
+                });
+                this.$set(this.playBoardObj, 'pdfOptions', {
+                    kjId: item.kjId,
+                });
+                this.$set(this.playBoardObj, 'iframeOptions', {
+                    kjId:   item.kjId,
+                    oldUrl: item.oldUrl,
+                });
+                this.playBoardObj.ibl = this.inIframeBackList(item.oldUrl);
+
+                if (this.playBoardObj.ibl) {
+                    window.open(item.oldUrl, '_blank', 'width=950,height=550,left=300,top=250px');
+                }
+
+
+                this.tempKjId = item.kjId;
+                this.tempKjtype = item.type;
+
+                this.$nextTick(() => {
+                    // 不完美逻辑:当点击视频 音频以外的资源列表时 算100%完成
+                    if (item.type === 3
+                        || item.type === 4
+                        || item.type === 5
+                        || item.type === 6
+                        || item.type === 8
+                    ) {
+                        commonUtils.saveCourseProgress(100, 100);
+                        commonUtils.updateSectionProgress();
+                    }
+
+                    /*if (item.type === 7) {
+                        window.open(this.playBoardObj.iframeOptions.oldUrl);
+                    }*/
+
+                    // 计算流量
+                    kechengApi.kcCRecord({
+                                             kcId: sectionPage.kcId,
+                                             kjId: item.kjId,
+                                         }).then(res => {
+                        if (res.code === 0) {
+                            // console.log('流量已被记录:kcId|' + sectionPage.kcId + ' ==== kjId|' + item.kjId);
+                        }
+                    });
+                });
+
+            },
+            iconClivk() {
+                const menuList = document.querySelector('.menu-list');
+                const menuListUl = document.querySelector('.menu-list-ul');
+                const menuIcon = document.querySelector('.menu-icon');
+                const playBoard = document.querySelector('.play-board');
+
+                if (this.menuFlag) {
+                    menuList.style.width = '0';
+                    menuListUl.style.display = 'none';
+                    menuIcon.style.right = '1px';
+                    menuIcon.style.borderRadius = '20px 0 0 20px';
+                    playBoard.style.borderRadius = '8px';
+                    this.menuFlag = false;
+                } else {
+                    menuList.style.width = '340px';
+                    menuListUl.style.display = 'block';
+                    menuIcon.style.borderRadius = '0 20px 20px 0';
+                    menuIcon.style.right = '320px';
+                    playBoard.style.borderRadius = '8px 0 0 8px';
+                    this.menuFlag = true;
+                }
+
+            },
+            inIframeBackList(url) {
+                for (const iframeBlack of this.playBoardObj.iframeBlackList) {
+                    if (url.indexOf(iframeBlack) > -1) {
+                        return true;
+                    }
+                }
+                return false;
+            },
+        },
+        created() {
+            console.log(this.kecheng);
+        },
+        mounted() {
+            function getItemByKecheng(kecheng, sectionPage) {
+                let kejianArr = kecheng[sectionPage.iChapter].children[sectionPage.iSection].kejianArr;
+                for (const kejian of kejianArr) {
+                    if (kejian.kjId === sectionPage['kjId']) {
+                        return kejian;
+                    }
+                }
+                return null;
+            }
+
+            // no cache, default play first
+            let sectionPage = commonUtils.getCache('CourseInfo', 'sectionPage');
+            if (!sectionPage || !sectionPage['kjId']) {
+                let kejian = this.kecheng[0].children[0].kejianArr[0];
+                this.genPlayboard(kejian);
+            } else {
+                let kejian = getItemByKecheng(this.kecheng, sectionPage);
+                if (kejian) {
+                    this.genPlayboard(kejian);
+                }
+
+            }
+        },
+    };
+</script>
+<style lang="scss" scoped>
+    .div-mta-bus-audio-player {
+        margin-top: 295px;
+    }
+</style>

+ 243 - 0
src/components/custom/MtaBusPdfPlayer.vue

@@ -0,0 +1,243 @@
+<template>
+    <div ref="stPdf">
+        <el-button
+                class="icon-qdf-screen"
+                :class="fullscreenclass"
+                type="primary"
+                circle
+                title="全屏显示"
+                @click="fullscreenToggle"
+        >
+        </el-button>
+        <el-button
+                v-if="pdfBtnShow"
+                class="icon-qdf-screen"
+                :class="gonativeclass"
+                type="primary"
+                circle
+                title="浏览器中显示"
+                @click="goNative"
+        >
+        </el-button>
+        <fullscreen
+                class="pdf-fullscreen"
+                ref="stFullscreen7789"
+                @change="fullscreenChange"
+                v-infinite-scroll="loadPdf"
+                :infinite-scroll-immediate="false"
+                style="overflow:auto"
+        >
+            <pdf v-if="pdfShow"
+                 v-for="i in numPages"
+                 :key="i"
+                 :src="src"
+                 :page="i"
+                 style="display: inline-block; width: 100%"
+                 @loaded="stLoaded"
+            ></pdf>
+        </fullscreen>
+
+
+
+    </div>
+</template>
+
+<script>
+    import pdf from 'vue-pdf'
+    import * as kejian from '@/api/peixun/kejian';
+
+    export default {
+        name:       'MtaBusPdfPlayer',
+        props:    {
+            options: {
+                type: Object,
+                default() {
+                    return {};
+                },
+            },
+            loaded: {
+                type: Boolean,
+                default() {
+                    return true;
+                },
+            },
+        },
+        components: {
+            pdf,
+        },
+        data() {
+            return {
+                url: '',
+                src: null,
+                numPages: undefined,
+                totalPages: undefined,
+                pdfShow: false,
+                fullscreenclass: '',
+                gonativeclass: '',
+                stProgress: 0,
+                pageRendered: false,
+                pdfBtnShow: false,
+                pdfLoadCnt: 0,
+            };
+        },
+        methods:    {
+            initPdf() {
+                if (window.loading) {
+                    window.loading.close();
+                }
+                this.pdfShow = false;
+                this.pdfLoadCnt = 0;
+                this.$emit('update:loaded', false);
+                window.loading = this.$loading({
+                                                  background: "rgba(0, 0, 0, 0.7)",
+                                                  target:      '.pdf-fullscreen',                    // 需要遮罩的区域
+                                                  body:        false,
+                                              });
+                kejian.getKejian({
+                                     kjId: this.options.kjId
+                                 }).then(res=> {
+                    if (res.code === 0) {
+
+                        this.url = res.data.newUrl;
+                        this.src = pdf.createLoadingTask(res.data.newUrl);
+                        /*//大于50M显示按钮
+                        if(res.data.size > 52428800){
+                            this.pdfBtnShow = true;
+                        }else{
+                            this.pdfBtnShow = false;
+                        }*/
+                        this.src.promise.then(res => {
+                            window.loading.close();
+                            // console.log(res);
+                            if (res.numPages > 100) {
+                                this.numPages = 100;
+                                this.totalPages = res.numPages;
+                            } else {
+                                this.numPages = res.numPages;
+                                this.totalPages = res.numPages;
+                            }
+                            //大于200页显示按钮
+                            if (res.numPages > 200) {
+                                this.pdfBtnShow = true;
+                            }else {
+                                this.pdfBtnShow = false;
+                            }
+                            this.pageRendered = true;
+                            this.pdfShow = true;
+                        });
+                    }
+                });
+            },
+            loadPdf() {
+                // console.log('loadPdf');
+                if (this.pageRendered && this.numPages < this.totalPages) {
+                    if (this.totalPages - this.numPages > 100) {
+                        this.numPages = this.numPages + 100;
+                    } else {
+                        this.numPages = this.totalPages;
+                    }
+                }
+            },
+            fullscreenChange(fullscreen) {
+                this.fullscreen = fullscreen;
+            },
+            fullscreenToggle() {
+                this.$refs['stFullscreen7789'].toggle(); // recommended
+            },
+            goNative() {
+                window.open(this.url);
+            },
+            stLoaded() {
+                this.pdfLoadCnt ++;
+                if (this.pdfLoadCnt > this.totalPages - 2 || this.pdfLoadCnt > 99) {
+                    setTimeout(()=>{
+                        this.$emit('update:loaded', true);
+                    }, 1000);
+
+                }
+            },
+        },
+        mounted() {
+            if (this.options.type === 1) {
+                this.fullscreenclass = 'fullscreenclassa'
+                this.gonativeclass = 'gonativeclassa'
+            } else {
+                this.fullscreenclass = 'fullscreenclassc'
+                this.gonativeclass = 'gonativeclassc'
+            }
+            this.initPdf();
+        },
+        watch: {
+            options:            {
+                handler(value, oValue) {
+                    this.initPdf();
+                },
+            },
+        }
+    };
+</script>
+<style lang="scss" scoped>
+    .el-button {
+        font-size: 14px;
+    }
+
+    .fl {
+        display: block;
+        float: left;
+    }
+
+    .ml-30 {
+        margin-left: 30px;
+    }
+
+    .st-aplayer {
+        width: 320px;
+        margin: 0 auto;
+    }
+
+    .icon-qdf-screen {
+        position: absolute;
+        right: 30px;
+        background-color: transparent;
+        border: transparent;
+    }
+    .icon-qdf-screen:hover{
+        background-color: transparent;
+        border: transparent;
+    }
+
+    .fullscreenclassa {
+        top: 70px;
+        z-index: 66;
+    }
+    .gonativeclassa {
+        top: 130px;
+        z-index: 66;
+    }
+
+    .fullscreenclassc {
+        top: 30px;
+        z-index: 3;
+        background-image: url("../../assets/images/icons/pdf-full-screen.svg");
+        width: 32px;
+        height: 32px;
+        background-repeat: no-repeat;
+        background-position: center;
+    }
+    .gonativeclassc {
+        top: 90px;
+        z-index: 3;
+        background-image: url("../../assets/images/icons/pdf-jump.svg");
+        width: 32px;
+        height: 32px;
+        background-repeat: no-repeat;
+        background-position: center;
+    }
+    .pdf-fullscreen {
+        height: calc(100vh - 322px);
+    }
+    .go-native {
+        top: 120px;
+        z-index: 3;
+    }
+</style>

+ 180 - 0
src/components/custom/MtaBusVideoPlayer.vue

@@ -0,0 +1,180 @@
+<template>
+    <div>
+        <video-player
+                :options="videoOptions"
+                @sectionCompleted="sectionCompleted"
+                class="video-player"
+                ref="stVideoPlayer"
+                v-if="videoOptions.display"
+        >
+        </video-player>
+    </div>
+</template>
+
+<script>
+    import VideoPlayer      from '@/components/custom/VideoPlayer.vue';
+    import * as kejian      from '@/api/peixun/kejian';
+    import * as commonUtils from '@/utils/common';
+
+    export default {
+        name:       'MtaBusVideoPlayer',
+        components: {
+            VideoPlayer,
+        },
+        data() {
+            return {
+                videoOptions: {
+                    type:                  null,                    // 共用逻辑:1:预览 2:c端播放
+                    autoplay:              false,                   // 共用逻辑:自动播放
+                    controls:              true,                    // 共用逻辑:是否使播放器可控 为true
+                    sources:               [],                      // 共用逻辑:当前资源列表 默认支持一组相同资源的不同编码格式
+                    progressControlEnable: true,                    // 共用逻辑:进度条可操作 可用true/禁用false
+                    // theme:                 'city',               // 共用逻辑:主题枚举 'default', 'city', 'fantasy', 'forest', 'sea'
+                    height:                '500px',                 // 共用逻辑
+                    playlist:              [],                      // 共用逻辑
+                    display:               false,                   // 共用逻辑,
+                    questionDoneShow:      null,                    // 共用逻辑: [Boolbean] 拖拽后是否显示已完成的题
+                    drugControl:           null,                    // 共用逻辑: [String] none 不控制 forwardOnly 只能拖至已看过的
+                    hasPlaylist:           false,                   // 共用逻辑: 视频右侧条,现在默认不要
+                    playIndex:             0,                       // 不共用逻辑 可删除
+                    shitiList:             null,                    // 不共用逻辑 被插入的试题list
+                },
+            };
+        },
+        props:      {
+            options: {
+                type: Object,
+                default() {
+                    return {};
+                },
+            },
+        },
+        methods:    {
+            fileChange(event) {
+
+            },
+            videoOptionsInit() {
+                this.videoOptions.questionDoneShow = true;  // 预览和使用默认都是拖拽后显示已完成的题
+                this.videoOptions.type = this.options.type;
+
+                // 共用逻辑: none 不控制 forwardOnly 只能拖至已看过的
+                if (this.videoOptions.type === 1) { // 预览
+                    this.videoOptions.drugControl = 'none';
+                } else {    // c端播放
+                    if (this.videoOptions.shitiList && this.videoOptions.shitiList.length > 0) {  // 有题
+                        this.videoOptions.drugControl = 'forwardOnly';
+                    } else {    // 没有题 已后台状态为准
+                        if (this.options.progressFlag === 0) {
+                            this.videoOptions.drugControl = 'none';
+                        } else if (this.options.progressFlag === 1) {
+                            this.videoOptions.drugControl = 'forwardOnly';
+                        } else {
+                            this.videoOptions.drugControl = 'none';
+                        }
+                    }
+                }
+
+            },
+            videoPlayerShow() {
+                kejian.getKejian({
+                                     kjId: this.options.kjId,
+                                 }).then(res => {
+                    if (res.code === 0) {
+                        let videoItem = {
+                            name:      res.data.name,
+                            duration:  res.data.duration,  // 该视频播放时长
+                            sources:   [
+                                {
+                                    src:  res.data.newUrl,
+                                    type: 'video/mp4',
+                                },
+                            ],
+                            thumbnail: [
+                                {
+                                    srcset: res.data.cover,
+                                    type:   'image/jpeg',
+                                    style:  'width:180px;',
+                                    // media: '(min-width: 367px;)'
+                                },
+                            ],
+                            // poster:      'http://media.w3.org/2010/05/sintel/poster.png',
+                        };
+                        // console.log(res.data);
+                        this.videoOptions.shitiList = res.data.shitiList;
+                        this.videoOptionsInit();
+                        this.videoOptions.display = true;
+                        this.$nextTick(() => {
+                            if (this.videoOptions.hasPlaylist) {
+                                this.videoOptions.playlist.push(videoItem);
+                            } else {
+                                this.videoOptions.sources = videoItem.sources;
+                            }
+                        });
+                    }
+                });
+            },
+            hidDialog() {
+                this.$refs.stVideoPlayer.hidDialog();
+            },
+            sectionCompleted(payload) {
+                commonUtils.updateSectionProgress();
+            },
+        },
+        mounted() {
+            this.videoPlayerShow();
+        },
+
+        watch: {
+            options: {
+                handler(value, oValue) {
+                    // console.log(value);
+                    this.videoPlayerShow();
+                },
+                deep: true,
+            },
+        },
+    };
+</script>
+<style lang="scss">
+    .el-button {
+        font-size: 14px;
+    }
+
+    .fl {
+        display: block;
+        float: left;
+    }
+
+    .ml-30 {
+        margin-left: 30px;
+    }
+
+    .answer-dialog,.success-answer-dialog {
+        text-align: center;
+        line-height: 40px;
+        position: absolute;
+        z-index: 9999;
+        left: 46%;
+        top: 10%;
+        background-color: #fef0f0;
+        border-color: #fde2e2;
+        color: #F56C6C;
+        padding: 0 20px;
+        i{
+            font-size: 19px;
+            display: inline-block;
+            vertical-align: middle;
+        }
+        >div{
+            display: inline-block;
+            font-size: 14px;
+            vertical-align: middle;
+        }
+    }
+    //成功弹窗
+    .success-answer-dialog{
+        background-color: #f0f9eb;
+        border-color: #e1f3d8;
+        color: #67C23A;
+    }
+</style>

+ 151 - 0
src/components/custom/MtaBusVideoPlayer_bk.vue

@@ -0,0 +1,151 @@
+<template>
+    <div>
+        <!--<input type="file" multiple @change="fileChange" value="上传视频"/>
+        <el-button @click="createTracks">createTracks</el-button>
+        <el-button @click="createButton">createButton</el-button>-->
+        <!--<el-button @click="setSrc">setSrc</el-button>-->
+        <video-player :options="videoOptions" class="video-player" ref="stVideoPlayer" v-if="videoOptions.display"/>
+    </div>
+</template>
+
+<script>
+    import VideoPlayer from '@/components/custom/VideoPlayer.vue';
+    import * as kejian from '@/api/peixun/kejian';
+
+    export default {
+        name:       'MtaBusVideoPlayer',
+        components: {
+            VideoPlayer,
+        },
+        data() {
+            return {
+                videoOptions: {
+                    autoplay:              false,                   // 共用逻辑:自动播放
+                    controls:              true,                    // 共用逻辑:是否使播放器可控 为true
+                    sources:               [],                      // 共用逻辑:当前资源列表 默认支持一组相同资源的不同编码格式
+                    progressControlEnable: true,                    // 共用逻辑:进度条可操作 可用true/禁用false
+                    // theme:                 'city',               // 共用逻辑:主题枚举 'default', 'city', 'fantasy', 'forest', 'sea'
+                    height:                '500px',                 // 共用逻辑
+                    playlist:               [],                     // 共用逻辑
+                    display:                false,                  // 共用逻辑,
+                    playIndex:             0,                       // 不共用逻辑 可删除
+                    shitiList:              null,                   // 不共用逻辑 被插入的试题list
+                },
+            };
+        },
+        props:    {
+            options: {
+                kjId: String,
+                default() {
+                    return '';
+                },
+
+            },
+        },
+        methods:    {
+            fileChange(event) {
+                /*var files = event.target.files;
+                if (files && files.length > 0) {
+                    for (const file of files) {
+                        /!*name: "129个美女类无印小视频素材第二季 (100).mp4"
+    lastModified: 1577773604272
+    lastModifiedDate: Tue Dec 31 2019 14:26:44 GMT+0800 (中国标准时间) {}
+    webkitRelativePath: ""
+    size: 1325755
+    type: "video/mp4"*!/
+                    }
+                }*/
+            },
+            createTracks() {
+                this.$refs.stVideoPlayer.createTracks();
+            },
+            createButton() {
+                this.$refs.stVideoPlayer.createButton();
+            },
+            /*setSrc() {
+                this.videoOptions.playIndex++;
+                if (this.videoOptions.playIndex >= 5) {
+                    this.videoOptions.playIndex = 0;
+                }
+                let videos = [
+                    {
+                        src:  '/videos/1.mp4',
+                        type: 'video/mp4',
+                    },
+                    {
+                        src:  'http://192.168.0.179/v/542.mp4',
+                        type: 'video/mp4',
+                    },
+                    {
+                        src:  '/videos/3.mp4',
+                        type: 'video/mp4',
+                    },
+
+                    {
+                        src:  'http://mtafa.com/da6afd4724c24b47970d177f4a133aad/5c88b72c09ee445c97cbbb704c803870-c9b683dbc9af6e1a71cd545340fbbe2b-ld.mp4',
+                        type: 'video/mp4',
+                    },
+                    {
+                        src:  '/videos/2.mp4',
+                        type: 'video/mp4',
+                    },
+                ];
+                this.$refs.stVideoPlayer.setSrc(videos[this.videoOptions.playIndex]);
+            },*/
+        },
+        mounted() {
+            kejian.getKejian({
+                kjId: this.options.kjId
+            }).then(res=>{
+                if (res.code === 0) {
+                    let videoItem = {
+                        name:        res.data.name,
+                        duration:    res.data.duration,  // 该视频播放时长
+                        sources:     [
+                            {
+                                src:  res.data.newUrl,
+                                type: 'video/mp4',
+                            },
+                        ],
+                        thumbnail:   [
+                            {
+                                srcset: res.data.cover,
+                                type:   'image/jpeg',
+                                style:  'width:180px;',
+                                // media: '(min-width: 367px;)'
+                            },
+                        ],
+                        // poster:      'http://media.w3.org/2010/05/sintel/poster.png',
+                    };
+                    // console.log(res.data);
+                    this.videoOptions.shitiList = res.data.shitiList;
+                    this.videoOptions.display = true;
+                    this.$nextTick(()=>{
+                        this.videoOptions.playlist.push(videoItem);
+                    });
+                }
+
+            });
+
+
+        },
+    };
+</script>
+<style lang="scss" scoped>
+    .el-button {
+        font-size: 14px;
+    }
+
+    .fl {
+        display: block;
+        float: left;
+    }
+
+    .ml-30 {
+        margin-left: 30px;
+    }
+
+    /*.video-player {
+        margin: 0 auto;
+    }*/
+</style>

+ 75 - 0
src/components/custom/StDialog.vue

@@ -0,0 +1,75 @@
+<template>
+    <div>
+        <el-dialog
+                class="BatchProcessDL"
+                title=""
+                :visible.sync="visible"
+                width="400px"
+                :close-on-click-modal="false"
+                :modal="false"
+                :modal-append-to-body="modalAppendToBody"
+                center>
+            <!--<div class="be_careful" v-if="dType === 'warning'"><i class="warn-img"></i><p>{{dMessage}}</p></div>-->
+            <div class="img-box">
+                <i class="warn-img" v-if="dType === 'warning'"></i>
+                <i class="delete-img" v-if="dType === 'delete'"></i>
+            </div>
+            <p>{{dMessage}}</p>
+            <span slot="footer" class="dialog-footer">
+              <el-button @click="visible = false">取 消</el-button>
+              <el-button type="primary" @click="comfirm()">确 定</el-button>
+            </span>
+        </el-dialog>
+    </div>
+</template>
+<script>
+    export default {
+        props:      {
+            dVisible: {
+                type: Boolean,
+            },
+            dType:    {
+                type: String,   // error/warning
+            },
+            dMessage: {
+                type: String,   // error/warning
+            },
+            modalAppendToBody:   {
+                type:    Boolean,
+                require: true,
+                default: false,
+            },
+        },
+        components: {},
+        data() {
+            return {
+                visible:         false,
+                uploadfileParam: {},
+            };
+        },
+        methods: {
+            init(type, message) {
+                this.dType = type;
+                this.dMessage = message;
+                this.show();
+            },
+            show() {
+                this.visible = true;
+            },
+            comfirm() {
+                this.visible = false;
+                this.$emit("comfirmCallback");
+            }
+        },
+        mounted() {
+        },
+        watch:{
+            dVisible(val) {
+                this.visible = val;
+            },
+            visible(val) {
+                this.$emit("update:dVisible", val);
+            }
+        }
+    };
+</script>

+ 149 - 0
src/components/custom/StFormulaDialog.vue

@@ -0,0 +1,149 @@
+<template>
+    <div>
+        <el-dialog
+                title="公式"
+                class="response-middle-dialog"
+                :visible.sync="formula.dialogVisible"
+                :close-on-click-modal="false"
+                @close="formulaDialogClose"
+                center>
+            <div class="kf-editor" ref="kfEditorContainer">
+                <!--<div class="tips" ref="tips">
+                    sorry! Beta版本仅支持IE9及以上版本的浏览器,正式版本将会支持低版本浏览器,谢谢您的关注!
+                </div>-->
+            </div>
+            <span slot="footer" class="dialog-footer">
+                <el-button type="primary" size="small" @click="passValue">确 定</el-button>
+            </span>
+        </el-dialog>
+    </div>
+</template>
+<script>
+    import store from '@/store';
+
+    export default {
+        components: {},
+        data() {
+            return {
+                formula: {
+                    dialogVisible:  false,
+                    imgsrc:         '',
+                    latex:          '',
+                    type:           '',
+                    quillEditorRef: '',
+                },
+            };
+        },
+
+        methods: {
+            formulaDialogClose() {
+                // console.dir(window.kfe);
+                // window.kfe.initResource();
+                // delete window.kfe;
+            },
+            passValue() {
+                kfe.execCommand('get.image.data', (data) => {
+                    let latex = kfe.execCommand('get.source');
+
+                    this.formula.dialogVisible = false;
+                    this.formula.latex = latex;
+                    this.formula.imgsrc = data.img;
+
+                    store.commit({
+                                     type:      'setFormula'
+                                     , formula: this.formula,
+                                 });
+                });
+            },
+            initFormula() {
+                if (document.body.addEventListener) {
+
+                    let that = this;
+
+                    // $( "#tips").html('<div id="loading"><img src="/external/kityformula/kityformula/loading.gif" alt="loading" /><p>正在加载,请耐心等待...</p></div>' );
+                    // console.dir(this.$refs.kfEditorContainer);
+
+                    let renderFormula = '';
+                    if (this.formula.latex && this.formula.latex !== '') {
+                        renderFormula = this.formula.latex;
+                    } else {
+                        renderFormula = '\\placeholder';
+                    }
+
+                    this.$nextTick(() => {
+                        if (!window.kfe) {
+                            var factory = kf.EditorFactory.create(this.$refs.kfEditorContainer, {
+                                /*autoresize: false,
+                                    fontsize: 30,
+                                    padding: [ 20, 50 ]*/
+                                render:   {
+                                    fontsize: 20,
+                                    padding:  [0],
+                                },
+                                resource: {
+                                    path: '../../external/kityformula/kityformula/resource/',
+                                },
+                            });
+
+                            // console.dir(factory);
+
+                            factory.ready(function (KFEditor) {
+                                // console.log('factory.ready!!');
+                                // $( "#tips").remove();
+
+                                // this指向KFEditor
+                                /*var rng = editor.selection.getRange(),
+                                    img = rng.getClosedNode(),
+                                    imgLatex = img && $(img).attr('data-latex');
+
+                                this.execCommand( "render", imgLatex || "\\placeholder" );
+                                this.execCommand( "focus" );
+
+                                window.kfe = this;*/
+                                this.execCommand('render', renderFormula);
+                                this.execCommand('focus');
+                                window.kfe = this;
+                            });
+                        } else {
+                            // console.log(renderFormula);
+                            kfe.execCommand('render', renderFormula);
+                            // kfe.execCommand('focus'); does`t work?
+                        }
+                    });
+
+
+                    /*dialog.onok = function(){
+                        kfe.execCommand('get.image.data', function(data){
+                            var latex = kfe.execCommand('get.source');
+                            console.log(latex);
+                            // editor.execCommand('inserthtml', '<img class="kfformula" src="'+ data.img +'" data-latex="' + latex + '" />');
+                            // dialog.close();
+                        });
+
+                        return false;
+                    }*/
+
+                } else {
+                    /*$( "#tips").css( "color", "black" );
+                    $( "#tips").css( "padding", "10px" );*/
+                }
+            },
+        },
+        mounted() {
+
+        },
+        watch:   {
+            '$store.state.formula': {
+                handler(newVal, oldVal) {
+                    // console.log('in store.state.formula');
+                    // console.log(newVal);
+                    this.formula = newVal;
+                    if (this.formula.dialogVisible === true) {
+                        this.initFormula();
+                    }
+                },
+                deep: true,
+            },
+        },
+    };
+</script>

+ 503 - 0
src/components/custom/VideoPlayer.vue

@@ -0,0 +1,503 @@
+<template>
+    <div class="video-container">
+        <div class="video-main">
+            <video class="video-js vjs-theme-city" id="my-player" preload="auto" ref="videoPlayer1"></video>
+        </div>
+        <div class="vjs-playlist" v-if="clonedOptions.hasPlaylist">
+        </div>
+        <div id="wxyDialog" style="visibility: hidden; position: absolute;">
+            <div class="dl-shiti">
+                <mta-single-choice
+                        :getShowModel="false"
+                        :questionData="shiti"
+                        @reply="replyCallback"
+                        ref="shiti1"
+                        v-if="shiti.stTypeId === 1"
+                ></mta-single-choice>
+                <mta-multiple-choice
+                        :getShowModel="false"
+                        :questionData="shiti"
+                        @reply="replyCallback"
+                        ref="shiti2"
+                        v-if="shiti.stTypeId === 2"
+                ></mta-multiple-choice>
+                <mta-true-or-false
+                        :getShowModel="false"
+                        :questionData="shiti"
+                        @reply="replyCallback"
+                        ref="shiti3"
+                        v-if="shiti.stTypeId === 3"
+                ></mta-true-or-false>
+                <mta-gap-filling
+                        :getShowModel="false"
+                        :questionData="shiti"
+                        @reply="replyCallback"
+                        ref="shiti4"
+                        v-if="shiti.stTypeId === 4"
+                ></mta-gap-filling>
+                <mta-read-the-topic
+                        :getShowModel="false"
+                        :questionData="shiti"
+                        @reply="replyCallback"
+                        ref="shiti5"
+                        v-if="shiti.stTypeId === 5"
+                ></mta-read-the-topic>
+            </div>
+
+            <div class="dl-btn">
+                <el-button @click="isReplyRight" size="small" type="primary">确定</el-button>
+            </div>
+            <div class="answer-dialog" v-show="answerDialogVisible">
+                <i class="el-message__icon el-icon-error"></i>
+                <div>答案不正确</div>
+            </div>
+            <!--<el-dialog
+                    title="提示"
+                    class="response-tip-dialog"
+                    :visible.sync="answerDialogVisible"
+                    :append-to-body="options.type === 1"
+                    center>
+                <div style="padding-bottom: 20px">答案不正确</div>
+            </el-dialog>-->
+        </div>
+
+    </div>
+</template>
+
+<script>
+    import 'video.js/dist/video-js.css';
+    import '@/assets/css/video/city.css';
+    import 'videojs-playlist-ui/dist/videojs-playlist-ui.vertical.css';
+
+    import videojs    from 'video.js';
+    import playlist   from 'videojs-playlist';
+    import playlistUi from 'videojs-playlist-ui';
+    import {
+        isObject, clone, getZhcnOption,
+        saveCourseProgress, updateSectionProgress,
+        getCourseProgress,
+    }                 from '@/utils/common';
+
+    import MtaSingleChoice   from '@/components/client/QuestionsForCuoti/SingleChoice.vue';
+    import MtaMultipleChoice from '@/components/client/QuestionsForCuoti/MultipleChoice.vue';
+    import MtaTrueOrFalse    from '@/components/client/QuestionsForCuoti/TrueOrFalse.vue';
+    import MtaGapFilling     from '@/components/client/QuestionsForCuoti/GapFilling.vue';
+    import MtaReadTheTopic   from '@/components/client/QuestionsForCuoti/ReadTheTopic.vue';
+
+    export default {
+        name:       'VideoPlayer',
+        props:      {
+            options: {
+                type: Object,
+                default() {
+                    return {};
+                },
+
+            },
+        },
+        computed:   {},
+        components: {
+            MtaSingleChoice,
+            MtaMultipleChoice,
+            MtaTrueOrFalse,
+            MtaGapFilling,
+            MtaReadTheTopic,
+        },
+        data() {
+            return {
+                player:              null,
+                clonedOptions:       {},
+                modalDialog:         null,
+                shiti:               {},
+                shitiIndex:          null,
+                shitiList:           null,
+                shitiTimeArr:        null,
+                shitiTimeDoneArr:    [],
+                doneTempFlg:         false,
+                dialogDisplay:       false,
+                lastOpenTime:        0,
+                preTimeupdateTime:   0,
+                maxPlayTime:         0,
+                maxPlayTimeRecord:   0,
+                hasInitPlayer:       false,
+                duration:            0,
+                answerDialogVisible: false,
+            };
+        },
+        mounted() {
+            this.initShiti();
+            this.setVideoZhCN();
+            videojs.hook('beforesetup', function (videoEl, options) {
+                options.language = 'zh-CN';
+                options.userActions = {};
+                options.userActions.hotkeys = {};
+                options.userActions.hotkeys.playPauseKey = function (e) {
+                    videojs.log(e);
+                };
+                return options;
+            });
+            if (this.clonedOptions.hasPlaylist) {
+                videojs.registerPlugin('playlist', playlist);
+                videojs.registerPlugin('playlistUi', playlistUi);
+            }
+        },
+        methods:    {
+            displayQuestionByCurrentTime() {
+                const curTime = this.player.currentTime();
+                let curSecInt = parseInt(curTime);
+                if (this.shitiTimeArr) {
+                    if (this.shitiTimeArr.indexOf(curSecInt) > -1
+                        && this.dialogDisplay === false) {
+                        if (this.clonedOptions.questionDoneShow) {
+                            this.player.pause();
+                            this.callShiti(this.shitiTimeArr.indexOf(curSecInt));
+                        } else {
+                            if (this.shitiTimeDoneArr.indexOf(curSecInt) === -1) {
+                                this.player.pause();
+                                this.callShiti(this.shitiTimeArr.indexOf(curSecInt));
+                            }
+                        }
+
+                    }
+                }
+
+            },
+            setVideoZhCN() {
+                videojs.addLanguage('zh-CN', getZhcnOption());
+            },
+            dialogOpen() {
+                this.dialogDisplay = true;
+                this.lastOpenTime = this.player.currentTime();
+                let ModalDialog = videojs.getComponent('ModalDialog');
+                var contentEl = document.getElementById('wxyDialog');
+                contentEl.style.visibility = 'visible';
+                let opt = {
+                    content:     contentEl,
+                    description: 'description st9988',
+                    fillAlways:  false,
+                    label:       'stlabel',
+                    pauseOnOpen: true,
+                    temporary:   false,
+                    uncloseable: false,
+                };
+                this.modalDialog = new ModalDialog(this.player, opt);
+                this.player.addChild(this.modalDialog);
+                this.modalDialog.on('dispose', () => {
+                    this.player.removeChild(this.modalDialog);
+                });
+                this.modalDialog.on('modalclose', () => {
+                    console.log('modalDialog closed');
+                    this.dialogDisplay = false;
+                });
+                this.modalDialog.open();
+                // this.render();
+                // let stModalDialog = this.player.addChild(modalDialog);
+                // this.divShow = true;
+            },
+            callShiti(index) {
+                this.shiti = this.shitiList[index];
+                this.shitiIndex = index;
+                this.dialogOpen();
+                this.preventStep();
+            },
+            /**
+             * 弹出窗口时,防止步进
+             */
+            preventStep() {
+                if (this.player.currentTime() - this.lastOpenTime < 1) {
+                    console.log('preventStep');
+                    this.player.currentTime(this.lastOpenTime);
+                }
+            },
+            replyCallback(payLoad) {
+                // console.log('replyCallback', payLoad);
+                this.shitiList[this.shitiIndex].reply = payLoad.data;
+            },
+            isReplyRight() {
+                // right
+                // console.log(this.shitiList[this.shitiIndex].reply, this.shitiList[this.shitiIndex]);
+                function checkAnswer(shiti) {
+                    // console.log(shiti);
+                    if (shiti.stTypeId === 1 || shiti.stTypeId === 3) { // 单选
+                        if (shiti.reply === shiti.result) {
+                            return true;
+                        }
+                    } else if (shiti.stTypeId === 2) { // 多选
+                        const _r = shiti.result.join(',');
+                        let _d = '';
+                        if (shiti.reply === null) {
+                            _d = '';
+                        } else {
+                            _d = shiti.reply.join(',');
+                        }
+                        if (_r === _d) {
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    } else if (shiti.stTypeId === 4) {
+                        if (shiti.reply === null) {
+                            return false;
+                        }
+                        const _r = [];
+                        for (let i = 0; i < shiti.reply.length; i++) {
+                            if (shiti.result[i].indexOf(shiti.reply[i]) !== -1) {
+                                _r.push(true);
+                            }
+                        }
+                        if (_r.length === shiti.reply.length) {
+                            return true;
+                        }
+                        return false;
+                    } else if (shiti.stTypeId === 5) {
+                        return true;
+                    } else {
+                        throw new Error('checkAnswer没有类型匹配');
+                    }
+                }
+
+                if (checkAnswer(this.shitiList[this.shitiIndex])) {
+                    this.$message.success('答案正确');
+                    this.shitiTimeDoneArr.push(this.shitiTimeArr[this.shitiIndex]);
+                    // 由于现在不判断答案正确性,只判断视频是否播放完成
+                    // 所以这里什么都不做
+
+                    // close dialog
+                    document.getElementById('wxyDialog').style.visibility = 'hidden';
+                    this.modalDialog.close();
+                    this.doneTempFlg = true;
+                    this.$nextTick(() => {
+                        this.player.play();
+                        setTimeout(() => {
+                            this.doneTempFlg = false;
+                        }, 2000);
+                    });
+
+                } else {
+                    // show error msg
+                    this.answerDialogVisible = true;
+                    setTimeout(()=>{
+                        this.answerDialogVisible = false;
+                    }, 1500)
+                }
+            },
+            initShiti() {
+                let hasShitiNull = false;
+                this.clonedOptions = clone(this.options);
+                if (this.clonedOptions.shitiList) {
+                    this.shitiList = [];
+                    this.shitiTimeArr = [];
+                    for (const shiti of this.clonedOptions.shitiList) {
+                        if (shiti === null) {
+                            hasShitiNull = true;
+                            break;
+                        }
+                        this.shitiTimeArr.push(shiti.showTime);
+                        this.shitiList.push(shiti.shiti);
+                    }
+                    if (hasShitiNull) {
+                        this.$message.error('业务逻辑错误: 试题实体没有取得');
+                    }
+                } else {
+                    this.shitiTimeArr = null;
+                    this.shitiTimeDoneArr = [];
+                }
+            },
+            initPlayer() {
+                if (this.hasInitPlayer) {
+                    // do refresh
+                    this.player.src(this.options.sources[0]);
+                    this.maxPlayTime = 0;
+                    this.maxPlayTimeRecord = 0;
+                    return;
+                }
+                this.player = videojs(this.$refs.videoPlayer1, this.options, function onPlayerReady() {
+
+                });
+
+
+                this.player.on('play', () => {
+                    if (!this.clonedOptions.progressControlEnable && this.clonedOptions.progressControlEnable === false) {
+                        this.player.controlBar.progressControl.disable();
+                    }
+                    if (!this.doneTempFlg) {
+                        this.displayQuestionByCurrentTime();
+                    }
+
+                    console.log(this.player.currentTime());
+                    if (this.player.currentTime() === 0) {
+                        // getTemp
+                        let progressObj = getCourseProgress(this.duration);
+                        this.maxPlayTime = progressObj.max;
+                        let cur = this.duration - progressObj.cur < 20 ? 0 : progressObj.cur;
+                        this.preTimeupdateTime = cur;
+                        this.maxPlayTimeRecord = cur;
+                        this.player.currentTime(cur);
+                    }
+                });
+
+                this.player.on('loadedmetadata', () => {
+                    this.duration = this.player.duration();
+                });
+
+                this.player.on('timeupdate', (event) => {
+                    // console.log(this.preTimeupdateTime, this.player.currentTime(), this.clonedOptions.drugControl);
+
+                    if (this.player.currentTime() - this.preTimeupdateTime > 0.6) { // 向后拖动
+                        console.log(this.clonedOptions.drugControl);
+                        if (this.clonedOptions.drugControl === 'forwardOnly') {
+                            console.log('forwardOnly 2323');
+                            if (this.player.currentTime() >= this.maxPlayTime) {
+                                this.player.currentTime(this.maxPlayTime);
+                            } else if (this.player.currentTime() < this.maxPlayTime) {
+                                // default
+                            } else {
+                                this.player.currentTime(this.preTimeupdateTime);
+                            }
+                        }
+
+                    } else if (this.preTimeupdateTime - this.player.currentTime() > 0) { // 向前拖动
+
+                    } else { // 正常播放
+                        this.maxPlayTime = this.preTimeupdateTime > this.maxPlayTime
+                                           ? this.preTimeupdateTime
+                                           : this.maxPlayTime;
+
+                        // 每1分钟同步一下进度
+                        if (this.maxPlayTime - this.maxPlayTimeRecord > 60 * 1) {
+                            this.maxPlayTimeRecord = this.maxPlayTime;
+                            // save course progress
+                            saveCourseProgress(this.maxPlayTime, this.duration);
+                            updateSectionProgress();
+                        }
+
+                        if (!this.doneTempFlg) {
+                            this.displayQuestionByCurrentTime();
+                        }
+                    }
+                    this.preTimeupdateTime = this.player.currentTime();
+
+                });
+
+                this.player.on('ended', () => {
+                    videojs.log('播放结束了!');
+                    // save to cache 课程 章 节 课件id 答案数组
+                    saveCourseProgress(this.duration, this.duration);
+                    updateSectionProgress();
+                    this.$emit('sectionCompleted', true);
+                });
+
+                this.hasInitPlayer = true;
+            },
+            hidDialog() {
+                if (this.dialogDisplay) {
+                    this.dialogDisplay = false;
+                    this.modalDialog.close();
+                }
+            },
+        },
+        beforeDestroy() {
+            if (this.player) {
+                this.player.dispose();
+            }
+        },
+        watch:      {
+            'options.playlist': {
+                handler(newVal, oldVal) {
+                    if (this.clonedOptions.hasPlaylist) {
+                        this.initPlayer();
+                        this.player.playlist(newVal);
+                        // 延迟播放
+                        this.player.playlist.autoadvance(5);
+                        this.player.playlistUi();
+                    }
+                },
+                immediate: false,
+            },
+            'options.sources':  {
+                handler(newVal, oldVal) {
+                    this.initPlayer();
+                    this.initShiti();
+                },
+                immediate: false,
+            },
+        },
+    };
+</script>
+<style lang="scss">
+    .video-container {
+        text-align: left;
+        display: flex;
+        width: 100%;
+
+        .vjs-playlist {
+            margin-left: 5px;
+            width: 180px;
+            flex-shrink: 0;
+
+            .vjs-playlist-item {
+                font-size: 12px;
+                height: 95px;
+            }
+        }
+
+        .vjs-play-control.vjs-control.vjs-button {
+            outline-color: transparent;
+        }
+
+        .vjs-modal-dialog-content {
+            margin: 0 auto;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            background-color: #FFF;
+        }
+
+        /*.vjs-icon-hand {
+            content:          "";
+            display:          inline-block;
+            width:            10px;
+            height:           10px;
+            background-color: rgba(132, 132, 132, 1);
+            border-radius:    50%;
+            margin-right:     8px;
+        }*/
+
+        /*.video-js .vjs-play-progress:before {
+            top: -0.4em;
+            background-color: red;
+        }
+        .video-js .vjs-play-progress:hover:before {
+            top: -0.3333333333333333em;
+            background-color: blue;
+        }*/
+        .video-main {
+            position: relative;
+            display: contents;
+
+            .video-js {
+                width: 100%;
+            }
+        }
+
+    }
+
+    #wxyDialog {
+        width: 100%;
+        height: 100%;
+    }
+
+    .dl-shiti {
+        position: relative;
+        width: 80%;
+        margin: 15% auto auto auto;
+    }
+
+    .dl-btn {
+        position: absolute;
+        left: 48%;
+        top: 90%;
+        z-index: 500;
+    }
+
+</style>

+ 392 - 0
src/components/custom/VideoPlayerBk.vue

@@ -0,0 +1,392 @@
+<template>
+    <div class="video-container">
+        <div style="position: relative;margin: 0 auto;">
+            <video class="video-js vjs-theme-city" id="my-player" ref="videoPlayer1"></video>
+
+        </div>
+        <div class="vjs-playlist">
+        </div>
+        <div id="wxy111" style="visibility: hidden; position: absolute;">
+            <mta-single-choice
+                    :getShowModel="false"
+                    :questionData="qa"
+                    @reply="singleChoiceReplyCallback"
+                    shitiIndex="1"
+            ></mta-single-choice>
+
+        </div>
+    </div>
+</template>
+
+<script>
+    import 'video.js/dist/video-js.css';
+    import '@/assets/css/video/city.css';
+    import '@/assets/css/video/fantasy.css';
+    import '@/assets/css/video/forest.css';
+    import '@/assets/css/video/sea.css';
+    import 'videojs-playlist-ui/dist/videojs-playlist-ui.vertical.css';
+
+    import videojs             from 'video.js';
+    import playlist            from 'videojs-playlist';
+    import playlistUi          from 'videojs-playlist-ui';
+    import { isObject, clone } from '@/utils/common';
+
+    import MtaSingleChoice from '@/components/client/Questions/SingleChoice.vue';
+
+    export default {
+        name:     'VideoPlayer',
+        props:    {
+            options: {
+                type: Object,
+                default() {
+                    return {};
+                },
+
+            },
+        },
+        computed: {},
+        components: {
+            MtaSingleChoice
+        },
+        data() {
+            return {
+                player:        null,
+                clonedOptions: {},
+                divShow: false,
+                qa: {"stId":631,"stTypeId":1,"stClassifyId":2,"stLevelId":1,"name":"在中国制造业中,\"智能制造”理念是由哪首次提出的","score":"1","content":["华为","河南重工","三一重工","北方重工"],"reply":null,"marked":false,"type":"danxuan","onlyNum":1,"order":0}
+            };
+        },
+        mounted() {
+            /*console.dir(videojs);
+            console.dir(playlist);*/
+            this.clonedOptions = clone(this.options);
+            this.setVideoZhCN();
+            videojs.hook('beforesetup', function (videoEl, options) {
+                // videoEl.className += ' vjs-layout-x-small';
+
+                /*if (options.autoplay) {
+                    options.autoplay = false
+                }*/
+
+                options.language = 'zh-CN';
+                options.userActions = {};
+                options.userActions.hotkeys = {};
+                options.userActions.hotkeys.playPauseKey = function (e) {
+                    videojs.log(e);
+                };
+
+                // videojs.log(videoEl, options);
+
+                return options;
+            });
+            videojs.registerPlugin('playlist', playlist);
+            videojs.registerPlugin('playlistUi', playlistUi);
+            this.player = videojs(this.$refs.videoPlayer1, this.options, function onPlayerReady() {
+                // videojs.log('onPlayerReady', this);
+                /*var tech = this.tech({IWillNotUseThisInPlugins: true});
+                videojs.log(tech);*/
+                /*let preload = this.preload();
+                videojs.log(preload);*/
+                // this.currentTime(30);
+                // this.play();
+            });
+
+
+            this.player.on('play', () => {
+                /*const curTime = this.player.currentTime();
+                const duration = this.player.duration();
+                if (curTime > duration / 3) {
+                    this.player.pause();
+                }*/
+                if (!this.clonedOptions.progressControlEnable && this.clonedOptions.progressControlEnable === false) {
+                    this.player.controlBar.progressControl.disable();
+                }
+
+            });
+            /*this.player.on('pause', () => {
+
+                // Modals are temporary by default. They dispose themselves when they are
+                // closed; so, we can create a new one each time the player is paused and
+                // not worry about leaving extra nodes hanging around.
+                var modal = this.player.createModal('This is a modal!');
+
+                // When the modal closes, resume playback.
+                modal.on('modalclose', () => {
+                    this.player.play();
+                });
+            });*/
+            // tech() will error with no argument
+
+            this.player.on('loadedmetadata', () => {
+                var duration = this.player.duration();
+                videojs.log('loadedmetadata duration: ', duration);
+            });
+
+            this.player.on('timeupdate', (event) => {
+                const curTime = this.player.currentTime();
+                const duration = this.player.duration();
+                // videojs.log('timeupdate-------------', curTime, duration);
+                /*if (curTime > duration / 3) {
+                    this.player.pause();
+                }*/
+            });
+
+            this.player.on('ended', function () {
+                videojs.log('播放结束了!');
+            });
+
+        },
+        methods:  {
+            setVideoZhCN() {
+                videojs.addLanguage('zh-CN', {
+                    'Play':                                                                                                                        '播放',
+                    'Pause':                                                                                                                       '暂停',
+                    'Current Time':                                                                                                                '当前时间',
+                    'Duration':                                                                                                                    '时长',
+                    'Remaining Time':                                                                                                              '剩余时间',
+                    'Stream Type':                                                                                                                 '媒体流类型',
+                    'LIVE':                                                                                                                        '直播',
+                    'Loaded':                                                                                                                      '加载完毕',
+                    'Progress':                                                                                                                    '进度',
+                    'Fullscreen':                                                                                                                  '全屏',
+                    'Non-Fullscreen':                                                                                                              '退出全屏',
+                    'Mute':                                                                                                                        '静音',
+                    'Unmute':                                                                                                                      '取消静音',
+                    'Playback Rate':                                                                                                               '播放速度',
+                    'Subtitles':                                                                                                                   '字幕',
+                    'subtitles off':                                                                                                               '关闭字幕',
+                    'Captions':                                                                                                                    '内嵌字幕',
+                    'captions off':                                                                                                                '关闭内嵌字幕',
+                    'Chapters':                                                                                                                    '节目段落',
+                    'Close Modal Dialog':                                                                                                          '关闭弹窗',
+                    'Descriptions':                                                                                                                '描述',
+                    'descriptions off':                                                                                                            '关闭描述',
+                    'Audio Track':                                                                                                                 '音轨',
+                    'You aborted the media playback':                                                                                              '视频播放被终止',
+                    'A network error caused the media download to fail part-way.':                                                                 '网络错误导致视频下载中途失败。',
+                    'The media could not be loaded, either because the server or network failed or because the format is not supported.':          '视频因格式不支持或者服务器或网络的问题无法加载。',
+                    'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.': '由于视频文件损坏或是该视频使用了你的浏览器不支持的功能,播放终止。',
+                    'No compatible source was found for this media.':                                                                              '无法找到此视频兼容的源。',
+                    'The media is encrypted and we do not have the keys to decrypt it.':                                                           '视频已加密,无法解密。',
+                    'Play Video':                                                                                                                  '播放视频',
+                    'Close':                                                                                                                       '关闭',
+                    'Modal Window':                                                                                                                '弹窗',
+                    'This is a modal window':                                                                                                      '这是一个弹窗',
+                    'This modal can be closed by pressing the Escape key or activating the close button.':                                         '可以按ESC按键或启用关闭按钮来关闭此弹窗。',
+                    ', opens captions settings dialog':                                                                                            ', 开启标题设置弹窗',
+                    ', opens subtitles settings dialog':                                                                                           ', 开启字幕设置弹窗',
+                    ', opens descriptions settings dialog':                                                                                        ', 开启描述设置弹窗',
+                    ', selected':                                                                                                                  ', 选择',
+                    'captions settings':                                                                                                           '字幕设定',
+                    'Audio Player':                                                                                                                '音频播放器',
+                    'Video Player':                                                                                                                '视频播放器',
+                    'Replay':                                                                                                                      '重播',
+                    'Progress Bar':                                                                                                                '进度条',
+                    'Volume Level':                                                                                                                '音量',
+                    'subtitles settings':                                                                                                          '字幕设定',
+                    'descriptions settings':                                                                                                       '描述设定',
+                    'Text':                                                                                                                        '文字',
+                    'White':                                                                                                                       '白',
+                    'Black':                                                                                                                       '黑',
+                    'Red':                                                                                                                         '红',
+                    'Green':                                                                                                                       '绿',
+                    'Blue':                                                                                                                        '蓝',
+                    'Yellow':                                                                                                                      '黄',
+                    'Magenta':                                                                                                                     '紫红',
+                    'Cyan':                                                                                                                        '青',
+                    'Background':                                                                                                                  '背景',
+                    'Window':                                                                                                                      '视窗',
+                    'Transparent':                                                                                                                 '透明',
+                    'Semi-Transparent':                                                                                                            '半透明',
+                    'Opaque':                                                                                                                      '不透明',
+                    'Font Size':                                                                                                                   '字体尺寸',
+                    'Text Edge Style':                                                                                                             '字体边缘样式',
+                    'None':                                                                                                                        '无',
+                    'Raised':                                                                                                                      '浮雕',
+                    'Depressed':                                                                                                                   '压低',
+                    'Uniform':                                                                                                                     '均匀',
+                    'Dropshadow':                                                                                                                  '下阴影',
+                    'Font Family':                                                                                                                 '字体库',
+                    'Proportional Sans-Serif':                                                                                                     '比例无细体',
+                    'Monospace Sans-Serif':                                                                                                        '单间隔无细体',
+                    'Proportional Serif':                                                                                                          '比例细体',
+                    'Monospace Serif':                                                                                                             '单间隔细体',
+                    'Casual':                                                                                                                      '舒适',
+                    'Script':                                                                                                                      '手写体',
+                    'Small Caps':                                                                                                                  '小型大写字体',
+                    'Reset':                                                                                                                       '重置',
+                    'restore all settings to the default values':                                                                                  '恢复全部设定至预设值',
+                    'Done':                                                                                                                        '完成',
+                    'Caption Settings Dialog':                                                                                                     '字幕设定视窗',
+                    'Beginning of dialog window. Escape will cancel and close the window.':                                                        '开始对话视窗。离开会取消及关闭视窗',
+                    'End of dialog window.':                                                                                                       '结束对话视窗',
+                    'Seek to live, currently behind live':                                                                                         '试图直播,当前延时播放',
+                    'Seek to live, currently playing live':                                                                                        '试图直播,当前实时播放',
+                    'progress bar timing: currentTime={1} duration={2}':                                                                           '{1}/{2}',
+                    '{1} is loading.':                                                                                                             '正在加载 {1}。',
+                    'Picture-in-Picture':                                                                                                          '画中画',
+                    'Now Playing':                                                                                                                 '正在播放',
+                    'Up Next':                                                                                                                     '播放下一个',
+                    'Untitled Video':                                                                                                              '无标题视频',
+                });
+            },
+            createTracks() {
+                console.log('createTracks');
+                for (let i = 0; i < 3; i++) {
+                    /*var videoTrack = new videojs.VideoTrack({
+                                                           id:       'st-video-track-' + (i + 1),
+                                                           kind:     'translation',
+                                                           label:    'videoTrack-' + (i + 1),
+                                                           language: 'cn',
+                                                       });*/
+                    var audioTrack = new videojs.AudioTrack({
+                                                                id:       'st-audio-track-' + (i + 1),
+                                                                kind:     'translation',
+                                                                label:    'audioTrack-' + (i + 1),
+                                                                language: 'cn',
+                                                            });
+                    // this.player.videoTracks().addTrack(videoTrack);
+                    this.player.audioTracks().addTrack(audioTrack);
+                }
+            },
+            createButton() {
+                videojs.log('createButton');
+                var player = this.player;
+                var Button = videojs.getComponent('Button');
+                var button = new Button(player, {
+                    clickHandler: (event) => {
+                        videojs.log('Clicked');
+                        // console.log('Clicked')
+                        this.dialogOpen();
+                    },
+                });
+                var stButton = player.controlBar.addChild(button);
+                stButton.addClass('vjs-icon-hand');
+            },
+            dialogOpen() {
+
+                console.log(this);
+                let ModalDialog = videojs.getComponent('ModalDialog');
+                /*content	Mixed	<optional> Provide customized content for this modal.
+                    description	string	<optional> A text description for the modal, primarily for accessibility.
+                    fillAlways	boolean	<optional> false Normally, modals are automatically filled only the first time they open. This tells the modal to refresh its content every time it opens.
+                    label	string	<optional> A text label for the modal, primarily for accessibility.
+                    pauseOnOpen	boolean	<optional> true	If true, playback will will be paused if playing when the modal opens, and resumed when it closes.
+                    temporary	boolean	<optional> true If true, the modal can only be opened once; it will be disposed as soon as it's closed.
+                    uncloseable	boolean	<optional> false If true, the user will not be able to close the modal through the UI in the normal ways. Programmatic closing is still possible.*/
+
+
+
+
+                /*var modal_content = '<button @click="stBtnClick">st按钮7788</button>';
+
+                // where the magic happens
+                var contentEl = document.createElement('div');
+                // probably better to just build the entire thing via DOM methods
+                contentEl.innerHTML = modal_content;*/
+
+
+                var contentEl = document.getElementById('wxy111');
+                contentEl.style.visibility="visible";
+                let opt = {
+                    content:     contentEl,
+                    description: 'description st9988',
+                    fillAlways:  false,
+                    label:       'stlabel',
+                    pauseOnOpen: true,
+                    temporary:   false,
+                    uncloseable: false,
+                };
+                let modalDialog = new ModalDialog(this.player, opt);
+                this.player.addChild(modalDialog);
+                modalDialog.on('dispose', () => {
+                    this.player.removeChild(modalDialog);
+                });
+                modalDialog.open();
+                // this.render();
+                // let stModalDialog = this.player.addChild(modalDialog);
+                // this.divShow = true;
+            },
+            stBtnClick() {
+                alert(1119863);
+            },
+            /*setSrc(params) {
+                if (typeof params === 'string') {
+                    this.player.src(params);
+                }
+                if (isObject(params)) {
+                    this.player.src({
+                                        type: params.type,
+                                        src:  params.src,
+                                    });
+                }
+
+            },*/
+            btnClick() {
+                alert(1)
+            },
+            singleChoiceReplyCallback() {}
+        },
+        beforeDestroy() {
+            if (this.player) {
+                this.player.dispose();
+            }
+        },
+        watch:    {
+            'options.playlist': {
+                handler(newVal, oldVal) {
+                    this.player.playlist(newVal);
+                    // 延迟播放
+                    this.player.playlist.autoadvance(5);
+                    this.player.playlistUi();
+                },
+                immediate: false,
+            },
+        },
+    };
+</script>
+<style lang="scss">
+    .video-container {
+        display: flex;
+
+        .vjs-playlist {
+            margin-left: 5px;
+            width: 180px;
+
+            .vjs-playlist-item {
+                font-size: 12px;
+                height: 95px;
+            }
+        }
+
+        .vjs-play-control.vjs-control.vjs-button {
+            outline-color: transparent;
+        }
+        .vjs-modal-dialog-content {
+            margin: 0 auto;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            background-color: #cccccc
+        }
+        /*.vjs-icon-hand {
+            content:          "";
+            display:          inline-block;
+            width:            10px;
+            height:           10px;
+            background-color: rgba(132, 132, 132, 1);
+            border-radius:    50%;
+            margin-right:     8px;
+        }*/
+
+        /*.video-js .vjs-play-progress:before {
+            top: -0.4em;
+            background-color: red;
+        }
+        .video-js .vjs-play-progress:hover:before {
+            top: -0.3333333333333333em;
+            background-color: blue;
+        }*/
+    }
+
+</style>

+ 320 - 0
src/components/management/AliUploadVideo.vue

@@ -0,0 +1,320 @@
+<template>
+    <div class="aliUpload-container">
+        <div class="upload">
+            <div>
+                <el-upload
+                        :before-upload="beforeUpload"
+                        :http-request="fileChange"
+                        :on-remove="filesRemove"
+                        :show-file-list="true"
+                        action="aaa"
+                        class="upload-box"
+                        ref="aliUpload"
+                >
+                    <el-button class="response-middle-btn" type="primary">选择视频文件</el-button>
+                </el-upload>
+                <el-tooltip content="注:音、视频课件上传大小限制1.5G以内,麻烦请留意上传音、视频文件大小!!!"  placement="top" popper-class="c-aliUpload-tooltip"><i class="aliUpload-note-word"></i></el-tooltip>
+                <label class="status">上传状态: <span>{{statusText}}</span></label>
+            </div>
+            <div class="upload-type">
+                <!--上传方式一, 使用 UploadAuth 上传:-->
+                <el-progress :percentage="authProgress" class="aly-progressbar"></el-progress>
+                <button :disabled="uploadDisabled" @click.prevent="authUpload" class="start-btn"></button>
+                <button :disabled="resumeDisabled" @click.prevent="resumeUpload" class="upload-btn"></button>
+                <button :disabled="pauseDisabled" @click.prevent="pauseUpload" class="pause-btn"></button>
+             <!--   <span class="progress">上传进度: <i id="auth-progress">{{authProgress}}</i> %</span>-->
+
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+    // import axios from 'axios'
+    import * as videoPolicy from '@/api/peixun/videoPolicy';
+    import { mapGetters }   from 'vuex';
+
+    export default {
+        name:     'AliUploadVideo',
+        data() {
+            return {
+                timeout:        null,
+                partSize:       null,
+                parallel:       null,
+                retryCount:     null,
+                retryDuration:  null,
+                region:         'cn-shanghai',
+                userId:         '1815477461878711',
+                file:           null,
+                authProgress:   0,
+                uploadDisabled: true,
+                resumeDisabled: true,
+                pauseDisabled:  true,
+                uploader:       null,
+                statusText:     '',
+                policy:         {
+                    fileName: '',
+                    title:    '',
+                },
+            };
+        },
+        computed: {
+            ...mapGetters([
+                              'getTenantCode',
+                          ]),
+        },
+        methods:  {
+            /*initAliUpload(truck, userData) {
+
+            },*/
+            clear() {
+                if (this.$refs.aliUpload.uploadFiles.length > 0) {
+                    this.$refs.aliUpload.uploadFiles.splice(0, 1);
+                }
+                this.authProgress = 0;
+                this.statusText = '';
+                this.pauseDisabled = true;
+                this.resumeDisabled = true;
+            },
+            fileChange(truck) {
+                if (!truck.file) {
+                    this.$message.warning('请先选择需要上传的文件!');
+                    return;
+                }
+
+                var userData = '{"Vod":{}}';
+
+                if (this.$refs.aliUpload.uploadFiles.length > 1) {
+                    this.$refs.aliUpload.uploadFiles.splice(0, 1);
+                    // this.initAliUpload(truck, userData);
+                }
+
+                // console.log(truck);
+                var time = new Date().getTime();
+                this.policy.fileName = this.getTenantCode + time + '.' + truck.file.name.split('.').pop();
+                this.$emit('fileSuffix', truck.file.name.split('.').pop());
+                this.policy.title = this.getTenantCode + time;
+
+                if (this.uploader) {
+                    this.uploader.stopUpload();
+                    this.authProgress = 0;
+                    this.statusText = '';
+                }
+                this.uploader = this.createUploader();
+                console.log(userData);
+                this.uploader.addFile(truck.file, null, null, null, userData);
+                this.authUpload();
+            },
+            authUpload() {
+                // 然后调用 startUpload 方法, 开始上传
+                if (this.uploader !== null) {
+                    this.uploader.startUpload();
+                }
+            },
+            // 暂停上传
+            pauseUpload() {
+                if (this.uploader !== null) {
+                    this.uploader.stopUpload();
+                }
+            },
+            // 恢复上传
+            resumeUpload() {
+                if (this.uploader !== null) {
+                    this.uploader.startUpload();
+                }
+            },
+            createUploader(type) {
+                let self = this;
+                let uploader = new AliyunUpload.Vod({
+                                                        timeout:              self.timeout || 60000,
+                                                        partSize:             self.partSize || 1048576,
+                                                        parallel:             self.parallel || 5,
+                                                        retryCount:           self.retryCount || 3,
+                                                        retryDuration:        self.retryDuration || 2,
+                                                        region:               self.region,
+                                                        userId:               self.userId,
+                                                        // 添加文件成功
+                                                        addFileSuccess:       (uploadInfo) => {
+                                                            /*this.uploadDisabled = false;
+                                                            this.pauseDisabled = true;
+                                                            this.resumeDisabled = false;*/
+                                                            self.statusText = '添加文件成功, 等待上传...';
+                                                            console.log('addFileSuccess: ' + uploadInfo.file.name);
+                                                        },
+                                                        // 开始上传
+                                                        onUploadstarted:      (uploadInfo) => {
+                                                            // 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
+                                                            // 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress
+                                                            // 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
+                                                            // 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
+                                                            // 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
+                                                            // 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
+                                                            if (!uploadInfo.videoId) {
+                                                                // let createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6'
+                                                                let createUrl = '';
+                                                                videoPolicy.videoPolicy({
+                                                                                            'fileName': this.policy.fileName,
+                                                                                            'title':    this.policy.title,
+                                                                                        }).then(res => {
+                                                                    if (res.code == 0) {
+                                                                        let uploadAddress = res.data.uploadAddress;
+                                                                        let uploadAuth = res.data.uploadAuth;
+                                                                        let videoId = res.data.videoId;
+                                                                        uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId);
+                                                                        self.statusText = '文件开始上传...';
+                                                                        console.log('onUploadStarted1:' + uploadInfo.file.name + ', endpoint:' + uploadInfo.endpoint + ', bucket:' + uploadInfo.bucket + ', object:' + uploadInfo.object);
+
+                                                                        this.uploadDisabled = true;
+                                                                        this.pauseDisabled = false;
+                                                                        this.resumeDisabled = true;
+
+                                                                    } else {
+                                                                        console.error('videoPolicy code error!');
+                                                                    }
+                                                                });
+
+                                                            } else {
+                                                                // 如果videoId有值,根据videoId刷新上传凭证
+                                                                // https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY
+                                                                // let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
+                                                                videoPolicy.videoRefresh({
+                                                                                             'videoId': uploadInfo.videoId,
+                                                                                         }).then(res => {
+                                                                    if (res.code == 0) {
+                                                                        let uploadAuth = res.data.uploadAuth;
+                                                                        let uploadAddress = res.data.uploadAddress;
+                                                                        let videoId = res.data.videoId;
+                                                                        console.log(uploadInfo, uploadAuth, uploadAddress, videoId);
+                                                                        uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId);
+                                                                        self.statusText = '刷新凭证 文件开始上传...';
+                                                                        console.log('onUploadStarted2:' + uploadInfo.file.name + ', endpoint:' + uploadInfo.endpoint + ', bucket:' + uploadInfo.bucket + ', object:' + uploadInfo.object);
+
+                                                                        this.uploadDisabled = true;
+                                                                        this.pauseDisabled = false;
+                                                                        this.resumeDisabled = true;
+
+                                                                    } else {
+                                                                        console.error('videoRefresh code error!');
+                                                                    }
+                                                                });
+                                                            }
+                                                        },
+                                                        // 文件上传成功
+                                                        onUploadSucceed:      (uploadInfo) => {
+                                                            console.log(uploadInfo);
+                                                            console.log('onUploadSucceed: ' + uploadInfo.file.name + ', endpoint:' + uploadInfo.endpoint + ', bucket:' + uploadInfo.bucket + ', object:' + uploadInfo.object);
+                                                            self.statusText = '文件上传成功!';
+                                                            this.$emit('fileState', {
+                                                                state:     9,
+                                                                videoId:   uploadInfo.videoId,
+                                                                videoInfo: uploadInfo,
+                                                            });
+
+                                                            this.uploadDisabled = true;
+                                                            this.pauseDisabled = true;
+                                                            this.resumeDisabled = true;
+                                                        },
+                                                        // 文件上传失败
+                                                        onUploadFailed:       (uploadInfo, code, message) => {
+                                                            console.log('onUploadFailed: file:' + uploadInfo.file.name + ',code:' + code + ', message:' + message);
+                                                            self.statusText = '文件上传失败!';
+                                                            this.$emit('fileState', {
+                                                                state:   3,
+                                                                videoId: uploadInfo.videoId,
+                                                            });
+
+                                                            this.uploadDisabled = true;
+                                                            this.pauseDisabled = true;
+                                                            this.resumeDisabled = false;
+                                                        },
+                                                        // 取消文件上传
+                                                        onUploadCanceled:     (uploadInfo, code, message) => {
+                                                            console.log('Canceled file: ' + uploadInfo.file.name + ', code: ' + code + ', message:' + message);
+                                                            self.statusText = '文件已暂停上传';
+                                                            this.$emit('fileState', {
+                                                                state:   2,
+                                                                videoId: uploadInfo.videoId,
+                                                            });
+
+                                                            this.uploadDisabled = true;
+                                                            this.pauseDisabled = true;
+                                                            this.resumeDisabled = false;
+
+                                                        },
+                                                        // 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
+                                                        onUploadProgress:     (uploadInfo, totalSize, progress) => {
+                                                            console.log('onUploadProgress:file:' + uploadInfo.file.name + ', fileSize:' + totalSize + ', percent:' + Math.ceil(progress * 100) + '%');
+                                                            let progressPercent = Math.ceil(progress * 100);
+                                                            self.authProgress = progressPercent;
+                                                            self.statusText = '文件上传中...';
+                                                            this.$emit('fileState', {
+                                                                state:   1,
+                                                                videoId: uploadInfo.videoId,
+                                                            });
+                                                            this.uploadDisabled = true;
+                                                            this.pauseDisabled = false;
+                                                            this.resumeDisabled = true;
+                                                        },
+                                                        // 上传凭证超时
+                                                        onUploadTokenExpired: (uploadInfo) => {
+                                                            // 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
+                                                            // 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
+                                                            // 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
+                                                            // let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
+
+                                                            self.statusText = '文件超时...';
+                                                            this.$emit('fileState', {
+                                                                state:   5,
+                                                                videoId: uploadInfo.videoId,
+                                                            });
+                                                            videoPolicy.videoRefresh({
+                                                                                         'videoId': uploadInfo.videoId,
+                                                                                     }).then(res => {
+                                                                if (res.code == 0) {
+                                                                    let uploadAuth = res.data.UploadAuth;
+                                                                    let uploadAddress = res.data.UploadAddress;
+                                                                    let videoId = res.data.VideoId;
+                                                                    uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId);
+                                                                    self.statusText = '刷新凭证 文件开始上传...';
+                                                                    console.log('onUploadStarted3:' + uploadInfo.file.name + ', endpoint:' + uploadInfo.endpoint + ', bucket:' + uploadInfo.bucket + ', object:' + uploadInfo.object);
+                                                                    this.uploadDisabled = true;
+                                                                    this.pauseDisabled = true;
+                                                                    this.resumeDisabled = false;
+                                                                } else {
+                                                                    console.error('videoRefresh code error!');
+                                                                }
+                                                            });
+                                                        },
+                                                        // 全部文件上传结束
+                                                        onUploadEnd:          () => {
+                                                            // console.log('onUploadEnd: uploaded all the files');
+                                                            /*self.statusText = '全部文件上传完毕';
+                                                            this.$emit('fileState', {
+                                                                state:   10,
+                                                            });
+                                                            this.uploadDisabled = false;
+                                                            this.pauseDisabled = false;
+                                                            this.resumeDisabled = false;*/
+
+                                                        },
+                                                    });
+                return uploader;
+            },
+            beforeUpload(file) {
+                if (file.size > 1.5 * 1024 * 1024 * 1024) {
+                    this.$message.warning('文件超过1.5G');
+                    return false;
+                }
+                let url = URL.createObjectURL(file);
+                var audioElement = new Audio(url);
+                audioElement.addEventListener('loadedmetadata', () => {
+                    let playTime = audioElement.duration; //playTime就是当前视频长度
+                    console.log(playTime);
+                    this.$emit('syncBaseInfo', audioElement);
+                });
+            },
+            filesRemove() {
+                console.log('filesRemove');
+            }
+        },
+    };
+</script>

+ 333 - 0
src/components/management/Layout/AliPlay/AliPlay.vue

@@ -0,0 +1,333 @@
+<template>
+    <div>
+        <div class="prism-player" :id="playerId" :style="playStyle"></div>
+    </div>
+</template>
+
+<script>
+    // import '../api/aliplayer-h5-min'
+    // import '../api/aliplayer-min'
+    export default {
+        name: "Aliplayer",
+        props: {
+            playStyle: {
+                type: String,
+                default: ""
+            },
+            aliplayerSdkPath: {
+                // Aliplayer 代码的路径
+                type: String,
+                default: "//g.alicdn.com/de/prismplayer/2.8.2/aliplayer-min.js"
+            },
+            autoplay: {
+                type: Boolean,
+                default: false
+            },
+            isLive: {
+                type: Boolean,
+                default: false
+            },
+            playsinline: { // H5是否内置播放,有的Android浏览器不起作用。
+                type: Boolean,
+                default: false
+            },
+            width: {
+                type: String,
+                default: "100%"
+            },
+            height: {
+                type: String,
+                default: "320px"
+            },
+            controlBarVisibility: { // 控制面板的实现,默认为‘hover’, 可选的值为:‘click’、‘hover’、‘always’。
+                type: String,
+                default: "hover"
+            },
+            useH5Prism: {
+                type: Boolean,
+                default: false
+            },
+            useFlashPrism: {
+                type: Boolean,
+                default: false
+            },
+            vid: {
+                type: String,
+                default: ""
+            },
+            playauth: {
+                type: String,
+                default: ""
+            },
+            source: {
+                type: String,
+                default: "rtmp://test.noichina.com/app1/stream1?auth_key=1568256446-0-0-4b22ba4312c1f6343f4f8994a6f51cdd"
+            },
+            cover: {
+                type: String,
+                default: ""
+            },
+            format: {
+                type: String,
+                default: "m3u8"
+            },
+            skinLayout: {
+                type: Array,
+                default: function () {
+                    return [];
+                }
+            },
+            x5_video_position: {
+                type: String,
+                default: "top"
+            },
+            x5_type: {
+                type: String,
+                default: "h5"
+            },
+            x5_fullscreen: {
+                type: Boolean,
+                default: false
+            },
+            x5_orientation: {
+                type: Number,
+                default: 2
+            },
+            autoPlayDelay: {
+                type: Number,
+                default: 0
+            },
+            autoPlayDelayDisplayText: {
+                type: String
+            }
+        },
+        data() {
+            return {
+                playerId: "aliplayer_" + Math.random().toString(36).substr(2),
+                scriptTagStatus: 0,
+                isReload: false,
+                instance: null
+            }
+        },
+        created() {
+            if (window.Aliplayer !== undefined) {
+                // 如果全局对象存在,说明编辑器代码已经初始化完成,直接加载编辑器
+                this.scriptTagStatus = 2;
+                this.initAliplayer();
+            } else {
+                // 如果全局对象不存在,说明编辑器代码还没有加载完成,需要加载编辑器代码
+                this.insertScriptTag();
+            }
+        },
+        mounted() {
+            if (window.Aliplayer !== undefined) {
+                // 如果全局对象存在,说明编辑器代码已经初始化完成,直接加载编辑器
+                this.scriptTagStatus = 2;
+                this.initAliplayer();
+            } else {
+                // 如果全局对象不存在,说明编辑器代码还没有加载完成,需要加载编辑器代码
+                this.insertScriptTag();
+            }
+        },
+        methods: {
+            insertScriptTag() {
+                const _this = this;
+                let playerScriptTag = document.getElementById("playerScriptTag");
+                // 如果这个tag不存在,则生成相关代码tag以加载代码
+                if (playerScriptTag === null) {
+                    playerScriptTag = document.createElement("script");
+                    playerScriptTag.type = "text/javascript";
+                    playerScriptTag.src = this.aliplayerSdkPath;
+                    playerScriptTag.id = "playerScriptTag";
+                    let s = document.getElementsByTagName("head")[0];
+                    s.appendChild(playerScriptTag);
+                }
+                if (playerScriptTag.loaded) {
+                    _this.scriptTagStatus++;
+                } else {
+                    playerScriptTag.addEventListener("load", () => {
+                        _this.scriptTagStatus++;
+                        playerScriptTag.loaded = true;
+                        _this.initAliplayer();
+                    });
+                }
+                _this.initAliplayer();
+            },
+            initAliplayer() {
+                const _this = this;
+                // scriptTagStatus 为 2 的时候,说明两个必需引入的 js 文件都已经被引入,且加载完成
+                if (_this.scriptTagStatus === 2 && (_this.instance === null || _this.reloadPlayer)) {
+                    _this.instance && _this.instance.dispose();
+
+                    // document.querySelector("#" + _this.playerId).innerHTML = "";
+                    // Vue 异步执行 DOM 更新,这样一来代码执行到这里的时候可能 template 里面的 script 标签还没真正创建
+                    // 所以,我们只能在 nextTick 里面初始化 Aliplayer
+                    _this.$nextTick(() => {
+                        _this.instance = window.Aliplayer({
+                                                              id: _this.playerId,
+                                                              autoplay: _this.autoplay,
+                                                              isLive: _this.isLive,
+                                                              playsinline: _this.playsinline,
+                                                              format: _this.format,
+                                                              width: _this.width,
+                                                              height: _this.height,
+                                                              controlBarVisibility: _this.controlBarVisibility,
+                                                              useH5Prism: _this.useH5Prism,
+                                                              useFlashPrism: _this.useFlashPrism,
+
+                                                              vid: _this.vid,
+                                                              playauth: _this.playauth,
+                                                              source: _this.source,
+                                                              cover: _this.cover,
+
+                                                              // skinLayout: _this.skinLayout, // 说明:功能组件布局配置,不传该字段使用默认布局传false隐藏所有功能组件,请参照皮肤定制
+                                                              x5_video_position: _this.x5_video_position,
+                                                              x5_type: _this.x5_type,
+                                                              x5_fullscreen: _this.x5_fullscreen,
+                                                              x5_orientation: _this.x5_orientation,
+                                                              autoPlayDelay: _this.autoPlayDelay,
+                                                              autoPlayDelayDisplayText: _this.autoPlayDelayDisplayText
+                                                          });
+                        // 绑定事件,当 AliPlayer 初始化完成后,将编辑器实例通过自定义的 ready 事件交出去
+                        _this.instance.on("ready", () => {
+                            this.$emit("ready", _this.instance);
+                        });
+                        _this.instance.on("play", () => {
+                            this.$emit("play", _this.instance);
+                        });
+                        _this.instance.on("pause", () => {
+                            this.$emit("pause", _this.instance);
+                        });
+                        _this.instance.on("ended", () => {
+                            this.$emit("ended", _this.instance);
+                        });
+                        _this.instance.on("liveStreamStop", () => {
+                            this.$emit("liveStreamStop", _this.instance);
+                        });
+                        _this.instance.on("m3u8Retry", () => {
+                            this.$emit("m3u8Retry", _this.instance);
+                        });
+                        _this.instance.on("hideBar", () => {
+                            this.$emit("hideBar", _this.instance);
+                        });
+                        _this.instance.on("waiting", () => {
+                            this.$emit("waiting", _this.instance);
+                        });
+                        _this.instance.on("snapshoted", () => {
+                            this.$emit("snapshoted", _this.instance);
+                        });
+
+                        _this.instance.on("timeupdate", () => {
+                            _this.$emit("timeupdate", _this.instance);
+                        });
+                        _this.instance.on("requestFullScreen", () => {
+                            _this.$emit("requestFullScreen", _this.instance);
+                        });
+                        _this.instance.on("cancelFullScreen", () => {
+                            _this.$emit("cancelFullScreen", _this.instance);
+                        });
+                        _this.instance.on("error", () => {
+                            _this.$emit("error", _this.instance);
+                        });
+                        _this.instance.on("startSeek", () => {
+                            _this.$emit("startSeek", _this.instance);
+                        });
+                        _this.instance.on("completeSeek", () => {
+                            _this.$emit("completeSeek", _this.instance);
+                        });
+                    });
+                }
+            },
+            /**
+             * 播放视频
+             */
+            play: function () {
+                this.instance.play();
+            },
+            /**
+             * 暂停视频
+             */
+            pause: function () {
+                this.instance.pause();
+            },
+            /**
+             * 重播视频
+             */
+            replay: function () {
+                this.instance.replay();
+            },
+            /**
+             * 跳转到某个时刻进行播放
+             * @argument time 的单位为秒
+             */
+            seek: function (time) {
+                this.instance.seek(time);
+            },
+            /**
+             * 获取当前时间 单位秒
+             */
+            getCurrentTime: function () {
+                return this.instance.getCurrentTime();
+            },
+            /**
+             *获取视频总时长,返回的单位为秒
+             * @returns 返回的单位为秒
+             */
+            getDuration: function () {
+                return this.instance.getDuration();
+            },
+            /**
+             获取当前的音量,返回值为0-1的实数ios和部分android会失效
+             */
+            getVolume: function () {
+                return this.instance.getVolume();
+            },
+            /**
+             设置音量,vol为0-1的实数,ios和部分android会失效
+             */
+            setVolume: function (vol) {
+                this.instance.setVolume(vol);
+            },
+            /**
+             *直接播放视频url,time为可选值(单位秒)目前只支持同种格式(mp4/flv/m3u8)之间切换暂不支持直播rtmp流切换
+             *@argument url 视频地址
+             *@argument time 跳转到多少秒
+             */
+            loadByUrl: function (url, time) {
+                this.instance.loadByUrl(url, time);
+            },
+            /**
+             * 设置播放速度
+             *@argument speed 速度
+             */
+            setSpeed: function (speed) {
+                this.instance.setSpeed(speed);
+            },
+            /**
+             * 设置播放器大小w,h可分别为400px像素或60%百分比chrome浏览器下flash播放器分别不能小于397x297
+             *@argument w 播放器宽度
+             *@argument h 播放器高度
+             */
+            setPlayerSize: function (w, h) {
+                this.instance.setPlayerSize(w, h);
+            },
+            /**
+             * 目前只支持HTML5界面上的重载功能,暂不支持直播rtmp流切换m3u8)之间切换,暂不支持直播rtmp流切换
+             *@argument vid 视频id
+             *@argument playauth 播放凭证
+             */
+            reloaduserPlayInfoAndVidRequestMts: function (vid, playauth) {
+                this.instance.reloaduserPlayInfoAndVidRequestMts(vid, playauth);
+            },
+            reloadPlayer: function () {
+                this.isReload = true;
+                this.initAliplayer();
+                this.isReload = false;
+            },
+        },
+    }
+</script>
+
+<style lang="postcss" scoped>
+    @import 'https://g.alicdn.com/de/prismplayer/2.8.2/skins/default/aliplayer-min.css';
+</style>

+ 226 - 0
src/components/management/Layout/EditableTree/EditableTree.vue

@@ -0,0 +1,226 @@
+<template>
+    <div class="editableTree fsize-m4">
+        <div class="editableTree-filter" v-show="needTextFilter">
+            <el-input
+                    placeholder="输入关键字进行过滤"
+                    v-model="filterText">
+            </el-input>
+        </div>
+        <div class="wrap-tree">
+            <el-tree
+                    :data="newData"
+                    :node-key="nodeKey"
+                    :default-expanded-keys="stDefaultExpandedKeys"
+                    @node-drag-start="handleDragStart"
+                    @node-drag-enter="handleDragEnter"
+                    @node-drag-leave="handleDragLeave"
+                    @node-drag-over="handleDragOver"
+                    @node-drag-end="handleDragEnd"
+                    @node-drop="handleDrop"
+                    @node-click="handleNodeClick"
+                    @node-expand="handleNodeExpand"
+                    @node-collapse="handleNodeCollapse"
+                    :allow-drop="allowDrop"
+                    :draggable="treeDraggable"
+                    :expand-on-click-node="false"
+                    ref="tree"
+                    :filter-node-method="filterNode"
+                    :highlight-current="true"
+            >
+                <div class="custom-tree-node" slot-scope="{ node, data }">
+                    <editor-tree-item
+                            :node="node"
+                            :data="data"
+                            :needControl="isControl"
+                            @labelClick="handleLabelClick"
+                            @treeItemAppend="treeAppend"
+                            @treeItemRemove="treeRemove"
+                            @changeDragState="changeDragState"
+                            @treeItemEditor="getTreeItemEditor">
+                    </editor-tree-item>
+                </div>
+            </el-tree>
+        </div>
+    </div>
+</template>
+
+<script>
+    import EditorTreeItem from '@/components/management/Layout/EditableTree/EditableTreeItem.vue';
+    import { mapGetters } from 'vuex';
+
+    export default {
+        name:       'EditableTree',
+        components: {
+            EditorTreeItem,
+        },
+        props:      {
+            newData:         {
+                // 树的数据
+                type:    Array,
+                default: () => arr,
+            },
+            allowDropStatue: {
+                // 是否允许设置拖拽
+                type:    Boolean,
+                default: false,
+            },
+            isDraggable:     {
+                type:    Boolean,
+                default: true,
+            },
+            needTextFilter:  {
+                type:    Boolean,
+                default: false,
+            },
+            isControl:       {
+                type:    Boolean,
+                default: true,
+            },
+            nodeKey:         {
+                type:    String,
+                default: 'id',
+            },
+            expandedKeys:    {
+                // 树的数据
+                type:    Array,
+                default: () => [],
+            },
+        },
+        data() {
+            return {
+                input:                 '',
+                startData:             '',
+                showItem:              true,
+                treeDraggable:         this.isDraggable,
+                filterText:            '',
+                currentNodekey:        '',
+                stDefaultExpandedKeys: [],
+            };
+        },
+        methods:    {
+            filterNode(value, data) {
+                if (!value) {
+                    return true;
+                }
+                return data.lable.indexOf(value) !== -1;
+            },
+            handleLabelClick(data) {
+                this.$refs.tree.setCurrentKey(data.data.id);
+                this.$emit('handleClick', data);
+            },
+            setCurrentKey(id) {
+                this.$refs.tree.setCurrentKey(id);
+            },
+            handleNodeClick(data, Node, self) {
+                /*const opt = {data, Node, self};
+                this.$emit('handleClick', opt);*/
+            },
+            handleNodeExpand(data) {
+                this.$emit('syncExpandedKeys', {
+                    data:     data,
+                    isExpand: true,
+                });
+            },
+            handleNodeCollapse(data) {
+                this.$emit('syncExpandedKeys', {
+                    data:     data,
+                    isExpand: false,
+                });
+            },
+            handleDragStart(node, ev) {
+                this.startData = node;
+            },
+            handleDragEnter(draggingNode, dropNode, ev) {
+            },
+            handleDragLeave(draggingNode, dropNode, ev) {
+            },
+            handleDragOver(draggingNode, dropNode, ev) {
+            },
+            handleDragEnd(draggingNode, dropNode, dropType, ev) {
+            },
+            // 拖拽放置时触发
+            handleDrop(draggingNode, dropNode, dropType, ev) {
+                this.$emit('handleDrop', dropNode, draggingNode, dropType, ev, this.startData);
+            },
+            // 是否允许拖拽
+            allowDrop(draggingNode, dropNode, dropType) {
+                const c = dropType !== 'prev' && dropType !== 'next';
+
+                if (!c && dropNode.data.lable === this.newData[0].lable && this.allowDropStatue) {
+
+                    return false;
+                } else {
+                    return true;
+                }
+            },
+            // 编辑事件回调触发
+            getTreeItemEditor(data) {
+                this.$emit('treeEditor', data);
+            },
+            // 新增事件回调触发
+            treeAppend(payload) {
+                let { node, data } = payload;
+                this.$emit('treeAppend', data);
+            },
+            // 删除事件回调触发
+            treeRemove(payload) {
+                this.$emit('treeRemove', payload);
+            },
+
+            changeDragState(b) {
+                this.treeDraggable = b;
+            },
+            setExpanded(treeData, expandedKeys) {
+                function setTreeChildeExpanded(children, that) {
+                    for (const child of children) {
+                        if (expandedKeys.indexOf(child[that.nodeKey]) > -1) {
+                            that.stDefaultExpandedKeys.push(child[that.nodeKey]);
+                        }
+                        if (child.children) {
+                            setTreeChildeExpanded(child.children, that);
+                        }
+                    }
+                }
+
+                // default expand;
+                if (!expandedKeys || expandedKeys.length === 0) {
+                    for (const treeDataItem of treeData) {
+                        this.stDefaultExpandedKeys.push(treeDataItem[this.nodeKey]);
+                    }
+                } else if (Array.isArray(expandedKeys)) {
+                    for (const treeDataItem of treeData) {
+                        this.stDefaultExpandedKeys.push(treeDataItem[this.nodeKey]);
+                        if (treeDataItem.children) {
+                            setTreeChildeExpanded(treeDataItem.children, this);
+                        }
+                    }
+                }
+            },
+            pushExpandedKey(expandedKeys, id) {
+                if (expandedKeys.indexOf(id) === -1) {
+                    expandedKeys.push(id);
+                }
+            }
+        },
+        computed:   {},
+        mounted() {
+        },
+        watch:      {
+            newData: {
+                handler(newVal, oldVal) {
+                    this.setExpanded(newVal, this.expandedKeys);
+                },
+            },
+            /*expandedKeys: {
+                handler(newVal, oldVal) {
+                    this.setExpanded(this.newData, newVal);
+                },
+                // immediate: true
+            },*/
+            filterText(val) {
+                this.$refs.tree.filter(val);
+            },
+        },
+    };
+</script>
+

+ 125 - 0
src/components/management/Layout/EditableTree/EditableTreeItem.vue

@@ -0,0 +1,125 @@
+<template>
+    <div class="editorTreeComponent fsize-m4" :class="{ editorTreeItem: showItem }">
+        <div class="editorTreeContent">
+            <span class="treeShowLabel" @click.stop="() => labelClick(node, data)"
+                  :title="node.data.lable  === undefined ? node.data.name: node.data.lable"
+                  v-text="node.data.lable === undefined ? node.data.name: node.data.lable"></span>
+            <span class="treeCtrlBtn" slot="treeItem">
+              <i class="el-icon-edit " v-if="needControl && data.number !== 0" @click.stop="() => editor(node, data)"></i>
+              <i class="el-icon-plus " v-if="needControl" @click.stop="() => append(node, data)"></i>
+              <i class="el-icon-delete " v-if="needControl && data.number !== 0" @click.stop="remove(node, data)"></i>
+            </span>
+        </div>
+        <el-input v-model="input" ref="treeInput" placeholder="请输入内容"
+                  :key="node.id === undefined ? node.roleId : node.id" :autofocus="true"
+                  @blur="onblurFun" @keydown.native.13.prevent="onEnterFun"></el-input>
+        <!--<MtaStDialog
+                :dVisible.sync="mtaStDialogVisible"
+                dType="warning"
+                :modalAppendToBody="false"
+                dMessage="选中的数据将彻底删除"
+                @comfirmCallback="comfirmCallback()"
+        ></MtaStDialog>-->
+        <el-dialog
+                :close-on-click-modal="false"
+                :visible.sync="mtaStDialogVisible"
+                center
+                class="response-tip-dialog"
+                title="删除"
+        >
+            <div>选中的数据将彻底删除。</div>
+            <span class="dialog-footer" slot="footer">
+                    <el-button @click="comfirmCallback" type="primary">确 定</el-button>
+                    <el-button @click="mtaStDialogVisible = false">取 消</el-button>
+                    </span>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    export default {
+        name:    'EditableTreeItem',
+        props:   {
+            node: {
+                type:    Object,
+                default: {},
+            },
+            data: {
+                type:    Object,
+                default: {},
+            },
+            needControl: {
+                type: Boolean,
+                default: true,
+            }
+        },
+        data() {
+            return {
+                mtaStDialogVisible: false,
+                input:    '',
+                showItem: true,
+                oldValue: '',
+                mydata: '',
+            };
+        },
+        methods: {
+            comfirmCallback() {
+                this.$emit('treeItemRemove', this.mydata);
+            },
+            // label 点击触发事件
+            labelClick(node, data) {
+                this.$emit('labelClick', {
+                    'data': data,
+                    'Node': node,
+                });
+            },
+            // 新增事件触发
+            append(node, data) {
+                this.$emit('treeItemAppend', {
+                    node: node,
+                    data: data,
+                });
+            },
+            // 删除事件触发
+            remove(node, data) {
+                this.mydata = {
+                    node: node,
+                    data: data
+                };
+                this.mtaStDialogVisible = true
+            },
+            // 编辑事件触发
+            editor(node, data) {
+                this.$emit('changeDragState', false);
+                // 记录当前数据缓存
+                this.oldValue = data.lable;
+                this.showItem = false;
+                this.$nextTick(() => {
+                    this.input = data.lable;
+                    this.$refs.treeInput.focus();
+                    this.$emit('drapTreeList', false);
+
+                });
+            },
+            // 失去焦点时触发
+            onEnterFun() {
+                this.$refs.treeInput.blur();
+            },
+            onblurFun() {
+                const treeItemObj = {
+                    node:       this.node,
+                    data:       this.data,
+                    editorText: _.trim(this.input),
+                };
+
+                if (this.oldValue !== this.input) {
+                    this.$emit('treeItemEditor', treeItemObj);
+                }
+                this.$emit('drapTreeList', true);
+                this.showItem = true;
+                this.$emit('changeDragState', true);
+            },
+        },
+    };
+</script>
+

+ 213 - 0
src/components/management/Layout/EditableTreeOrg/EditableTree.vue

@@ -0,0 +1,213 @@
+<template>
+    <div class="editableTree fsize-m6">
+        <el-input
+                placeholder="输入关键字进行过滤"
+                v-model="filterText">
+        </el-input>
+        <div class="wrap-tree">
+            <el-tree
+                    ref="tree"
+                    :data="newData"
+                    :filter-node-method="filterNode"
+                    :node-key="nodeKey"
+                    :default-expanded-keys="stDefaultExpandedKeys"
+                    @node-drag-start="handleDragStart"
+                    @node-drag-enter="handleDragEnter"
+                    @node-drag-leave="handleDragLeave"
+                    @node-drag-over="handleDragOver"
+                    @node-drag-end="handleDragEnd"
+                    @node-drop="handleDrop"
+                    @node-click="handleNodeClick"
+                    @node-expand="handleNodeExpand"
+                    @node-collapse="handleNodeCollapse"
+                    :draggable="treeDraggable"
+                    :allow-drag="allowDrag"
+                    :allow-drop="allowDrop"
+                    :highlight-current="true"
+                    :expand-on-click-node="false">
+                <div class="custom-tree-node" slot-scope="{ node, data }">
+                    <editor-tree-item
+                            :node="node"
+                            :data="data"
+                            @labelClick="handleLabelClick"
+                            @treeItemAppend="treeAppend"
+                            @treeItemRemove="treeRemove"
+                            @changeDragState="changeDragState"
+                            @treeItemEditor="getTreeItemEditor">
+                    </editor-tree-item>
+                </div>
+            </el-tree>
+        </div>
+    </div>
+</template>
+
+<script>
+    import EditorTreeItem               from '@/components/management/Layout/EditableTreeOrg/EditableTreeItem.vue';
+
+    export default {
+        name:       'EditableTree',
+        components: {
+            EditorTreeItem,
+        },
+        props:      {
+            nodeKey:         {
+                type:    String,
+                default: 'id',
+            },
+            newData:         {
+                // 树的数据
+                type:    Array,
+                default: () => arr,
+            },
+            draggable:         {
+                // 树的数据
+                type:    Boolean,
+                default: true,
+            },
+            expandedKeys:    {
+                // 树的数据
+                type:    Array,
+                default: () => [],
+            },
+        },
+        watch:      {
+            newData: {
+                handler(newVal, oldVal) {
+                    console.log('handler', this.expandedKeys);
+                    this.setExpanded(newVal, this.expandedKeys);
+                },
+            },
+            filterText(val) {
+                this.$refs.tree.filter(val);
+            },
+        },
+        data() {
+            return {
+                input:         '',
+                startData:     '',
+                showItem:      true,
+                treeDraggable: this.draggable,
+                filterText:    '',
+                stDefaultExpandedKeys: [],
+            };
+        },
+        methods:    {
+            changeDragState(b) {
+                this.treeDraggable = b;
+            },
+            filterNode(value, data) {
+                // console.log('filter =>', value, data, this.newData);
+                if (!value) {
+                    return true;
+                }
+                return data.lable.indexOf(value) !== -1;
+            },
+            handleLabelClick(data) {
+                this.$refs.tree.setCurrentKey(data.data.id);
+                this.$emit('handleClick', data);
+            },
+            handleNodeClick(data, Node, self) {
+                /*const opt = {data, Node, self};
+                this.$emit('handleClick', opt);*/
+            },
+            handleDragStart(node, ev) {
+                this.startData = node;
+            },
+            handleDragEnter(draggingNode, dropNode, ev) {
+            },
+            handleDragLeave(draggingNode, dropNode, ev) {
+            },
+            handleDragOver(draggingNode, dropNode, ev) {
+            },
+            handleDragEnd(draggingNode, dropNode, dropType, ev) {
+            },
+            // 拖拽放置时触发
+            handleDrop(draggingNode, dropNode, dropType, ev) {
+                this.$emit('handleDrop', dropNode, draggingNode, dropType, ev, this.startData);
+            },
+            // 编辑事件回调触发
+            getTreeItemEditor(data) {
+                this.$emit('treeEditor', data);
+            },
+            // 新增事件回调触发
+            treeAppend(data) {
+                this.$emit('treeAppend', data);
+            },
+            // 删除事件回调触发
+            treeRemove(node, data) {
+                this.$emit('treeRemove', node);
+            },
+            setExpanded(treeData, expandedKeys) {
+                function setTreeChildeExpanded(children, that) {
+                    for (const child of children) {
+                        if (expandedKeys.indexOf(child[that.nodeKey]) > -1) {
+                            that.stDefaultExpandedKeys.push(child[that.nodeKey]);
+                        }
+                        if (child.children) {
+                            setTreeChildeExpanded(child.children, that);
+                        }
+                    }
+                }
+
+                // default expand;
+                if (!expandedKeys || expandedKeys.length === 0) {
+                    for (const treeDataItem of treeData) {
+                        this.stDefaultExpandedKeys.push(treeDataItem[this.nodeKey]);
+                    }
+                } else if (Array.isArray(expandedKeys)) {
+                    for (const treeDataItem of treeData) {
+                        this.stDefaultExpandedKeys.push(treeDataItem[this.nodeKey]);
+                        if (treeDataItem.children) {
+                            setTreeChildeExpanded(treeDataItem.children, this);
+                        }
+                    }
+                }
+            },
+            pushExpandedKey(expandedKeys, id) {
+                if (expandedKeys.indexOf(id) === -1) {
+                    expandedKeys.push(id);
+                }
+            },
+            allowDrag(node) {
+                if (node.data.isConstant && node.data.isConstant === true) {
+                    return true;
+                } else {
+                    return false;
+                }
+            },
+            allowDrop(draggingNode, dropNode, type) {
+                // console.log(draggingNode, dropNode, type);
+                if (draggingNode.data.isConstant && draggingNode.data.isConstant === true
+                    && dropNode.data.isConstant && dropNode.data.isConstant === true) {
+                    return true;
+                } else if (draggingNode.data.isRoot && draggingNode.data.isRoot === true
+                    && dropNode.data.isRoot && dropNode.data.isRoot === true) {
+                    return false;
+                } else {
+                    return false;
+                }
+            },
+            setCurrentKey(id) {
+                this.$refs.tree.setCurrentKey(id);
+            },
+            handleNodeExpand(data) {
+                this.$emit('syncExpandedKeys', {
+                    data:     data,
+                    isExpand: true,
+                });
+            },
+            handleNodeCollapse(data) {
+                this.$emit('syncExpandedKeys', {
+                    data:     data,
+                    isExpand: false,
+                });
+            },
+        },
+        computed:   {
+        },
+        created() {
+            this.setExpanded(this.newData, this.stDefaultExpandedKeys);
+        }
+    };
+</script>
+

+ 123 - 0
src/components/management/Layout/EditableTreeOrg/EditableTreeItem.vue

@@ -0,0 +1,123 @@
+<template>
+    <div class="editorTreeComponent fsize-m4" :class="{ editorTreeItem: showItem }">
+        <div class="editorTreeContent">
+            <span  class="treeShowLabel org" @click.stop="() => labelClick(node, data)"
+                   :title="node.data.lable">{{ node.data.lable }}</span>
+            <span v-if="getAuth.orgFlag === 0" class="treeCtrlBtn" slot="treeItem">
+                <span v-if="data.isRoot && data.isRoot === true">
+
+                    <i class="el-icon-edit " v-if="!(data.isAdmin  && data.isAdmin === true)" @click.stop="() => editor(node, data)"></i>
+                    <i class="el-icon-plus " @click.stop="() => append(node, data)"></i>
+                </span>
+                <span v-else-if="data.isConstant && data.isConstant === true">
+                    <i class="el-icon-edit " @click.stop="() => editor(node, data)"></i>
+                    <i class="el-icon-plus " @click.stop="() => append(node, data)"></i>
+                    <i class="el-icon-delete " @click.stop="remove(node, data)"></i>
+                </span>
+            </span>
+        </div>
+        <el-input v-model="input" ref="treeInput" placeholder="请输入内容" :key="node.id" :autofocus="true"
+                  @blur="onblurFun" @keydown.native.13.prevent="onEnterFun"></el-input>
+        <MtaStDialog
+                :dVisible.sync="BatchFailureDLShow"
+                dType="warning"
+                dMessage="选中的数据将彻底删除"
+                @comfirmCallback="ToSaveDelete()"
+        ></MtaStDialog>
+    </div>
+</template>
+
+<script>
+    import { mapGetters } from 'vuex';
+
+    export default {
+        name:     'EditableTreeItem',
+        props:    {
+            node: {
+                type:    Object,
+                default: {},
+            },
+            data: {
+                type:    Object,
+                default: {},
+            },
+        },
+        data() {
+            return {
+                input:              '',
+                showItem:           true,
+                oldValue:           '',
+                BatchFailureDLShow: false,
+                mydata:             '',
+            };
+        },
+        computed: {
+            getAuthOrgId() {
+                return this.getAuth.orgId;
+            },
+            ...mapGetters([
+                              'getAuth',
+                          ]),
+        },
+        methods:  {
+            ToSaveDelete() {
+                this.$emit('treeItemRemove', this.mydata);
+            },
+            // label 点击触发事件
+            labelClick(node, data) {
+                this.$emit('labelClick', {
+                    'data': data,
+                    'Node': node,
+                });
+            },
+            // 新增事件触发
+            append(node, data) {
+                this.$emit('treeItemAppend', data);
+            },
+            // 删除事件触发
+            remove(node, data) {
+                this.BatchFailureDLShow = true;
+                this.mydata = node;
+
+            },
+            // 编辑事件触发
+            editor(node, data) {
+                this.$emit('changeDragState', false);
+                // 记录当前数据缓存
+                this.oldValue = data.lable;
+                this.showItem = false;
+                this.$nextTick(() => {
+                    this.input = data.lable;
+                    this.$refs.treeInput.focus();
+                    this.$emit('drapTreeList', false);
+
+                });
+            },
+            // 失去焦点时触发
+            onEnterFun() {
+                this.$refs.treeInput.blur();
+            },
+            onblurFun() {
+                const treeItemObj = {
+                    node:       this.node,
+                    data:       this.data,
+                    editorText: _.trim(this.input),
+                };
+
+                if (this.oldValue !== this.input) {
+                    this.$emit('treeItemEditor', treeItemObj);
+                }
+                this.$emit('drapTreeList', true);
+                this.showItem = true;
+                this.$emit('changeDragState', true);
+            },
+            handleDialogClose() {
+                this.mydata = '';
+                this.BatchFailureDLShow = false;
+            }
+
+        },
+    };
+</script>
+
+

+ 22 - 0
src/components/management/Layout/Footer.vue

@@ -0,0 +1,22 @@
+<template>
+    <div class="mta-footer">
+        <p>{{footText }}</p>
+    </div>
+</template>
+
+<script>
+    import { mapGetters } from 'vuex';
+
+    export default {
+        name:     'Footer',
+        computed: {
+            footText() {
+                let str = 'Copyright © 2019 llisoft.com All rights reserved 大连栋科软件工程有限公司 版权所有 辽ICP备09022904号-19';
+                return this.getReplaceContext && this.getReplaceContext.footText ? this.getReplaceContext.footText : str;
+            },
+            ...mapGetters([
+                              'getReplaceContext',
+                          ]),
+        },
+    };
+</script>

+ 138 - 0
src/components/management/Layout/FormFieldset/FormFieldset.vue

@@ -0,0 +1,138 @@
+<template>
+    <div class="mta-formFieldset fsize-m4-l">
+        <fieldset class="re_fieldset">
+            <legend class="bt_legend"><i></i>{{list.name}}</legend>
+<!--            :indeterminate="isIndeterminate"  未全选中状态-->
+            <el-checkbox v-if="list.name !== '首页'" class="qx_box"  v-model="checkAll"
+                         @change="handleCheckAllChange">全选
+            </el-checkbox>
+            <el-checkbox v-else class="qx_box"  v-model="checkAll"
+                         @change="handleCheckAllChange">首页
+            </el-checkbox>
+            <div class="group_box">
+                <el-checkbox-group v-model="checkedList" @change="handleCheckedChange">
+                    <el-checkbox v-for="child in list.children" :label="child.menuId" :key="child.menuId">
+                        {{child.name}}
+                    </el-checkbox>
+                </el-checkbox-group>
+            </div>
+        </fieldset>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: "FormFieldset",
+        props: {
+            list: { // 标题数据
+                type: Object,
+                default: () => {
+                },
+            },
+            OriginCheckedList: { // 每项checkbox数据
+                type: Array,
+                default: () => [],
+            },
+        },
+        data() {
+            return {
+                isIndeterminate: true,
+                checkAll: false,
+                checkedList: this.OriginCheckedList,
+                backListData: {},
+            };
+        },
+        methods: {
+            checkForm() {
+                this.checkAll = this.list.checked.length === this.list.children.length+1;
+            },
+            handleCheckedChange(value) {
+
+                if (value.includes(this.list.menuId)){
+                    value.splice(value.indexOf(this.list.menuId),1)
+                }
+                const checkedCount = value.length;
+                this.checkAll = checkedCount === this.list.children.length;
+                this.isIndeterminate = checkedCount > 0 && checkedCount < this.list.children.length;
+                this.backListData = {
+                    checkedMenuListFromChild: value,
+                    originList: this.list.children.map((item) => item.menuId),
+                };
+                if (this.backListData.checkedMenuListFromChild.length > 0 && !this.backListData.checkedMenuListFromChild.includes(this.list.menuId)) {
+                    this.backListData.checkedMenuListFromChild.push(this.list.menuId);
+                }
+                if (this.backListData.checkedMenuListFromChild.length === 1 && this.backListData.checkedMenuListFromChild.includes(this.list.menuId)) {
+                    this.backListData.checkedMenuListFromChild = [];
+                }
+                if (this.backListData.checkedMenuListFromChild.length === 0 ) {
+                    const checkedListArr = this.list.children.map((item) => item.menuId);
+                    checkedListArr.push(this.list.menuId);
+                    this.backListData = {
+                        checkedMenuListFromChild: [],
+                        originList: checkedListArr,
+                    };
+                }
+                this.$emit('checkedRAMIChild', this.backListData);
+            },
+            handleCheckAllChange(val) {
+                const checkedListArr = this.list.children.map((item) => item.menuId);
+                checkedListArr.push(this.list.menuId);
+                this.checkedList = val ? checkedListArr : [];
+                this.isIndeterminate = false;
+                this.backListData = {
+                    checkedMenuListFromChild: this.checkedList,
+                    originList: checkedListArr,
+                };
+                this.$emit('checkedRAMIChild', this.backListData);
+            },
+        },
+        created() {
+            this.checkForm();
+        }
+    }
+</script>
+
+<!--<style lang="scss" scoped>
+    .mta-formFieldset {
+        display: block;
+
+        fieldset {
+            padding: 10px;
+        }
+
+        .qx_box {
+            width: 60px;
+            float: left;
+            margin-right: 10px;
+        }
+
+        .bt_legend {
+            /*font-family: PingFangSC-Regular;*/
+            font-size: 16px;
+            /*color: #333333;*/
+        }
+
+        .group_box {
+            margin-left: 70px;
+        }
+
+        .el-checkbox {
+            margin-bottom: 8px;
+        }
+
+        .bt_legend i {
+            width: 4px;
+            height: 11px;
+            //background: #2196f5;
+            display: block;
+            margin-right: 10px;
+            float: left;
+            margin-top: 4px;
+        }
+
+        .re_fieldset {
+            border: 0;
+        }
+
+    }
+</style>-->

+ 482 - 0
src/components/management/Layout/Header.vue

@@ -0,0 +1,482 @@
+<template>
+    <div class="mta-aframe-header">
+        <div class="mta-logo-title">
+            <img :src="logoImg" alt="logo">
+            <span>{{logoText}}</span>
+        </div>
+        <nav>
+            <ul style="display: flex;flex-direction: row;align-items: center">
+                <li style="margin-right: 30px;" v-if="false">
+                    <el-popover
+                            placement="bottom"
+                            width="200"
+                            v-model="visible1"
+                            trigger="click">
+                        <div class="header-help-buttons">
+                            <el-button type="primary" @click="resetHelpStatus">新手引导</el-button>
+                            <el-button type="warning" @click="openHelpCenter">帮助文档</el-button>
+                        </div>
+                        <el-button slot="reference" style="background: none;color: #FFF" icon="el-icon-question">
+                            帮助中心
+                        </el-button>
+                    </el-popover>
+                </li>
+                <li style="margin-right: 30px;" v-if="showAppFlag">
+                    <el-button @click="goAppDownload" icon="el-icon-download" slot="reference"
+                               style="background: none;color: #FFF">
+                        APP下载
+                    </el-button>
+                </li>
+                <li v-if="getcode !== 'admin'" style="margin-right: 30px;">
+                    <popover-login></popover-login>
+                </li>
+                <li>
+                    <el-popover
+                            placement="bottom"
+                            width="160"
+                            trigger="click"
+                            v-model="visible3"
+                    >
+                        <ul class="userControl">
+                            <li>
+                                <el-button type="text" icon="el-icon-key" @click="changePassword">修改密码</el-button>
+                            </li>
+                            <li v-if="!(getTenantCode === 'admin')">
+                                <el-button type="text" icon="el-icon-s-order" @click="changeInformation">修改资料
+                                </el-button>
+                            </li>
+                            <!--<li v-if="isDev">
+                                <el-button type="text" icon="el-icon-location" @click="configThemeShow">系统配置
+                                </el-button>
+                            </li>-->
+                            <li>
+                                <el-button type="text" icon="el-icon-setting" @click="exitSystem">退出系统</el-button>
+                            </li>
+                        </ul>
+                        <span class="h-right-img" slot="reference" @click="changeStatus">
+                          <img :src="headerImg" alt="头像">
+                          <span></span>
+                        </span>
+                    </el-popover>
+                </li>
+            </ul>
+        </nav>
+        <el-dialog
+                title="修改密码"
+                :visible.sync="headerPasswordDialog"
+                :close-on-click-modal="false"
+                @close="closePassDl"
+                class="response-small-dialog"
+                center>
+            <el-form :model="HeaderPassChangeDate" status-icon :rules="HeaderPassRules" ref="ruleHeaderForm"
+                     class="demo-ruleForm response-dialog-form-input" label-width="100px">
+                <el-form-item label="原始密码:" prop="orgPass">
+                    <el-input v-model="HeaderPassChangeDate.orgPass"></el-input>
+                </el-form-item>
+                <el-form-item label="新的密码:" prop="pass">
+                    <el-input type="password" v-model="HeaderPassChangeDate.pass" autocomplete="off"></el-input>
+                </el-form-item>
+                <el-form-item label="确认密码:" prop="checkPass">
+                    <el-input type="password" v-model="HeaderPassChangeDate.checkPass" autocomplete="off"></el-input>
+                </el-form-item>
+            </el-form>
+
+            <span slot="footer" class="dialog-footer">
+        <el-button @click="headerPasswordDialog = false">取 消</el-button>
+        <el-button type="primary" @click="saveHeaderPass">确 定</el-button>
+      </span>
+        </el-dialog>
+        <el-dialog
+                title="资料编辑"
+                :visible.sync="editorMessage"
+                :close-on-click-modal="false"
+                @close="ClosePassDl2('editorUserInfo')"
+                class="response-big-dialog"
+                center>
+            <el-form ref="formUserInfor" :rules="formRules3" status-icon :model="userInfoData" label-width="85px">
+                <div class="editorUserWrap response-dialog-form-input">
+                    <div>
+                        <el-form-item label="用户名:" prop="userName">
+                            <el-input v-model="userInfoData.userName"
+                                      @blur="formatDataFun(userInfoData.userName)"></el-input>
+                        </el-form-item>
+                        <el-form-item label="真实姓名:" prop="realName">
+                            <el-input v-model="userInfoData.realName"></el-input>
+                        </el-form-item>
+                        <el-form-item label="出生年月:" prop="birthday">
+                            <el-date-picker
+                                    v-model="userInfoData.birthday"
+                                    type="date"
+                                    :default-value="userInfoData.defaultBirthday"
+                                    format="yyyy-MM-dd"
+                                    value-format="yyyy-MM-dd"
+                                    placeholder="选择日期">
+                            </el-date-picker>
+                        </el-form-item>
+                        <el-form-item label="电话号码:" prop="tel">
+                            <el-input v-model="userInfoData.tel"></el-input>
+                        </el-form-item>
+                    </div>
+                    <div>
+                        <el-form-item label="性别:" prop="gender" label-width="90px">
+                            <el-radio-group v-model="userInfoData.gender">
+                                <el-radio border :label="1">男</el-radio>
+                                <el-radio border :label="2">女</el-radio>
+                            </el-radio-group>
+                        </el-form-item>
+                        <el-form-item label="邮箱:" prop="email" label-width="90px">
+                            <el-input v-model="userInfoData.email"></el-input>
+                        </el-form-item>
+                        <el-form-item label="身份证号:" prop="idcard" label-width="90px">
+                            <el-input v-model="userInfoData.idcard"></el-input>
+                        </el-form-item>
+                    </div>
+                    <div>
+                        <!--<mta-upload-al-cloud :imageUrl="imageUrl"
+                                             @uploadFileStart="uploadFileStart"></mta-upload-al-cloud>-->
+
+                        <upload-file :imageUrl="imageUrl" :showBtnFlag="false"
+                                     @getFileUrl="getImageUrl"></upload-file>
+                        <h5 class="picture-size-h5">(最佳尺寸为65*65像素)</h5>
+                    </div>
+                </div>
+            </el-form>
+            <span slot="footer" class="dialog-footer">
+        <el-button @click="cancelSaveUserInfo">取 消</el-button>
+        <el-button type="primary" @click="saveUserInfo">确 定</el-button>
+      </span>
+        </el-dialog>
+        <system-config :show="systemThemeDialog" @close="systemThemeDialogClose"></system-config>
+        <!--<video-guid-content code="welcome" :visible.sync="wxyTestFun"></video-guid-content>
+        <video-guid-content code="welcome2" :visible.sync="wxyTestFun2"></video-guid-content>-->
+
+    </div>
+</template>
+
+<script>
+    import {
+        getAdminUserMypass,
+        getAdminUserMyUpdate,
+        getAdminUserMyInfo,
+        getExitStatus,
+    }                              from '@/api/user';
+    import MtaUploadAlCloud        from '@/components/management/Layout/UploadAlCloud/UploadAlCloud';
+    import uploadFile              from '@/components/management/Layout/UploadAlCloud/uploadFile';
+    import SystemConfig            from '@/components/management/Layout/SystemConfig/SystemThemeConfig';
+    import { mapGetters }          from 'vuex';
+    import { setAuth, removeAuth } from '@/utils/auth';
+    import {
+        setUserIcon,
+        checkUrlIsNotHttpUrl,
+        aliUploadAndDownLoadFun,
+        getTrimData,
+    }                              from '@/utils/common';
+    import videoGuidContent        from '@/components/management/Layout/videoBanner/dialogGuideContent';
+    import { setGuide }            from '@/utils/guide';
+    import PopoverLogin            from '@/components/management/Layout/popoverLogin/popoverLogin';
+
+    export default {
+        name:       'Header',
+        data() {
+            const validatePass2 = (rule, value, callback) => {
+                if (value === '') {
+                    callback(new Error('请再次输入密码'));
+                } else if (value !== this.HeaderPassChangeDate.pass) {
+                    callback(new Error('两次输入密码不一致!'));
+                } else {
+                    callback();
+                }
+            };
+
+            return {
+                visible1:             false,
+                visible2:             false,
+                visible3:             false,
+                isDev:                process.env.NODE_ENV !== 'production',
+                wxyTestFun:           false,
+                wxyTestFun2:          false,
+                visible:              false,
+                headerPasswordDialog: false,
+                HeaderPassChangeDate: {
+                    orgPass:   '',
+                    pass:      '',
+                    checkPass: '',
+                },
+                HeaderPassRules:      {
+                    orgPass:   [
+                        { message: '请输入原始密码', trigger: 'blur', required: true },
+                    ],
+                    pass:      [
+                        { message: '请输入新密码', trigger: 'blur', required: true },
+                    ],
+                    checkPass: [
+                        { validator: validatePass2, trigger: 'blur', required: true },
+                    ],
+                },
+                editorMessage:        false,
+                userInfoData:         {
+                    userName:        '',
+                    birthday:        '',
+                    defaultBirthday: '',
+                    email:           '',
+                    gender:          '',
+                    userId:          '',
+                    idcard:          '',
+                    realName:        '',
+                    tel:             '',
+                },
+                formRules3:           {
+                    userName: [
+                        { trigger: 'blur', required: true, message: '请输入用户名' },
+                    ],
+                },
+                imageUrl:             '',
+                systemThemeDialog:    false,
+                headerimgUrl:         require('../../../assets/images/default/user-img.png'),
+                showAppFlag:          false,
+            };
+        },
+        components: {
+            MtaUploadAlCloud,
+            SystemConfig,
+            videoGuidContent,
+            PopoverLogin,
+            uploadFile,
+        },
+        computed:   {
+            //个人头像
+            headerImg() {
+                return checkUrlIsNotHttpUrl(this.getReplaceIcon.headImg)
+                       ? this.headerimgUrl
+                       : this.getReplaceIcon.headImg;
+            },
+            logoImg() {
+                if (this.getReplaceContext === null) {
+                    return;
+                }
+                return this.getReplaceContext.logoImg;
+            },
+            logoText() {
+                if (this.getReplaceContext === null) {
+                    return;
+                }
+                return this.getReplaceContext.logoText;
+            },
+            getcode() {
+                return this.getTenantCode;
+            },
+            getClient() {
+                if (this.getAuth === null) {
+                    return;
+                }
+                return this.getAuth.client;
+            },
+            getAframe() {
+                if (this.getAuth === null) {
+                    return;
+                }
+                return this.getAuth.aframe;
+            },
+            //个人头像
+            ...mapGetters([
+                              'getReplaceContext',
+                              'getTenantCode',
+                              'getAuth',
+                              'getReplaceIcon',
+                              'getGuide',
+                          ]),
+        },
+        methods:    {
+            getImageUrl(data) {
+                this.imageUrl = data;
+            },
+            formatDataFun(data) {
+                this.userInfoData.userName = getTrimData(data);
+            },
+            goAppDownload() {
+                let url = this.getAuth.appUrl;
+                window.open(url);
+            },
+            checkString(data) {
+                if (data === '') {
+                    return '';
+                }
+
+                if (data === null) {
+                    return '';
+                }
+
+                if (data === undefined) {
+                    return '';
+                }
+
+                return data;
+            },
+            resetHelpStatus() {
+                let guide = this.getGuide;
+                guide = {
+                    show:    true,
+                    details: {},
+                };
+                setGuide(guide);
+                location.reload();
+                this.visible1 = false;
+            },
+            /**
+             * 主题配置
+             */
+            configThemeShow() {
+                this.visible3 = false;
+                this.systemThemeDialog = true;
+            },
+            /**
+             * 主题配置弹窗关闭
+             */
+            systemThemeDialogClose(data) {
+                this.systemThemeDialog = data;
+            },
+            openHelpCenter() {
+                this.visible1 = false;
+                /* if (process.env.VUE_APP_PROJECT === 'sa') {
+                     window.location.href = this.getAuth.docMaita;
+                 }
+                 if (process.env.VUE_APP_PROJECT === 'qinggu') {
+                     window.location.href = this.getAuth.docQinggu;
+                 }*/
+
+            },
+            // 保存用户信息
+            saveUserInfo() {
+                const option = {
+                    birthday: this.userInfoData.birthday,
+                    email:    this.userInfoData.email,
+                    gender:   this.userInfoData.gender,
+                    icon:     this.imageUrl === undefined ? '' : this.imageUrl,
+                    idcard:   this.userInfoData.idcard,
+                    realName: this.userInfoData.realName,
+                    userId:   this.userInfoData.userId,
+                    userName: this.userInfoData.userName,
+                    tel:      this.userInfoData.tel,
+                };
+                // 请求后台
+                getAdminUserMyUpdate(option)
+                .then(res => {
+                    if (res.data) {
+                        this.editorMessage = false;
+                        // 个人头像 待定
+                        const opt = {
+                            headImg: option.icon,
+                        };
+                        setUserIcon(opt);
+                        this.$message.success('修改成功');
+                    }
+                }).catch(err => {
+                    // this.$message.error('请确定验证是否通过');
+                    console.error('错误', err);
+                });
+            },
+            // 取消
+            cancelSaveUserInfo() {
+                this.editorMessage = false;
+            },
+            changeStatus() {
+                this.visible = !this.visible;
+            },
+            changePassword() {
+                this.headerPasswordDialog = true;
+                this.visible3 = false;
+            },
+            // 确认修改
+            saveHeaderPass() {
+                this.$refs.ruleHeaderForm.validate((valid) => {
+                    if (valid) {
+                        // 请求后台
+                        const options = {
+                            passwordNew: this.HeaderPassChangeDate.pass,
+                            passwordOld: this.HeaderPassChangeDate.orgPass,
+                        };
+
+                        getAdminUserMypass(options)
+                        .then(res => {
+                            if (res.code === 0 && res.data) {
+                                this.headerPasswordDialog = false;
+                                this.$message.success('修改成功');
+                            } else {
+                                this.$message.error('修改失败');
+                            }
+                        });
+                    } else {
+                        this.$message.error('请确定验证是否通过');
+                        return false;
+                    }
+                });
+            },
+            //
+            closePassDl() {
+                this.$refs.ruleHeaderForm.resetFields();
+            },
+            ClosePassDl2() {
+                this.$refs.formUserInfor.resetFields();
+            },
+            /**
+             * 上传文件
+             */
+            uploadFileStart(params) {
+                if (!params) {
+                    return;
+                }
+                aliUploadAndDownLoadFun(params, 'resource/', 'images/')
+                .then(res => {
+                    this.imageUrl = `${res.downloadUrl}/${res.key}`;
+                })
+                .catch(err => {
+                    console.log(err);
+                });
+            },
+            changeInformation() {
+                this.visible3 = false;
+                getAdminUserMyInfo()
+                .then(res => {
+                    const userInfoObj = res.data;
+                    this.userInfoData.userName = userInfoObj.userName;
+                    this.userInfoData.birthday = userInfoObj.birthday === null
+                                                 ? new Date('1991-05-01')
+                                                 : new Date(userInfoObj.birthday);
+                    this.userInfoData.defaultBirthday = new Date(userInfoObj.birthday);
+                    this.imageUrl = userInfoObj.icon;
+                    this.userInfoData.email = userInfoObj.email;
+                    this.userInfoData.gender = userInfoObj.gender;
+                    this.userInfoData.idcard = userInfoObj.idcard;
+                    this.userInfoData.realName = userInfoObj.realName;
+                    this.userInfoData.tel = userInfoObj.tel;
+                    this.userInfoData.userId = userInfoObj.userId;
+                    this.editorMessage = true;
+                }).catch(err => {
+                    console.error('获取用户信息', err);
+                });
+            },
+            exitSystem() {
+                this.visible3 = false;
+                getExitStatus().then(res => {
+                    if (res.code === 0 && res.data) {
+                        setTimeout(() => {
+                            removeAuth();
+                        }, 500)
+                        this.$router.push({ path: '/a/login' });
+                        // setAuth({});
+                        this.$message.success('系统退出成功');
+                    } else {
+                        this.$message.error('系统退出失败');
+                    }
+
+                });
+
+            },
+        },
+        created() {
+            if (this.getAuth && this.getAuth.appUrl) {
+                this.showAppFlag = true;
+            }
+        },
+    };
+</script>

+ 116 - 0
src/components/management/Layout/ImageDownloadPreview/ImageDownloadPreview.vue

@@ -0,0 +1,116 @@
+<template>
+    <ImgageDownloadPreviewBase
+            :btnStyle="btnStyle"
+            :imageStyle="imageStyle"
+            :myData="BASEDATA"
+            :typeCode="typeCode"
+            @download="handleDownload"
+    />
+</template>
+
+<script>
+    /**
+     * @summary 缩略图 业务对接组件
+     * @author wxy
+     * @date 2020/3/27
+     */
+    import ImgageDownloadPreviewBase from './ImgageDownloadPreviewBase';
+
+    export default {
+        name:       'ImageDownloadPreview',
+        components: {
+            [ImgageDownloadPreviewBase.name]: ImgageDownloadPreviewBase,
+        },
+        props:      {
+            // 业务识别 code
+            typeCode:   {
+                type:     String,
+                required: true,
+            },
+            // 图片地址数据
+            imgUrl:     {
+                type:     String,
+                required: true,
+            },
+            // 按钮样式修正
+            btnStyle:   {
+                type:    Object,
+                default: () => ({}),
+            },
+            // 缩略图样式修正
+            imageStyle: {
+                type:    Object,
+                default: () => ({}),
+            },
+        },
+        data() {
+            return {
+                // 业务数据转化
+                BASEDATA: {
+                    smallImgUrl: '',
+                    imgUrlList:  [],
+                    btnIsShow:   true,
+                },
+            };
+        },
+        methods:    {
+
+            /**
+             * @summary 业务鉴别函数
+             * @des     根据typeCode 进行业务区分掉用不同初始化函数 修正数据
+             * @author  wxy
+             * @date    2020/3/27
+             */
+            checkTypeCode() {
+                //
+                switch (this.typeCode) {
+                    case 'rengongpingjuan':
+                        this.handleChengjiDataProcessing();
+                        break;
+                    default:
+                        throw new Error('ImgageDownloadPreview:未找到业务识别码:typeCode字段映射');
+                }
+            },
+            /**
+             * @summary 人工pingjuan 业务数据转换
+             * @author  wxy
+             * @date    2020/3/27
+             */
+            handleChengjiDataProcessing() {
+                // 初始化业务数据
+                this.BASEDATA = {
+                    smallImgUrl: '',
+                    imgUrlList:  [],
+                    btnIsShow:   true,
+                };
+
+                // 组件业务数据整理
+                this.BASEDATA.btnIsShow = true;
+                this.BASEDATA.smallImgUrl = this.imgUrl;
+                this.BASEDATA.imgUrlList.push(this.imgUrl);
+
+
+            },
+            /**
+             * @summary 反向提供业务触发功能 至 页面
+             * @param data
+             */
+            handleDownload(data) {
+                this.$emit('download', data);
+            },
+        },
+        watch:      {
+            imgUrl: {
+                handler(a, b) {
+                    // 传入数据变更 业务校验开始
+                    this.checkTypeCode();
+                },
+                immediate: true,
+            },
+        },
+    };
+</script>
+
+<style scoped>
+
+</style>

+ 18 - 0
src/components/management/Layout/ImageDownloadPreview/ImageDownloadPreviewSlot.vue

@@ -0,0 +1,18 @@
+<template>
+    <div class="mta-ImageDownloadPreviewSlot">
+        <slot name="image"></slot>
+        <slot name="deleteBtn"></slot>
+    </div>
+</template>
+
+<script>
+    /**
+     * @summary 布局 组件 缩略图布局
+     *
+     */
+    export default {
+        name: 'ImageDownloadPreviewSlot',
+    };
+</script>
+
+<style scoped></style>

+ 94 - 0
src/components/management/Layout/ImageDownloadPreview/ImgageDownloadPreviewBase.vue

@@ -0,0 +1,94 @@
+<template>
+    <ImageDownloadPreviewSlot class="ImageDownloadPreviewSlot">
+        <template slot="image">
+            <el-image
+                    :preview-src-list="imgUrlList"
+                    :src="smallImgUrl"
+                    :style="imageStyle"
+                    class="my-image-Preview">
+            </el-image>
+        </template>
+        <template slot="deleteBtn">
+            <el-button @click="handleDownload" type="text" v-if="btnIsShow">
+                <i :style="btnStyle" class="el-icon-download myDownloadBtn"></i>
+            </el-button>
+        </template>
+    </ImageDownloadPreviewSlot>
+</template>
+
+<script>
+    /**
+     * @summary 业务层组件 提供缩略图预览与下载功能
+     * @author wxy
+     * @date 2020/3/27
+     */
+    import ImageDownloadPreviewSlot from './ImageDownloadPreviewSlot';
+
+    export default {
+        name:       'ImgageDownloadPreviewBase',
+        props:      {
+            /**
+             * 业务识别字段
+             */
+            typeCode:   {
+                type:     String,
+                required: true,
+            },
+            /**
+             * 数据结构 【smallImgUrl: string, imgUrlList: array】
+             */
+            myData:     {
+                type:      Object,
+                validator: function (value) {
+                    // 这个值必须匹配下列字符串中的一个
+                    return (value.hasOwnProperty('smallImgUrl') && value.hasOwnProperty('imgUrlList') && value.hasOwnProperty('btnIsShow'));
+                },
+                require:   true,
+            },
+            /**
+             * 下载按钮样式
+             */
+            btnStyle:   {
+                type:    Object,
+                default: () => ({}),
+            },
+            /**
+             * 缩略图图片样式
+             */
+            imageStyle: {
+                type:    Object,
+                default: () => ({}),
+            },
+        },
+        components: {
+            [ImageDownloadPreviewSlot.name]: ImageDownloadPreviewSlot,
+        },
+        data() {
+            return {
+                smallImgUrl: '',
+                imgUrlList:  [],
+                btnIsShow:   true,
+            };
+        },
+        watch:      {
+            myData: {
+                handler(a, b) {
+                    this.smallImgUrl = a.smallImgUrl;
+                    this.imgUrlList = a.imgUrlList;
+                    this.btnIsShow = a.btnIsShow;
+                },
+                immediate: true,
+                deep:      true,
+            },
+        },
+        methods:    {
+            /**
+             * 下载 业务功能触发 推送
+             */
+            handleDownload() {
+                this.$emit('download', this.smallImgUrl);
+            },
+        },
+    };
+</script>
+

+ 272 - 0
src/components/management/Layout/InfiniteScrollInput/InfiniteScrollInput3.vue

@@ -0,0 +1,272 @@
+<template>
+    <div class="Mta-InfiniteScrollInput3" :ref="currentRef">
+        <el-input
+                :placeholder="placeholder"
+                :requestData="requestData"
+                :clickInputName="clickInputName"
+                v-model="inputValue"
+                v-on:input="watchNum"
+                @focus="handleOnFocus"
+        ></el-input>
+        <div class="Infinite-wrap-box">
+            <div class="infinite-list" v-if="showUl">
+                <ul  v-infinite-scroll="load"
+                     infinite-scroll-disabled="busy"
+                     :infinite-scroll-immediate="false"
+                >
+                    <li v-for="(item,index) in dataArr" :key="index" class="infinite-list-item" @click="chooseData(item)">
+                        <span>{{item[structure[0]]}}</span>
+                        <span class="mta-li-span2" v-if="structure[1] !== undefined">{{item[structure[1]]}}</span>
+                    </li>
+                </ul>
+                <p v-if="noMore">没有更多了</p>
+                <p v-if="noData">无符合标准记录</p>
+            </div>
+        </div>
+
+    </div>
+</template>
+
+<script>
+    //   describe: 模糊搜索公共方法 author: Wgy date:2019-07-25
+    // :requestData 接口需要额外入参 使用   {默认入参为 page name size}
+    // :dataSelect   选择数据
+    // @loadData2="loadUserLists2"  下拉加载 配合requestData使用 增加额外入参
+    import {fuzzySearch} from '@/api/exam'
+    export default {
+        name: "scrollInput",
+        props: {
+            placeholder: {
+                type: String,
+                default: '请选择用户'
+            },
+            requestData:{
+                type: Object,
+            },
+            clickInputName:{
+                type: String,
+                default:'user'
+            },
+            currentRef: {
+                require: true,
+                type: String,
+            },
+            structure: {
+                type: Array,
+                default: () => ['userName', 'realName']
+            },
+        },
+        watch: {
+            inputValue(curVal, oldVal) {
+                if(curVal===''){
+                    this.showUl=false;
+                    this.noMore = false;
+                    this.busy = true;
+                    this.pageNumber = 1;
+                    this.chooseData(curVal)
+                    return
+                }
+                if(curVal!==oldVal&&this.chooseFlag){
+                    // 实现input连续输入,只发一次请求
+                    this.noMore=false;
+                    this.pageNumber=1;
+                    clearTimeout(this.timeout);
+                    //this.handleSearch(curVal);
+                    this.timeout = setTimeout(() => {
+                        if(curVal!==''){
+                            this.handleSearch(curVal);
+                        }
+                    }, 100);
+                }
+            }
+        },
+        data() {
+            return {
+                inputValue: '',
+                showUl: false,
+                dataArr: [],
+                noMore: false,// 下拉阀值显示
+                noData: false,// 是否有值 flag
+                chooseFlag: true,//  选择数据flag
+                busy: true,//  下拉加载flag  true 为禁止
+                pageNumber: 1,
+                timeout: null,// 长轮循
+                blurFlag: false,// 焦点flag
+            }
+        },
+        computed: {
+
+        },
+        methods: {
+            watchNum(val){
+            },
+
+            handleOnFocus(){
+                this.chooseFlag = true;
+                const data = {
+                    user:'/admin/user/search',
+                    kaoshi:'/admin/kaoshi/search',
+                    kaoshiAll:'/admin/kaoshi/search/all',
+                    supportUser:'/admin/user/search',
+                    supportTenant:'/admin/tenant/search',
+                    searchCourse:'/admin/kecheng/search',
+                    searchCourseAll:'/admin/kecheng/search/all',
+                    searchKaoshiCourse:'/admin/kecheng/kaoshi/search',
+                };
+                window.localStorage.setItem(`getFuzzySearchUrl_key`, JSON.stringify(data[this.clickInputName]));
+
+            },
+
+            handleSearch(inputval){
+                let value = {name:this.inputValue,page:1,size:10};
+                let req = Object.assign(value,this.requestData);
+                if(req.name!==''){
+                    fuzzySearch(req).then(res =>{
+                        if(this.inputValue === inputval){
+                            this.dataArr = [];
+                            if(res.data.data.length===0){
+                                this.noData = true;
+                                this.noMore = false;
+                                this.busy = true;
+                            }else {
+                                this.busy = false;
+                                this.noData = false;
+                                this.dataArr = res.data.data;
+                            }
+                            this.showUl = true;
+                            this.blurFlag = false;
+
+                        }
+                    })
+                }
+
+            },
+            //失去焦点
+            handleOnBlur(e) {
+                if (this.blurFlag === false) {
+                    this.showUl = false;
+                    this.noMore = false;
+                    // this.chooseFlag = true;
+                    this.noData = false;
+                    this.pageNumber = 1;
+                    this.inputValue = ''
+                }else {
+                    this.showUl = false;
+                    this.noMore = false;
+                    // this.chooseFlag = true;
+                    this.noData = false;
+                    this.pageNumber = 1;
+                }
+            },
+
+            chooseData(data) {
+                console.log(data);
+                if(data===''){
+                    this.$emit('dataSelect'+this.clickInputName, data)
+                }else {
+                    this.inputValue = data[this.structure[0]];
+                    this.showUl = false;
+                    this.chooseFlag = false;
+                    this.blurFlag = true;
+                    this.$emit('dataSelect'+this.clickInputName, data)
+                }
+
+            },
+
+            load() {
+                // if (this.inputValue === '') {
+                //     return;
+                // }
+                this.$emit('loadData2')
+                this.pageNumber = this.pageNumber+1;
+                let value = {name:this.inputValue,page:this.pageNumber,size:10};
+                let req = Object.assign(value,this.requestData);
+                fuzzySearch(req).then(res =>{
+                    if (req.page > Math.ceil(Number(res.data.total)/ 10)) {
+                        this.noMore = true;
+                        this.busy = true;
+                        this.noData = false;
+                        return
+                    }
+                    res.data.data.forEach(item => {
+                        this.dataArr.push(item);
+                    });
+
+                })
+            }
+        },
+        beforeMount() {
+            document.addEventListener('click', this.handleOnBlur);
+        },
+        beforeDestroy() {
+            document.removeEventListener('click', this.handleOnBlur);
+        },
+    }
+</script>
+
+<style lang="scss" scoped>
+    .Mta-InfiniteScrollInput3 {
+        /*width: 100%;
+        margin: 0 20px 0 0px;*/
+        position: relative;
+        .show-Ul {
+            height: 200px;
+
+        }
+
+        .Infinite-wrap-box {
+            position: absolute;
+            z-index: 99;
+            width: 100%;
+        }
+
+        div.infinite-list {
+            box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
+            border-radius: 4px;
+            border: 1px solid #E4E7ED;
+            box-sizing: border-box;
+            background-color: #FFF;
+            margin: 5px 0;
+            display: flex;
+            flex-direction: column;
+            font-size: 14px;
+
+            > ul {
+                height: 180px;
+                overflow: auto;
+                margin: 0;
+            }
+
+            li {
+                padding: 5px;
+                display: flex;
+                flex-direction: row;
+                justify-content: space-between;
+
+                .mta-li-span2 {
+                    font-size: 10px;
+                    color: #ccc;
+                }
+            }
+
+            li:hover {
+                background-color: #ccc;
+            }
+        }
+
+        p {
+            text-align: center;
+            font-size: 10px;
+            color: #ccc;
+            margin: 8px 0;
+        }
+        @media screen and (max-width: 1440px) {
+            div.infinite-list {
+                font-size: 13px;
+            }
+            p {
+                margin: 5px 0;
+            }
+        }
+    }
+</style>

+ 185 - 0
src/components/management/Layout/PaperPreview/PaperPreview.vue

@@ -0,0 +1,185 @@
+<template>
+    <div class="mta-paper-preview">
+        <div class="title-h3">{{previewData.name}}</div>
+
+        <div v-for="(opt, index) in previewData.duanluoList" :key="index">
+            <mta-shiti-structure
+                    :title="opt.name"
+            >
+                <!-- 单选题 -->
+                <div v-if="opt.danxuan && opt.danxuan.length > 0">
+                    <mta-single-choice
+                            v-for="(item,index) in opt.danxuan" :key="index"
+                            :questionData.sync="item"
+                            :shitiIndex="item.onlyNum"
+                            :typeCase="typeCase"
+                            @reply="reply"
+                    ></mta-single-choice>
+
+
+                </div>
+                <!-- 多选题 -->
+                <div v-if="opt.duoxuan && opt.duoxuan.length > 0">
+                    <mta-multiple-choice
+                            v-for="(item,index)  in opt.duoxuan" :key="index"
+                            :questionData="item"
+                            :typeCase="typeCase"
+                            :shitiIndex="item.onlyNum"
+                            @reply="reply2"
+                    ></mta-multiple-choice>
+                </div>
+                <!-- 判断题 -->
+                <div v-if="opt.panduan && opt.panduan.length > 0">
+                    <mta-true-or-false
+                            v-for="(item,index)  in opt.panduan" :key="index"
+                            :questionData="item"
+                            :typeCase="typeCase"
+                            :shitiIndex="item.onlyNum"
+                            @reply="reply3"
+                    ></mta-true-or-false>
+                </div>
+                <!-- 填空题 -->
+                <div v-if="opt.tiankong && opt.tiankong.length > 0">
+                    <mta-gap-filling
+                            v-for="(item,index)  in opt.tiankong" :key="index"
+                            :questionData="item"
+                            :typeCase="typeCase"
+                            :shitiIndex="item.onlyNum"
+                            @reply="reply4"
+                    ></mta-gap-filling>
+                </div>
+                <!-- 简答题 -->
+                <div v-if="opt.jianda && opt.jianda.length > 0">
+                    <mta-short-answer-question
+                            v-for="(item,index)  in opt.jianda" :key="index"
+                            :questionData="item"
+                            :typeCase="typeCase"
+                            @reply="reply5"
+                            :shitiIndex="item.onlyNum"
+
+                    ></mta-short-answer-question>
+
+
+                </div>
+                <!-- 阅读 -->
+                <div v-if="opt.jianda && opt.yuedu.length > 0">
+                    <mta-read-the-topic
+                            v-for="(item,index)  in opt.yuedu" :key="index"
+                            :questionData="item"
+                            :typeCase="typeCase"
+                            :shitiIndex="item.onlyNum"
+                    ></mta-read-the-topic>
+
+                </div>
+            </mta-shiti-structure>
+        </div>
+    </div>
+
+</template>
+
+<script>
+    import MtaShitiStructure from './shitiStructure.vue'
+    import MtaSingleChoice from '@/components/client/QuestionsForCuoti/SingleChoice.vue';
+    import MtaTrueOrFalse from '@/components/client/QuestionsForCuoti/TrueOrFalse.vue';
+    import MtaMultipleChoice from '@/components/client/QuestionsForCuoti/MultipleChoice.vue';
+    import MtaGapFilling from '@/components/client/QuestionsForCuoti/GapFilling.vue';
+    import MtaShortAnswerQuestion from '@/components/client/QuestionsForCuoti/ShortAnswerQuestion.vue';
+    import MtaReadTheTopic from '@/components/client/QuestionsForCuoti/ReadTheTopic.vue';
+
+    export default {
+        name: "PaperPreview",
+        components: {
+            MtaSingleChoice,
+            MtaMultipleChoice,
+            MtaTrueOrFalse,
+            MtaGapFilling,
+            MtaShortAnswerQuestion,
+            MtaReadTheTopic,
+            MtaShitiStructure,
+        },
+        props: {
+            DuanLuoData: {
+                require: true,
+                type: Object,
+            },
+            // 组件接环状态, kaoshi,geren,yulan,cuoti2
+            typeCase: {
+                type: String,
+                default: 'kaoshi'
+            }
+        },
+        data() {
+            return {
+                num: 1,
+                previewData: {},
+            }
+        },
+        watch: {
+            DuanLuoData: {
+                handler(newVal, oldVal) {
+
+                    if (newVal.duanluoList) {
+                        let indexNum = 1;
+                        newVal.duanluoList.forEach(item => {
+                            if (item.danxuan && item.danxuan.length > 0) {
+                                item.danxuan.forEach(a => {
+                                    a.onlyNum = indexNum++;
+                                    return a;
+                                })
+                            }
+                            if (item.duoxuan && item.duoxuan.length > 0) {
+                                item.duoxuan.forEach(b => {
+                                    b.onlyNum = indexNum++;
+                                    return b;
+                                })
+                            }
+                            if (item.panduan && item.panduan.length > 0) {
+                                item.panduan.forEach(c => {
+                                    c.onlyNum = indexNum++;
+                                    return c;
+                                })
+                            }
+                            if (item.tiankong && item.tiankong.length > 0) {
+                                item.tiankong.forEach(c => {
+                                    c.onlyNum = indexNum++;
+                                    return c;
+                                })
+                            }
+                            if (item.jianda && item.jianda.length > 0) {
+                                item.jianda.forEach(d => {
+                                    d.onlyNum = indexNum++;
+                                    return d;
+                                })
+                            }
+                            if (item.yuedu && item.yuedu.length > 0) {
+                                item.yuedu.forEach(e => {
+                                    e.onlyNum = indexNum++;
+                                    return e;
+                                })
+                            }
+                            return item;
+                        })
+                    }
+
+                    this.previewData = newVal;
+                },
+                immediate: true
+            }
+        },
+
+        methods: {
+            reply(data) {
+            },
+            reply2(data) {
+            },
+            reply3(data) {
+            },
+            reply4(data) {
+            },
+            reply5(data) {
+            }
+
+        }
+    }
+</script>
+

+ 45 - 0
src/components/management/Layout/PaperPreview/shitiStructure.vue

@@ -0,0 +1,45 @@
+<template>
+    <div class="mta-shiti-structure">
+        <div class="mta-ss-title">{{title}}</div>
+        <div class="mta-ss-content">
+            <slot></slot>
+        </div>
+
+    </div>
+</template>
+
+<script>
+    export default {
+        name: "shitiStructure",
+        props: {
+            title: {
+                require: true,
+                type: String
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+.mta-shiti-structure {
+    border: 1px solid #ccc;
+    margin: 10px 20px 32px 20px;
+
+    .mta-ss-content {
+        padding: 20px 30px;
+        word-break: break-word;
+    }
+
+    .mta-ss-title {
+        text-align: left;
+        padding-left: 30px;
+        height: 40px;
+        line-height: 40px;
+        font-weight: bolder;
+        font-size: 16px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+}
+</style>

+ 268 - 0
src/components/management/Layout/SelectInput/SelectInputTree.vue

@@ -0,0 +1,268 @@
+<template>
+    <div class="selectInput" :ref="currentRef">
+        <el-input
+                ref="inputRef"
+                :placeholder="placeholder"
+                v-model="inputValue"
+                @focus="handleOnFocus"
+                :readonly="false"
+                @keyup.native="killKeyFun"
+                @clear="handleOnClear"
+                clearable
+        >
+            <template slot="suffix">
+                <!--<i class="el-select__caret el-input__icon el-icon-arrow-up" :class="suffixStyle"
+                   @click.stop="inputSuffixClick"></i>-->
+                <i class="el-select__caret el-input__icon el-icon-arrow-up"
+                   :class="{'isReverse':suffixStyle, 'showIcon':!showIcon}"
+                   @click.stop="inputSuffixClick"></i>
+            </template>
+        </el-input>
+
+        <div class="tree-wrap-box show-Tree">
+
+            <!--<el-scrollbar class="selectInputRef-scroll" wrap-class="scrollbar-wrapper">-->
+            <perfect-scrollbar>
+                <collapse-transition>
+                    <el-tree
+                            class="filter-tree"
+                            :data="originalTreeData"
+                            :props="treeDefaultProps"
+                            default-expand-all
+                            v-if="showTree"
+                            @node-click="handleNodeClick"
+                            ref="tree">
+                    </el-tree>
+                </collapse-transition>
+            </perfect-scrollbar>
+            <!--</el-scrollbar>-->
+
+        </div>
+
+    </div>
+</template>
+
+<script>
+    /**
+     * 使用须知:
+     * input下拉树 需要指定currentRef
+     * stClassifyId --- currentRef 内含shiti
+     * stLevelId --- currentRef 内含Difficulty
+     * stKnowledgeId --- currentRef 内含Knowledge
+     */
+
+    import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
+    import CollapseTransition   from 'element-ui/lib/transitions/collapse-transition';
+
+    export default {
+        name:       'SelectInputTree',
+        props:      {
+            placeholder:       {
+                type:    String,
+                default: '请选择',
+            },
+            selectDate:        { // 双向绑定的选中数据
+                type:    Object,
+                default: () => {
+                    return {};
+                },
+                require: true,
+            },
+            defaultSelectData: { // 默认选中的数据
+                type:    Object,
+                default: () => {
+                    return {};
+                },
+            },
+            originalTreeData:  { // 树的数据
+                type:    Array,
+                default: () => {
+                    return [];
+                },
+                require: true,
+            },
+            treeDefaultProps:  { // 树的结构数据例子
+                type:    Object,
+                default: () => {
+                    return {};
+                },
+                require: true,
+            },
+            currentRef:        { // 当前dom索引
+                type:    String,
+                require: true,
+            },
+        },
+        computed:   {
+            suffixStyle: function () {
+                /*return {
+                    isReverse: this.suffixIsReverse,
+                };*/
+                return this.suffixIsReverse;
+            },
+            showIcon() {
+                if (this.inputValue) {
+                    return false;
+                } else {
+                    return true;
+                }
+            },
+        },
+        data() {
+            return {
+                inputValue:      '',
+                showTree:        false,
+                // selectInputIcon: 'el-icon-arrow-up',
+                suffixIsReverse: true,
+            };
+        },
+        components: {
+            CollapseTransition,
+            PerfectScrollbar,
+        },
+        watch:      {
+            defaultSelectData:          {
+                handler(val) {
+                    // input框赋值
+                    this.inputValue = val.lable;
+                    // 同步外侧selectData
+                    this.$emit('update:selectDate', val);
+                },
+                immediate: true,
+            },
+            'selectDate.stClassifyId':  {
+                handler(newV, oldV) {
+                    this.handleId(newV, oldV);
+                },
+            },
+            'selectDate.stLevelId':     {
+                handler(newV, oldV) {
+                    this.handleId(newV, oldV);
+                },
+            },
+            'selectDate.id':     {
+                handler(newV, oldV) {
+                    this.handleId(newV, oldV);
+                },
+            },
+        },
+        methods:    {
+            handleId(newV, oldV) {
+                // console.log(this.currentRef);
+                if (this.currentRef.indexOf('shiti') > -1 && this.currentRef.indexOf('Type') > -1) {
+                    if (newV === undefined) {
+                        this.inputValue = '';
+                        return;
+                    }
+                    this.setTreeVal(newV);
+                } else if (this.currentRef.indexOf('Difficulty') > -1) {
+                    if (newV === undefined) {
+                        this.inputValue = '';
+                        return;
+                    }
+                    this.setTreeVal(newV);
+                }  else {
+                    // throw new Error('currentRef命名不符合规范');
+                    this.setTreeVal(newV);
+                }
+            },
+            handleOnClear() {
+                this.$emit('update:selectDate', {});
+            },
+            killKeyFun(event) {
+                this.inputValue = '';
+                return false;
+            },
+            // 清理缓存数据
+            clearInputData() {
+                this.inputValue = '';
+            },
+            // 获取焦点时触发事件
+            handleOnFocus() {
+                this.showTree = true;
+                this.suffixIsReverse = false;
+                // this.selectInputIcon = 'el-icon-arrow-down';
+            },
+            // 失去焦点时触发事件
+            handleOnBlur(e) {
+                if (this.$refs[this.currentRef].contains(e.target) === false) {
+                    this.showTree = false;
+                    this.suffixIsReverse = true;
+                    // this.selectInputIcon = 'el-icon-arrow-up';
+                }
+            },
+            /**
+             * Tree 节点点击后触发事件
+             * @param data
+             * @param node
+             * @param self
+             */
+            handleNodeClick(data, node, self) {
+
+                // 同步input显示
+                this.inputValue = data.lable;
+                // input图标改变
+                this.suffixIsReverse = true;
+                // this.selectInputIcon = 'el-icon-arrow-up';
+                // tree 组件隐藏
+                this.showTree = false;
+                // 同步外侧selectData && 业务查看返回数据 同步vuex
+                this.$emit('update:selectDate', data);
+                this.$emit('changeSelectData', data);
+                this.$nextTick(() => {
+                    this.$emit('callDepartmentValidate', data);
+                });
+            },
+
+            findNodeObj(children, idName, id) {
+                for (const node of children) {
+                    if (parseInt(node[idName]) === parseInt(id)) {
+                        return node;
+                    }
+                    if (node.children instanceof Array) {
+                        let _node = this.findNodeObj(node.children, idName, id);
+                        if (_node) {
+                            return _node;
+                        }
+                    }
+                }
+                return null;
+
+            },
+            setTreeVal(id) {
+                /**
+                 * {id: 7,
+                 * lable: "二级 3-1"}
+                 */
+                let idName = '';
+                if (this.currentRef.indexOf('shiti') > -1 && this.currentRef.indexOf('Type') > -1) {
+                    // idName = 'stClassifyId';
+                    idName = 'id';
+                } else if (this.currentRef.indexOf('Difficulty') > -1) {
+                    // idName = 'stLevelId';
+                    idName = 'id';
+                } else {
+                    idName = 'id';
+                    // throw new Error('currentRef命名不符合规范');
+                }
+                let node = this.findNodeObj(this.originalTreeData, idName, id);
+                if (node === null) {
+                    throw new Error('id不存在');
+                }
+                this.inputValue = node.lable;
+                this.$emit('update:selectDate', node);
+            },
+            inputSuffixClick() {
+                this.suffixIsReverse = !this.suffixIsReverse;
+                this.showTree = !this.showTree;
+            },
+        },
+
+        beforeMount() {
+            document.addEventListener('click', this.handleOnBlur);
+        },
+        beforeDestroy() {
+            document.removeEventListener('click', this.handleOnBlur);
+        },
+    };
+</script>

+ 275 - 0
src/components/management/Layout/SelectInput/StSelectInputTree.vue

@@ -0,0 +1,275 @@
+<template>
+    <StSelectInputTreeSlot>
+        <template slot="input">
+            <el-input
+                    :placeholder="placeholder"
+                    :readonly="false"
+                    :ref="currentRef"
+                    @clear="handleOnClear"
+                    @focus="handleOnFocus"
+                    @keyup.native="killKeyFun"
+                    clearable
+                    v-model="inputValue"
+            >
+                <template slot="suffix">
+                    <!--<i class="el-select__caret el-input__icon el-icon-arrow-up" :class="suffixStyle"
+                       @click.stop="inputSuffixClick"></i>-->
+                    <i :class="{'isReverse':suffixStyle, 'showIcon':!showIcon}"
+                       @click.stop="inputSuffixClick"
+                       class="el-select__caret el-input__icon el-icon-arrow-up"></i>
+                </template>
+            </el-input>
+        </template>
+        <template slot="tree">
+            <div class="st-picker-panel" v-if="showTree">
+                <el-tree
+                        :data="originalTreeData"
+                        :props="treeDefaultProps"
+                        @node-click="handleNodeClick"
+                        class="filter-tree"
+                        default-expand-all
+                        ref="tree">
+                </el-tree>
+            </div>
+        </template>
+    </StSelectInputTreeSlot>
+</template>
+
+<script>
+    /**
+     * 使用须知:
+     * input下拉树 需要指定currentRef
+     * stClassifyId --- currentRef 内含shiti
+     * stLevelId --- currentRef 内含Difficulty
+     * stKnowledgeId --- currentRef 内含Knowledge
+     */
+
+
+    import StSelectInputTreeSlot   from '@/components/management/Layout/SelectInput/StSelectInputTreeSlot.vue';
+
+    export default {
+        name:       'SelectInputTree',
+        props:      {
+            type:              {
+                type:    String,
+                require: true,
+            },
+            selectDate:        { // 双向绑定的选中数据
+                type:    Object,
+                default: () => {
+                    return {};
+                },
+                require: true,
+            },
+            defaultSelectData: { // 默认选中的数据
+                type:    Object,
+                default: () => {
+                    return {};
+                },
+            },
+            originalTreeData:  { // 树的数据
+                type:    Array,
+                default: () => {
+                    return [];
+                },
+                require: true,
+            },
+            mapping:  { // 映射
+                type:    Object,
+                default: () => {
+                    return {};
+                },
+                require: true,
+            },
+        },
+        computed:   {
+            suffixStyle: function () {
+                /*return {
+                    isReverse: this.suffixIsReverse,
+                };*/
+                return this.suffixIsReverse;
+            },
+            showIcon() {
+                if (this.inputValue) {
+                    return false;
+                } else {
+                    return true;
+                }
+            },
+        },
+        data() {
+            return {
+                placeholder:     '请选择',
+                inputValue:      '',
+                showTree:        false,
+                // selectInputIcon: 'el-icon-arrow-up',
+                suffixIsReverse: true,
+                currentRef: '',
+                treeDefaultProps: {},
+            };
+        },
+        components: {
+            StSelectInputTreeSlot,
+        },
+        watch:      {
+            selectDate:                {
+                handler(val) {
+                    // input框赋值
+                    this.inputValue = val[this.mapping['label'] ? this.mapping['label'] : 'label'];
+                    let id = '';
+                    if (val.id) {
+                        id = val.id;
+                    } else if (val.stClassifyId) {
+                        id = val.stClassifyId;
+                    } else if (val.stLevelId) {
+                        id = val.stLevelId;
+                    } else {
+                        // throw new Error('Id找不到 或者 不符合规范');
+                        // return;
+                    }
+
+                    this.handleId(id);
+                },
+                immediate: true,
+                deep:      true,
+            },
+        },
+        created() {
+            if (this.type === 'department') {
+                this.placeholder = '请选择部门';
+            } else if (this.type === 'curse') {
+                this.placeholder = '请选择课程分类';
+            } else if (this.type === 'exam') {
+                this.placeholder = '请选择考试分类';
+            } else if (this.type === 'kejian') {
+                this.placeholder = '请选择课件分类';
+            } else if (this.type === 'shitiType') {
+                this.placeholder = '请选择试题分类';
+            } else if (this.type === 'shitiDifficulty') {
+                this.placeholder = '请选择难度';
+            }
+            this.currentRef = 'selectInput' + Math.floor(Math.random() * 100000);
+            this.treeDefaultProps = {
+                label: this.mapping['label'] ? this.mapping['label'] : 'label',
+                data:  'children',
+            }
+        },
+        methods:    {
+            handleId(newV, oldV) {
+                if (newV === undefined) {
+                    this.inputValue = '';
+                    return;
+                }
+                this.setTreeVal(newV);
+            },
+            handleOnClear() {
+                this.$emit('update:selectDate', {});
+            },
+            killKeyFun(event) {
+                this.inputValue = '';
+                return false;
+            },
+            // 清理缓存数据
+            clearInputData() {
+                this.inputValue = '';
+            },
+            // 获取焦点时触发事件
+            handleOnFocus() {
+                this.showTree = true;
+                this.suffixIsReverse = false;
+                // this.selectInputIcon = 'el-icon-arrow-down';
+            },
+            // 失去焦点时触发事件
+            handleOnBlur(e) {
+                if (this.$refs[this.currentRef].$el.parentElement.contains(e.target) === false) {
+                    this.showTree = false;
+                    this.suffixIsReverse = true;
+                    // this.selectInputIcon = 'el-icon-arrow-up';
+                }
+            },
+            /**
+             * Tree 节点点击后触发事件
+             * @param data
+             * @param node
+             * @param self
+             */
+            handleNodeClick(data, node, self) {
+
+                // 同步input显示
+                this.inputValue = data[this.mapping['label'] ? this.mapping['label'] : 'label'];
+                // input图标改变
+                this.suffixIsReverse = true;
+                // this.selectInputIcon = 'el-icon-arrow-up';
+                // tree 组件隐藏
+                this.showTree = false;
+                // 同步外侧selectData && 业务查看返回数据 同步vuex
+                this.$emit('update:selectDate', data);
+                this.$emit('changeSelectData', data);
+                this.$nextTick(() => {
+                    this.$emit('callDepartmentValidate', data);
+                });
+            },
+
+            findNodeObj(children, idName, id) {
+                for (const node of children) {
+                    if (parseInt(node[idName]) === parseInt(id)) {
+                        return node;
+                    }
+                    if (node.children instanceof Array) {
+                        let _node = this.findNodeObj(node.children, idName, id);
+                        if (_node) {
+                            return _node;
+                        }
+                    }
+                }
+                return null;
+
+            },
+            setTreeVal(id) {
+                /**
+                 * {id: 7,
+                 * lable: "二级 3-1"}
+                 */
+                if (!id) {
+                    this.inputValue = '';
+                    // this.$emit('update:selectDate', {});
+                    return;
+                }
+                let node = this.findNodeObj(this.originalTreeData, 'id', id);
+                if (node === null) {
+                    console.log(this.originalTreeData, 'id', id);
+                    throw new Error('id不存在');
+                }
+                this.inputValue = node[this.mapping['label'] ? this.mapping['label'] : 'label'];
+                this.$emit('update:selectDate', node);
+            },
+            inputSuffixClick() {
+                this.suffixIsReverse = !this.suffixIsReverse;
+                this.showTree = !this.showTree;
+            },
+        },
+
+        beforeMount() {
+            document.addEventListener('click', this.handleOnBlur);
+        },
+        beforeDestroy() {
+            document.removeEventListener('click', this.handleOnBlur);
+        },
+    };
+</script>
+<style lang="scss" scoped>
+    .st-picker-panel {
+        color: #606266;
+        /*border: 1px solid #E4E7ED;*/
+        /*box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);*/
+        background: #FFF;
+        border-radius: 4px;
+        line-height: 30px;
+        /**
+         * @summary 修改 高度闪烁问题
+         * @author Wxy
+         * @date 2020/4/26
+         */
+        max-height: 250px;
+    }
+</style>

+ 23 - 0
src/components/management/Layout/SelectInput/StSelectInputTreeSlot.vue

@@ -0,0 +1,23 @@
+<template>
+    <div class="selectInput">
+        <slot name="input"></slot>
+        <div class="tree-wrap-box show-Tree">
+                <collapse-transition>
+                    <slot name="tree"></slot>
+                </collapse-transition>
+        </div>
+    </div>
+</template>
+<script>
+    import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
+    import CollapseTransition   from 'element-ui/lib/transitions/collapse-transition';
+    export default {
+        components: {
+            CollapseTransition,
+            PerfectScrollbar,
+        },
+        mounted() {
+            // console.log(this.$slots.input);
+        }
+    }
+</script>

+ 206 - 0
src/components/management/Layout/SelectInput/clientSelectInputTree.vue

@@ -0,0 +1,206 @@
+<template>
+    <el-select :clearable="clearable" :placeholder="selectPlaceholder" :value="valueTitle" @clear="clearHandle" ref="selectblur">
+        <el-input
+                :placeholder="placeholder"
+                class="selectInput"
+                v-model="filterText">
+        </el-input>
+
+        <el-option :label="valueTitle" :value="valueTitle" class="options">
+            <el-tree :accordion="accordion"
+                     :data="options"
+                     :default-expanded-keys="defaultExpandedKey"
+                     :filter-node-method="filterNode"
+                     :node-key="props.value"
+                     :props="props"
+                     @node-click="handleNodeClick"
+                     class="c-select-input-tree"
+                     id="tree-option"
+                     ref="selectTree">
+            </el-tree>
+        </el-option>
+    </el-select>
+</template>
+
+
+<script>
+    export default {
+        //  describe: 下拉树组件 author: Wgy date:2020-04-02
+        /*
+        * 支持节点过滤   可清空  支持初始化默认参数
+        *
+        *   isClearable:true,      // 可清空(可选)
+            isAccordion:true,      // 可收起(可选)
+            自定义配置项
+        *
+        * */
+        name:    'clientSelectInputTree',
+        props:   {
+            // 配置项
+            props:       {
+                type:    Object,
+                default: () => {
+                    return {
+                        value:    'id',
+                        label:    'lable',
+                        children: 'children',
+                    };
+                },
+            },
+            // 选项列表数据(树形结构的对象数组)
+            options:     {
+                type:    Array,
+                default: () => {
+                    return [];
+                },
+            },
+            // 初始值
+            value:       {
+                type:    Number,
+                default: () => {
+                    return null;
+                },
+            },
+            // 可清空选项
+            clearable:   {
+                type:    Boolean,
+                default: () => {
+                    return true;
+                },
+            },
+            // 自动收起
+            accordion:   {
+                type:    Boolean,
+                default: () => {
+                    return true;
+                },
+            },
+            placeholder: {
+                type:    String,
+                default: () => {
+                    return '检索关键字';
+                },
+            },
+            selectPlaceholder: {
+                type:    String,
+                default: `请选择`,
+            }
+        },
+        data() {
+            return {
+                filterText:         '', //过滤值
+                valueId:            this.value,    // 初始值
+                valueTitle:         '', //selcet 值
+                defaultExpandedKey: [],  //默认展开的节点的 key 的数组
+            };
+        },
+        mounted() {
+            this.initHandle();
+        },
+        methods: {
+            // 初始化值
+            initHandle() {
+                if (this.valueId) {
+                    this.valueTitle = this.$refs.selectTree.getNode(this.valueId).data[this.props.label];     // 初始化显示
+                    this.$refs.selectTree.setCurrentKey(this.valueId);       // 设置默认选中
+                    this.defaultExpandedKey = [this.valueId];      // 设置默认展开
+                }
+                this.initScroll();
+            },
+            // 初始化滚动条
+            initScroll() {
+                this.$nextTick(() => {
+                    // let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0];
+                    // @date 2020/06/23 @summary 修正下拉树样式
+                    let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap');
+                    let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar');
+                    // scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;';
+                    // @date 2020/06/23 @summary 修正下拉树样式
+                    scrollWrap.forEach(ele => ele.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;')
+                    scrollBar.forEach(ele => ele.style.width = 0);
+                });
+            },
+            // 点击节点
+            handleNodeClick(node) {
+                // console.log(node);
+                this.valueTitle = node[this.props.label];
+                this.valueId = node[this.props.value];
+                this.$emit('getValue', node);
+                this.defaultExpandedKey = [];
+                this.$refs.selectblur.blur();
+                //this.filterText = ''
+            },
+            // 清除选中
+            clearHandle() {
+                this.valueTitle = '';
+                this.valueId = null;
+                this.defaultExpandedKey = [];
+                this.clearSelected();
+                this.$emit('getValue', {});
+            },
+            // 清空选中样式
+            clearSelected() {
+                let allNode = document.querySelectorAll('#tree-option .el-tree-node');
+                allNode.forEach((element) => element.classList.remove('is-current'));
+            },
+            //过滤节点
+            filterNode(value, data) {
+                if (!value) {
+                    return true;
+                }
+                return data.lable.indexOf(value) !== -1;
+            },
+        },
+        watch:   {
+            value(){
+                this.valueId = this.value
+                this.initHandle()
+            },
+            filterText(val) {
+                this.$refs.selectTree.filter(val);
+            },
+        },
+    };
+</script>
+
+<style scoped>
+    .el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
+        height:     auto;
+        max-height: 274px;
+        padding:    0;
+        overflow:   hidden;
+        overflow-y: auto;
+    }
+
+    .el-select-dropdown__item.selected {
+        font-weight: normal;
+    }
+
+    ul li >>> .el-tree .el-tree-node__content {
+        height:  auto;
+        padding: 0 20px;
+    }
+
+    .el-tree-node__label {
+        font-weight: normal;
+    }
+
+    .el-tree >>> .el-tree-node__label {
+        font-size: 13px;
+    }
+
+    .el-tree >>> .is-current .el-tree-node__label {
+        font-weight: 700;
+    }
+
+    .el-tree >>> .is-current .el-tree-node__children .el-tree-node__label {
+        color:       #606266;
+        font-weight: normal;
+    }
+
+    .selectInput {
+        padding:    0 5px;
+        box-sizing: border-box;
+    }
+</style>
+

+ 60 - 0
src/components/management/Layout/Sidebar/SidebarItem.vue

@@ -0,0 +1,60 @@
+<template>
+    <!-- 传入如果有children 时 -->
+    <el-submenu v-if="sidebar.children && sidebar.children.length > 0" :key="sidebar.url" :index="stIndex.toString()">
+
+        <!-- 分组名 -->
+        <template slot="title">
+            <i v-if="sidebar.classUrl" :class="sidebar.classUrl"></i>
+            <!--<st-svg
+                    height="23px"
+                    width="23px"
+                    v-if="sidebar.classUrl"
+                    :class="sidebar.classUrl"
+                    :name="sidebar.classUrl"
+                    :color="$route.path === sidebar.url ? activeColor : defaultColor"
+            ></st-svg>-->
+            <span class="mta-menu-span" slot="title">{{sidebar.name}}</span>
+        </template>
+        <!-- 子项如果有children时 -->
+        <sidebar-item
+                :activeColor="activeColor"
+                :defaultColor="defaultColor"
+                v-for="child in sidebar.children"
+                :sidebar="child" :key="child.url"></sidebar-item>
+    </el-submenu>
+    <!-- children 不存在时 :route="{path: item.url}"-->
+    <el-menu-item v-else :index="sidebar.url" >
+        <i v-if="sidebar.classUrl" :class="sidebar.classUrl"></i>
+        <!--<st-svg
+                height="23px"
+                width="23px"
+                v-if="sidebar.classUrl"
+                :class="sidebar.classUrl"
+                :name="sidebar.classUrl"
+                :color="$route.path === sidebar.url ? activeColor : defaultColor"
+        ></st-svg>-->
+        <span class="mta-menu-span" slot="title">{{sidebar.name}}</span>
+    </el-menu-item>
+</template>
+
+<script>
+export default {
+    name: "SidebarItem",
+    props: {
+        sidebar: { // 菜单数据
+            type: Object,
+            default: () => {},
+        },
+        stIndex: {
+            type: Number,
+        },
+        defaultColor: {
+            require: true
+        },
+        activeColor: {
+            require: true
+        }
+    },
+}
+</script>
+

+ 113 - 0
src/components/management/Layout/SlidingBlock/SlidingBlock.js

@@ -0,0 +1,113 @@
+export const SlidingBlock = {
+    data() {
+        return {
+            sliderData: {},
+            appKey: 'FFFF0N00000000007EC0'
+        }
+    },
+    methods: {
+        /**
+         * 串联加载指定的脚本
+         * 串联加载[异步]逐个加载,每个加载完成后加载下一个
+         * 全部加载完成后执行回调
+         * @param array|string 指定的脚本们
+         * @param function 成功后回调的函数
+         * @return array 所有生成的脚本元素对象数组
+         * 异步加载js后运行回调函数callback / scripts 为数组或字符串
+         */
+        seriesLoadScripts(scripts, callback) {
+            if (typeof (scripts) != "object") var scripts = [scripts];
+            var HEAD = document.getElementsByTagName("head").item(0) || document.documentElement;
+            var s = new Array(), last = scripts.length - 1, recursiveLoad = function (i) { //递归
+                s[i] = document.createElement("script");
+                s[i].setAttribute("type", "text/javascript");
+                s[i].onload = s[i].onreadystatechange = function () { //Attach handlers for all browsers
+                    if (!/*@cc_on!@*/0 || this.readyState == "loaded" || this.readyState == "complete") {
+                        this.onload = this.onreadystatechange = null;
+                        this.parentNode.removeChild(this);
+                        if (i != last) recursiveLoad(i + 1); else if (typeof (callback) == "function") callback();
+                    }
+                };
+                s[i].setAttribute("src", scripts[i]);
+                HEAD.appendChild(s[i]);
+            };
+            recursiveLoad(0);
+        },
+        // 滑块
+        sliderFun() {
+            const nc_token = [this.appKey, (new Date()).getTime(), Math.random()].join(':');
+            const NC_Opt =  {
+                //声明滑动验证需要渲染的目标元素ID。
+                renderTo: "#your-dom-id",
+                //应用类型标识。它和使用场景标识(scene字段)一起决定了滑动验证的业务场景与后端对应使用的策略模型。您可以在人机验证控制台的配置管理页签找到对应的appkey字段值,请务必正确填写。
+                appkey: this.appKey,
+                //使用场景标识。它和应用类型标识(appkey字段)一起决定了滑动验证的业务场景与后端对应使用的策略模型。您可以在人机验证控制台的配置管理页签找到对应的scene值,请务必正确填写。
+                scene: "nc_message",
+                //滑动验证码的主键,请勿将该字段定义为固定值。确保每个用户每次打开页面时,其token值都是不同的。系统默认的格式为:”您的appkey”+”时间戳”+”随机数”。
+                token: nc_token,
+                //滑动条的宽度。
+                customWidth: 300,
+                //业务键字段,可为空。为便于线上问题的排查,建议您按照线上问题定位文档中推荐的方法配置该字段值。
+                trans:{"key1":"code0"},
+                //通过Dom的ID属性自动填写trans业务键,可为空。建议您按照线上问题定位文档中推荐的方法配置该字段值。
+                elementID: ["usernameID"],
+                //是否自定义配置底层采集组件。如无特殊场景,请使用默认值(0),即不自定义配置底层采集组件。
+                is_Opt: 0,
+                //语言。PC端Web页面场景默认支持18国语言,详细配置方法请参见自定义文案与多语言文档。
+                language: "cn",
+                //是否启用。一般情况,保持默认值(true)即可。
+                isEnabled: true,
+                //内部网络请求的超时时间。一般情况建议保持默认值(3000ms)。
+                timeout: 3000,
+                //允许服务器超时重复次数,默认5次。超过重复次数后将触发报错。
+                times:5,
+                //用于自定义滑动验证各项请求的接口地址。一般情况,请勿配置该参数。
+                apimap: {
+                    // 'analyze': '//a.com/nocaptcha/analyze.jsonp',
+                    // 'get_captcha': '//b.com/get_captcha/ver3',
+                    // 'get_captcha': '//pin3.aliyun.com/get_captcha/ver3'
+                    // 'get_img': '//c.com/get_img',
+                    // 'checkcode': '//d.com/captcha/checkcode.jsonp',
+                    // 'umid_Url': '//e.com/security/umscript/3.2.1/um.js',
+                    // 'uab_Url': '//aeu.alicdn.com/js/uac/909.js',
+                    // 'umid_serUrl': 'https://g.com/service/um.json'
+                },
+                //前端滑动验证通过时会触发该回调参数。您可以在该回调参数中将请求标识(token)、会话ID(sessionid)、签名串(sig)字段记录下来,随业务请求一同发送至您的服务端调用验签。
+                callback:     (data) => {
+                    this.sliderData = Object.assign({}, data, {
+                        appkey: NC_Opt.appkey,
+                        scene: NC_Opt.scene
+                    });
+                }
+            };
+
+            let date11 = () => {
+
+                let _date = new Date();
+                let year=_date.getFullYear().toString();
+                let month=_date.getMonth().toString();
+                let day=_date.getDate().toString();
+
+                return year+month+day;
+            };
+            let c = date11();
+
+            const scripts = [
+                `https://g.alicdn.com/sd/ncpc/nc.js?t=${c}`
+            ];
+            this.seriesLoadScripts(scripts, function () {
+                var nc = new noCaptcha(NC_Opt);
+                    nc.upLang('cn', {
+                    _startTEXT: "请按住滑块,拖动到最右边",
+                    _yesTEXT: "验证通过",
+                    _error300: "哎呀,出错了,点击<a href=\"javascript:__nc.reset()\">刷新</a>再来一次",
+                    _errorNetwork: "网络不给力,请<a href=\"javascript:__nc.reset()\">点击刷新</a>",
+                });
+            })
+
+        },
+    },
+    mounted() {
+        this.sliderFun();
+    }
+};

+ 243 - 0
src/components/management/Layout/SystemConfig/SystemThemeConfig.vue

@@ -0,0 +1,243 @@
+<template>
+    <el-dialog
+            title="系统配置"
+            :visible.sync="dialogStatus"
+            :close-on-click-modal="false"
+            :modal="false"
+            @close="configThemeClose"
+            class="response-tip-dialog"
+            center>
+        <div class="config-dialog-body">
+            <div>
+                <span>基准字号:</span>
+                <el-select v-model="fontValue" placeholder="请选择" @change="fontSizeChange">
+                    <el-option
+                            v-for="item in fontArr"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value">
+                    </el-option>
+                </el-select>
+            </div>
+            <div>
+                <span>基准颜色:</span>
+                <el-select v-model="colorValue" placeholder="请选择" @change="colorChange">
+                    <el-option
+                            v-for="item in colorArr"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value">
+                    </el-option>
+                </el-select>
+            </div>
+        </div>
+        <span slot="footer" class="dialog-footer">
+<!--        <el-button @click="dialogStatus = false">取 消</el-button>-->
+            <!--        <el-button type="primary" @click="saveConfigTheme">确 定</el-button>-->
+      </span>
+    </el-dialog>
+</template>
+
+<script>
+    /**
+     * @Time 20191029
+     * @Author Wxy
+     * @Description 修正主题设置说明:
+     * 1. 修改`主题数据配置`
+     * 2. 设定`默认主题`
+     * 3. data 中增加类似结构
+     * 4. initSystemThemeControl函数中更新调整主题风格
+     * 5. initdefaultThemeByProject 初始化是需要设置风格
+     */
+    import { handleLocalstorgeData, checkLocalKeyGetDataOrFalse } from '@/utils/theme';
+    import { mapActions }                                         from 'vuex';
+    import { initThemeColor }                                     from '@/utils/theme';
+
+    /**
+     * 主题数据配置
+     */
+    const AframeOpt = {
+        font:  [
+            {
+                value: 'nor-small',
+                label: '普通小号',
+            }, {
+                value: 'nor-middle',
+                label: '普通标准',
+            }, {
+                value: 'nor-big',
+                label: '普通大号',
+            },
+            {
+                value: 'ali-small',
+                label: '阿里小号',
+            }, {
+                value: 'ali-middle',
+                label: '阿里标准',
+            }, {
+                value: 'ali-big',
+                label: '阿里大号',
+            },
+        ],
+        color: [
+            {
+                value: 'blue',
+                label: '蓝色',
+            },
+            {
+                value: 'red',
+                label: '红色',
+            },
+            {
+                value: 'purple',
+                label: '紫色',
+            },
+            {
+                value: 'green',
+                label: '绿色',
+            },
+        ],
+    };
+    /**
+     * saas 主题默认
+     */
+    const defaultTheme = {
+        font:  'nor-middle',
+        color: 'blue',
+    };
+
+    export default {
+        name:    'SystemConfig',
+        props:   {
+            show: {
+                type:     Boolean,
+                required: true,
+            },
+        },
+        data() {
+            return {
+                dialogStatus: this.show,
+                fontArr:      AframeOpt.font,
+                colorArr:     AframeOpt.color,
+                fontValue:    null,
+                fontConfig:   {
+                    text:     'themeFont',
+                    attrText: 'data-fontsize',
+                },
+                colorValue:   null,
+                colorConfig:  {
+                    text:     'themeColor',
+                    attrText: 'data-theme',
+                },
+
+            };
+        },
+        watch:   {
+            show(newVal, oldVal) {
+                this.dialogStatus = newVal;
+            },
+        },
+        methods: {
+            /**
+             * 保存更改主题设置
+             */
+            /*saveConfigTheme() {
+
+            },*/
+            /**
+             * 关闭主题设置弹窗回调函数
+             */
+            configThemeClose() {
+                this.$emit('close', this.dialogStatus);
+            },
+            /**
+             * 字体大小切换触发回调
+             * @param value
+             */
+            fontSizeChange(value) {
+                handleLocalstorgeData(this.fontConfig.text, this.fontConfig.attrText, value);
+            },
+            /**
+             * 字体大小切换触发回调
+             * @param value
+             */
+            colorChange(value) {
+                handleLocalstorgeData(this.colorConfig.text, this.colorConfig.attrText, value);
+
+                this.setThemeColorObj = value;
+                initThemeColor();
+            },
+            /**
+             * 初始化需要本地化的系统配置
+             * @param data 指定数据结构对象 {text,attrText} text:localstorage 的key, attrText:html的自定义属性配合scss更改系统
+             * @param code 指定初始化系统内容 目前值为 `font` , `color`,
+             */
+            initSystemThemeControl(code) {
+                let data = {};
+
+                if (code === 'font') {
+                    data = this.fontConfig;
+                } else if (code === 'color') {
+                    data = this.colorConfig;
+                } else {
+                    throw new Error('未找到需要设置的主题内容');
+                }
+
+                if (checkLocalKeyGetDataOrFalse(data.text)) {
+                    const c = handleLocalstorgeData(data.text, data.attrText, checkLocalKeyGetDataOrFalse(data.text));
+                    // 设置字体大小
+                    switch (code) {
+                        case 'font':
+                            this.fontValue = c;
+                            break;
+                        case 'color':
+                            this.colorValue = c;
+                            break;
+                    }
+
+                } else {
+
+                    switch (code) {
+                        case 'font':
+                            /**
+                             * 字体默认情况
+                             */
+                            this.fontSizeChange(this.fontValue);
+                            break;
+                        case 'color':
+                            /**
+                             * 颜色默认情况
+                             */
+                            this.colorChange(this.colorValue);
+                            break;
+
+                    }
+
+                }
+            },
+            initdefaultThemeByProject() {
+                let data = {
+                    font:  null,
+                    color: null,
+                };
+                data = defaultTheme;
+                this.fontValue = data.font;
+                this.colorValue = data.color;
+            },
+            ...mapActions(['setThemeColorObj']),
+        },
+        created() {
+
+            // 设置默认值
+            this.initdefaultThemeByProject();
+
+            // 初始化系统主题控制
+            this.initSystemThemeControl('font');
+
+            this.initSystemThemeControl('color');
+
+
+        },
+    };
+</script>
+

+ 73 - 0
src/components/management/Layout/UploadAlCloud/UploadAlCloud.vue

@@ -0,0 +1,73 @@
+<template>
+    <div class="uploadAlCloud">
+        <el-upload
+                ref="upload"
+                action="aaa"
+                class="avatar-uploader"
+                :http-request="uploadFileImage"
+                :show-file-list="false">
+            <img v-if="imageUrl" :src="imageUrl" class="avatar" alt="头像">
+            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+        </el-upload>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: "UploadAlCloud",
+        props: {
+            imageUrl: {
+                type: String,
+                default: '',
+            },
+        },
+        methods: {
+            // 自定义上传
+            uploadFileImage(params) {
+                this.$emit('uploadFileStart', params);
+            }
+        }
+    }
+</script>
+
+<style lang="scss">
+    .uploadAlCloud {
+        display: block;
+
+        .avatar-uploader .el-upload {
+            border: 1px dashed #d9d9d9;
+            border-radius: 6px;
+            cursor: pointer;
+            position: relative;
+            overflow: hidden;
+        }
+        /*.avatar-uploader .el-upload:hover {
+            border-color: #409EFF;
+        }*/
+        .avatar-uploader-icon {
+            font-size: 28px;
+            color: #8c939d;
+            width: 178px;
+            height: 178px;
+            line-height: 178px;
+            text-align: center;
+        }
+        .avatar {
+            width: 178px;
+            height: 178px;
+            display: block;
+        }
+        @media screen and (max-width: 1440px){
+            .avatar{
+                width: 140px; // 必须指定宽高
+                height: 140px;
+                line-height: 140px;
+            }
+            .avatar-uploader-icon{
+                width: 140px;
+                height: 140px;
+                line-height: 140px;
+            }
+        }
+    }
+</style>

+ 152 - 0
src/components/management/Layout/UploadAlCloud/uploadFile.vue

@@ -0,0 +1,152 @@
+<template>
+    <div class="response-uploadAlCloud">
+        <el-upload
+                :http-request="uploadFun"
+                :show-file-list="false"
+                action="aaa"
+                class="avatar-uploader"
+                ref="upload">
+            <img :src="imageUrl" alt="头像" class="avatar" v-if="imageUrl">
+            <i class="el-icon-plus avatar-uploader-icon" v-else></i>
+        </el-upload>
+        <el-button @click="btnClick(btnType)" class="delete-btn" size="small" type="primary" v-if="showBtnFlag&&btnType==='delete'">删除</el-button>
+    </div>
+</template>
+
+<script>
+
+    import { getUploadImg }           from '@/api/AlCloud.js';
+    import { mapGetters, mapActions } from 'vuex';
+    import axios                      from 'axios';
+    //  describe: 上传组件 author: Wgy date:2020-03-30
+    /*
+    * showBtnFlag:是否显现按钮
+    * btnType:按钮类型 显示按钮 默认type为 'delete'
+    *imageUrl:入参图片
+    * 父页面 example:<upload-file  :imageUrl="imageUrl1" @btnFun="btnFun" @getFileUrl="getFileFun"></upload-file>
+    * */
+
+    export default {
+        name: "UploadAlCloud",
+        props: {
+            showBtnFlag: {
+                type: Boolean,
+                default: true
+            },
+            btnType: {
+                type: String,
+                default: 'delete',
+            },
+            imageUrl: {
+                type: String,
+                default: '',
+            },
+        },
+        data() {
+            return {
+                afterUploadImg: '',
+            };
+        },
+        methods: {
+            //按钮
+            btnClick(data){
+                let response ={
+                    type:data,
+                    imageUrl:this.imageUrl
+                }
+                this.$emit('btnFun', response);
+            },
+            // 自定义上传
+            uploadFun(params) {
+                console.log(params);
+                if (!params) {
+                    return;
+                }
+                const suffixList = params.file.name.split('.').pop();
+                const options = {
+                    prefix: 'resource/' + this.getTenantCode + '/',
+                    suffix: suffixList,
+                };
+                const loading = this.$loading({background:"rgba(0, 0, 0, 0.7)"});
+                getUploadImg(options).then((res) => {
+                    if (res.code === 0) {
+                        // 二进制文件通过forData对象进行传递
+                        const FormDataForAl = new FormData();
+                        const multipartParams = Object.assign({}, res.data, {
+                            Filename:              `images/${params.filename}`,
+                            success_action_status: '200',
+                        });
+                        // 参数数据
+                        FormDataForAl.append('key', multipartParams.key);
+                        FormDataForAl.append('policy', multipartParams.policy);
+                        FormDataForAl.append('signature', multipartParams.signature);
+                        FormDataForAl.append('OSSAccessKeyId', multipartParams.accessid);
+                        FormDataForAl.append('success_action_status', multipartParams.success_action_status);
+                        // OSS要求, file放到最后
+                        FormDataForAl.append('file', params.file);
+
+                        axios.post(multipartParams.uploadUrl, FormDataForAl).then(alRes => {
+                            if (alRes.status === 200) {
+                                this.afterUploadImg = `${multipartParams.downloadUrl}/${multipartParams.key}`;
+                                this.$emit('getFileUrl', this.afterUploadImg);
+                                loading.close();
+                            }else {
+                                loading.close();
+                            }
+                        });
+                    }else {
+                        loading.close();
+                    }
+                });
+            }
+        },
+        computed:   {
+            getcode() {
+                return this.getTenantCode;
+            },
+            ...mapGetters(['getTenantCode']),
+        },
+    }
+</script>
+
+<style lang="scss">
+    .uploadAlCloud {
+        display: block;
+
+        .avatar-uploader .el-upload {
+            border: 1px dashed #d9d9d9;
+            border-radius: 6px;
+            cursor: pointer;
+            position: relative;
+            overflow: hidden;
+        }
+        /*.avatar-uploader .el-upload:hover {
+            border-color: #409EFF;
+        }*/
+        .avatar-uploader-icon {
+            font-size: 28px;
+            color: #8c939d;
+            width: 178px;
+            height: 178px;
+            line-height: 178px;
+            text-align: center;
+        }
+        .avatar {
+            width: 178px;
+            height: 178px;
+            display: block;
+        }
+        @media screen and (max-width: 1440px){
+            .avatar{
+                width: 140px; // 必须指定宽高
+                height: 140px;
+                line-height: 140px;
+            }
+            .avatar-uploader-icon{
+                width: 140px;
+                height: 140px;
+                line-height: 140px;
+            }
+        }
+    }
+</style>

+ 73 - 0
src/components/management/Layout/eChartTableExpand/eChartTableExpand.vue

@@ -0,0 +1,73 @@
+<template>
+    <!--    <div class="mta-eChart-table-expand">-->
+    <div id="nestedPiesChart" ref="echartDiv" :style="styleStr"></div>
+    <!--    </div>-->
+</template>
+
+<script>
+    import * as HttpTongji     from '@/api/statistics.js';
+    import { getStringByHtml } from '@/utils/common';
+
+    export default {
+        name:    'eChartTableExpand',
+        props:   {
+            MyId:     {
+                type:     Number,
+                required: true,
+            },
+            /*option: {
+                type: Object,
+                require: true
+            },*/
+            styleStr: {
+                type: String,
+                // default: `width: 100%; height: 100%`
+            },
+        },
+        data() {
+            return {
+                nestedPiesChart: '',
+                isFirstGetData:  true,
+                option: null,
+            };
+        },
+        methods: {
+            // ***************** eChart **************
+            drawEchart(option) {
+                const that = this;
+                this.option = option;
+                this.nestedPiesChart = that.$echarts.init(that.$refs.echartDiv);
+                this.nestedPiesChart.setOption(option);
+                this.nestedPiesChart.on('click', function (params) {
+                    that.$emit('eChartClick', params);
+                });
+                this.resizeEchart();
+            },
+            resizeEchart() {
+                this.nestedPiesChart.resize();
+            },
+            getEchartData() {
+                const opt = {
+                    ksId: this.MyId,
+                };
+                this.$emit('data', opt);
+            },
+        },
+        watch:   {
+            styleStr: function (newVal, oldVal) {
+                this.resizeEchart();
+            },
+        },
+        mounted() {
+            // this.drawEchart(this.option);
+            window.onresize = () => {
+                return (() => {
+                    this.$nextTick(() => {
+                        this.resizeEchart();
+                    });
+                })();
+            };
+        },
+    };
+</script>
+

+ 21 - 0
src/components/management/Layout/pageLayout/pageLayout.vue

@@ -0,0 +1,21 @@
+<template>
+    <div class="mta-PageLayout fsize-m4">
+        <el-breadcrumb separator-class="el-icon-arrow-right fsize-m4">
+            <el-breadcrumb-item class="fsize-m4">{{breadcrumb.name}}</el-breadcrumb-item>
+            <el-breadcrumb-item class="fsize-m4" v-for="item in breadcrumb.children" :key="item"><span class="breadcrumb-text fsize-m4">{{item}}</span></el-breadcrumb-item>
+        </el-breadcrumb>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: "pageLayout",
+        props: {
+            breadcrumb: { // 面包屑数据{name,childName}
+                type: Object,
+                default: () => {},
+                require: true
+            },
+        }
+    }
+</script>

+ 136 - 0
src/components/management/Layout/popoverLogin/popoverLogin.vue

@@ -0,0 +1,136 @@
+<template>
+    <el-popover
+            class="popover-login"
+            placement="bottom"
+            width="200"
+            v-model="visible2"
+            trigger="click">
+        <div class="header-li-button">
+
+            <div style="display: flex;flex-direction: column;justify-content: flex-start"><span
+                    style="width: 100%;line-height: 40px;margin-right: 1px;">管理端登录地址:</span>
+                <el-input v-model="getAframe" placeholder="登录地址" :readonly="true"></el-input>
+            </div>
+            <p style="text-align: center">
+                <el-button type="primary" size="small" @click="btnClick(2)">点击登录</el-button>
+                <el-button type="success" size="small"
+                           v-clipboard:copy="getAframe"
+                           v-clipboard:success="copy"
+                           v-clipboard:error="onError"
+                           @click="visible2 = false"
+                >
+                    复制链接
+                </el-button>
+            </p>
+            <div style="display: flex;flex-direction: column;justify-content: flex-start">
+                <div style="width: 100%;line-height: 40px;margin-right: 1px;">学员端登录地址:</div>
+                <el-input v-model="getClient" placeholder="登录地址" :readonly="true"></el-input>
+            </div>
+            <p style="text-align: center">
+                <el-button type="primary" size="small" @click="btnClick(1)">点击登录</el-button>
+
+                <el-button type="success" size="small"
+                           v-clipboard:copy="getClient"
+                           v-clipboard:success="copy"
+                           v-clipboard:error="onError"
+                           @click="visible2 = false"
+                >
+                    复制链接
+                </el-button>
+
+            </p>
+
+            <!--H5-->
+            <div style="display: flex;flex-direction: column;justify-content: flex-start">
+                <div style="width: 100%;line-height: 40px;margin-right: 1px;">H5登录地址:</div>
+                <el-input :readonly="true" placeholder="登录地址" v-model="getH5"></el-input>
+            </div>
+            <p style="text-align: center">
+                <el-button @click="visible2 = false" size="small"
+                           type="success"
+                           v-clipboard:copy="getH5"
+                           v-clipboard:error="onError"
+                           v-clipboard:success="copy"
+                >
+                    复制链接
+                </el-button>
+
+            </p>
+        </div>
+
+        <el-button v-if="model === 'button'" slot="reference" class="popover-login" style="background: none;color: #FFF"
+                   icon="el-icon-user-solid">
+            登录地址管理
+        </el-button>
+        <el-button v-if="model === 'text'" class="popover-login2" slot="reference" type="text"
+                   style="background: none;color: #1E1E1E">
+            登录地址管理
+        </el-button>
+    </el-popover>
+</template>
+
+<script>
+    import { mapGetters } from 'vuex';
+
+    export default {
+        name:     'popoverLogin',
+        props:    {
+            model:   {
+                type:    String,
+                default: 'button',
+            },
+            getcode: {
+                require: true,
+            },
+        },
+        data() {
+            return {
+                visible2: false,
+            };
+        },
+        computed: {
+            getClient() {
+                if (this.getAuth === null) {
+                    return;
+                }
+                return `${document.location.host}${process.env.BASE_URL}c/login`;
+            },
+            getAframe() {
+                if (this.getAuth === null) {
+                    return;
+                }
+                return `${document.location.host}${process.env.BASE_URL}a/login`;
+            },
+            getH5(){
+                if (this.getAuth === null) {
+                    return;
+                }
+                return `${document.location.host}/`+ process.env.VUE_APP_DIST_NAME + `/#/login`;
+            },
+            ...mapGetters([
+                              'getAuth',
+                          ]),
+        },
+        methods:  {
+            btnClick(data) {
+                if (data === 1) {
+                    this.$router.push({
+                                          path: `/c/login`,
+                                      });
+                } else {
+                    this.$router.push({
+                                          path: `/a/login`,
+                                      });
+                }
+
+            },
+            copy(e) {
+                this.$message.success('复制成功');
+            },
+            onError(e) {
+                this.$message.warning('当前浏览器不支持复制功能');
+            },
+        },
+    };
+</script>
+

+ 39 - 0
src/components/management/Layout/popoverLogin/popoverWrap.vue

@@ -0,0 +1,39 @@
+<template>
+    <el-popover
+            class="popover-login"
+            placement="bottom"
+            width="200"
+            v-model="status"
+            trigger="click">
+        <slot></slot>
+        <span style="cursor: pointer" slot="reference" type="text" class="color-p">{{text}}</span>
+    </el-popover>
+</template>
+
+<script>
+    export default {
+        name: 'popoverWrap',
+        props: {
+            text: {
+                type: String,
+                required: true
+            },
+            visible: {
+                type: Boolean,
+                required: true,
+                default: false,
+            }
+        },
+        data() {
+            return {
+                status: false,
+            }
+        },
+        watch: {
+            visible(newVal, oldVal) {
+                this.status = newVal;
+            }
+        }
+    };
+</script>
+

+ 517 - 0
src/components/management/Layout/templateImport/AddUserImport.vue

@@ -0,0 +1,517 @@
+<template>
+    <div class="mta-role-import">
+        <el-dialog v-if="showDialogType===1"
+                   :title="dialogTitleText"
+                   :userRoleId="userRoleId"
+                   class="mta-dialog"
+                   :visible.sync="showDialog"
+                   :show-close="true"
+                   @close="dialogClose"
+                   :close-on-click-modal="false"
+                   center
+                   :width="dlWidth">
+            <div class="mta-role-import-wrap ">
+                <div :class="{'extendTree_width': isExtendTree, 'extendTree_position': isRemovePosition}"
+                     class="addUserInfo-body-left response-tree-div flex-column"
+                     style="border: 1px #CCC solid;padding-right:5px;">
+                        <span style="width: 98%; text-align: right;display: inline-block">
+                            <i @click="handleExtentTree" class="el-icon-arrow-left" v-if="isExtendTree"></i>
+                            <i @click="handleExtentTree" class="el-icon-arrow-right" v-else></i>
+                        </span>
+                    <perfect-scrollbar>
+                        <mta-editable-tree
+                                :allowDropStatue="true"
+                                :draggable="false"
+                                :newData="newUserData"
+                                @handleClick="getHandleClick"
+                                ref="orgTree"
+                        ></mta-editable-tree>
+                    </perfect-scrollbar>
+                </div>
+                <div class="mta-ur-table addUserInfo-body-right" :class="{extendTreeWrapdialogTable: isRemovePosition}">
+                    <el-row class="mta-UE-first-row" type="flex">
+                        <el-col>
+                            <!--用户名-->
+                            <div>
+                                <el-input
+                                        placeholder="请输入查询用户名"
+                                        class="response-dialog-input" v-model="searchUserName"></el-input>
+                                <el-input
+                                        placeholder="请输入查询姓名"
+                                        class="response-dialog-input" v-model="searchRealName"></el-input>
+                                <!--查询-->
+                            </div>
+                            <el-button @click="searchUsers" class="response-small-btn" size="small" type="primary"> 查询
+                            </el-button>
+                        </el-col>
+                    </el-row>
+                    <el-row class="response-dialog-row" type="flex">
+                        <el-col :span="24">
+                            <el-table
+                                    :data="UsersData"
+                                    border
+                                    height="100"
+                                    :stripe="true"
+                                    ref="usersTable"
+                                    header-row-class-name="headerBackgroundColor"
+                                    @selection-change="tableSelectChange"
+                                    class="response-simple-table"
+                                    style="width: 100%">
+                                <el-table-column
+                                        fixed
+                                        type="selection"
+                                        width="50">
+                                </el-table-column>
+                                <el-table-column
+                                        prop="userName"
+                                        label="用户名"
+                                        :show-overflow-tooltip="true"
+                                        min-width="125">
+                                </el-table-column>
+                                <el-table-column
+                                        prop="realName"
+                                        label="真实姓名"
+                                        :show-overflow-tooltip="true"
+                                        min-width="110">
+                                </el-table-column>
+                                <el-table-column
+                                        prop="orgName"
+                                        label="部门名称 "
+                                        :show-overflow-tooltip="true"
+                                        min-width="120">
+                                </el-table-column>
+                                <el-table-column
+                                        prop="positionName"
+                                        label="职位名称"
+                                        :show-overflow-tooltip="true"
+                                        min-width="110">
+                                </el-table-column>
+                                <!--<el-table-column
+                                        label="操作"
+                                        min-width="200">
+                                    <template slot-scope="scope">
+                                        <el-button @click="ToUserDelete(scope.row)" type="text">移除</el-button>
+                                    </template>
+                                </el-table-column>-->
+                            </el-table>
+                            <el-pagination
+                                    @size-change="handleSizeChange"
+                                    @current-change="handleCurrentChange"
+                                    :current-page="nowPage"
+                                    :page-sizes="[10, 50, 100]"
+                                    :page-size="pageSize"
+                                    layout="total, sizes, prev, pager, next, jumper"
+                                    class="response-el-pagination"
+                                    :total.sync="totalPageAdd">
+                            </el-pagination>
+                        </el-col>
+                    </el-row>
+                </div>
+            </div>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="handleCancel">取 消</el-button>
+                <el-button type="primary" @click="SaveAddUser">确 定</el-button>
+            </span>
+        </el-dialog>
+        <!--dialog2待使用-->
+        <el-dialog v-if="showDialogType===2"
+                   :title="dialogTitleText"
+                   :userRoleId="userRoleId"
+                   class="mta-dialog"
+                   :visible.sync="showDialog"
+                   :requestData="requestData"
+                   :show-close="true"
+                   @close="dialogClose"
+                   :close-on-click-modal="false"
+                   center
+                   :width="dlWidth">
+
+            <h2>待使用</h2>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="handleCancel">取 消</el-button>
+                <el-button type="primary" @click="SaveAddUser">确 定</el-button>
+            </span>
+        </el-dialog>
+        <!-- <el-dialog
+                 class="BatchFailureDL"
+                 title=""
+                 :close-on-click-modal="false"
+                 :visible.sync="BatchFailureDLShow"
+                 width="500px"
+                 center
+         >
+             <div class="be_careful"><i></i>你确定要将该人员移除么?</div>
+             <span slot="footer" class="dialog-footer">
+                 <el-button @click="BatchFailureDLShow = false">取 消</el-button>
+                 <el-button type="primary" @click="ToSaveUserDelete">确 定</el-button>
+             </span>
+         </el-dialog>-->
+    </div>
+</template>
+
+<script>
+    //组件说明
+    import MtaEditableTree
+                                from '../../../../components/management/Layout/EditableTreeOrg/EditableTree.vue';
+    import {
+        getOrganizationListTree,
+    }
+                                from '@/api/organizational';
+    import {
+        getUserAddList,
+        getUserRoleAdd,
+        getUserRoleDelete,
+    }                           from '@/api/userRoles';
+    import {
+        getAdminUserList,
+    }                           from '@/api/user.js';
+    import { mapGetters }       from 'vuex';
+    import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
+
+
+    export default {
+        name:       'AddUserImport',
+        data() {
+            return {
+                screenWidth:      0, // 监听 页面尺寸
+                isExtendTree:     false, // 展开树容器
+                isRemovePosition: false, // 移除定位
+                timer1:           null,
+                searchUserName:   '',//查询用户名
+                searchRealName:   '',//查询真实姓名
+                //UsersData:   [],//表格
+                userSelectArr:    [],//表格选中
+                nowPage:          1,//分页
+                pageSize:         10,
+                //totalPage:          0,
+                newUserData:      [],//树数据
+                treeSelectData:   '',//树
+                //BatchFailureDLShow: false,//删除弹窗
+                //diaRoleId:   '',//删除-RoleId
+                //delUserId:   '',//删除-UserId
+                showDialog:       this.dialogVisible,
+                curOrgId:         0,//
+
+
+            };
+        },
+        props:      {
+            //弹窗显隐 flag
+            dialogVisible:   {
+                type:    Boolean,
+                require: true,
+            },
+            // 1为模版下载 2为 试题批量导入
+            showDialogType:  {
+                type:    Number,
+                default: 1,
+            },
+            //入参 默认fileUrl
+            requestData:     {
+                type:    Array,
+                default: () => [],
+            },
+            UsersData:       {
+                type:    Array,
+                default: () => [],
+            },
+            // 弹窗title  文字描述
+            dialogTitleText: {
+                type:    String,
+                default: '新增人员',
+            },
+            //弹窗宽度
+            dlWidth:         {
+                type:    String,
+                default: '70%',
+            },
+            // 1为模版下载 2为 试题批量导入
+            userRoleId:      {
+                type:    Number,
+                default: 0,
+            },
+            totalPageAdd:    {
+                type:    Number,
+                default: 0,
+            },
+        },
+        components: {
+            MtaEditableTree,
+            PerfectScrollbar
+        },
+        computed:   {
+            ...mapGetters([
+                              'getAuth',
+                          ]),
+        },
+        methods:    {
+            // 清理时间 timer1
+            clearTimer1Fun() {
+                if (this.timer1) {
+                    clearTimeout(this.timer1);
+                    this.timer1 = null;
+                }
+            },
+            // 初始化 tree 展开状态
+            initTreeExtandStatu() {
+                this.screenWidth = 0; // 监听 页面尺寸
+                this.isExtendTree = false; // 展开树容器
+                this.isRemovePosition = false; // 移除定位
+                // 清理时间
+                this.clearTimer1Fun();
+            },
+            handleExtentTree() {
+                this.isExtendTree = !this.isExtendTree;
+                // console.log(this.$refs.fullscreen);
+            },
+            addShijuanFenlei() {
+                this.lianxiTypeVisible = true;
+                this.setTreeExtends();
+                this.getkaoShiClassifyListFun();
+            },
+            //查询按钮
+            searchUsers() {
+                this.nowPage = 1;
+                const option = {
+                    page:     this.nowPage,
+                    size:     this.pageSize,
+                    realName: this.searchRealName,
+                    userName: this.searchUserName,
+                    roleId:   this.userRoleId,
+                    orgId:    this.curOrgId,
+                };
+                this.getUserAddListData(option);
+            },
+            //分页
+            handleSizeChange(val) {
+                this.pageSize = val;
+                // 参数获取
+                const option = {
+                    page:     this.nowPage,
+                    size:     this.pageSize,
+                    realName: this.searchRealName,
+                    userName: this.searchUserName,
+                    roleId:   this.userRoleId,
+                    orgId:    this.curOrgId,
+                };
+                this.getUserAddListData(option);
+            },
+            handleCurrentChange(val) {
+                this.nowPage = val;
+                const option = {
+                    page:     this.nowPage,
+                    size:     this.pageSize,
+                    realName: this.searchRealName,
+                    userName: this.searchUserName,
+                    roleId:   this.userRoleId,
+                    orgId:    this.curOrgId,
+                };
+                this.getUserAddListData(option);
+            },
+            // 树点击事件
+            getHandleClick(NewData) {
+                this.treeSelectData = NewData;
+                this.curOrgId = NewData.data.id;
+                this.searchUserName = '';
+                this.searchRealName = '';
+                const option = {
+                    page:     this.nowPage,
+                    size:     this.pageSize,
+                    realName: this.searchRealName,
+                    userName: this.searchUserName,
+                    roleId:   this.userRoleId,
+                    orgId:    this.curOrgId,
+                };
+
+                this.getUserAddListData(option);
+
+            },
+            //树
+            getOrganizationTreeData() {
+                const loading = this.loading();
+                getOrganizationListTree()
+                .then(res => {
+                    if (res.code === 0) {
+                        // const {otherTree: other, selfTree: self} = res.data;
+                        const opt = [];
+
+                        if (res.data.selfTree) {
+
+                            if (this.getAuth.orgId === 0) {
+                                opt.push(res.data.selfTree);
+                            } else {
+                                /**
+                                 * @Time 2019/10/17
+                                 * @Author wxy
+                                 * @Description 此处children已和后台YJ确定只具备一个元素即children.length = 1
+                                 */
+                                opt.push(res.data.selfTree.children[0]);
+                            }
+                        }
+                        this.newUserData = opt;
+                    }
+
+                });
+                loading.close();
+            },
+
+
+            //新增人员-关闭弹窗
+            dialogClose() {
+                if (this.showDialogType === 1) {
+                    this.$emit('update:dialogVisible', false);
+                    this.searchUserName = '';//查询用户名
+                    this.searchRealName = '';//查询真实姓名
+                } else {
+                    this.$emit('update:dialogVisible', false);
+                    this.searchUserName = '';//查询用户名
+                    this.searchRealName = '';//查询真实姓名
+                }
+                this.initTreeExtandStatu();
+            },
+            //新增人员-保存按钮
+            SaveAddUser() {
+                const opt = {
+                    roleId:  this.userRoleId,
+                    userIds: this.userSelectArr,
+                };
+                window.loading1 = this.$loading({
+                                                    background: 'rgba(0, 0, 0, 0.7)',
+                                                });
+                getUserRoleAdd(opt).then(res => {
+                    if (res.code === 0 && res.data) {
+                        const option = {
+                            page:     this.nowPage,
+                            size:     this.pageSize,
+                            realName: this.searchRealName,
+                            userName: this.searchUserName,
+                            roleId:   this.userRoleId,
+                            orgId:    this.curOrgId,
+                        };
+                        const opt = {
+                            page: this.nowPage,
+                            size: this.pageSize,
+                            name: '',
+                        };
+                        this.getUserAddListData(option);
+                        this.$parent.getRoleListData(opt);
+                        this.$emit('update:dialogVisible', false);
+                        this.$message.success('新增成功');
+                    } else {
+                        this.$emit('update:dialogVisible', false);
+                        this.$message.error('新增失败');
+                    }
+                    window.loading1.close();
+                });
+            },
+            //新增人员-取消按钮
+            handleCancel() {
+                this.$emit('update:dialogVisible', false);
+            },
+            //表格选中
+            tableSelectChange(selection) {
+                this.userSelectArr = [];
+                selection.forEach((item) => {
+                    this.userSelectArr.push(item.userId);
+                });
+            },
+
+            //删除
+            /* ToUserDelete(data){
+                 //this.diaRoleId = this.userRoleId;
+                 this.delUserId = data.userId;
+                 this.BatchFailureDLShow = true;
+             },*/
+
+            //确定用户删除
+            /* ToSaveUserDelete(){
+                 const opt = {
+                     roleId: this.userRoleId,
+                     userId: this.delUserId,
+                 };
+                 window.loading1 = this.$loading();
+                 getUserRoleDelete(opt).then(res => {
+                     if (res.code === 0 && res.data) {
+                         const option = {
+                             page:      this.nowPage,
+                             size:      this.pageSize,
+                             realName: this.searchRealName,
+                             userName: this.searchUserName,
+                             roleId: this.userRoleId,
+                         };
+                         this.getUserListData(option);
+                         this.BatchFailureDLShow = false;
+                         this.$message.success('删除成功');
+                     } else {
+                         this.BatchFailureDLShow = false;
+                         this.$message.error('删除失败');
+                     }
+                     window.loading1.close();
+                 });
+             },*/
+            //用户列表
+            getUserAddListData(opt) {
+                const loading = this.$loading({background:"rgba(0, 0, 0, 0.7)"});
+                getUserAddList(opt)
+                .then(res => {
+                    if (res.code === 0) {
+                        const { data, total } = res.data;
+                        // this.UsersData = data;
+                        //this.totalPageAdd = total;
+                        this.nowPage = 1;
+                        this.$emit('update:totalPageAdd', total);
+                        this.$emit('update:UsersData', data);
+                    }
+                    loading.close();
+                })
+                .catch(err => {
+                    loading.close();
+                });
+            },
+        },
+        created() {
+            if (this.showDialogType === 1) {
+                // 待添加
+            } else {
+            }
+            this.getOrganizationTreeData();
+        },
+        watch:      {
+            dialogVisible: {
+                handler() {
+                    this.showDialog = this.dialogVisible;
+                },
+            },
+            screenWidth() {
+                // 屏幕尺寸变更 刷新table
+                ++this.mainKey;
+            },
+            isExtendTree() {
+                // 恢复树容器宽度 时 延时1秒删除定位css
+                if (this.isExtendTree === true) {
+                    this.isRemovePosition = true;
+                } else {
+                    if (this.timer1) {
+                        clearTimeout(this.timer1);
+                    }
+                    this.timer1 = setTimeout(() => {
+                        this.isRemovePosition = false;
+                    }, 1000);
+                }
+            },
+        },
+        beforeDestroy() {
+            // 清理vuex状态
+            this.clearTimer1Fun();
+        },
+        mounted() {
+            // 监听 页面变更 刷新table
+            window.onresize = () => {
+                return (() => {
+                    this.screenWidth = document.body.clientWidth;
+                })();
+            };
+        },
+    };
+</script>
+

+ 620 - 0
src/components/management/Layout/templateImport/import.vue

@@ -0,0 +1,620 @@
+<template>
+    <div class="mta-import">
+        <el-dialog v-if="showDialogType===1"
+                   :title="dialogTitleText"
+                   center
+                   :visible.sync="showDialog"
+                   :requestData="requestData"
+                   :show-close="true"
+                   @close="dialogClose"
+                   :modal-append-to-body="modalAppendToBody"
+                   :close-on-click-modal="false"
+                   class="response-middle-dialog">
+            <div class="import-head">
+                <div class="downloadTem" @click="downloadFun" style="cursor: pointer">
+                    <div class="img-box">
+                        <img :src="importDownImg" alt="">
+                    </div>
+                    <span>下载模版</span>
+                </div>
+                <div class="import-jt">
+                    <span></span>
+                </div>
+                <div class="downloadTem">
+
+                    <el-upload
+                            class="upload-demo"
+                            action="https://jsonplaceholder.typicode.com/posts/"
+                            :show-file-list="false"
+                            :http-request="importFun"
+                    >
+                        <div class="img-box">
+                            <img :src="importImg" alt="">
+                        </div>
+                        <span>{{dataImportText}}</span>
+                    </el-upload>
+                </div>
+            </div>
+            <div class="drjg-box">
+                <span>导入结果</span>
+                <div>
+                    <div class="successSpan">导入成功数据:<span class="color-p">{{listSuccess}}条</span></div>
+                    <div  class="errorSpan">导入失败数据:<span style="color: #e51c23;">{{listError}}条</span></div>
+                    <!-- <div>详细错误信息:{{errorMsg}}</div>-->
+                </div>
+            </div>
+            <el-table
+                    :data="errTableData"
+                    border
+                    height="200"
+                    :stripe="true"
+                    header-row-class-name="headerBackgroundColor"
+                    header-align="center"
+                    class="response-table"
+                    style="width: 100%">
+
+                <el-table-column
+                        type="index"
+                        width="50">
+                </el-table-column>
+                <el-table-column
+                        label="错误信息"
+                        align="center"
+                        :show-overflow-tooltip="true">
+                    <template  slot-scope="scope">
+                        <div v-if="Number(scope.row.line)!==0">
+                            第{{scope.row.line}}行&nbsp;&nbsp;{{scope.row.msg}}
+                        </div>
+                        <div v-if="Number(scope.row.line)===0">
+                            {{scope.row.msg}}
+                        </div>
+                    </template>
+
+                </el-table-column>
+            </el-table>
+            <span slot="footer" class="dialog-footer">
+            <slot name="footer"></slot>
+            <span>
+                 <el-button size="mini" @click="handleCancel">关闭</el-button>
+            </span>
+            </span>
+        </el-dialog>
+        <el-dialog v-if="showDialogType===2"
+                   :title="dialogTitleText"
+                   center
+                   :requestData="requestData"
+                   :visible.sync="showDialog"
+                   :show-close="true"
+                   @close="dialogClose"
+                   :modal-append-to-body="modalAppendToBody"
+                   :close-on-click-modal="false"
+                   class="response-middle-dialog">
+
+            <el-form ref="ImportForm" :rules="ImportRules" status-icon :model="ImportConfigData"
+                     label-width="90px">
+                <div class="response-dialog-form-input">
+                    <el-form-item class="pc-item" label="是否排重:">
+                        <el-select v-model="repeat" placeholder="请选择">
+                            <el-option :label="item.label" :value="item.value" v-for="item in repeatData"
+                                       :key="item.value"></el-option>
+                        </el-select>
+                        <span class="item-note">(排重操作大数据导入会使系统导入时间过长,200条以上请谨慎操作!)</span>
+                    </el-form-item>
+                   <!-- <div>(排重操作大数据导入会使系统导入时间过长,200条以上请谨慎操作!)</div>-->
+                    <el-form-item label="试题分类:" prop="selectSTClassify">
+                        <mta-select-input-tree
+                                class="mta-select-input"
+                                placeholder="请选择考试分类"
+                                ref="ccc"
+                                :select-date.sync="ImportConfigData.selectSTClassify"
+                                :original-tree-data="STClassifyArr"
+                                :tree-default-props="treeStructure"
+                                current-ref="ksClassify"></mta-select-input-tree>
+                    </el-form-item>
+                    <!--<el-form-item label="试题难度:" prop="selectLevel">
+                        <mta-select-input-tree
+                                class="mta-select-input2"
+                                placeholder="请选择考试分类"
+                                :select-date.sync="ImportConfigData.selectLevel"
+                                :original-tree-data="STLevelArr"
+                                :tree-default-props="treeStructure"
+                                ref="ccc3"
+                                current-ref="ksClassify"></mta-select-input-tree>
+                    </el-form-item>-->
+                </div>
+            </el-form>
+            <div class="import-head">
+                <div class="downloadTem">
+                    <div>
+                        <span class="wordChenked" style="cursor: pointer" :class="wordChenked === 0 ? 'wordChenked' : 'wordUnChenked'"
+                              @click="downloadFun('word')"></span>
+                        <span style="cursor: pointer" :class="excelChenked === 0 ? 'excelUnChenked' : 'excelChenked'"
+                              @click="downloadFun('excel')"></span>
+                        <span style="display: block;cursor: pointer">下载</span>
+                    </div>
+                </div>
+                <div class="import-jt import-jt-narrow">
+                    <span></span>
+                </div>
+                <div class="downloadTem">
+<!--                    <span class="testChenked"></span>-->
+                    <el-upload
+                            class="upload-demo"
+                            action="https://jsonplaceholder.typicode.com/posts/"
+                            :show-file-list="false"
+                            :http-request="importFun"
+                    >
+                        <div style="display: flex;flex-direction: column;margin-top: -8px">
+                            <span class="testChenked"></span>
+                            <span>{{dataImportText}}</span>
+                        </div>
+                    </el-upload>
+                </div>
+            </div>
+            <div class="drjg-box">
+                <div style="text-align: center">导入结果</div>
+                <div>
+                    <div class="successSpan">导入成功数据:<span class="color-p">{{listSuccess}}条</span></div>
+                    <div  class="errorSpan">导入失败数据:<span style="color: #e51c23;" >{{listError}}条</span></div>
+                </div>
+            </div>
+            <el-table
+                    :data="errTableData"
+                    border
+                    height="200"
+                    :stripe="true"
+                    header-row-class-name="headerBackgroundColor"
+                    header-align="center"
+                    class="response-table"
+                    style="width: 100%">
+
+                <el-table-column
+                        type="index"
+                        width="50">
+                </el-table-column>
+                <el-table-column
+                        label="错误信息"
+                        min-width="200"
+                        :show-overflow-tooltip="true">
+                    <template  slot-scope="scope">
+                        <div v-if="Number(scope.row.line)!==0">
+                            第{{scope.row.line}}行&nbsp;&nbsp;{{scope.row.msg}}
+                        </div>
+                        <div v-if="Number(scope.row.line)===0">
+                            {{scope.row.msg}}
+                        </div>
+                    </template>
+
+                </el-table-column>
+                <el-table-column
+                        prop="topicDry"
+                        label="题干"
+                        :show-overflow-tooltip="true"
+                        min-width="200">
+                </el-table-column>
+            </el-table>
+            <span slot="footer" class="dialog-footer">
+            <slot name="footer"></slot>
+            <span>
+                 <el-button size="mini" @click="handleCancel">关闭</el-button>
+            </span>
+            </span>
+        </el-dialog>
+        <el-dialog v-if="showDialogType===3"
+                   :title="dialogTitleText"
+                   center
+                   :visible.sync="showDialog"
+                   :requestData="requestData"
+                   :show-close="true"
+                   @close="dialogClose"
+                   :modal-append-to-body="modalAppendToBody"
+                   :close-on-click-modal="false"
+                   class="response-middle-dialog">
+            <div class="import-head">
+                <div class="downloadTem" @click="downloadFun" style="cursor: pointer">
+                    <div class="img-box">
+                        <img :src="importDownImg" alt="">
+                    </div>
+                    <span >下载模版</span>
+                </div>
+                <div class="import-jt">
+                    <span></span>
+                </div>
+                <div class="downloadTem">
+
+                    <el-upload
+                            class="upload-demo"
+                            action="https://jsonplaceholder.typicode.com/posts/"
+                            :show-file-list="false"
+                            :http-request="importFun"
+                    >
+                        <div class="img-box">
+                            <img :src="importShijuanImg" alt="">
+                        </div>
+                        <span>{{dataImportText}}</span>
+                    </el-upload>
+                </div>
+            </div>
+            <div class="drjg-box">
+                <span>导入结果</span>
+                <div>
+                    <div class="successSpan">导入成功数据:<span class="color-p">{{listSuccess}}条</span></div>
+                    <div class="errorSpan">导入失败数据:<span style="color: #e51c23">{{listError}}条</span></div>
+                </div>
+            </div>
+            <el-table
+                    :data="errTableData"
+                    border
+                    height="200"
+                    :stripe="true"
+                    header-row-class-name="headerBackgroundColor"
+                    header-align="center"
+                    class="response-table"
+                    style="width: 100%">
+
+                <el-table-column
+                        type="index"
+                        width="50">
+                </el-table-column>
+                <el-table-column
+                        label="错误信息"
+                        align="center"
+                        :show-overflow-tooltip="true">
+                    <template  slot-scope="scope">
+                        <div v-if="Number(scope.row.line)!==0">
+                            第{{scope.row.line}}行&nbsp;&nbsp;{{scope.row.msg}}
+                        </div>
+                        <div v-if="Number(scope.row.line)===0">
+                            {{scope.row.msg}}
+                        </div>
+                    </template>
+
+                </el-table-column>
+            </el-table>
+            <span slot="footer" class="dialog-footer">
+            <slot name="footer"></slot>
+            <span>
+                 <el-button size="mini" @click="handleCancel">关闭</el-button>
+            </span>
+            </span>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    import { getBaseUrlFromCommon ,getImg}                                                                from '@/utils/common.js';
+    import { getUploadImg }                                                                        from '@/api/AlCloud.js';
+    import axios                                                                                   from 'axios';
+    import MtaSelectInputTree
+                                                                                                   from '@/components/management/Layout/SelectInput/SelectInputTree';
+    import { getShitiClassifyList, getShitiLevelList, getShitiKnowledgeList,uploadTemplate } from '@/api/exam';
+    import { getAdminUserImport }                                                                  from '@/api/user';
+
+    //  describe: 组件说明 author: Wgy date:2019-10-31
+    //   <template-import :dialogVisible.sync="optenFlag" templateType="user" :requestData="requestData" :requestUrl="requestUrl"></template-import>
+    // 必传项 :templateType下载模版类型 (必传根据自己需要添加) 如 user  对应的下载地址为 如下: address.user
+    //必传项 :requestUrlType 导入模版获取成功失败条数类型  如下方 shitiImport  (写对应的接口地址格式按照例子写)
+    //必传项 :dialogVisible.sync    弹窗flag
+    // 弹窗title文字 dialogTitleText  导入模版文字 dataImportText  dlWidth弹窗宽度
+    // 非必传项  showDialogType (默认为1) 1为模版下载 2为 试题批量导入
+    /*   非必传项  requestData (接口需要额外入参 使用) 父页面 写在computed 中
+    *   computed:   {
+              requestData: function (val) {
+                  return {
+                      page:  1,
+                      page2:  2,
+                  };
+              },
+          },
+    *
+    * */
+    export default {
+        name:       'import',
+        components: {
+            MtaSelectInputTree,
+        },
+        data() {
+            const validate1 = (rule, value, callback) => {
+                const data = JSON.parse(JSON.stringify(value));
+                if (data && data.id !== undefined && data.id !== 0) {
+                    callback();
+                } else {
+                    callback(new Error('请选择知识点分类'));
+                }
+            };
+            const validate2 = (rule, value, callback) => {
+                const data = JSON.parse(JSON.stringify(value));
+                if (data && data.id !== undefined && data.id !== 0) {
+                    callback();
+                } else {
+                    callback(new Error('请选择试题分类'));
+                }
+            };
+            const validate3 = (rule, value, callback) => {
+                const data = JSON.parse(JSON.stringify(value));
+                if (data && data.id !== undefined && data.id !== 0) {
+                    callback();
+                } else {
+                    callback(new Error('请选择试题难度'));
+                }
+            };
+            return {
+                //模版下载地址汇总
+                address:          {
+                    user:    '/open/template/%E7%94%A8%E6%88%B7%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx',
+                    xianxia: '/open/template/%E7%BA%BF%E4%B8%8B%E6%88%90%E7%BB%A9%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx',
+                    word:    '/open/template/%E8%AF%95%E9%A2%98%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.docx',
+                    excel:   '/open/template/%E8%AF%95%E9%A2%98%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx',
+                    offlineResults: '/open/template/%E7%BA%BF%E4%B8%8B%E6%88%90%E7%BB%A9%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx',
+                    kaoshiAdduser:'/open/template/%E6%B7%BB%E5%8A%A0%E7%94%A8%E6%88%B7%E6%A8%A1%E6%9D%BF.xlsx'
+                },
+                repeatData:[
+                    {
+                    value: false,
+                    label: '是'
+                     },
+                    {
+                        value: true,
+                        label: '否'
+                    },
+                ],
+                repeat:true,
+                //formLabelWidth:    '120px',
+                showDialog:       this.dialogVisible,
+                wordChenked:      0, //word flag
+                excelChenked:     1, //excel flag
+                listSuccess:      '',//成功数量
+                listError:        '',//失败数量
+                errTableData:     [],
+                errorMsg:         '',//错误信息
+                STClassifyArr:    [],
+                STLevelArr:       [],
+                ImportConfigData: {
+                    selectSTClassify: {},
+                    selectLevel:      {},
+                },
+                treeStructure:    {
+                    children: 'children',
+                    label:    'lable',
+                },
+                ImportRules:      {
+                    selectSTClassify: [
+                        { required: true, validator: validate2 },
+                    ],
+                    /*selectLevel:      [
+                        { required: true, validator: validate3 },
+                    ],*/
+                },
+                //青谷麦塔图片区分
+                importDownImg:getImg('default/import-down','png'),
+                importImg:getImg('default/import-import','png'),
+                importShijuanImg:getImg('default/import-shijuan','png'),
+            };
+        },
+        props:      {
+            //弹窗显隐 flag
+            dialogVisible:   {
+                type:    Boolean,
+                require: true,
+            },
+            modalAppendToBody:   {
+                type:    Boolean,
+                require: true,
+            },
+            // 下载模版类型 (必传根据自己需要添加)
+            templateType:    {
+                type:    String,
+                require: true,
+            },
+            // 1为模版下载 2为 试题批量导入
+            showDialogType:  {
+                type:    Number,
+                default: 1,
+            },
+            //入参 默认fileUrl
+            requestData:     {
+                type: Object,
+            },
+            // 导入试题后调用接口类型(如:提交)
+            requestUrl:  {
+                type:    String,
+                require: true,
+            },
+            // 导入模版文字描述
+            dataImportText:  {
+                type:    String,
+                default: '导入试卷分类',
+            },
+            // 弹窗title  文字描述
+            dialogTitleText: {
+                type:    String,
+                default: '批量导入试卷分类',
+            },
+            //弹窗宽度
+            dlWidth:         {
+                type:    String,
+                default: '50%',
+            },
+        },
+        methods:    {
+            //  describe: 切换图片 author: Wgy date:2019-10-31
+            changeImg(data) {
+                if (data === 0) {
+                    this.wordChenked = 0;
+                    this.excelChenked = 0;
+                } else {
+                    this.wordChenked = 1;
+                    this.excelChenked = 1;
+                }
+            },
+            //  describe: 下载模版 author: Wgy date:2019-10-30
+            downloadFun(opt) {
+                if (this.showDialogType === 1) {
+                    window.location.href = getBaseUrlFromCommon() + this.address[this.templateType];
+                } else if (this.showDialogType === 2) {
+                    if (opt && opt === 'word') {
+                        window.location.href = getBaseUrlFromCommon() + this.address.word;
+                    } else if (opt && opt === 'excel') {
+                        window.location.href = getBaseUrlFromCommon() + this.address.excel;
+                    }
+                }else if(this.showDialogType === 3){
+                    window.location.href = getBaseUrlFromCommon() + this.address[this.templateType];
+                }
+            },
+            importFun(params) {
+                if (!params) {
+                    return false;
+                }
+                const suffixList = params.file.name.split('.');
+                const options = {
+                    prefix: 'temp/',
+                    suffix: suffixList[1],
+                };
+                getUploadImg(options).then((res) => {
+                    if (res.code === 0) {
+                        // 二进制文件通过forData对象进行传递
+                        const FormDataForAl = new FormData();
+                        const multipartParams = Object.assign({}, res.data, {
+                            Filename:              `images/${params.filename}`,
+                            success_action_status: '200',
+                        });
+                        // 参数数据
+                        FormDataForAl.append('key', multipartParams.key);
+                        FormDataForAl.append('policy', multipartParams.policy);
+                        FormDataForAl.append('signature', multipartParams.signature);
+                        FormDataForAl.append('OSSAccessKeyId', multipartParams.accessid);
+                        FormDataForAl.append('success_action_status', multipartParams.success_action_status);
+                        // OSS要求, file放到最后
+                        FormDataForAl.append('file', params.file);
+                        window.loading1 = this.$loading();
+                        axios.post(multipartParams.uploadUrl, FormDataForAl).then(alRes => {
+                            if (alRes.status === 200) {
+                                const fileUrl = `${multipartParams.downloadUrl}/${multipartParams.key}`;
+                                let fromParam = {
+                                    stClassifyId:  this.ImportConfigData.selectSTClassify.id,
+                                    stLevelId:     this.ImportConfigData.selectLevel.id,
+                                    url:           fileUrl,
+                                    repeat:this.repeat
+                                };
+                                let req = {};
+                                let url = this.requestUrl;
+                                if (this.showDialogType === 1||this.showDialogType === 3) {
+                                    req = Object.assign(fromParam, this.requestData);
+                                    uploadTemplate(req,url)
+                                    .then((res) => {
+                                       this.getDataResults(res)
+                                    }).catch(err => {
+                                        window.loading1.close();
+                                    });
+                                } else if (this.showDialogType === 2) {
+                                    req = Object.assign(fromParam,this.requestData);
+                                    this.$refs['ImportForm'].validate((valid) => {
+                                        if (valid) {
+                                            uploadTemplate(req,url)
+                                            .then((res) => {
+                                                this.getDataResults(res)
+                                            }).catch(err => {
+                                                window.loading1.close();
+                                            });
+                                        } else {
+                                            window.loading1.close();
+                                            this.$message.error('校验失败');
+                                        }
+                                    });
+                                }else if(this.showDialogType === 3){
+                                    req = Object.assign(fromParam,this.requestData);
+                                    uploadTemplate(req,url)
+                                    .then((res) => {
+                                        this.getDataResults(res)
+                                    }).catch(err => {
+                                        window.loading1.close();
+                                    });
+                                }
+                            }
+                        });
+                    }
+
+                });
+            },
+
+            //获得成功或者失败信息
+            getDataResults(res) {
+                if (res.code === 0) {
+                    this.errTableData = [];
+                    if (res.data.fail) {
+                        res.data.failList.forEach((item) => {
+                            const opt = {
+                                msg: item.msg,
+                                line: item.line,
+                                topicDry: item.data.name,
+                            };
+                            this.errTableData.push(opt);
+                        });
+                    }
+                    this.listError = res.data.fail;
+                    this.listSuccess = res.data.success;
+                    if(res.data.userList){
+                        this.$emit('importSuccess',res.data.userList);
+                    }else {
+                        this.$emit('importSuccess');
+                    }
+                }
+                window.loading1.close();
+            },
+            // 下拉树请求
+            getShitiClassifyList() {
+                getShitiClassifyList().then(res => {
+                    if (res.code === 0) {
+                        this.STClassifyArr = res.data.children;
+                    }
+                });
+            },
+            // 下拉树请求
+            getShitiLevelList() {
+                getShitiLevelList().then(res => {
+                    if (res.code === 0) {
+                        this.STLevelArr = res.data.children;
+                    }
+                });
+            },
+            handleCancel() {
+                this.$emit('update:dialogVisible', false);
+            },
+            dialogClose() {
+                if (this.showDialogType === 1) {
+                    this.$emit('update:dialogVisible', false);
+                    this.listError = '';
+                    this.listSuccess = '';
+                    this.errTableData = [];
+                } else {
+                    this.$emit('update:dialogVisible', false);
+                    this.errTableData = [];
+                    this.listError = '';
+                    this.listSuccess = '';
+                    this.$refs.ccc.clearInputData();
+                    this.$refs.ccc3.clearInputData();
+                    this.$refs.ImportForm.resetFields();
+                }
+                // description: 弹窗关闭事件; author: Wxy; date: 2019/11/25
+                this.$emit('dialogClose', true);
+            },
+        },
+        created() {
+
+        },
+        watch:      {
+            dialogVisible: {
+                handler() {
+                    this.showDialog = this.dialogVisible;
+                    if (this.showDialogType === 1 ||this.showDialogType === 3) {
+                        // 待添加
+                    } else if(this.showDialog&&this.showDialogType === 2) {
+                        this.getShitiClassifyList();
+                        this.getShitiLevelList();
+                    }
+                },
+                immediate: true,
+            },
+        },
+    };
+</script>

+ 107 - 0
src/components/management/Layout/videoBanner/dialogGuideContent.vue

@@ -0,0 +1,107 @@
+<template>
+    <el-dialog
+            :title="title"
+            :visible.sync="dialogVisible"
+            width="50%"
+            @close="handleClose"
+            custom-class="dialogGuideContent"
+            :close-on-click-modal="false"
+            center
+    >
+        <video-banner
+                ref="video"
+                :code="code"
+                @skip="handleSkip"
+                @status="getStatus"></video-banner>
+        <span slot="footer" class="dialog-footer">
+            <el-button v-if="step && (status === 'continue' || status === 'end')" @click="handlePrev">上一步</el-button>
+            <el-button v-if="step && (status === 'continue' || status === 'start')" @click="handleNext">下一步</el-button>
+            <el-button v-if="step &&  status === 'end'" type="primary" @click="handleClick">完  成</el-button>
+            <el-button v-if="!step" type="primary" @click="handleClick">关  闭</el-button>
+        </span>
+    </el-dialog>
+</template>
+
+<script>
+    import videoBanner from './index';
+
+    export default {
+        name:       'dialogGuideContent',
+        props:      {
+            title:   {
+                type:    String,
+                default: '使用提示',
+            },
+            visible: {
+                type:    Boolean,
+                default: false,
+            },
+            step:    {
+                type:    Boolean,
+                default: true,
+            },
+            /**
+             * mode : String
+             * @param video 视频组件
+             * @param image 图片组件
+             */
+            mode: {
+                type: String,
+                require: true
+            },
+            /**
+             * code: string
+             * 引导页的唯一code
+             */
+            code: {
+                type: String,
+                require: true
+            }
+        },
+        data() {
+            return {
+                dialogVisible: this.visible,
+                status:        `start`,
+            };
+        },
+        components: {
+            videoBanner,
+        },
+        watch:      {
+            visible(newVal, oldVal) {
+                this.dialogVisible = newVal;
+            },
+        },
+        methods:    {
+            handleSkip() {
+                this.dialogVisible = false;
+                this.$emit('update:visible', this.dialogVisible);
+                this.$emit('skip');
+            },
+            handleClick() {
+                this.dialogVisible = false;
+                this.$emit('update:visible', this.dialogVisible);
+                this.$emit('complete')
+            },
+            handleClose() {
+                /**
+                 * 注意 关闭弹窗时需要暂停视频
+                 */
+                this.$refs.video.pause();
+                this.$emit('update:visible', this.dialogVisible);
+            },
+            getStatus(data) {
+                this.status = data;
+            },
+
+            handlePrev() {
+                this.$refs.video.prev();
+            },
+            handleNext() {
+                this.$refs.video.next();
+            },
+        },
+
+    };
+</script>
+

+ 200 - 0
src/components/management/Layout/videoBanner/index.vue

@@ -0,0 +1,200 @@
+<template>
+    <div class="mta-Aliplay">
+        <!-- 上部video播放内容 -->
+        <AliPlay @ready="videoReady"
+                 :autoplay="true"
+                 :source="myArr[tabIndex].videoUrl"
+                 v-if="myArr[tabIndex].mode === 'video' && !!myArr[tabIndex].videoUrl"
+                 height="400px"
+                 @ended="videoEnd"
+                 ref="player"
+        ></AliPlay>
+        <el-button @click="handleClick" v-if="myArr[tabIndex].mode === 'video' && !!myArr[tabIndex].videoUrl" size="small" style="position: absolute;top: 5px; right: 5px;border:none; background-color: rgba(255,255,255,0.8)">跳过</el-button>
+        <img class="mta-img" height="397px" :src="myArr[tabIndex].imageUrl" alt="" v-if="myArr[tabIndex].mode === 'image' && !!myArr[tabIndex].imageUrl">
+
+        <!--    下部切换内容    -->
+        <el-carousel height="150px"
+                     :initial-index="tabIndex"
+                     trigger="click"
+                     :autoplay="false"
+                     :interval="0"
+                     v-if="myArr.length"
+                     :loop="false"
+                     @change="changeVideo"
+                     ref="carousel"
+                     @ready="videoReady">
+            <el-carousel-item v-for="item in myArr.length" :key="item">
+                <div class="mta-carousel-body">
+                    <h2>{{myArr[tabIndex].title}}</h2>
+                    <p>{{myArr[tabIndex].content}}</p>
+                </div>
+
+            </el-carousel-item>
+        </el-carousel>
+    </div>
+</template>
+
+<script>
+    import AliPlay      from '@/components/management/Layout/AliPlay/AliPlay.vue';
+    import { getuserGuide } from '@/utils/common.js';
+
+    export default {
+        name:       'videoBanner',
+        props:      {
+            height:  {
+                type:    String,
+                default: '0px',
+            },
+            /**
+             * 识别标识
+             * 1. welcome 欢迎页引导
+             */
+            code:    {
+                type:    String,
+                // require: true,
+                default: 'welcome'
+            },
+            /**
+             *    指示器的触发方式
+             */
+            trigger: {
+                type:    String,
+                default: 'click',
+            },
+            /**
+             * 序号默认起始点
+             */
+            index:   {
+                type:    Number,
+                default: 0,
+            },
+        },
+        data() {
+            return {
+                myArr:    getuserGuide(this.code),
+                tabIndex: this.index,
+                status:   false, // 首次进入页面
+            };
+        },
+        components: {
+            AliPlay,
+        },
+        watch: {
+            index(newVal, oldVal) {
+                this.tabIndex = newVal;
+            }
+        },
+        methods:    {
+            handleClick() {
+                this.$emit('skip');
+            },
+            changeVideo(data) {
+                this.tabIndex = data;
+                if (this.tabIndex === 0) {
+                    this.$emit('status', `start`)
+                } else if (this.tabIndex === this.myArr.length-1){
+                    this.$emit('status', `end`)
+                } else {
+                    this.$emit('status', `continue`)
+                }
+
+                // 播放器播放
+                if (this.status && this.myArr[this.tabIndex].mode === 'video') {
+                    this.handleEnd();
+                }
+            },
+            /*playVideo() {
+                if (this.$refs.player && this.mode === 'video') {
+
+                    this.$refs.player.play();
+                }
+            },*/
+            videoReady() {
+                this.status = true;
+                /**
+                 * @author Wxy
+                 * @description 视频播放
+                 * FIXME 我无法初始化视频时暂停播放
+                 * 折中解决 视频就绪是无差别暂停视频
+                 * 想连续播放就注释一下代码
+                 *
+                 * 【问题】: 一旦视频播放数据异常
+                 * 会导致 下一个视频 在暂停中进行播放
+                 *
+                 */
+                /*if (this.isVideoMode()) {
+                    this.$refs.player.pause();
+                }*/
+            },
+            videoEnd(data) {
+                if (this.isVideoMode()) {
+
+                }
+            },
+            handleEnd() {
+                if (this.isVideoMode()) {
+                    this.$refs.player.initAliplayer();
+                }
+            },
+            isVideoMode() {
+                return this.$refs.player && this.myArr[this.tabIndex].mode === 'video';
+            },
+            pause() {
+                if (this.isVideoMode()) {
+                    this.$refs.player.pause();
+                }
+            },
+            prev() {
+                this.$refs.carousel.prev();
+            },
+            next() {
+                this.$refs.carousel.next();
+            }
+        },
+    };
+</script>
+
+<style lang="scss">
+    .mta-Aliplay {
+        width: 100%;
+        text-align: center;
+        margin: 0 auto;
+
+        .el-carousel {
+            width: 100%;
+        }
+
+        .el-carousel__item h3 {
+            color: #475669;
+            font-size: 14px;
+            opacity: 0.75;
+            margin: 0;
+        }
+
+        .el-carousel__item:nth-child(2n) {
+            /*background-color: #99A9BF;*/
+        }
+
+        .el-carousel__item:nth-child(2n+1) {
+            /*background-color: #D3DCE6;*/
+        }
+
+        .mta-img {
+            /*width: 100%;*/
+
+        }
+
+        .mta-carousel-body {
+            text-align: center;
+
+            h1 {
+                margin: 30px 0 0 0
+            }
+
+            p {
+                margin-top: 5px
+            }
+        }
+    }
+
+</style>

+ 111 - 0
src/components/management/Layout/welcomeInfo/QuickEntry.vue

@@ -0,0 +1,111 @@
+<template>
+    <div class="Quick-Entry fsize-m2">
+        <div class="Quick-Entry-main">
+            <el-card class="box-card" @click.native="handleCard(item)" :key="index"
+                     v-for="(item, index) of pageData.cardControl"
+                     :class="{
+                first: index === 0,
+                two: index === 1,
+                three: index === 2,
+                four: index === 3
+            }" shadow="always">
+                <h3>{{item.name === null ? '自定义入口': item.name}}</h3>
+                <p>{{item.intro === null ? '管理员可根据自身实际需求自定义一个快捷入口按键': item.intro}}</p>
+                <span @click.stop="cardEditor(index)" class="icon-editor"><i class="el-icon-edit-outline"></i></span>
+            </el-card>
+        </div>
+
+        <el-dialog
+                title="自定义入口"
+                :visible.sync="showEditorDl"
+                width="60%"
+                center>
+            <div class="d-wrap">
+                <perfect-scrollbar>
+                    <div class="d-content">
+                        <el-card @click.native="changeControlCard(item)" class="d-box-card" shadow="hover" v-for="(item,index) in pageData.lists" :key="index">
+                            <div >
+                                <h3>{{item.name}}</h3>
+                                <p>{{item.intro === null ? '暂无描述': item.intro}}</p>
+                            </div>
+                        </el-card>
+                    </div>
+                </perfect-scrollbar>
+            </div>
+            <span slot="footer" class="dialog-footer">
+                </span>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    import {PerfectScrollbar} from 'vue2-perfect-scrollbar';
+
+    export default {
+        name: "QuickEntry",
+        props: {
+            busData: {
+                type: Object,
+                require: true,
+            },
+        },
+        components: {
+            PerfectScrollbar
+        },
+        data() {
+            return {
+                pageData: {
+                    cardControl: [],
+                    lists: [],
+                },
+                cardIndex: null,
+                showEditorDl: false
+            }
+        },
+        watch: {
+            busData: {
+                handler(newVal, oldVal) {
+                    this.pageData.lists = newVal.allData;
+                    this.pageData.cardControl = newVal.defaultData === null ? [
+                        {
+                            name: '自定义入口',
+                            intro: '管理员可根据自身实际需求自定义一个快捷入口按键',
+                            url: '',
+                        },
+                        {
+                            name: '自定义入口',
+                            intro: '管理员可根据自身实际需求自定义一个快捷入口按键',
+                            url: '',
+                        },
+                        {
+                            name: '自定义入口',
+                            intro: '管理员可根据自身实际需求自定义一个快捷入口按键',
+                            url: '',
+                        },
+                        {
+                            name: '自定义入口',
+                            intro: '管理员可根据自身实际需求自定义一个快捷入口按键',
+                            url: '',
+                        }
+                    ] : newVal.defaultData;
+                },
+                deep: true
+            }
+        },
+        methods: {
+            cardEditor(index) {
+                this.showEditorDl = true;
+                this.cardIndex = index;
+            },
+            handleCard(data) {
+                this.$emit('change', data);
+            },
+            changeControlCard(data) {
+                this.pageData.cardControl[this.cardIndex] = data;
+                this.$emit('getData', this.pageData.cardControl);
+                this.showEditorDl = false;
+            }
+        }
+    }
+</script>
+

+ 139 - 0
src/components/management/Layout/welcomeInfo/RecentTest.vue

@@ -0,0 +1,139 @@
+<template>
+    <div class="recent-test fsize-m2">
+        <div v-if="lists.length>0" class="recent-test-boxs">
+            <el-card class="box-card" shadow="hover" v-for="(item,index) in lists" :key="index">
+                <div @click="cardClick(item)">
+                    <h3>{{item.name}}</h3>
+                    <p>{{item.startTime}}至{{item.endTime}}</p>
+                    <p>总分:{{item.ksScore}}分</p>
+                    <p>时长:{{item.totalTm}}分钟</p>
+                    <p class="ks-status color-p" :class="{signUp: item.status !== 1}">{{item.status === 1 ? '考试中...':
+                        '报名中...'}}</p>
+                    <p class="ks-note">
+                        <span>{{item.count}}</span>人正在考试
+                    </p>
+                </div>
+            </el-card>
+        </div>
+        <div v-else class="recent-test-boxs">
+            <el-card class="box-card only" shadow="hover">
+                <img src="../../../../assets/images/default/c-exam-img.png" class="examImage">
+<!--                <img src="https://saas-mta.oss-cn-beijing.aliyuncs.com/resource/exam-img.png" class="examImage">-->
+            </el-card>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: "recentTest",
+        props: {
+            busdata: {
+                type: Object,
+                require: true
+            }
+        },
+        data() {
+            return {
+                lists: [],
+            }
+        },
+        watch: {
+            busdata: {
+                handler(newVal, oldVal) {
+                    if (newVal.option) {
+                        this.lists = newVal.option;
+                       /* setTimeout(() => {
+                            this.lists = [
+                                {
+                                    name: '应用工具考核',
+                                    startTime: '2018-10-17 18:20',
+                                    endTime: '2018-10-20 18:20',
+                                    ksScore: 100.0,
+                                    totalTime: '120分钟',
+                                    status: 1,
+                                    count: 10,
+                                },
+                                {
+                                    name: '应用工具考核',
+                                    startTime: '2018-10-17 18:20',
+                                    endTime: '2018-10-20 18:20',
+                                    ksScore: 100.0,
+                                    totalTime: '120分钟',
+                                    status: 1,
+                                    count: 10,
+                                },
+                                {
+                                    name: '应用工具考核',
+                                    startTime: '2018-10-17 18:20',
+                                    endTime: '2018-10-20 18:20',
+                                    ksScore: 100.0,
+                                    totalTime: '120分钟',
+                                    status: 2,
+                                    count: 10,
+                                },
+                                {
+                                    name: '应用工具考核',
+                                    startTime: '2018-10-17 18:20',
+                                    endTime: '2018-10-20 18:20',
+                                    ksScore: 100.0,
+                                    totalTime: '120分钟',
+                                    status: 2,
+                                    count: 10,
+                                },]
+                        }, 3000)*/
+                    }
+                },
+                deep: true
+            }
+        },
+        methods: {
+            cardClick() {
+                this.$router.push({path: `/a/kaoshi/jiankong`})
+            }
+
+
+        },
+        created() {
+            /*setTimeout(() => {
+                this.lists = [
+                    {
+                        ksName: '应用工具考核',
+                        startTime: '2018-10-17 18:20',
+                        endTime: '2018-10-20 18:20',
+                        score: 100.0,
+                        totalTime: '120分钟',
+                        status: 1
+                    },
+                    {
+                        ksName: '应用工具考核',
+                        startTime: '2018-10-17 18:20',
+                        endTime: '2018-10-20 18:20',
+                        score: 100.0,
+                        totalTime: '120分钟',
+                        status: 2
+                    },
+                    {
+                        ksName: '应用工具考核',
+                        startTime: '2018-10-17 18:20',
+                        endTime: '2018-10-20 18:20',
+                        score: 100.0,
+                        totalTime: '120分钟',
+                        status: 1
+                    },
+                    {
+                        ksName: '应用工具考核',
+                        startTime: '2018-10-17 18:20',
+                        endTime: '2018-10-20 18:20',
+                        score: 100.0,
+                        totalTime: '120分钟',
+                        status: 2
+                    },]
+            }, 3000000)*/
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 175 - 0
src/components/management/Layout/welcomeInfo/StatisticalAnalysis.vue

@@ -0,0 +1,175 @@
+<template>
+    <div class="statistical-analysis fsize-m2" v-if="option.length>0">
+        <div class="box-left">
+            <div class="box-left-top">
+                <h3>{{pageData.ksData.name}}</h3>
+                <p><span>实际参加人数:</span><span>{{pageData.ksData.ksUserJoin}}人</span></p>
+                <p><span>及格人数:</span><span>{{pageData.ksData.ksUserPass}}人</span></p>
+                <p><span>不及格人数:</span><span>{{pageData.ksData.ksNowPass}}人</span></p>
+                <p><span>平均考试时间:</span><span>{{pageData.ksData.timeAvg}}分钟</span></p>
+                <p><span>最高分:</span><span>{{pageData.ksData.scoreTop}}分</span></p>
+                <p><span>平均分:</span><span>{{pageData.ksData.scoreAvg}}分</span></p>
+            </div>
+            <el-pagination
+                    layout="prev, pager, next"
+                    :page-size="1"
+                    @current-change="handleEchart"
+                    :total="option.length">
+            </el-pagination>
+        </div>
+        <div class="box-right">
+            <div class="fra-charts" id="welcomeCharts"></div>
+        </div>
+    </div>
+    <div class="statistical-analysis fsize-m2" v-else>
+        <el-card class="box-card only" shadow="hover">
+            <img src="../../../../assets/images/default/c-exam-jiankong.png" class="examImage">
+        </el-card>
+    </div>
+</template>
+
+<script>
+    import { getScoreShow } from '@/api/statistics.js';
+
+    export default {
+        name:    'StatisticalAnalysis',
+        data() {
+            return {
+                pageData:      {
+                    dlEchartData: {
+                        xAxis:   {
+                            type: 'category',
+                            data: [],
+                        },
+                        yAxis:   {
+                            type:      'value',
+                            axisLabel: {
+                                formatter: '{value} 人',
+                            },
+                        },
+                        series:  [
+                            {
+                                name:      '得分人数',
+                                data:      [],
+                                barWidth:  '50%',
+                                type:      'bar',
+                                itemStyle: {
+                                    color: '#2196F5',
+                                },
+                            },
+                        ],
+                        tooltip: {
+                            trigger:     'axis',
+                            axisPointer: {
+                                type: 'shadow',
+                            },
+                        },
+                    },
+                    curPage:      0,
+                    ksData:       {},
+                },
+                welcomeCharts: null,
+            };
+        },
+        props:   {
+            option: {
+                type:    Array,
+                default: () => [],
+            },
+        },
+        watch:   {
+            option: {
+                handler() {
+                    this.initPageData();
+                },
+                deep: true,
+            },
+        },
+        methods: {
+            initPageData() {
+                if (this.option && this.option.length) {
+                    const data = this.option[this.pageData.curPage];
+                    //    文本init
+                    this.pageData.ksData = {
+                        ksUserJoin: data.ksUserJoin,
+                        ksUserPass: data.ksUserPass,
+                        ksNowPass:  data.ksUserJoin - data.ksUserPass,
+                        name:       data.name,
+                        scoreAvg:   data.scoreAvg,
+                        scoreTop:   data.scoreTop,
+                        startTime:  data.startTime,
+                        timeAvg:    data.timeAvg,
+                    };
+                    //    图表数据获取
+                    this.getEchartsData({
+                                            ksId: data.ksId,
+                                        });
+                }
+            },
+            // 分页触发数据改变
+            handleEchart(data) {
+
+                this.pageData.curPage = data - 1;
+
+                const newData = this.option[this.pageData.curPage];
+                this.pageData.ksData = {
+                    ksUserJoin: newData.ksUserJoin,
+                    ksUserPass: newData.ksUserPass,
+                    ksNowPass:  newData.ksUserJoin - newData.ksUserPass,
+                    name:       newData.name,
+                    scoreAvg:   newData.scoreAvg,
+                    scoreTop:   newData.scoreTop,
+                    startTime:  newData.startTime,
+                    timeAvg:    newData.timeAvg,
+                };
+
+                this.getEchartsData({
+                                        ksId: this.option[this.pageData.curPage].ksId,
+                                    });
+
+            },
+            getEchartsData(data) {
+                getScoreShow(data).then(res => {
+                    if (res.code === 0) {
+                        const xArr = [];
+                        const yArr = [];
+                        if (res.code === 0 && res.data.length) {
+                            res.data.forEach(item => {
+                                yArr.push(item.userCount);
+                                xArr.push(item.score);
+                            });
+                        }
+                        this.pageData.dlEchartData.xAxis.data = xArr;
+                        this.pageData.dlEchartData.series[0].data = yArr;
+
+                        this.initEcharts();
+
+                        this.welcomeCharts.setOption(this.pageData.dlEchartData);
+                    }
+                });
+            },
+            // 初始化图表
+            initEcharts() {
+                if (this.option && this.option.length) {
+                    this.welcomeCharts = this.$echarts.init(document.getElementById('welcomeCharts'));
+                    this.welcomeCharts.setOption(this.pageData.dlEchartData);
+                    window.onresize = () => {
+                        return (() => {
+                            this.$nextTick(() => {
+                                this.resizeEchart();
+                            });
+                        })();
+                    };
+
+                }
+            },
+            resizeEchart() {
+                this.welcomeCharts.resize();
+            },
+        },
+        beforeDestroy() {
+            this.onresize = null;
+        },
+    };
+</script>
+

+ 523 - 0
src/components/management/QuillEditor.vue

@@ -0,0 +1,523 @@
+<template>
+    <div>
+        <!-- 图片上传组件辅助-->
+        <el-upload
+                class="avatar-uploader"
+                action="notnull"
+                :http-request="uploadFileFun"
+                name="img"
+                :headers="header"
+                :show-file-list="false"
+                :on-success="uploadSuccess"
+                :on-error="uploadError"
+                :before-upload="beforeUpload"
+                v-show="false">
+            <el-button class="stBtnUpload" size="small" type="primary">点击上传</el-button>
+        </el-upload>
+        <el-button   style="display: none" @click="richtext()" size="small" type="primary">返回</el-button>
+        <quill-editor
+                class="editor"
+                v-model="content"
+                :ref="quillEditorRef"
+                :options="editorOption"
+                @blur="onEditorBlur($event)" @focus="onEditorFocus($event)"
+                @change="onEditorChange($event)">
+        </quill-editor>
+    </div>
+</template>
+<script>
+    let cusSpecial = ['α', 'β', 'γ'];
+    // 工具栏配置
+    const toolbarOptions = [
+        ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
+        ['blockquote', 'code-block', 'formula'], // 引用  代码块
+        [{ header: 1 }, { header: 2 }], // 1、2 级标题
+        [{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
+        [{ script: 'sub' }, { script: 'super' }], // 上标/下标
+        [{ indent: '-1' }, { indent: '+1' }], // 缩进
+        // [{'direction': 'rtl'}],                         // 文本方向
+        [{ size: ['small', false, 'large', 'huge'] }], // 字体大小
+        [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
+        [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
+        [{ font: [] }], // 字体种类
+        [{ align: [] }], // 对齐方式
+        ['clean'], // 清除文本格式
+        ['link', 'image', 'video'], // 链接、图片、视频
+        cusSpecial, // 特殊符号
+    ];
+
+    import { quillEditor } from 'vue-quill-editor';
+    import * as Quill      from 'quill';
+    import * as Delta      from 'quill-delta';
+    import ImageResize     from 'quill-image-resize-module';
+    import StImage         from './QuillEditorStBlockEmbed';
+
+    Quill.register('modules/imageResize', ImageResize);
+    Quill.register('formats/stImage', StImage);
+
+    import 'quill/dist/quill.core.css';
+    import 'quill/dist/quill.snow.css';
+    import 'quill/dist/quill.bubble.css';
+
+    import { getUploadImg }    from '@/api/AlCloud';
+    import { getUploadConfig } from '@/api/base64';
+    import axios               from 'axios';
+    import store               from '@/store';
+
+    export default {
+        props:      {
+            /*编辑器的内容*/
+            value:          {
+                type: String,
+            },
+            flg:            {
+                type: String,
+            },
+            quillEditorRef: {
+                type: String,
+            },
+            /*图片大小*/
+            maxSize:        {
+                type:    Number,
+                default: 4000, //kb
+            },
+        },
+        components: {
+            quillEditor,
+        },
+        data() {
+            return {
+                curLength:       0,
+                uploadfileParam: {},
+                content:         this.value,
+                quillUpdateImg:  false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
+                editorOption:    {
+                    theme:       'snow', // or 'bubble'
+                    placeholder: '请输入内容...',
+                    modules:     {
+                        toolbar:     {
+                            container: toolbarOptions,
+                            handlers:  {
+                                image:     function (value) {
+                                    if (value) {
+                                        // 触发input框选择图片文件
+                                        // this.$refs[this.btnRef].click();
+                                        this.container.parentElement.parentElement.children[0].children[0].children[0].click();
+                                        this.container.parentElement.parentElement.children[1].click();
+                                    } else {
+                                        this.quill.format('image', false);
+                                    }
+                                },
+                                'formula': (value) => {
+                                    let quill = this.$refs[this.quillEditorRef].quill;
+                                    this.curLength = quill.getSelection() ? quill.getSelection().index : 0;
+                                    let chuck = this.getSelectionLatex();
+                                    if (chuck) {
+                                        this.formula.latex = chuck.latex;
+                                        this.formula.type = chuck.type;
+                                    } else {
+                                        this.formula.latex = '';
+                                        this.formula.type = '';
+                                    }
+
+                                    this.formula.dialogVisible = true;
+                                    this.formula.quillEditorRef = this.quillEditorRef;
+
+                                    store.commit({
+                                                     type:      'setFormula'
+                                                     , formula: this.formula,
+                                                 });
+                                },
+                                'α':       (value) => {
+                                    let quill = this.$refs[this.quillEditorRef].quill;
+                                    // 获取光标所在位置
+                                    let length = quill.getSelection().index;
+                                    // 插入图片  res.url为服务器返回的图片地址
+                                    /*quill.insertEmbed(length, 'image', `${multipartParams.url}/${multipartParams.key}`);
+                                    // 调整光标到最后
+                                    quill.setSelection(length + 1);*/
+                                    quill.insertText(length, 'α');
+                                    quill.setSelection(length + 1);
+                                },
+                                'β':       (value) => {
+                                    let quill = this.$refs[this.quillEditorRef].quill;
+                                    // 获取光标所在位置
+                                    let length = quill.getSelection().index;
+                                    // 插入图片  res.url为服务器返回的图片地址
+                                    /*quill.insertEmbed(length, 'image', `${multipartParams.url}/${multipartParams.key}`);
+                                    // 调整光标到最后
+                                    quill.setSelection(length + 1);*/
+                                    quill.insertText(length, 'β');
+                                    quill.setSelection(length + 1);
+                                },
+                                'γ':       (value) => {
+                                    let quill = this.$refs[this.quillEditorRef].quill;
+                                    // 获取光标所在位置
+                                    let length = quill.getSelection().index;
+                                    // 插入图片  res.url为服务器返回的图片地址
+                                    /*quill.insertEmbed(length, 'image', `${multipartParams.url}/${multipartParams.key}`);
+                                    // 调整光标到最后
+                                    quill.setSelection(length + 1);*/
+                                    quill.insertText(length, 'γ');
+                                    quill.setSelection(length + 1);
+                                },
+                            },
+                        },
+                        imageResize: {},
+                    },
+                },
+                // serverUrl: "/v1/blog/imgUpload", // 这里写你要上传的图片服务器地址
+                header:          {
+                    // token: sessionStorage.token
+                }, // 有的图片服务器要求请求头需要有token
+                currentImgSrc:   '',
+                sttipinputw:     0,
+                sttipinputh:     0,
+                formula:         {
+                    dialogVisible:  false,
+                    imgsrc:         '',
+                    latex:          '',
+                    type:           '',
+                    quillEditorRef: '',
+                },
+            };
+        },
+
+        methods: {
+
+            //  describe: 此功能用户放切屏点击图片失去焦点 author: Wgy date:2020-07-07
+            richtext(){
+                window.onblur = null;
+                this.$emit('richText','richtext');
+            },
+            onEditorBlur() {
+                //失去焦点事件
+            },
+            onEditorFocus() {
+                //获得焦点事件
+            },
+            onEditorChange() {
+                //内容改变事件
+                this.$emit('syncValue', this.flg, this.content);
+            },
+
+            // 富文本图片上传前
+            beforeUpload() {
+                // 显示loading动画
+                this.quillUpdateImg = true;
+            },
+
+            uploadSuccess(res, file) {
+            },
+            // 富文本图片上传失败
+            uploadError() {
+                // loading动画消失
+                this.quillUpdateImg = false;
+            },
+            uploadFileFun(truck) {
+                var file = truck.file;
+                const suffix = file.name.split('.').pop();
+                var dd = new Date();
+                var y = dd.getFullYear();
+                var m = dd.getMonth() + 1;//获取当前月份的日期
+                const options = {
+                    prefix: 'resource/QuillEditor/' + y + m + '/',
+                    suffix: suffix,
+                };
+
+                getUploadImg(options).then((res) => {
+                    if (res.code === 0) {
+                        // 二进制文件通过forData对象进行传递
+                        const FormDataForAl = new FormData();
+                        const multipartParams = Object.assign({}, res.data, {
+                            Filename:              `images/${file.name}`,
+                            success_action_status: '200',
+                        });
+                        // 参数数据
+                        FormDataForAl.append('key', multipartParams.key);
+                        FormDataForAl.append('policy', multipartParams.policy);
+                        FormDataForAl.append('signature', multipartParams.signature);
+                        FormDataForAl.append('OSSAccessKeyId', multipartParams.accessid);
+                        FormDataForAl.append('success_action_status', multipartParams.success_action_status);
+                        // OSS要求, file放到最后
+                        FormDataForAl.append('file', file);
+
+                        axios.post(multipartParams.uploadUrl, FormDataForAl).then(alRes => {
+                            if (alRes.status === 200) {
+                                let quill = this.$refs[this.quillEditorRef].quill;
+                                // 获取光标所在位置
+                                let length = quill.getSelection().index;
+                                // 插入图片  res.url为服务器返回的图片地址
+                                quill.insertEmbed(length, 'image', `${multipartParams.downloadUrl}/${multipartParams.key}`);
+
+
+                                // 调整光标到最后
+                                quill.setSelection(length + 1);
+                            }
+                        }).catch(alerr => {
+                            this.$message.error('图片插入失败');
+                            console.error('阿里云错误', alerr);
+                        });
+                    }
+                });
+
+
+            },
+            clearContent() {
+                this.content = '';
+            },
+            setContent(val) {
+                this.content = val;
+            },
+            quillEditorAddEventListener() {
+                let dom = this.$refs[this.quillEditorRef].$el.children[1].children[0];
+                let quill = this.$refs[this.quillEditorRef].quill;
+                let that = this;
+                var pasteFun = function (e) {
+                    var clipboardData = e.clipboardData;
+                    if (!(clipboardData && clipboardData.items)) {//是否有粘贴内容
+                        return;
+                    }
+                    for (var i = 0; i < clipboardData.items.length; i++) {
+                        var item = clipboardData.items[i];
+                        if (item.kind === 'string' && item.type === 'text/plain') {
+                            item.getAsString((str) => {
+                                // str 是获取到的字符串,创建文本框
+                                //处理粘贴的文字内容
+                                setTimeout(() => {
+                                    let length = quill.getSelection().index;
+                                    // 插入
+                                    quill.insertText(length, str);
+                                    // 调整光标到最后
+                                    quill.setSelection(length + str.length);
+                                }, 50);
+                            });
+                        } else if (item.kind === 'file') {//file 一般是各种截图base64数据
+                            var pasteFile = item.getAsFile();
+                            // pasteFile就是获取到的文件
+                            var reader = new FileReader();
+                            reader.onload = function (event) {
+                                var base64Img = event.target.result;
+                                // image/png;base64,
+                                let base64ImgPrefix = base64Img.substring(0, base64Img.indexOf(',') + 1);
+                                let base64ImgData = base64Img.substring(base64Img.indexOf(',') + 1);
+                                let opt = {
+                                    'data':   base64ImgData,
+                                    'prefix': 'resource/',
+                                    'suffix': base64ImgPrefix.substring(base64ImgPrefix.indexOf('/') + 1, base64ImgPrefix.indexOf(';')),
+                                };
+                                getUploadConfig(opt).then(res => {
+                                    if (res.code === 0) {
+                                        let uri = res.data;
+
+                                        // 获取光标所在位置
+                                        let length = quill.getSelection().index;
+                                        // 插入图片  res.url为服务器返回的图片地址
+                                        quill.insertEmbed(length, 'image', uri);
+                                        // 调整光标到最后
+                                        quill.setSelection(length + 1);
+                                    } else {
+                                        that.$message.error('图片插入失败');
+                                    }
+
+                                });
+                            }; // data url
+                            reader.readAsDataURL(pasteFile);
+                        }
+                    }
+                };
+                dom.removeEventListener('paste', pasteFun);
+                dom.addEventListener('paste', pasteFun);
+            },
+
+            getSelectionLatex() {
+                let type = '';
+                let leaf = null;
+                // 如果光标前一个是公式 取得公式的latex
+                let quill = this.$refs[this.quillEditorRef].quill;
+                let selection = quill.getSelection();
+                if (!selection) {
+                    return null;
+                }
+
+                if (selection.length === 0) {
+                    type = 'rightSingle';
+                    leaf = quill.getLeaf(selection.index);
+                } else {
+                    type = 'selected';
+                    leaf = quill.getLeaf(selection.index + 1);
+                }
+                // let contents = quill.getContents();
+
+                // let line = quill.getLine(selection.index);
+                // let bounds = quill.getBounds(selection.index);
+                // console.log(selection, leaf);
+
+                if (leaf[0] instanceof StImage) {
+                    // console.log(leaf[0].domNode.dataset.latex);
+                    return {
+                        type:  type,
+                        latex: leaf[0].domNode.dataset.latex,
+                    };
+                }
+                return null;
+            },
+            // Delta operation
+            delQuillContent(index) {
+                let quill = this.$refs[this.quillEditorRef].quill;
+                /*console.dir(quill);
+                console.dir(quill.getSelection());
+                console.dir(quill.getContents());*/
+
+                //  .insert('White', { color: '#fff' })
+                var stDelta = new Delta().retain(index).delete(1);
+                let quillContents = quill.getContents().compose(stDelta);
+                quill.setContents(quillContents);
+            },
+            passValue() {
+                // console.log('do passValue!!');
+                let latex = this.formula.latex;
+                if (latex.indexOf('placeholder') > -1) {
+                    this.formula.dialogVisible = false;
+                    let quill = this.$refs[this.quillEditorRef].quill;
+                    quill.setSelection(this.curLength);
+                    return;
+                }
+
+                if (latex && latex !== '') {
+
+                    if (this.formula.type === 'rightSingle') {
+                        // del formula
+                        this.delQuillContent(this.curLength - 1);
+
+                        let quill = this.$refs[this.quillEditorRef].quill;
+                        // 获取光标所在位置
+                        // let length = quill.getSelection() ? quill.getSelection().index : 0;
+                        // 插入图片  res.url为服务器返回的图片地址
+                        quill.insertEmbed(this.curLength - 1, 'stimage', {
+                            src:          this.formula.imgsrc,
+                            'data-latex': latex,
+                        });
+                        // quill.formatText(length, 1, { 'data-latex': latex });
+                        /*var delta = quill.getContents();
+                        console.log(delta);*/
+                        // 调整光标到最后
+                        // quill.insertText(length, img);
+                        quill.setSelection(this.curLength);
+                    } else if (this.formula.type === 'selected') {
+                        // del formula
+                        this.delQuillContent(this.curLength);
+                        let quill = this.$refs[this.quillEditorRef].quill;
+                        // 获取光标所在位置
+                        // let length = quill.getSelection() ? quill.getSelection().index : 0;
+                        // 插入图片  res.url为服务器返回的图片地址
+                        quill.insertEmbed(this.curLength, 'stimage', {
+                            src:          this.formula.imgsrc,
+                            'data-latex': latex,
+                        });
+                        // quill.formatText(length, 1, { 'data-latex': latex });
+                        /*var delta = quill.getContents();
+                        console.log(delta);*/
+                        // 调整光标到最后
+                        // quill.insertText(length, img);
+                        quill.setSelection(this.curLength + 1);
+                    } else {
+                        let quill = this.$refs[this.quillEditorRef].quill;
+                        // 获取光标所在位置
+                        // let length = quill.getSelection() ? quill.getSelection().index : 0;
+                        // 插入图片  res.url为服务器返回的图片地址
+                        quill.insertEmbed(this.curLength, 'stimage', {
+                            src:          this.formula.imgsrc,
+                            'data-latex': latex,
+                        });
+                        // quill.formatText(length, 1, { 'data-latex': latex });
+                        /*var delta = quill.getContents();
+                        console.log(delta);*/
+                        // 调整光标到最后
+                        // quill.insertText(length, img);
+                        quill.setSelection(this.curLength + 1);
+                        this.formula.dialogVisible = false;
+                    }
+
+                    this.formula.dialogVisible = false;
+                }
+            },
+        },
+        mounted() {
+
+            let checkCount = 0;
+            let checkCountMax = 100;
+            let stInterval = setInterval(() => {
+                if (this.quillEditorRef) {
+                    clearInterval(stInterval);
+                    this.$nextTick(function () {
+                        this.quillEditorAddEventListener();
+                    });
+                    /*let dom = this.$refs[this.quillEditorRef].$el.children[1].children[0];
+                    const specialSignals = document.querySelectorAll('.ql-special-signal');
+                    for (const specialSignal of specialSignals) {
+                        // specialSignal.style.cssText = "width:80px; border:1px solid #ccc; border-radius:5px;";
+                        // specialSignal.style.cssText = "width:50px;";
+                        // specialSignal.innerText="特殊符号";
+
+                        specialSignal.classList.add('el-icon-edit-outline');
+                        specialSignal.title = "特殊符号";
+                    }*/
+
+                    for (const special of cusSpecial) {
+                        const specialSignals = document.querySelectorAll('.ql-' + special);
+                        for (const specialSignal of specialSignals) {
+                            // specialSignal.style.cssText = "width:80px; border:1px solid #ccc; border-radius:5px;";
+                            // specialSignal.style.cssText = "width:50px;";
+                            specialSignal.innerText = special;
+
+                            // specialSignal.classList.add('el-icon-edit-outline');
+                            specialSignal.title = special;
+                        }
+                    }
+
+
+                } else {
+                    if (checkCount > checkCountMax) {
+                        clearInterval(stInterval);
+                    }
+                }
+                checkCount++;
+            }, 100);
+            /*监听富文本复制粘贴*/
+            document.onpaste = function (e) {
+                let arrPath = e.path;
+                let has = false;
+                if (arrPath) {
+                    for (const path of arrPath) {
+                        if (path.className && path.className.indexOf('ql-editor') > -1) {
+                            has = true;
+                            break;
+                        }
+                    }
+                }
+                if (has) {
+                    e.preventDefault();
+                }
+            };
+        },
+        watch:   {
+            '$store.state.formula': {
+                handler(newVal, oldVal) {
+                    // console.log('in store.state.formula');
+                    // console.log(newVal);
+                    this.formula = newVal;
+                    /*console.dir('======start=======');
+                    console.dir(this.formula.quillEditorRef);
+                    console.dir(this.quillEditorRef);
+                    console.dir('======end=======');*/
+                    if (this.formula.dialogVisible === false && this.formula.quillEditorRef === this.quillEditorRef) {
+                        this.passValue();
+                    }
+                },
+                deep: true,
+            },
+        },
+    };
+
+</script>

+ 25 - 0
src/components/management/QuillEditorStBlockEmbed.js

@@ -0,0 +1,25 @@
+import Quill from 'quill'
+
+let Embed = Quill.import('blots/embed');
+
+class StImage extends Embed {
+    static create(value) {
+        // console.log('create value:', value);
+        let node = super.create();
+        node.setAttribute('data-latex', value['data-latex']);
+        node.setAttribute('src', value['src']);
+        return node;
+    }
+
+    static value(node) {
+        // console.log('value node:', node);
+        return {
+            'data-latex': node.getAttribute('data-latex'),
+            'src': node.getAttribute('src'),
+        };
+    }
+}
+StImage.blotName = 'stimage';
+StImage.tagName = 'img';
+
+export default StImage

+ 20 - 0
src/components/management/Transform.vue

@@ -0,0 +1,20 @@
+<template>
+    <transition
+            enter-class="enter"
+            enter-active-class="enterActive"
+            enter-to-class="enterTo"
+            leave-class="leave"
+            leave-active-class="leaveActive"
+            leave-to-class="leaveTo"
+            mode="out-in"
+    >
+        <slot/>
+    </transition>
+</template>
+
+<script>
+    export default {
+        name: "Transform"
+    }
+</script>
+

+ 34 - 0
src/components/management/UEditor.vue

@@ -0,0 +1,34 @@
+<template>
+    <div class="hello">
+        <vue-ueditor-wrap v-model="msg" :config="myConfig"></vue-ueditor-wrap>
+    </div>
+</template>
+
+<script>
+    import VueUeditorWrap from 'vue-ueditor-wrap'
+
+    export default {
+        data () {
+            return {
+                msg: '<h2><img src="http://img.baidu.com/hi/jx2/j_0003.gif"/>Vue + UEditor + v-model双向绑定</h2>',
+                myConfig: {
+                    // 编辑器不自动被内容撑高
+                    autoHeightEnabled: false,
+                    // 初始容器高度
+                    initialFrameHeight: 240,
+                    // 初始容器宽度
+                    initialFrameWidth: '100%',
+                    // 上传文件接口(这个地址是我为了方便各位体验文件上传功能搭建的临时接口,请勿在生产环境使用!!!)
+                    serverUrl: 'http://98.77.88.99:10/strequest',
+                    // UEditor 资源文件的存放路径,如果你使用的是 vue-cli 生成的项目,通常不需要设置该选项,vue-ueditor-wrap 会自动处理常见的情况,如果需要特殊配置,参考下方的常见问题2
+                    UEDITOR_HOME_URL: process.env.BASE_URL + 'thirdParty/ueditor/'
+                }
+            }
+        },
+        components: {
+            VueUeditorWrap
+        }
+    }
+
+</script>
+

+ 196 - 0
src/components/management/common/MtaBreadcrumb.vue

@@ -0,0 +1,196 @@
+<template>
+    <div class="a-breadcrumb-box">
+        <!--<div class="st-breadcrumb">
+            <i class="breadcrumb-icon-box"></i>
+            <router-link :to="item.path" :class="!item.last ? 'st-breadcrumb-item' : 'st-breadcrumb-item-last'"
+                         v-for="item in nodes"
+                         :key="item.path">
+                <div v-text="item.display" class="breadcrumb-div"></div>
+            </router-link>
+        </div>-->
+
+        <el-breadcrumb separator-class="el-icon-arrow-right">
+                <el-breadcrumb-item :to="item.path"
+                                    v-for="item in nodes"
+                                    :key="item.path" class="breadcrumb-text fsize-m4">{{item.display}}</el-breadcrumb-item>
+            <!--<el-breadcrumb-item><span class="breadcrumb-text fsize-m4">课件管理</span></el-breadcrumb-item>-->
+        </el-breadcrumb>
+
+
+        <div style="clear: both;"></div>
+    </div>
+</template>
+
+<script>
+    import * as commonUtils from '@/utils/common';
+
+    export default {
+        name:    'MtaBreadcrumb',
+        props:   {
+            options: {
+                type: Object,
+                default() {
+                    return {};
+                },
+            },
+        },
+        data() {
+            return {
+                nodes: [
+                    /*{
+                        path:    '/c/kecheng/lists',
+                        display: '课程列表',
+                        last:    false,
+                    },
+                    {
+                        path:    '/c/kecheng/details',
+                        display: '课程大纲',
+                        last:    false,
+                    },
+                    {
+                        path:    '/c/kecheng/courseInfo',
+                        display: '第一章 第一节',
+                        last:    true,
+                    },*/
+                ],
+            };
+        },
+        methods: {
+            getParentRoute(route) {
+
+                let parentPath = route.meta.parentPath;
+                if (parentPath && parentPath !== 'aroot') {
+                    if (typeof parentPath === 'string') {
+                        return this.$router.match(parentPath, this.current);
+                    } else if (typeof parentPath === 'object' && Array.isArray(parentPath)) {
+                        if (parentPath.length < 2) {
+                            throw new Error(' 业务逻辑错误 parentPath 作为数组长度不能小于2');
+                        }
+                        let arrPaths = commonUtils.getCache('Breadcrumb', 'arrPaths');
+                        let maxIndex = -1;
+                        let retJ = 0;
+                        for (let j = 0; j < parentPath.length; j++) {
+                            const pathRoute = parentPath[j];
+                            for (let i = arrPaths.length - 1; i > -1; i--) {
+                                const pathCache = arrPaths[i];
+                                if (pathRoute === pathCache) {
+                                    if (maxIndex < i) {
+                                        maxIndex = i;
+                                        retJ = j;
+                                    }
+                                }
+                            }
+                        }
+                        return this.$router.match(parentPath[retJ], this.current);
+                    } else {
+                        console.log(route);
+                        throw new Error('Route meta.parentPath 异常');
+                    }
+                } else {
+                    return null;
+                }
+
+            },
+            getDisplayByRoute(route) {
+                let chaptersection = commonUtils.getCache('Breadcrumb', 'chaptersection');
+                let breadcrumbDisplayParamsObj = {};
+                if (chaptersection) {
+                    breadcrumbDisplayParamsObj.courseInfo = {
+                        p1: chaptersection.chapter,
+                        p2: chaptersection.section,
+                    }
+                }
+
+                let retDisplay = '';
+                // key value
+                let p1 = '';
+                let p2 = '';
+                for (var key in breadcrumbDisplayParamsObj) {
+                    if (route.path.indexOf(key) > -1) {
+                        p1 = breadcrumbDisplayParamsObj[key].p1;
+                        p2 = breadcrumbDisplayParamsObj[key].p2;
+                        break;
+                    }
+                }
+
+                if (!route.meta.breadcrumb) {
+                    throw new Error('路由:' + route.path + '  未标记breadcrumb');
+                }
+                // console.log(route, route.meta.breadcrumb, p1, p2);
+
+                if (p2 !== '') {
+                    retDisplay = route.meta.breadcrumb.display.stformat(p1, p2);
+                } else if (p1 !== '') {
+                    retDisplay = route.meta.breadcrumb.display.stformat(p1);
+                } else {
+                    retDisplay = route.meta.breadcrumb.display;
+                }
+                return retDisplay;
+            },
+            /**
+             * 通过路由构筑单节点
+             * @param nodesReverse
+             * @param route 路由
+             */
+            getNodeByRoute(nodesReverse, route) {
+                let node = {};
+                node.path = route.path;
+                node.display = this.getDisplayByRoute(route);
+
+                if (node) {
+                    nodesReverse.push(node);
+                }
+
+                const parentRoute = this.getParentRoute(route);
+                if (parentRoute) {
+                    this.getNodeByRoute(nodesReverse, parentRoute);
+                } else {
+                    let aRootNode = {};
+                    // for admin
+                    aRootNode.path = '';
+                    aRootNode.display = route.meta.parentDisplay;
+                    nodesReverse.push(aRootNode);
+                }
+            },
+        },
+        created() {
+            /*// console.log('this.$route');
+            console.dir(this.$route);
+            // console.log('this.$router');
+            console.dir(this.$router);*/
+
+
+            let nodesReverse = [];
+            // 通过路由构筑单节点
+            this.getNodeByRoute(nodesReverse, this.$route);
+
+            // 添加首页
+            /*let tenantFlag_key = window.localStorage.getItem(`tenantFlag_key`)
+            if (tenantFlag_key === '1') {
+                nodesReverse.push({
+                                      path:    '/c/Index',
+                                      display: '首页',
+                                  });
+            } else if (tenantFlag_key === '2') {
+                nodesReverse.push({
+                                      path:    '/c/IndexPeixun',
+                                      display: '首页',
+                                  });
+            }*/
+
+
+            for (let i = nodesReverse.length - 1; i > -1; i--) {
+                const nodeReverse = nodesReverse[i];
+                if (i !== 0) {
+                    nodeReverse.last = false;
+                } else {
+                    nodeReverse.last = true;
+                }
+                this.nodes.push(nodeReverse);
+            }
+        },
+        mounted() {
+        },
+        watch:   {},
+    };
+</script>

+ 3 - 0
src/components/management/globalUploader/bus.js

@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();

+ 464 - 0
src/components/management/globalUploader/globalUploader.vue

@@ -0,0 +1,464 @@
+<template>
+    <div>
+        <div id="global-uploader">
+
+            <!-- 上传 -->
+            <uploader
+                    ref="uploader"
+                    :options="options"
+                    :autoStart="false"
+                    @file-added="onFileAdded"
+                    @file-success="onFileSuccess"
+                    @file-progress="onFileProgress"
+                    @file-error="onFileError"
+                    :file-status-text="statusText"
+                    class="uploader-app">
+                <uploader-unsupport></uploader-unsupport>
+
+                <uploader-btn  :attrs="attrs"  id="global-uploader-btn" ref="uploadBtn" @click="btnClick">{{btnText}}</uploader-btn>
+                <span style="margin-left:10px" v-if="showFlag&&showUoloadStatus">文件上传成功!</span>
+
+                <uploader-list v-show="panelShow">
+                    <div class="file-panel" slot-scope="props" :class="{'collapse': collapse}">
+                        <div class="file-title">
+                            <h2>文件列表</h2>
+                            <div class="operate">
+                                <el-button @click="fileListShow" type="text" :title="collapse ? '展开':'折叠' ">
+                                    <i class="iconfont" :class="collapse ? 'inuc-fullscreen': 'inuc-minus-round'"></i>
+                                </el-button>
+                                <el-button @click="close" type="text" title="关闭">
+                                    <i class="iconfont icon-close"></i>
+                                </el-button>
+                            </div>
+                        </div>
+
+                        <ul class="file-list">
+                            <li v-for="file in props.fileList" :key="file.id">
+                                <uploader-file :class="'file_' + file.id" ref="files" :file="file"
+                                               :list="true"></uploader-file>
+                            </li>
+                            <div class="no-file" v-if="!props.fileList.length"><i class="iconfont icon-empty-file"></i>
+                                暂无待上传文件
+                            </div>
+                        </ul>
+                    </div>
+                </uploader-list>
+
+            </uploader>
+
+        </div>
+        <el-dialog :close-on-click-modal="false"
+                   :visible.sync="UploadingDialog"
+                   center
+                   class="uploading-dialog"
+                   title=""
+                   @close="closeUploadDialog"
+        >
+            <div class="uploading-dialog-box">
+                <i></i>
+                <p>正在上传中,请稍后……</p>
+                <div class="warning"></div>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+    /**
+     *   全局上传插件
+     *   调用方法:Bus.$emit('openUploader', {}) 打开文件选择框,参数为需要传递的额外参数
+     *   监听函数:Bus.$on('fileAdded', fn); 文件选择后的回调
+     *            Bus.$on('fileSuccess', fn); 文件上传成功的回调
+     */
+    //import Bus              from './bus';
+    import SparkMD5         from 'spark-md5';
+    import { getUploadImg } from '@/api/AlCloud.js';
+    import { videoPolicy } from '@/api/peixun/videoPolicy.js';
+    import { mapGetters }   from 'vuex';
+    import axios                from 'axios';
+
+    export default {
+        props:{
+            //  describe: 文字 author: Wgy date:2020-05-28
+            btnText:{
+                type: String,
+                default: '点击上传'
+            },
+            //  describe: attributes接收属性值 author: Wgy date:2020-05-28
+            acceptConfig:{
+                type: Array,
+                default: () => []
+            },
+            //  describe: 状态 author: Wgy date:2020-05-28
+            showUoloadStatus:{
+                type: Boolean,
+                default: false
+            },
+            //  describe: 状态 author: Wgy date:2020-05-28
+            isVideo:{
+                type: Boolean,
+                default: false
+            }
+        },
+        data() {
+            return {
+                //  describe: 配置信息 author: Wgy date:2020-05-26
+                options:   {
+                    target:       '',
+                    chunkSize:                    '10240000',
+                    fileParameterName:            'file',//与后端商议
+                    maxChunkRetries:              3,
+                    simultaneousUploads:          3,
+                    testChunks:                   true,   //是否开启服务器分片校验
+                    //  describe: 服务器分片校验函数 get请求检测是否每块是否上传成功 true为跳过 author: Wgy date:2020-05-26
+                    checkChunkUploadedByResponse: function (chunk, message) {
+                        let objMessage = JSON.parse(message);
+                     /*   if (objMessage.skipUpload) {
+                            //  describe:  此块 用于之后秒传功能 当前未实现   author: Wgy date:2020-05-26
+                            return true;
+                        }*/
+                        return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0;
+                    },
+                    headers: {
+                       // 'content-disposition':'form-data; name="file"; filename=1111'
+                    },
+                    query() {
+
+                    },
+                    singleFile:true
+                },
+                attrs:     {
+                  //  describe: 校验类型 此版本未添加   author: Wgy date:2020-05-26
+                  //  describe: :attrs="attrs"  btn中 author: Wgy date:2020-05-26
+                    accept:this.acceptConfig||[]
+                },
+                statusText: {
+                    success: '成功了',
+                    error: '出错了',
+                    uploading: '上传中',
+                    paused: '暂停中',
+                    waiting: '等待中'
+                },
+                panelShow: false,   //选择文件后,展示上传panel
+                downloadUrl:'',
+                collapse:  false,
+                UploadingDialog:false,
+                newFile:{},
+                showFlag:false
+
+            };
+        },
+        mounted() {
+       /*     this.$on('openUploader', query => {
+                this.params = query || {};
+                console.log(this.params);
+                if (this.$refs.uploadBtn) {
+                    $('#global-uploader-btn').click();
+                }
+            });*/
+        },
+        computed:   {
+            //Uploader实例
+            uploader() {
+                return this.$refs.uploader.uploader;
+            },
+            ...mapGetters([
+                              'getTenantCode',
+                          ]),
+        },
+        methods:    {
+
+            btnClick(){
+                this.$on('openUploader', query => {
+                    this.params = query || {};
+                    console.log(this.params);
+                    if (this.$refs.uploadBtn) {
+                        $('#global-uploader-btn').click();
+                    }
+                });
+            },
+            closeUploadDialog(){
+                this.newFile.cancel()
+                this.UploadingDialog=false
+                console.log('cancel');
+            },
+            onFileAdded(file) {
+                this.newFile = file
+                const suffix = file.name.split('.').pop();
+                if(this.isVideo){
+                    var time = new Date().getTime();
+                    const options = {
+                        fileName: file.name,
+                        title: this.getTenantCode + time,
+                    };
+                    videoPolicy(options).then(res => {
+                        if (res.code === 0) {
+                            //  console.log( this.$refs.uploader);
+                            this.$refs.uploader.uploader.opts.target = res.data.partUrl;
+                            this.downloadUrl = res.data.downloadUrl;
+                            this.UploadingDialog = true;
+                            this.computeMD5(file);
+                            //  Bus.$emit('fileAdded');
+                        }
+                    });
+                }else {
+                    const options = {
+                        prefix: 'resource/',
+                        suffix: suffix,
+                    };
+                    getUploadImg(options).then(res => {
+                        if (res.code === 0 && res.data.type !== 'oss') {
+                            //  console.log( this.$refs.uploader);
+                            this.$refs.uploader.uploader.opts.target = res.data.partUrl;
+                            this.downloadUrl = res.data.downloadUrl;
+                            this.UploadingDialog = true;
+                            this.computeMD5(file);
+                            //  Bus.$emit('fileAdded');
+                        }else{
+                            this.UploadingDialog = true;
+                            // 二进制文件通过forData对象进行传递
+                            const FormDataForAl = new FormData();
+                            const multipartParams = Object.assign({}, res.data, {
+                                Filename:              `images/${file.name}`,
+                                success_action_status: '200',
+                            });
+                            console.log("FormDataForAl",FormDataForAl);
+                            console.log("multipartParams",multipartParams)
+                            // 参数数据
+                            FormDataForAl.append('key', multipartParams.key);
+                            FormDataForAl.append('policy', multipartParams.policy);
+                            FormDataForAl.append('signature', multipartParams.signature);
+                            FormDataForAl.append('OSSAccessKeyId', multipartParams.accessid);
+                            FormDataForAl.append('success_action_status', multipartParams.success_action_status);
+                            FormDataForAl.append('x-oss-object-acl','private');
+                            FormDataForAl.append('Content-Disposition','form-data; name="file"; filename='+ file.name);
+
+                            // OSS要求, file放到最后
+                            FormDataForAl.append('file', file);
+
+                            axios.post(multipartParams.uploadUrl, FormDataForAl).then(alRes => {
+                                if (alRes.status === 200) {
+                                    this.imageUrl = `${multipartParams.uploadUrl}/${multipartParams.key}`;
+                                    this.$refs.uploader.uploader.opts.target = res.data.partUrl;
+                                    this.downloadUrl = res.data.downloadUrl;
+                                    this.$emit('filesuccess',{url:res.data.downloadUrl,fileName:file.name,fileSize:file.size});
+                                    this.UploadingDialog = false;
+                                    this.$message.error('文件上传成功');
+                                } else {
+                                    this.UploadingDialog = false;
+                                    this.$message.error('文件上传失败');
+                                }
+                            });
+                        }
+                    });
+                }
+
+            },
+            onFileProgress(rootFile, file, chunk) {
+                //  describe: 上传进程 author: Wgy date:2020-05-26
+                console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`);
+            },
+            onFileSuccess(rootFile, file, response, chunk) {
+                let res = JSON.parse(response);
+                // 服务器自定义的错误(即虽返回200,但是是错误的情况),这种错误是Uploader无法拦截的
+                if (!res.result) {
+                    this.$message({ message: res.message, type: 'error' });
+                    // 文件状态设为“失败”
+                    this.statusSet(file.id, 'failed');
+                    return;
+                }
+                if(res.url){
+                    this.UploadingDialog = false
+                    let downloadUrl = this.downloadUrl+'/'+res.url
+                    this.showFlag = true
+                    this.$emit('filesuccess',{url:downloadUrl,fileName:file.name,fileSize:file.size});
+                    console.log('上传成功');
+                }else {
+                    this.statusSet(file.id, 'failed');
+                    this.UploadingDialog = false
+                    return false;
+                }
+                /*// 如果服务端返回需要合并
+                if (res.needMerge) {
+                    // 文件状态设为“合并中”
+                    this.statusSet(file.id, 'merging');
+
+                    api.mergeSimpleUpload({
+                                              tempName: res.tempName,
+                                              fileName: file.name,
+                                              ...this.params,
+                                          }).then(res => {
+                        // 文件合并成功
+                        Bus.$emit('fileSuccess');
+
+                        this.statusRemove(file.id);
+                    }).catch(e => {
+                    });
+
+                    // 不需要合并
+                } else {
+                    Bus.$emit('fileSuccess',{url:res.url});
+                    console.log('上传成功');
+                }*/
+            },
+            onFileError(rootFile, file, response, chunk) {
+                this.$message({
+                                  message: response,
+                                  type:    'error',
+                              });
+            },
+
+            /**
+             * 计算md5,实现断点续传及秒传
+             * @param file
+             */
+            computeMD5(file) {
+                //  describe: FileReader 异步读取存储在用户计算机上的文件( author: Wgy date:2020-05-26
+                let fileReader = new FileReader();
+                let time = new Date().getTime();
+                //  describe: //兼容方式获取slice方法 author: Wgy date:2020-05-26
+                let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
+                let currentChunk = 0;
+                const chunkSize = 10 * 1024 * 1000;
+                let chunks = Math.ceil(file.size / chunkSize);
+                let spark = new SparkMD5.ArrayBuffer();
+
+                // 文件状态设为"计算MD5"
+                this.statusSet(file.id, 'md5');
+                file.pause();
+
+                loadNext();
+                //  describe: 载入完毕后执行 author: Wgy date:2020-05-26
+                fileReader.onload = (e => {
+                    console.log(e);
+                    spark.append(e.target.result);
+
+                    if (currentChunk < chunks) {
+                        currentChunk++;
+                        loadNext();
+
+                        // 实时展示MD5的计算进度
+                        this.$nextTick(() => {
+                            $(`.myStatus_${file.id}`).text('校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%');
+                        });
+                    } else {
+                        let md5 = spark.end();
+                        this.computeMD5Success(md5, file);
+                        console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
+
+                    }
+                });
+
+                fileReader.onerror = function () {
+                    this.error(`文件${file.name}读取出错,请检查该文件`);
+                    file.cancel();
+                };
+
+                function loadNext() {
+                    let start = currentChunk * chunkSize;
+                    let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
+
+                    fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
+                }
+            },
+            //  describe: md5成功  author: Wgy date:2020-05-26
+            computeMD5Success(md5, file) {
+                // 将自定义参数直接加载uploader实例的opts上
+                Object.assign(this.uploader.opts, {
+                    query: {
+                        ...this.params,
+                    },
+                });
+                //  describe: file 自带唯一标示 author: Wgy date:2020-05-26
+                file.uniqueIdentifier = md5;
+                file.resume();
+                this.statusRemove(file.id);
+            },
+            //  describe: 展开 author: Wgy date:2020-05-26
+            fileListShow() {
+                let $list = $('#global-uploader .file-list');
+
+                if ($list.is(':visible')) {
+                    $list.slideUp();
+                    this.collapse = true;
+                } else {
+                    $list.slideDown();
+                    this.collapse = false;
+                }
+            },
+            close() {
+                this.uploader.cancel();
+
+                this.UploadingDialog = false;
+            },
+
+            /**
+             * 新增的自定义的状态: 'md5'、'transcoding'、'failed'
+             * @param id
+             * @param status
+             */
+            statusSet(id, status) {
+                let statusMap = {
+                    md5:         {
+                        text: '校验MD5',
+                        bgc:  '#fff',
+                    },
+                    merging:     {
+                        text: '合并中',
+                        bgc:  '#e2eeff',
+                    },
+                    transcoding: {
+                        text: '转码中',
+                        bgc:  '#e2eeff',
+                    },
+                    failed:      {
+                        text: '上传失败',
+                        bgc:  '#e2eeff',
+                    },
+                };
+
+                this.$nextTick(() => {
+                    $(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
+                                                                                                              'position':        'absolute',
+                                                                                                              'top':             '0',
+                                                                                                              'left':            '0',
+                                                                                                              'right':           '0',
+                                                                                                              'bottom':          '0',
+                                                                                                              'zIndex':          '1',
+                                                                                                              'backgroundColor': statusMap[status].bgc,
+                                                                                                          }).text(statusMap[status].text);
+                });
+            },
+            statusRemove(id) {
+                this.$nextTick(() => {
+                    $(`.myStatus_${id}`).remove();
+                });
+            },
+
+            error(msg) {
+                //  describe: 消息通知组件$notify author: Wgy date:2020-05-26
+                this.$notify({
+                                 title:    '错误',
+                                 message:  msg,
+                                 type:     'error',
+                                 duration: 2000,
+                             });
+            },
+        },
+        watch:      {},
+        destroyed() {
+            this.$off('openUploader');
+            this.$off('fileAdded');
+            this.$off('fileSuccess');
+        },
+        components: {},
+    };
+</script>
+
+<style scoped lang="scss">
+    /* 隐藏上传按钮 */
+  /*  #global-uploader-btn {
+        position: absolute;
+        clip: rect(0, 0, 0, 0);
+    }*/
+</style>

BIN
src/components/management/globalUploader/images/image-icon.png


BIN
src/components/management/globalUploader/images/text-icon.png


BIN
src/components/management/globalUploader/images/video-icon.png