刘洋 1 rok pred
rodič
commit
24b0f844a1

+ 4 - 2
src/api/apiConfig/index.js

@@ -35,7 +35,8 @@ axiosInstance.interceptors.request.use(
   (config) => {
   (config) => {
     setAuth(config);
     setAuth(config);
     if (config.loading) {
     if (config.loading) {
-      showLoadingToast({
+      allowMultipleToast();
+      config.toast = showLoadingToast({
         forbidClick: true,
         forbidClick: true,
         loadingType: "spinner",
         loadingType: "spinner",
         duration: 0,
         duration: 0,
@@ -51,7 +52,7 @@ axiosInstance.interceptors.request.use(
 axiosInstance.interceptors.response.use(
 axiosInstance.interceptors.response.use(
   async (response) => {
   async (response) => {
     if (response.config.loading) {
     if (response.config.loading) {
-      closeToast();
+      response.config.toast?.close();
     }
     }
     return response.data;
     return response.data;
     // if (response.request.responseType === "blob") {
     // if (response.request.responseType === "blob") {
@@ -74,6 +75,7 @@ axiosInstance.interceptors.response.use(
     console.log("[request error] > ", error);
     console.log("[request error] > ", error);
 
 
     if (error && error.response) {
     if (error && error.response) {
+      error.response.config.toast?.close();
       switch (error.response.status) {
       switch (error.response.status) {
         case 400:
         case 400:
           error.message = "请求参数错误(400)";
           error.message = "请求参数错误(400)";

+ 23 - 0
src/api/user.js

@@ -20,6 +20,26 @@ export function login(data) {
   });
   });
 }
 }
 
 
+export function logout() {
+  return request({
+    url: "/api/student/logout",
+    method: "post",
+    loading: true,
+    headers: {
+      "Content-Type": "application/x-www-form-urlencoded",
+    },
+  });
+}
+export function wxUnbind() {
+  return request({
+    url: "/api/student/wechat/unbind",
+    method: "post",
+    headers: {
+      "Content-Type": "application/x-www-form-urlencoded",
+    },
+  });
+}
+
 export function wxLogin(openId) {
 export function wxLogin(openId) {
   return request({
   return request({
     url: "/api/student/login/for/wechat",
     url: "/api/student/login/for/wechat",
@@ -64,6 +84,7 @@ export function reservationSave(data) {
     url: "/api/student/apply/save",
     url: "/api/student/apply/save",
     method: "post",
     method: "post",
     data,
     data,
+    loading: true,
     headers: {
     headers: {
       "Content-Type": "application/x-www-form-urlencoded",
       "Content-Type": "application/x-www-form-urlencoded",
     },
     },
@@ -75,6 +96,7 @@ export function reservationCancel(data) {
     url: "/api/student/apply/cancel",
     url: "/api/student/apply/cancel",
     method: "post",
     method: "post",
     data,
     data,
+    loading: true,
     headers: {
     headers: {
       "Content-Type": "application/x-www-form-urlencoded",
       "Content-Type": "application/x-www-form-urlencoded",
     },
     },
@@ -110,6 +132,7 @@ export function getDateList() {
   return request({
   return request({
     url: "/api/student/apply/date/list",
     url: "/api/student/apply/date/list",
     method: "post",
     method: "post",
+    loading: true,
   });
   });
 }
 }
 
 

+ 0 - 2
src/assets/styles/main.css

@@ -97,8 +97,6 @@ a {
   text-decoration: none !important;
   text-decoration: none !important;
 }
 }
 img {
 img {
-  max-width: 100% !important;
-  width: 100%;
   vertical-align: middle;
   vertical-align: middle;
 }
 }
 input {
 input {

+ 0 - 2
src/assets/styles/main.less

@@ -101,8 +101,6 @@ a {
   text-decoration: none !important;
   text-decoration: none !important;
 }
 }
 img {
 img {
-  max-width: 100% !important;
-  width: 100%;
   vertical-align: middle;
   vertical-align: middle;
 }
 }
 input {
 input {

+ 2 - 2
src/components/noData.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <div class="no-data">
   <div class="no-data">
-    <img src="../assets/imgs/no_data.png" />
+    <slot name="img" />
     <p>
     <p>
       <slot />
       <slot />
     </p>
     </p>
@@ -10,7 +10,7 @@
 <style lang="less" scoped>
 <style lang="less" scoped>
 .no-data {
 .no-data {
   text-align: center;
   text-align: center;
-  img {
+  :deep(img) {
     width: 62px;
     width: 62px;
   }
   }
   p {
   p {

+ 62 - 16
src/pages/admissionCard.vue

@@ -7,8 +7,10 @@
       </div>
       </div>
       <div class="text-center title">准考证</div>
       <div class="text-center title">准考证</div>
       <div class="media-box">
       <div class="media-box">
-        <img :src="appStore.globalConfig + info.photoPath" />
-        <div class="media-right">
+        <div class="img-box">
+          <img :src="appStore.globalConfig.fileUrlPrefix + info.photoPath" />
+        </div>
+        <div class="media-right info-rows">
           <div><span class="label">姓名:</span>{{ info.studentName }}</div>
           <div><span class="label">姓名:</span>{{ info.studentName }}</div>
           <div><span class="label">性别:</span>{{ info.gender }}</div>
           <div><span class="label">性别:</span>{{ info.gender }}</div>
           <div>
           <div>
@@ -19,20 +21,33 @@
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
+      <div class="py-20 d-flex flex-v justify-content-between info-rows">
+        <div>
+          <span class="label">考试时间:</span>
+          {{ $filters.dateFormat(info.timePeriodStart, "yyyy年MM月dd日") }}
+          (星期{{ getWeek(info.timePeriodStart) }})
+          {{ $filters.dateFormat(info.timePeriodStart, "HH:mm") }}至{{
+            $filters.dateFormat(info.timePeriodEnd, "HH:mm")
+          }}
+        </div>
+        <div class="mt-12">
+          <span class="label">考场地址:</span>{{ info.examRoomAddress }}
+        </div>
+      </div>
+      <div class="black-info-box">
+        <div class="d-flex align-items-center">
+          <div style="width: 50%" class="col1">
+            考场代码:{{ info.examRoomCode }}
+          </div>
+          <div style="width: 50%" class="col2">
+            座位号:{{ info.seatNumber }}
+          </div>
+        </div>
+        <div class="d-flex align-items-center">
+          准考证号:{{ info.ticketNumber }}
+        </div>
+      </div>
     </div>
     </div>
-
-    <div>
-      考试时间:{{
-        $filters.dateFormat(info.timePeriodStart, "yyyy年MM月dd日")
-      }}
-      (星期{{ getWeek(info.timePeriodStart) }})
-      {{ $filters.dateFormat(info.timePeriodStart, "HH:mm") }}至{{
-        $filters.dateFormat(info.timePeriodEnd, "HH:mm")
-      }}
-    </div>
-    <div>考场代码:{{ info.examRoomCode }}</div>
-    <div>座位号:{{ info.seatNumber }}</div>
-    <div>准考证号:{{ info.ticketNumber }}</div>
   </div>
   </div>
 </template>
 </template>
 <script name="AdmissionCard" setup>
 <script name="AdmissionCard" setup>
@@ -106,10 +121,41 @@ function getWeek(time) {
       color: #262626;
       color: #262626;
       margin-top: 8px;
       margin-top: 8px;
     }
     }
+    .black-info-box {
+      font-size: 16px;
+
+      div {
+        font-weight: bold;
+      }
+      & > div {
+        margin-bottom: 10px;
+      }
+    }
     .media-box {
     .media-box {
-      & > img {
+      margin-top: 16px;
+      display: flex;
+      .img-box {
         height: 126px;
         height: 126px;
+        width: 23vw;
         margin-right: 16px;
         margin-right: 16px;
+        img {
+          max-width: 100%;
+          max-height: 100%;
+        }
+      }
+
+      .media-right {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+      }
+    }
+    .info-rows {
+      color: #262626;
+      .label {
+        color: #8c8c8c;
+        margin-right: 6px;
       }
       }
     }
     }
   }
   }

+ 22 - 0
src/pages/myHistory.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="my-history page p-16">
+    <ApplyItem
+      :item="item"
+      @update="_getMyHistory"
+      v-for="item in list"
+      :key="item.applyId"
+    ></ApplyItem>
+  </div>
+</template>
+<script name="MyHistory" setup>
+import { ref } from "vue";
+import { getMyHistory } from "@/api/user";
+const list = ref([]);
+function _getMyHistory() {
+  getMyHistory().then((res) => {
+    list.value = res || [];
+  });
+}
+_getMyHistory();
+</script>
+<style lang="less" scoped></style>

+ 18 - 8
src/pages/tab-pages/index.vue

@@ -1,6 +1,7 @@
 <template>
 <template>
   <div class="index tab-page p-16">
   <div class="index tab-page p-16">
-    <template v-if="list.length">
+    <template v-if="loading"></template>
+    <template v-else-if="list.length">
       <ApplyItem
       <ApplyItem
         :item="item"
         :item="item"
         @update="_getIndexList"
         @update="_getIndexList"
@@ -10,7 +11,12 @@
     </template>
     </template>
     <div class="vh-100 flex-h-center" v-else>
     <div class="vh-100 flex-h-center" v-else>
       <div>
       <div>
-        <NoData>当前无考试预约订单</NoData>
+        <NoData>
+          <template #img>
+            <img src="../../assets/imgs/no_data.png" />
+          </template>
+          当前无考试预约订单</NoData
+        >
         <div class="cus-btn flex-h-center" @click="toReservation">去预约</div>
         <div class="cus-btn flex-h-center" @click="toReservation">去预约</div>
       </div>
       </div>
     </div>
     </div>
@@ -19,17 +25,21 @@
 <script name="Index" setup>
 <script name="Index" setup>
 import { ref } from "vue";
 import { ref } from "vue";
 
 
-import { useUserStore } from "@/store";
 import { getIndexList } from "@/api/user";
 import { getIndexList } from "@/api/user";
 import { useRouter } from "vue-router";
 import { useRouter } from "vue-router";
 const router = useRouter();
 const router = useRouter();
-const userStore = useUserStore();
-userStore.requestStuInfo();
+
 const list = ref([]);
 const list = ref([]);
+const loading = ref(false);
 function _getIndexList() {
 function _getIndexList() {
-  getIndexList().then((res) => {
-    list.value = res || [];
-  });
+  loading.value = true;
+  getIndexList()
+    .then((res) => {
+      list.value = res || [];
+    })
+    .finally(() => {
+      loading.value = false;
+    });
 }
 }
 _getIndexList();
 _getIndexList();
 
 

+ 46 - 16
src/pages/tab-pages/mine.vue

@@ -1,28 +1,58 @@
 <template>
 <template>
-  <div class="tab-page mine">
-    <div class="card" v-for="item in list" :key="item.applyId">
-      <ApplyItem :item="item" @update="_getMyHistory"></ApplyItem>
-    </div>
+  <div class="tab-page mine p-16">
+    <van-cell-group inset>
+      <van-cell title="姓名" :value="stuInfo.name" />
+      <van-cell title="证件号" :value="stuInfo.identityNumber" />
+      <van-cell title="学号" :value="stuInfo.studentCode" />
+      <van-cell title="教学点" :value="stuInfo.categoryName" />
+    </van-cell-group>
+
+    <van-cell-group inset class="cell-group2">
+      <van-cell is-link @click="toHistory">
+        <template #title>
+          <img
+            src="../../assets/imgs/icon_apply.png"
+            style="height: 20px; margin-top: -4px; margin-right: 6px"
+          />
+          <span class="custom-title">我的预约订单</span>
+        </template>
+      </van-cell>
+    </van-cell-group>
+
+    <van-button plain block type="success" class="mt-16" @click="logout"
+      >退出登录</van-button
+    >
   </div>
   </div>
 </template>
 </template>
 <script name="Mine" setup>
 <script name="Mine" setup>
-import { ref } from "vue";
-import { getMyHistory } from "@/api/user";
-const list = ref([]);
-function _getMyHistory() {
-  getMyHistory().then((res) => {
-    list.value = res || [];
+import { computed } from "vue";
+import { useUserStore } from "@/store";
+import { useRouter } from "vue-router";
+
+const router = useRouter();
+const userStore = useUserStore();
+userStore.requestStuInfo();
+const stuInfo = computed(() => {
+  return userStore.stuInfo || {};
+});
+
+function toHistory() {
+  router.push({ name: "MyHistory" });
+}
+
+function logout() {
+  showConfirmDialog({
+    title: "系统提示",
+    message: "确认退出登录吗?",
+  }).then(() => {
+    userStore.logout();
   });
   });
 }
 }
-_getMyHistory();
 </script>
 </script>
 <style lang="less" scoped>
 <style lang="less" scoped>
 .mine {
 .mine {
-  padding: 15px;
-  .card {
-    background-color: #fff;
-    border-radius: 6px;
-    margin-bottom: 10px;
+  .cell-group2 {
+    margin-top: 16px !important;
   }
   }
 }
 }
 </style>
 </style>

+ 143 - 121
src/pages/tab-pages/reservation.vue

@@ -1,129 +1,143 @@
 <template>
 <template>
   <div class="reservation tab-page position-relative p-16">
   <div class="reservation tab-page position-relative p-16">
-    <van-cell-group inset>
-      <van-field
-        v-model="params.aaa.text"
-        name="城市"
-        label="城市"
-        placeholder=""
-        input-align="right"
-        is-link
-        readonly
-        :rules="[{ required: true, message: '请选择城市' }]"
-        @click="cityStates.show = true"
-      />
-      <van-field
-        v-model="params.bbb.text"
-        name="教学点"
-        label="教学点"
-        placeholder=""
-        input-align="right"
-        is-link
-        readonly
-        :rules="[{ required: true, message: '请选择教学点' }]"
-        @click="teachStates.show = true"
-        :disabled="!params.aaa.value"
-      />
-      <van-field
-        v-model="params.examSiteId.text"
-        name="考点"
-        label="考点"
-        placeholder=""
-        input-align="right"
-        is-link
-        readonly
-        :rules="[{ required: true, message: '请选择考点' }]"
-        @click="toChooseSite"
-        :disabled="!params.bbb.value"
-      />
-      <van-field
-        v-model="params.date.text"
-        name="日期"
-        label="日期"
-        placeholder=""
-        input-align="right"
-        is-link
-        readonly
-        :rules="[{ required: true, message: '请选择日期' }]"
-        @click="dateStates.show = true"
-      />
-    </van-cell-group>
+    <template v-if="dateLoading"></template>
+    <template v-else-if="dateStates.columns.length">
+      <van-cell-group inset>
+        <van-field
+          v-model="params.aaa.text"
+          name="城市"
+          label="城市"
+          placeholder=""
+          input-align="right"
+          is-link
+          readonly
+          :rules="[{ required: true, message: '请选择城市' }]"
+          @click="cityStates.show = true"
+        />
+        <van-field
+          v-model="params.bbb.text"
+          name="教学点"
+          label="教学点"
+          placeholder=""
+          input-align="right"
+          is-link
+          readonly
+          :rules="[{ required: true, message: '请选择教学点' }]"
+          @click="teachStates.show = true"
+          :disabled="!params.aaa.value"
+        />
+        <van-field
+          v-model="params.examSiteId.text"
+          name="考点"
+          label="考点"
+          placeholder=""
+          input-align="right"
+          is-link
+          readonly
+          :rules="[{ required: true, message: '请选择考点' }]"
+          @click="toChooseSite"
+          :disabled="!params.bbb.value"
+        />
+        <van-field
+          v-model="params.date.text"
+          name="日期"
+          label="日期"
+          placeholder=""
+          input-align="right"
+          is-link
+          readonly
+          :rules="[{ required: true, message: '请选择日期' }]"
+          @click="dateStates.show = true"
+        />
+      </van-cell-group>
 
 
-    <van-popup v-model:show="cityStates.show" round position="bottom">
-      <van-picker
-        v-model="cityStates.value"
-        :columns="cityStates.columns"
-        @cancel="cityStates.show = false"
-        @confirm="cityStates.onConfirm"
-      />
-    </van-popup>
-    <van-popup v-model:show="teachStates.show" round position="bottom">
-      <van-picker
-        v-model="teachStates.value"
-        :columns="teachStates.columns"
-        @cancel="teachStates.show = false"
-        @confirm="teachStates.onConfirm"
-      />
-    </van-popup>
-    <van-popup v-model:show="dateStates.show" round position="bottom">
-      <van-picker
-        v-model="dateStates.value"
-        :columns="dateStates.columns"
-        @cancel="dateStates.show = false"
-        @confirm="dateStates.onConfirm"
-      />
-    </van-popup>
+      <van-popup v-model:show="cityStates.show" round position="bottom">
+        <van-picker
+          v-model="cityStates.value"
+          :columns="cityStates.columns"
+          @cancel="cityStates.show = false"
+          @confirm="cityStates.onConfirm"
+        />
+      </van-popup>
+      <van-popup v-model:show="teachStates.show" round position="bottom">
+        <van-picker
+          v-model="teachStates.value"
+          :columns="teachStates.columns"
+          @cancel="teachStates.show = false"
+          @confirm="teachStates.onConfirm"
+        />
+      </van-popup>
+      <van-popup v-model:show="dateStates.show" round position="bottom">
+        <van-picker
+          v-model="dateStates.value"
+          :columns="dateStates.columns"
+          @cancel="dateStates.show = false"
+          @confirm="dateStates.onConfirm"
+        />
+      </van-popup>
 
 
-    <van-popup
-      v-model:show="showSites"
-      round
-      position="bottom"
-      safe-area-inset-bottom
-    >
-      <ChooseSite
-        :site-list="siteList"
-        @site-confirm="siteConfirm"
-      ></ChooseSite>
-    </van-popup>
+      <van-popup
+        v-model:show="showSites"
+        round
+        position="bottom"
+        safe-area-inset-bottom
+      >
+        <ChooseSite
+          :site-list="siteList"
+          @site-confirm="siteConfirm"
+        ></ChooseSite>
+      </van-popup>
 
 
-    <div class="result-box" v-if="resultList.length">
-      <div class="tip">选择预约时段</div>
-      <div class="sub-tip">
-        <van-icon name="warning" color="#FF7D00" size="20" />
-        <span class="txt">我的剩余可约时段:{{ unApplyNumber }}</span>
-      </div>
-      <div class="list-box">
-        <div
-          v-for="item in resultList"
-          :key="item.timePeriodId"
-          class="flex-h-between list-item"
-        >
-          <div class="left d-flex align-items-center">
-            <span class="s1"
-              >{{ $filters.dateFormat(item.timePeriodStart, "HH:mm") }}-{{
-                $filters.dateFormat(item.timePeriodEnd, "HH:mm")
-              }}</span
+      <div class="result-box" v-if="resultList.length">
+        <div class="tip">选择预约时段</div>
+        <div class="sub-tip">
+          <van-icon name="warning" color="#FF7D00" size="20" />
+          <span class="txt">我的剩余可约时段:{{ unApplyNumber }}</span>
+        </div>
+        <div class="list-box">
+          <div
+            v-for="item in resultList"
+            :key="item.timePeriodId"
+            class="flex-h-between list-item"
+          >
+            <div class="left d-flex align-items-center">
+              <span class="s1"
+                >{{ $filters.dateFormat(item.timePeriodStart, "HH:mm") }}-{{
+                  $filters.dateFormat(item.timePeriodEnd, "HH:mm")
+                }}</span
+              >
+              <span class="s2">|</span>
+              <span class="s3">剩余{{ item.availableCount }}</span>
+            </div>
+            <van-button
+              v-if="item.status === 'AVAILABLE'"
+              type="success"
+              size="small"
+              @click="reservationHandle(item)"
+              >预约</van-button
             >
             >
-            <span class="s2">|</span>
-            <span class="s3">剩余{{ item.availableCount }}</span>
+            <span class="full" v-else-if="item.status === 'FULL'">约满</span>
+            <span class="stop" v-else>停止</span>
           </div>
           </div>
-          <van-button
-            v-if="item.status === 'AVAILABLE'"
-            type="success"
-            size="small"
-            @click="reservationHandle(item)"
-            >预约</van-button
-          >
-          <span class="full" v-else-if="item.status === 'FULL'">约满</span>
-          <span class="stop" v-else>停止</span>
         </div>
         </div>
       </div>
       </div>
+    </template>
+    <div class="vh-100 flex-h-center" v-else>
+      <div>
+        <NoData>
+          <template #img>
+            <img src="../../assets/imgs/no_data.png" />
+          </template>
+          当前无考试预约时段</NoData
+        >
+      </div>
     </div>
     </div>
-    <router-view v-slot="{ Component }">
+
+    <!-- <router-view v-slot="{ Component }">
       <transition name="zoom-fade" mode="out-in" appear>
       <transition name="zoom-fade" mode="out-in" appear>
         <component :is="Component" />
         <component :is="Component" />
       </transition>
       </transition>
-    </router-view>
+    </router-view> -->
   </div>
   </div>
 </template>
 </template>
 <script name="Reservation" setup>
 <script name="Reservation" setup>
@@ -220,13 +234,19 @@ function _getCategoryList() {
 }
 }
 _getCategoryList();
 _getCategoryList();
 
 
+const dateLoading = ref(false);
 function _getDateList() {
 function _getDateList() {
-  getDateList().then((res) => {
-    dateStates.columns = (res || []).map((item) => ({
-      value: item,
-      text: `${item.slice(0, 4)}-${item.slice(4, 6)}-${item.slice(6)}`,
-    }));
-  });
+  dateLoading.value = true;
+  getDateList()
+    .then((res) => {
+      dateStates.columns = (res || []).map((item) => ({
+        value: item,
+        text: `${item.slice(0, 4)}-${item.slice(4, 6)}-${item.slice(6)}`,
+      }));
+    })
+    .finally(() => {
+      dateLoading.value = false;
+    });
 }
 }
 _getDateList();
 _getDateList();
 
 
@@ -243,7 +263,7 @@ function _getReservationList() {
 
 
 function reservationHandle(item) {
 function reservationHandle(item) {
   showConfirmDialog({
   showConfirmDialog({
-    // title: '标题',
+    title: "是否确认预约?",
     message: `${params.date.text} ${filters.dateFormat(
     message: `${params.date.text} ${filters.dateFormat(
       item.timePeriodStart,
       item.timePeriodStart,
       "HH:mm"
       "HH:mm"
@@ -252,7 +272,9 @@ function reservationHandle(item) {
     reservationSave({
     reservationSave({
       examSiteId: params.examSiteId.value,
       examSiteId: params.examSiteId.value,
       timePeriodId: item.timePeriodId,
       timePeriodId: item.timePeriodId,
-    }).then(() => {});
+    }).then(() => {
+      showSuccessToast("预约成功");
+    });
   });
   });
 }
 }
 
 

+ 6 - 0
src/router/routes.js

@@ -49,6 +49,12 @@ const routes = [
     component: () => import("@/pages/admissionCard.vue"),
     component: () => import("@/pages/admissionCard.vue"),
     meta: { title: "电子准考证" },
     meta: { title: "电子准考证" },
   },
   },
+  {
+    path: "/myHistory",
+    name: "MyHistory",
+    component: () => import("@/pages/myHistory.vue"),
+    meta: { title: "预约订单" },
+  },
   {
   {
     path: "/404",
     path: "/404",
     name: "NotFound",
     name: "NotFound",

+ 8 - 1
src/store/modules/user.js

@@ -1,5 +1,6 @@
 import { defineStore } from "pinia";
 import { defineStore } from "pinia";
-import { getStuInfo, fetchOpenId } from "@/api/user";
+import { getStuInfo, fetchOpenId, logout, wxUnbind } from "@/api/user";
+import router from "@/router/index";
 
 
 const useUserStore = defineStore("user", {
 const useUserStore = defineStore("user", {
   persist: {
   persist: {
@@ -29,6 +30,12 @@ const useUserStore = defineStore("user", {
         return false;
         return false;
       }
       }
     },
     },
+    async logout() {
+      let res = await wxUnbind().catch(() => {});
+      logout().then(() => {
+        router.replace("/login");
+      });
+    },
     resetUser() {
     resetUser() {
       this.$reset();
       this.$reset();
     },
     },