Просмотр исходного кода

[新增] loading 控件和样式

Kunduin 7 лет назад
Родитель
Сommit
7704296b3d

+ 40 - 0
src/assets/scss/_loading.scss

@@ -0,0 +1,40 @@
+@import "variables";
+
+$skeleton-color: #f2f2f2;
+$skeleton-to-color: darken($skeleton-color, 5%);
+
+.skeleton-title{
+  width: 100%;
+  max-width: 400px;
+  height: $size-3;
+  @extend %skeleton-loading;
+}
+
+.skeleton-normal{
+  width: 100%;
+  max-width: 700px;
+  height: $size-normal;
+  @extend %skeleton-loading;
+}
+
+
+%skeleton-loading {
+  border-radius: $default-radius;
+  background: linear-gradient(
+                  90deg,
+                  $skeleton-color 25%,
+                  $skeleton-to-color 37%,
+                  $skeleton-color 63%
+  );
+  animation: skeleton-loading 1.4s ease infinite;
+  background-size: 400% 100%;
+}
+
+@keyframes skeleton-loading {
+  0% {
+    background-position: 100% 50%;
+  }
+  100% {
+    background-position: 0 50%;
+  }
+}

+ 17 - 0
src/assets/scss/_variables.scss

@@ -152,3 +152,20 @@ $size-small: $size-7 !default;
 $size-normal: $size-6 !default;
 $size-medium: $size-5 !default;
 $size-large: $size-4 !default;
+
+
+
+// basic
+
+$default-shadow:0 3px 8px rgba(211, 215, 222, 0.4);
+$default-shadow-lighter:0 3px 8px rgba(211, 215, 222, 0.2);
+
+$default-radius:4px;
+
+$default-background:#f4f7fc;
+
+$default-border-color:rgba(84, 66, 251, 0.56);
+$default-border:2px solid $default-border-color;
+
+$default-margin:0.75rem;
+$default-margin-between:1.5rem;

+ 1 - 15
src/assets/scss/app.scss

@@ -1,26 +1,12 @@
 @import "~bulma/sass/utilities/initial-variables";
 @import "~bulma/sass/utilities/functions";
 @import "variables";
-
+@import "loading";
 @import "~bulma/sass/utilities/derived-variables";
 
 @import "~bulma";
 @import "~buefy/src/scss/buefy";
 
-
-$default-shadow:0 3px 8px rgba(211, 215, 222, 0.4);
-$default-shadow-lighter:0 3px 8px rgba(211, 215, 222, 0.2);
-
-$default-radius:4px;
-
-$default-background:#f4f7fc;
-
-$default-border-color:rgba(84, 66, 251, 0.56);
-$default-border:2px solid $default-border-color;
-
-$default-margin:0.75rem;
-$default-margin-between:1.5rem;
-
 .page-section{
   padding: $default-margin-between;
   border-radius: $default-radius;

+ 21 - 2
src/components/Card/index.vue

@@ -1,10 +1,14 @@
 <template>
   <div
     class="custom-card"
-    :class="{ hoverable: hoverable }"
+    :class="{ hoverable: hoverable, 'custom-card__loading': loading }"
     :style="{ padding: hasPadding ? '20px' : 0 }"
   >
-    <slot></slot>
+    <div v-if="loading">
+      <div class="loading" style="width: 50%;margin-bottom: 1rem"></div>
+      <div class="loading"></div>
+    </div>
+    <slot v-else></slot>
   </div>
 </template>
 
@@ -18,6 +22,10 @@ export default {
     hasPadding: {
       type: Boolean,
       default: true
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   }
 };
@@ -25,6 +33,13 @@ export default {
 
 <style lang="scss" scoped>
 @import "../../assets/scss/app";
+
+.loading {
+  width: 100%;
+  height: $size-normal;
+  @extend %skeleton-loading;
+}
+
 .custom-card {
   width: 100%;
   height: 100%;
@@ -34,6 +49,10 @@ export default {
   border-radius: $default-radius;
   color: #152935;
   border: $default-border;
+
+  &__loading {
+    border-color: lighten($default-border-color, 40%);
+  }
 }
 .hoverable {
   cursor: pointer;

+ 37 - 0
src/components/PageBody/PageSectionLoading/index.vue

@@ -0,0 +1,37 @@
+<template>
+  <div v-if="show" class="page-section">
+    <h1 class="loading-title title" />
+    <p class="loading-line" />
+    <p class="loading-line" />
+    <p class="loading-line" style="width: 60%" />
+  </div>
+</template>
+
+<script>
+export default {
+  name: "PageSectionLoading",
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../assets/scss/app";
+.loading-title {
+  @extend %skeleton-loading;
+  height: $size-large;
+  width: 100%;
+  max-width: 300px;
+}
+
+.loading-line {
+  @extend %skeleton-loading;
+  height: $size-normal;
+  width: 100%;
+  margin-top: $size-normal;
+}
+</style>

+ 19 - 3
src/components/PageHeader/index.jsx

@@ -12,6 +12,10 @@ export default {
     tabList: {
       type: Array,
       default: () => []
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   computed: {
@@ -21,7 +25,7 @@ export default {
   },
   mixins: [pathMixin],
   render() {
-    const { prefix, course, routes = [], tabList = [] } = this;
+    const { prefix, course, routes = [], tabList = [], loading } = this;
     const { logo, title, action, content, extraContent } = this.$slots;
     const showTabs = tabList && tabList.length > 0;
     return (
@@ -53,11 +57,23 @@ export default {
           {logo && <div class="logo">{logo}</div>}
           <div class="main">
             <div class="row">
-              {title && <h1 class="title">{title}</h1>}
+              {loading ? (
+                <h1 class="title">
+                  <div class="skeleton-title" />
+                </h1>
+              ) : (
+                title && <h1 class="title">{title}</h1>
+              )}
               {action && <div class="action">{action}</div>}
             </div>
             <div class="row">
-              {content && <div class="content">{content}</div>}
+              {loading ? (
+                <div class="content">
+                  <div class="skeleton-normal" />
+                </div>
+              ) : (
+                content && <div class="content">{content}</div>
+              )}
               {extraContent && <div class="extra-content">{extraContent}</div>}
             </div>
           </div>

+ 14 - 0
src/components/PageHeader/index.scss

@@ -22,6 +22,13 @@
     width: 100%;
   }
 
+  .skeleton-basic{
+    @extend %skeleton-loading;
+    height: $size-normal;
+    width: 100%;
+    max-width: 800px;
+  }
+
   .breadcrumb {
     margin-bottom: $default-margin-between;
   }
@@ -51,10 +58,17 @@
   }
 
   .title {
+    font-size: $size-3;
     font-weight: 500;
+    line-height: $size-3;
     //color: $default;
   }
 
+  .content{
+    font-size: $size-normal;
+    line-height: $size-normal;
+  }
+
   .action {
     margin-left: 56px;
     min-width: 266px;

+ 6 - 3
src/views/student/Group/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <page-header :routes="[{ name: '我的小组' }]">
+    <page-header :loading="isLoadingGroup" :routes="[{ name: '我的小组' }]">
       <template slot="title">
         我的小组
         <span v-if="groups.length > 0">
@@ -9,7 +9,9 @@
       </template>
     </page-header>
     <page-body>
-      <div v-if="groups.length > 0">
+      <page-section-loading :show="isLoadingGroup" />
+
+      <div v-if="!isLoadingGroup && groups.length > 0">
         <div class="page-section">
           <div class="columns is-desktop">
             <div class="column">
@@ -142,8 +144,9 @@ import {
   postStudentGroup,
   putStudentGroup
 } from "@/api/group";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 export default {
-  components: { PageBody, PageHeader, Card },
+  components: { PageSectionLoading, PageBody, PageHeader, Card },
   data() {
     return {
       showCreateModal: false,

+ 7 - 0
src/views/student/Review/ReviewDetail/ReviewDocs/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div class="columns is-multiline is-desktop">
+    <div v-if="detailLoading" class="column is-4-desktop"><card loading /></div>
+
     <div
+      v-else
       :key="item.docReviewId"
       v-for="item in docs"
       class="column is-4-desktop"
@@ -23,6 +26,10 @@ export default {
     docs: {
       type: Array,
       default: () => []
+    },
+    detailLoading: {
+      type: Boolean,
+      default: false
     }
   }
 };

+ 21 - 12
src/views/student/Review/ReviewDetail/ReviewSelfDetail/index.vue

@@ -2,7 +2,8 @@
   <div>
     <div class="columns">
       <div class="column is-8">
-        <div class="page-section">
+        <page-section-loading :show="selfDetailLoading" />
+        <div v-if="!selfDetailLoading" class="page-section">
           <div v-if="detail.docContent" class="content">
             <vue-markdown>{{ detail.docContent }}</vue-markdown>
           </div>
@@ -10,13 +11,16 @@
       </div>
       <div class="column is-4">
         <div class="right-part">
-          <result-check-list-item
-            v-for="item in detail.checkListResult"
-            :arguable="true"
-            :key="item.id"
-            :check-result-item="item"
-            @argue="argue"
-          />
+          <page-section-loading :show="selfDetailLoading" />
+          <div v-if="!selfDetailLoading">
+            <result-check-list-item
+              v-for="item in detail.checkListResult"
+              :arguable="true"
+              :key="item.id"
+              :check-result-item="item"
+              @argue="argue"
+            />
+          </div>
         </div>
       </div>
     </div>
@@ -30,8 +34,10 @@ import {
 } from "@/api/review";
 import timeMixins from "@/mixins/time";
 import ResultCheckListItem from "@/components/CheckList/ResultCheckListItem";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 export default {
   components: {
+    PageSectionLoading,
     ResultCheckListItem,
     VueMarkdown
   },
@@ -41,15 +47,18 @@ export default {
         review: {},
         checkListResult: []
       },
-      submitLoading: false
+      submitLoading: false,
+      selfDetailLoading: false
     };
   },
   mixins: [timeMixins],
   mounted() {
     const { reviewId } = this.$route.params;
-    getStudentReviewSelfDetail(reviewId).then(
-      resp => (this.detail = resp.data)
-    );
+    this.selfDetailLoading = true;
+    getStudentReviewSelfDetail(reviewId).then(resp => {
+      this.detail = resp.data;
+      this.selfDetailLoading = false;
+    });
   },
   methods: {
     async argue(item) {

+ 11 - 3
src/views/student/Review/ReviewDetail/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="detailLoading"
       :routes="[{ name: '互评作业', path: 'review' }, { name: '文档详情' }]"
       :tab-list="[
         { name: '待评文档', path: `review/${$route.params.reviewId}` },
@@ -30,7 +31,9 @@
         </div>
       </template>
     </page-header>
-    <page-body> <router-view :docs="detail.documents" /> </page-body>
+    <page-body>
+      <router-view :docs="detail.documents" :detail-loading="detailLoading" />
+    </page-body>
   </div>
 </template>
 <script>
@@ -43,13 +46,18 @@ export default {
   components: { ReviewStateTag, PageBody, PageHeader },
   data() {
     return {
-      detail: {}
+      detail: {},
+      detailLoading: false
     };
   },
   mixins: [timeMixins],
   mounted() {
     const { reviewId } = this.$route.params;
-    getStudentReviewDetail(reviewId).then(resp => (this.detail = resp.data));
+    this.detailLoading = true;
+    getStudentReviewDetail(reviewId).then(resp => {
+      this.detail = resp.data;
+      this.detailLoading = false;
+    });
   }
 };
 </script>

+ 27 - 17
src/views/student/Review/ReviewDocDetail/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="fetchLoading"
       :routes="[
         { name: '互评作业', path: 'review' },
         { name: '文档详情', path: `review/${$route.params.reviewId}` },
@@ -19,7 +20,8 @@
     <page-body>
       <div class="columns">
         <div class="column is-8">
-          <div class="page-section">
+          <div v-if="fetchLoading"><page-section-loading show /></div>
+          <div v-else class="page-section">
             <div v-if="detail.docContent" class="content">
               <vue-markdown>{{ detail.docContent }}</vue-markdown>
             </div>
@@ -27,19 +29,22 @@
         </div>
         <div class="column is-4">
           <div class="right-part">
-            <div
-              class="button  is-medium is-primary action-button"
-              :class="{ 'is-loading': submitLoading }"
-              @click="submitCheckList"
-            >
-              提 交
+            <div v-if="fetchLoading"><page-section-loading show /></div>
+            <div v-else>
+              <div
+                class="button  is-medium is-primary action-button"
+                :class="{ 'is-loading': submitLoading }"
+                @click="submitCheckList"
+              >
+                提 交
+              </div>
+              <check-list
+                v-model="detail.checkList"
+                @submit-solution="submitSolution"
+                @accept-argue="acceptArgue"
+                @refuse-argue="refuseArgue"
+              />
             </div>
-            <check-list
-              v-model="detail.checkList"
-              @submit-solution="submitSolution"
-              @accept-argue="acceptArgue"
-              @refuse-argue="refuseArgue"
-            />
           </div>
         </div>
       </div>
@@ -59,8 +64,10 @@ import {
 import timeMixins from "@/mixins/time";
 import ReviewStateTag from "@/components/ReviewStateTag/index";
 import CheckList from "@/components/CheckList/index";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 export default {
   components: {
+    PageSectionLoading,
     CheckList,
     ReviewStateTag,
     PageBody,
@@ -73,15 +80,18 @@ export default {
         review: {},
         checkList: []
       },
-      submitLoading: false
+      submitLoading: false,
+      fetchLoading: false
     };
   },
   mixins: [timeMixins],
   mounted() {
     const { reviewId, docReviewId } = this.$route.params;
-    getStudentReviewDocDetail(reviewId, docReviewId).then(
-      resp => (this.detail = resp.data)
-    );
+    this.fetchLoading = true;
+    getStudentReviewDocDetail(reviewId, docReviewId).then(resp => {
+      this.detail = resp.data;
+      this.fetchLoading = false;
+    });
   },
   methods: {
     submitCheckList() {

+ 0 - 0
src/views/student/Review/ReviewSelf/index.vue


+ 20 - 6
src/views/student/Review/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <page-header :routes="[{ name: '互评作业', path: 'review' }]">
+    <page-header
+      :loading="loadingReviewList"
+      :routes="[{ name: '互评作业', path: 'review' }]"
+    >
       <template slot="title">
         互评作业
       </template>
@@ -10,7 +13,15 @@
     </page-header>
     <page-body>
       <div class="columns is-multiline is-desktop">
-        <div :key="item.id" v-for="item in review" class="column is-4-desktop">
+        <div v-if="loadingReviewList" class="column is-4-desktop">
+          <card loading />
+        </div>
+        <div
+          v-else
+          :key="item.id"
+          v-for="item in review"
+          class="column is-4-desktop"
+        >
           <router-link :to="`${$route.path}/${item.reviewId}`">
             <card hoverable>
               {{ item.name }}
@@ -45,13 +56,16 @@ export default {
   data() {
     return {
       courses: [],
-      review: []
+      review: [],
+      loadingReviewList: false
     };
   },
   mounted() {
-    getStudentCourseReviewList(this.courses.id).then(
-      value => (this.review = value.data)
-    );
+    this.loadingReviewList = true;
+    getStudentCourseReviewList(this.courses.id).then(value => {
+      this.review = value.data;
+      this.loadingReviewList = false;
+    });
   }
 };
 </script>