소스 검색

Merge branch 'develop'

# Conflicts:
#	src/views/teacher/components/TeacherCourseSlides.vue
ChenYangfan 6 년 전
부모
커밋
1e58f5fd86

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "core-js": "^3.4.3",
     "highlight.js": "^9.17.1",
+    "laue": "^0.2.1",
     "marked": "^0.8.0",
     "moment": "^2.24.0",
     "register-service-worker": "^1.6.2",

+ 2 - 2
src/animations/ShowTransition.vue

@@ -31,14 +31,14 @@ export default {
   }
   100% {
     opacity: 1;
-    max-height: 200px;
+    max-height: 300px;
   }
 }
 
 @keyframes hide {
   0% {
     opacity: 1;
-    max-height: 200px;
+    max-height: 300px;
   }
   50% {
     opacity: 0;

+ 4 - 0
src/common-style.scss

@@ -78,6 +78,10 @@
   margin-bottom: 32px;
 }
 
+.mt-6 {
+  margin-top: 6px;
+}
+
 .mt-8 {
   margin-top: 8px;
 }

+ 16 - 11
src/components/Cards/AnswerCard.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="answer-card" @click="$emit('click')">
-    <div class="info">学生姓名:{{answer.studentName}}</div>
-    <div class="info">提交次数:{{answer.submitNumber}}</div>
-    <div class="info">最近提交:{{momentFromNow(answer.submitAt)}}</div>
-    <div class="info orange">分数:{{answer.score}}</div>
+    <div class="info grey">学生姓名:{{answer.studentName}}</div>
+    <div class="info grey">提交次数:{{answer.submitNumber}}</div>
+    <div class="info grey">最近提交:{{momentFromNow(answer.submitAt)}}</div>
+    <div class="info green">分数:{{answer.score}}</div>
   </div>
 </template>
 
@@ -23,22 +23,27 @@ export default {
 
 <style lang="scss" scoped>
 @import '~@/style/theme.scss';
+
 .answer-card {
   margin: 12px 0;
-  padding: 8px 12px;
-  border-radius: 12px;
-  background: $card-background;
+  padding: 6px 8px;
+  border-radius: 4px;
   cursor: pointer;
-  transition: .2s;
+  transition: .3s;
+  border: 1px solid darken($card-background, 5);
+
+  &:hover {
+    background-color: $card-background;
+    box-shadow: 2px 4px 6px rgba(0, 0, 0, .05);
+  }
 }
 
 .info {
   line-height: 1.5;
   font-size: 14px;
-  color: #6a7a8a;
 }
 
-.orange {
-  color: $theme-orange;
+.grey {
+  color: #4a5a6a;
 }
 </style>

+ 17 - 12
src/components/Cards/NoticeCard.vue

@@ -1,13 +1,13 @@
 <template>
-  <div class="notice-card" @click="showMore = !showMore">
-    <div class="notice-card-title" :class="showMore ? 'cancel-bottom-radius' : null">
-      <c-icon>notifications</c-icon>
+  <div class="notice-card">
+    <div class="notice-card-title" @click="showMore = !showMore" :class="showMore ? 'cancel-bottom-radius' : null">
+      <c-icon color="orange">notifications</c-icon>
       课程公告:{{notice.title}}
       <c-icon bold class="more-icon">{{showMore ? 'expand_less' : 'expand_more'}}</c-icon>
     </div>
     <show-transition>
       <div v-if="showMore" class="notice-card-info">
-        {{notice.content}}
+        <pre>{{notice.content}}</pre>
       </div>
     </show-transition>
     <show-transition-small>
@@ -57,29 +57,28 @@ export default {
   margin: 8px 0;
   border-radius: 12px;
   transition: .2s;
-  cursor: pointer;
-  background: $theme-green-transparent;
+  background: lighten($card-background, 2);
 }
 
 .notice-card-title {
-  background-color: $theme-green;
+  background-color: darken($card-background, 3);
   border-radius: 12px;
   font-size: 14px;
-  color: white;
+  color: #2a3a4a;
   padding: 8px 12px;
+  cursor: pointer;
 
   .c-icon {
     transform-origin: top;
     transform: scale(1.25);
-    color: white;
   }
 }
 
 .notice-card-info {
   font-size: 13px;
-  color: #839a91;
+  color: #4a5a6a;
   line-height: 1.7;
-  margin: 6px 12px;
+  margin: 0 12px;
 }
 
 .notice-card-time {
@@ -92,7 +91,8 @@ export default {
 
   .c-button {
     padding: 4px 12px;
-    background: rgba(255, 255, 255, .8);
+    background: white;
+    box-shadow: 0 2px 6px rgba(0, 0, 0, .08);
 
     &:hover {
       background: white;
@@ -108,4 +108,9 @@ export default {
   display: inline-block;
   float: right;
 }
+
+pre {
+  white-space: pre-wrap;
+  word-break: break-all;
+}
 </style>

+ 1 - 1
src/components/Pagination.vue

@@ -165,7 +165,7 @@ input {
   border-radius: 1000px;
   width: 34px;
   height: 34px;
-  margin: 0 8px;
+  margin: 0 6px;
 }
 
 .page-input {

+ 5 - 3
src/config/server-info.js

@@ -4,7 +4,9 @@
 
 // const host = 'http://106.14.178.184:8080/seec_helper'
 // const host = 'http://192.168.3.70:18080'
-const host = process.env.NODE_ENV === 'development' ? 'http://localhost:8080' : ''
+const host = process.env.NODE_ENV === 'development' ? 'http://helper.seecoder.cn' : ''
+
+// const host = process.env.NODE_ENV === 'development' ? 'http://localhost:8080' : ''
 const wsHost = process.env.NODE_ENV === 'development' ? 'ws://localhost:8080' : 'ws://' + location.host
 
 const adminUrl = host + '/api/admin'
@@ -37,12 +39,12 @@ const useNoticeServer = postfix => postfix ? noticeUrl + postfix : noticeUrl
 
 // 题库(问题)
 // const questionUrl = host + '/api/question'
-const questionUrl = '/api/question'
+const questionUrl = host + '/api/question'
 const useQuestionServer = (postfix = '') => questionUrl + postfix
 
 // 测试
 // const quizUrl = host + '/api/quiz'
-const quizUrl = '/api/quiz'
+const quizUrl = host + '/api/quiz'
 const useQuizServer = (postfix = '') => quizUrl + postfix
 
 // 教师回复学生问题

+ 3 - 3
src/enums/slide-state-translation.js

@@ -2,8 +2,8 @@ import slideState from '@/enums/slide-state'
 
 export default {
   [slideState.draft]: '草稿',
-  [slideState.beforeClass]: '未上课',
+  [slideState.beforeClass]: '课',
   [slideState.inClass]: '课堂中',
-  [slideState.afterClass]: '已上课',
-  [slideState.finish]: '测验结束'
+  [slideState.afterClass]: '课',
+  [slideState.finish]: '课后测验结束'
 }

+ 3 - 0
src/main.js

@@ -7,11 +7,14 @@ import uuid from 'uuid'
 import store from './store'
 import { SET_MESSAGE } from '@/store/types/message-types'
 import moment from '@/plugins/moment'
+import { Laue } from 'laue'
 
 Vue.config.productionTip = false
 Vue.prototype.$uuid = uuid
 Vue.prototype.$moment = moment
 
+Vue.use(Laue)
+
 Vue.prototype.$setMessage = function ({ message, type = 'info' }) {
   this.$store.commit(SET_MESSAGE, {
     message: message,

+ 5 - 0
src/router/quiz.js

@@ -8,6 +8,11 @@ export default [{
   path: '/quiz-detail/:quizId',
   name: 'quiz-detail',
   component: () => import(/* webpackChunkName: "teacher-quiz" */ '@/views/quiz/QuizDetail.vue')
+}, {
+  meta: { roles: ['teacher'] },
+  path: '/all-quiz',
+  name: 'all-quiz',
+  component: () => import(/* webpackChunkName: "teacher-quiz" */ '@/views/quiz/TeacherCheckAllQuiz.vue')
 }, {
   meta: { roles: ['teacher'] },
   path: '/quiz-detail/:quizId/edit',

+ 7 - 0
src/server/quiz.js

@@ -90,3 +90,10 @@ export function getOneQuizStudentAnswer ({ quizId }) {
     .get(useQuizServer(`/${quizId}/student-answer`))
     .then(res => res.data)
 }
+
+// 获取测试作答的统计信息
+export function getQuizStatistic ({ quizId }) {
+  return axios
+    .get(useQuizServer(`/${quizId}/statistic`))
+    .then(res => res.data)
+}

+ 5 - 3
src/utils/state-color.js

@@ -1,9 +1,11 @@
+import slideState from '@/enums/slide-state'
+
 export default function stateColor(state) {
-  if (state === 'IN_CLASS') {
+  if (state === slideState.inClass) {
     return 'green'
-  } else if (state === 'DRAFT') {
+  } else if (state === slideState.draft) {
     return 'red'
-  } else if (state === 'BEFORE_CLASS' || state === 'FINISH') {
+  } else if (state === slideState.beforeClass) {
     return 'orange'
   } else {
     return 'blue'

+ 5 - 1
src/views/forget-password/ForgetPassword.vue

@@ -22,6 +22,7 @@ import CInput from '@/components/Basic/CInput'
 import CButton from '@/components/Basic/CButton'
 import { resetPasswordByEmail } from '@/server/user'
 import { sendEmailCode } from '@/server/code'
+
 export default {
   components: { CButton, CInput, BackTitle },
   data () {
@@ -65,7 +66,8 @@ export default {
       }
 
       resetPasswordByEmail({ email: this.email, code: this.code, password: this.password }).then(res => {
-        this.$setInfoMessage('密码修改成功')
+        this.$setSuccessMessage('密码修改成功')
+        this.$router.push({ name: 'login' })
       }).catch(err => {
         this.$setErrorMessage(err.response.data.msg)
       })
@@ -76,11 +78,13 @@ export default {
 
 <style lang="scss" scoped>
 @import "~@/style/theme.scss";
+
 .forget-password {
   display: flex;
   min-height: 100vh;
   align-items: center;
   justify-content: center;
+
   & > div {
     flex: 1;
     max-width: 400px;

+ 6 - 1
src/views/login/Login.vue

@@ -9,7 +9,7 @@
       <section class="tip mb-24">
         <div>邮箱要求是国内教育邮箱(即以 edu.cn 结尾)</div>
       </section>
-      <c-input class="mb-24" placeholder="密码" type="password" v-model="password"></c-input>
+      <c-input class="mb-24" placeholder="密码" type="password" v-model="password" @keydown.native="handleEnterKey"></c-input>
       <div class="button-area">
         <c-button standout theme="blue" @click="handleSubmit" :no-click-warn="!formChecked()">登录</c-button>
         <c-button theme="blue" @click="handleToRegister">去注册</c-button>
@@ -77,6 +77,11 @@ export default {
           .catch(err => { this.$setErrorMessage(err.response.data.msg) })
       }
     },
+    handleEnterKey (e) {
+      if (e.key === 'Enter') {
+        this.handleSubmit()
+      }
+    },
     handleToRegister () {
       this.$router.push({ name: 'register' })
     }

+ 45 - 14
src/views/quiz/QuizDetail.vue

@@ -5,14 +5,16 @@
         {{ quizDetail.name }}
         <c-button class="delete-button" :left="12" text theme="red" @click="handleDelete" icon="close">删除测试</c-button>
       </back-title>
-      <div class="sub-title">学生提交结果列表</div>
-      <search-bar></search-bar>
+      <quiz-statistic :quiz-id="$route.params.quizId"></quiz-statistic>
+      <search-bar v-model="searchKey"></search-bar>
       <pagination @change="handlePageChange" v-show="result && result.length > 0" :page="page" :size="size"
                   :total-elements="totalElements"></pagination>
-      <template v-if="quizDetail.state === QuizState.closed || quizDetail.state === QuizState.ongoing">
+      <div class="no-content-warning" v-if="result === null">加载中...</div>
+      <div class="answer-card-container"
+           v-else-if="quizDetail.state === QuizState.closed || quizDetail.state === QuizState.ongoing">
         <answer-card @click="handleSelectAnswer(answer)" v-for="answer of result" :key="answer.studentId"
                      :answer="answer"></answer-card>
-      </template>
+      </div>
     </div>
     <div class="quiz">
       <div class="sub-title">学生答案:{{showStudentName ? showStudentName : '[ 未选择 ]'}}</div>
@@ -32,29 +34,42 @@ import AnswerCard from '@/components/Cards/AnswerCard'
 import CButton from '@/components/Basic/CButton'
 import Pagination from '@/components/Pagination'
 import SearchBar from '@/components/Basic/SearchBar'
+import debounce from '@/utils/debounce'
+import QuizStatistic from '@/views/quiz/components/QuizStatistic'
 
 export default {
   name: 'QuizDetail',
-  components: { SearchBar, Pagination, CButton, AnswerCard, QuizDetailFactory, BackTitle },
+  components: { QuizStatistic, SearchBar, Pagination, CButton, AnswerCard, QuizDetailFactory, BackTitle },
   data () {
     return {
       page: 1,
-      size: 12,
+      size: 9,
       totalElements: 1,
+      searchKey: '',
 
       quizDetail: createDefaultQuiz(),
-      result: [],
+      result: null,
+
       showStudentName: '',
       showScore: '',
       showSubmitAt: '',
       showAnswers: [],
 
-      QuizState
+      QuizState,
+      debounceSearch: debounce(() => {
+        this.page = 1
+        this.fetchQuizStudentAnswers({})
+      }, 500)
     }
   },
   created () {
     this.fetchQuizDetail()
-    this.fetchQuizStudentAnswers({ page: 1 })
+    this.fetchQuizStudentAnswers({})
+  },
+  watch: {
+    searchKey () {
+      this.debounceSearch()
+    }
   },
   methods: {
     fetchQuizDetail () {
@@ -65,9 +80,10 @@ export default {
     },
     fetchQuizStudentAnswers ({ page = this.page }) {
       const { quizId } = this.$route.params
-      getQuizStudentAnswers({ quizId: quizId })
+      getQuizStudentAnswers({ quizId: quizId, page, size: this.size, key: this.searchKey })
         .then(res => {
           this.result = res.data
+          this.totalElements = res.page.totalElements
         })
         .catch(err => { this.$setErrorMessage(err.response.data.msg) })
     },
@@ -89,7 +105,8 @@ export default {
       }
     },
     handlePageChange ({ page }) {
-
+      this.page = page
+      this.fetchQuizStudentAnswers({ page })
     }
   }
 }
@@ -97,15 +114,26 @@ export default {
 
 <style lang="scss" scoped>
 .quiz-detail {
-  max-width: 1000px;
+  max-width: 1100px;
   margin: 0 auto;
-  padding: 64px 24px;
+  padding: 32px 24px;
 }
 
 .delete-button {
   float: right;
 }
 
+.answer-card-container {
+  margin: 12px 0 0 0 ;
+  column-width: 150px;
+  column-gap: 12px;
+
+  .answer-card {
+    margin: 0 0 12px 0;
+    break-inside: avoid;
+  }
+}
+
 @media (max-width: 800px) {
 
 }
@@ -121,9 +149,12 @@ export default {
     flex: 1;
   }
   .quiz {
+    margin: -16px 0;
     width: 100px;
-    padding: 0 12px;
+    padding: 16px 24px 16px 12px;
     flex: 1;
+    max-height: calc(100vh - 64px);
+    overflow: auto;
   }
 }
 

+ 15 - 0
src/views/quiz/TeacherCheckAllQuiz.vue

@@ -0,0 +1,15 @@
+<template>
+  <div>
+    // 在这里查看各个状态的所有测试,等待后端添加 api
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TeacherCheckAllQuiz'
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 4 - 2
src/views/quiz/components/QuizDetailFactory.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="quiz-detail-factory">
     <div class="info">
       <template v-if="quizDetail.state === QuizState.closed">
         <template v-if="!isEmpty(score) || !isEmpty(submitAt)">
@@ -23,7 +23,9 @@
             {{ answers[question.id].correct ? 'check' : 'close' }}
           </c-icon>
           <div v-if="answers[question.id]">
-            {{answers[question.id].answer}}
+            {{typeof answers[question.id].answer === 'boolean'
+              ? answers[question.id].answer ? '对' : '错'
+              : answers[question.id].answer}}
           </div>
         </template>
       </div>

+ 99 - 0
src/views/quiz/components/QuizStatistic.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class="quiz-statistic">
+    <div>
+      <la-cartesian :data="scoreValues" v-if="statistic" :width="250" :height="180">
+        <la-bar dot prop="pv"></la-bar>
+        <la-x-axis gridline dashed :tick-size="5" prop="name" :font-size="10"></la-x-axis>
+        <la-y-axis gridline dashed :font-size="10"></la-y-axis>
+      </la-cartesian>
+      分数分布
+    </div>
+    <div>
+      <la-polar fill-container v-if="statistic" :data="submitTimesValues"  :width="200" :height="200">
+        <la-pie show-label label-prop="name" :font-size="12" prop="pv"></la-pie>
+      </la-polar>
+      提交次数分布
+    </div>
+  </div>
+</template>
+
+<script>
+import { getQuizStatistic } from '@/server/quiz'
+
+export default {
+  props: ['quizId'],
+  created () {
+    this.getStatistic()
+  },
+  data () {
+    return {
+      statistic: null
+    }
+  },
+  watch: {
+    quizId () {
+      this.getStatistic()
+    }
+  },
+  computed: {
+    scoreValues () {
+      const scores = [
+        { name: '', num: 0 },
+        { name: '<60', num: 0 },
+        { name: '60-79.9', num: 0 },
+        { name: '80-99.9', num: 0 },
+        { name: '100', num: 0 }
+      ]
+      Object.keys(this.statistic.scores).sort((a, b) => {
+        return parseFloat(a) - parseFloat(b)
+      }).map(key => {
+        const score = this.statistic.scores[key]
+        const keyFloat = parseFloat(key)
+        switch (true) {
+          case keyFloat <= 60:
+            scores[1].num += score
+            break
+          case keyFloat <= 80:
+            scores[2].num += score
+            break
+          case keyFloat < 100:
+            scores[3].num += score
+            break
+          case Math.abs(keyFloat - 100) < 0.1:
+            scores[4].num += score
+            break
+        }
+      })
+      return scores.map(({ name, num }) => ({ name, pv: num }))
+    },
+    submitTimesValues () {
+      return Object.keys(this.statistic.submitNums).sort((a, b) => {
+        return parseFloat(a) - parseFloat(b)
+      }).map(key => ({ name: key, pv: parseInt(this.statistic.submitNums[key]) }))
+    }
+  },
+  methods: {
+    getStatistic () {
+      getQuizStatistic({ quizId: this.quizId })
+        .then(res => (this.statistic = res))
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "~@/style/theme.scss";
+
+.quiz-statistic {
+  padding: 12px 0;
+  margin-bottom: 16px;
+  border-radius: 4px;
+  border: 1px solid darken($card-background, 5);
+  display: flex;
+  justify-content: center;
+  > div {
+    text-align: center;
+  }
+}
+</style>

+ 13 - 2
src/views/shared/UserHomeLayout.vue

@@ -33,6 +33,7 @@
         </icon-button>
       </template>
     </div>
+    <c-button class="home" text @click="handleToHome" icon="home">主页</c-button>
     <c-button class="exit" text @click="handleLogout" icon="exit_to_app">注销</c-button>
   </div>
 </template>
@@ -74,6 +75,9 @@ export default {
     handleIconButtonClick (name) {
       this.$router.push(`/${this.role}/${name}`)
     },
+    handleToHome () {
+      this.$router.push(`/${this.role}`)
+    },
     handleLogout () {
       logout()
       this.$setSuccessMessage('您已退出登录')
@@ -167,17 +171,24 @@ export default {
   }
 }
 
-.exit {
+.exit, .home {
   z-index: 4;
   cursor: pointer;
   position: absolute;
   top: 10px;
-  right: 10px;
   display: flex;
   align-items: center;
   color: #6a7a8a;
 }
 
+.exit {
+  right: 10px;
+}
+
+.home {
+  left: 10px;
+}
+
 @supports (not(backdrop-filter: saturate(180%) blur(15px))) {
   .menu-container-small {
     background: rgb(255, 255, 255);

+ 2 - 2
src/views/student/components/StudentCourseSlides.vue

@@ -28,13 +28,13 @@
                    :notice="notice">
       </notice-card>
     </template>
-    <div v-for="slide of slides" class="mt-8" :key="'slide-' + slide.id">
+    <div v-for="slide of slides" class="mt-6" :key="'slide-' + slide.id">
       <slide-card :hide-course="!showMore" :hide-teacher="!showMore"
                   :slide="slide" :hide-update-time="!showMore"
                   :click-handler="handleSlideCardClick"></slide-card>
     </div>
     <template v-for="courseFile of courseFiles">
-      <course-file-card :key="'file-' + courseFile.id" :hide-time="!showMore" :click-handler="handleCourseFileCardClick"
+      <course-file-card class="mt-6" :key="'file-' + courseFile.id" :hide-time="!showMore" :click-handler="handleCourseFileCardClick"
                         :courseFile="courseFile">
       </course-file-card>
     </template>

+ 2 - 2
src/views/teacher/components/TeacherCourseSlides.vue

@@ -32,12 +32,12 @@
       <div class="no-content-warning-tip">本课程尚无课件~</div>
       <c-button block theme="blue" @click="handleToCreateCourse">去创建课件</c-button>
     </div>
-    <div v-for="slide of slides" :key="slide.id">
+    <div v-for="slide of slides" class="mt-6" :key="slide.id">
       <slide-card :hide-teacher="!showMore" :hide-course="!showMore" :hide-update-time="!showMore"
                   :slide="slide" :click-handler="handleSlideCardClick"></slide-card>
     </div>
     <template v-for="courseFile of courseFiles">
-      <course-file-card :key="'file-' + courseFile.id" :hide-time="!showMore" :click-handler="handleCourseFileCardClick"
+      <course-file-card class="mt-6" :key="'file-' + courseFile.id" :hide-time="!showMore" :click-handler="handleCourseFileCardClick"
                   :courseFile="courseFile">
       </course-file-card>
     </template>

+ 11 - 5
src/views/teacher/tabs/TeacherExplore.vue

@@ -4,18 +4,24 @@
       <h1 class="title">
         我的主页
         <c-button :theme="showActions ? 'orange' : 'normal'" :left="8" text icon="settings"
-                  @click="showActions = !showActions">设置 & 题库
+                  @click="showActions = !showActions">设置
         </c-button>
       </h1>
       <tab-actions :if-condition="showActions">
-        <router-link :to="{name: 'questions'}">
-          <c-button :right="8" theme="green" icon="assignment">题库设置</c-button>
-        </router-link>
-        <c-button theme="blue" :icon="showMore ? 'unfold_less' : 'unfold_more'" @click="showMore = !showMore">
+        <c-button theme="green" :icon="showMore ? 'unfold_less' : 'unfold_more'" @click="showMore = !showMore">
           {{showMore ? '收起' : '展开'}}详情
         </c-button>
       </tab-actions>
       <!--      <search-bar></search-bar>-->
+      <section class="mb-24">
+        <h1 class="sub-title">我的测试</h1>
+        <router-link :to="{ name: 'all-quiz'}">
+          <c-button theme="green" icon="edit">所有测试</c-button>
+        </router-link>
+        <router-link :to="{name: 'questions'}">
+          <c-button :left="8" theme="blue" icon="assignment">题库设置</c-button>
+        </router-link>
+      </section>
       <section class="mb-24">
         <h1 class="sub-title">最近编辑</h1>
         <div class="no-content-warning" v-if="slides === null">

+ 25 - 0
yarn.lock

@@ -3480,6 +3480,23 @@ cz-conventional-changelog@^3.0.2:
   optionalDependencies:
     "@commitlint/load" ">6.1.1"
 
+d3-array@^1.2.1:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
+  integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
+
+d3-path@1:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
+  integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
+
+d3-shape@^1.2.0:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
+  integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
+  dependencies:
+    d3-path "1"
+
 dashdash@^1.12.0:
   version "1.14.1"
   resolved "https://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -6781,6 +6798,14 @@ koa@^2.11.0:
     type-is "^1.6.16"
     vary "^1.1.2"
 
+laue@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/laue/-/laue-0.2.1.tgz#5bd19c7c0315902e3b4297a8040c3218160b2e6e"
+  integrity sha512-BDKpX/Vzy6e4ZtoFDrYkY94Pr6wzyXTMtQzCtEUBeH7jbOlTM9wvPDPoR7it7otycDMpDF9g2E3LpnitnzlF0Q==
+  dependencies:
+    d3-array "^1.2.1"
+    d3-shape "^1.2.0"
+
 launch-editor-middleware@^2.2.1:
   version "2.2.1"
   resolved "https://registry.npm.taobao.org/launch-editor-middleware/download/launch-editor-middleware-2.2.1.tgz#e14b07e6c7154b0a4b86a0fd345784e45804c157"