刘洋 il y a 1 an
Parent
commit
ce3e89c8d5

+ 9 - 0
src/App.vue

@@ -4,8 +4,17 @@
 <script setup name="App">
 import { getUrlParam } from "./utils";
 import LibForWeixin from "./utils/LibForWeixin";
+import { useAppStore } from "./store";
+import { useRouter } from "vue-router";
+
+const router = useRouter();
+const appStore = useAppStore();
+appStore.getGlobalConfig();
 const code = getUrlParam("code");
 if (!code) {
   // LibForWeixin.auth();
+} else {
+  // userStore.requestOpenId(code);
+  router.push({ name: "WxLogin", query: { code } });
 }
 </script>

+ 10 - 0
src/api/common.js

@@ -1,5 +1,15 @@
 import request from "@/api/apiConfig";
 
+//获取系统常用属性
+export function getProperties() {
+  return request({
+    url: "/api/system/properties",
+    method: "post",
+    noAuth: true,
+  });
+}
+
+//该接口是假的,待后续有相关需求时,再替换成真实接口
 export function getJSSDKConfigs(url) {
   return request({
     url: "/api/common/jssdkConfigs",

+ 24 - 7
src/api/user.js

@@ -1,5 +1,15 @@
 import request from "@/api/apiConfig";
 
+//获取openId
+//暂时是假接口,等冯德胜出接口再替换
+export function fetchOpenId(code) {
+  return request({
+    url: "/api/system/getOpenId",
+    method: "post",
+    data: { code },
+  });
+}
+
 export function login(data) {
   return request({
     url: "/api/student/login",
@@ -10,20 +20,20 @@ export function login(data) {
   });
 }
 
-//获取考生信息
-export function getStuInfo() {
+export function wxLogin(openId) {
   return request({
-    url: "/api/student/info",
+    url: "/api/student/login/for/wechat",
     method: "post",
+    data: { openId },
+    noAuth: true,
   });
 }
 
-//获取系统常用属性
-export function getProperties() {
+//获取考生信息
+export function getStuInfo() {
   return request({
-    url: "/api/system/properties",
+    url: "/api/student/info",
     method: "post",
-    noAuth: true,
   });
 }
 
@@ -54,6 +64,9 @@ export function reservationSave(data) {
     url: "/api/student/apply/save",
     method: "post",
     data,
+    headers: {
+      "Content-Type": "application/x-www-form-urlencoded",
+    },
   });
 }
 //取消考生预约结果
@@ -62,6 +75,9 @@ export function reservationCancel(data) {
     url: "/api/student/apply/cancel",
     method: "post",
     data,
+    headers: {
+      "Content-Type": "application/x-www-form-urlencoded",
+    },
   });
 }
 
@@ -103,6 +119,7 @@ export function getAdmissionInfo(data) {
     url: "/api/student/apply/ticket",
     method: "post",
     data,
+    loading: true,
     headers: {
       "Content-Type": "application/x-www-form-urlencoded",
     },

BIN
src/assets/imgs/icon_apply.png


BIN
src/assets/imgs/no_data.png


BIN
src/assets/imgs/radio_check.png


BIN
src/assets/imgs/radio_uncheck.png


+ 44 - 73
src/assets/styles/index.css

@@ -42,7 +42,6 @@
 *::after {
   box-sizing: border-box;
   margin: 0;
-  position: relative;
   font-weight: normal;
 }
 body {
@@ -50,9 +49,8 @@ body {
   color: var(--color-text);
   background: var(--color-background);
   transition: color 0.5s, background-color 0.5s;
-  line-height: 1.6;
-  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-  font-size: 15px;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+  font-size: 14px;
   text-rendering: optimizeLegibility;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
@@ -108,45 +106,55 @@ body {
   animation: ani-cursor 0.5s linear alternate infinite;
 }
 @font-face {
-  font-family: 'DIN-Bold';
-  src: url('/fonts/din-bold.otf');
+  font-family: "DIN-Bold";
+  src: url("/fonts/din-bold.otf");
 }
 @font-face {
-  font-family: 'DIN-Regular';
-  src: url('/fonts/din-regular.otf');
+  font-family: "DIN-Regular";
+  src: url("/fonts/din-regular.otf");
 }
 @font-face {
-  font-family: 'DIN-Black';
-  src: url('/fonts/DIN-Black.otf');
+  font-family: "DIN-Black";
+  src: url("/fonts/DIN-Black.otf");
 }
 /* 字体名称 */
 .ff-DIN-Bold {
-  font-family: 'DIN-Bold';
+  font-family: "DIN-Bold";
 }
 .ff-DIN-Black {
-  font-family: 'DIN-Black';
+  font-family: "DIN-Black";
 }
 .ff-DIN-Regular {
-  font-family: 'DIN-Regular';
+  font-family: "DIN-Regular";
 }
 /* H5 */
 .tab-page {
-  padding-bottom: calc(var(--van-tabbar-height) + env(safe-area-inset-bottom));
+  padding-bottom: calc(var(--van-tabbar-height) + env(safe-area-inset-bottom)) !important;
 }
 .page {
-  padding-bottom: env(safe-area-inset-bottom);
+  padding-bottom: env(safe-area-inset-bottom) !important;
 }
 .page,
 .tab-page {
   width: 100%;
   min-height: 100vh;
-  background-color: #F9F9F9;
+  background-color: #f0f0f0;
   font-family: PingFangSC-Regular, PingFang SC;
+  overflow: auto;
   -webkit-overflow-scrolling: touch;
   display: flex;
   flex-direction: column;
   justify-content: flex-start;
 }
+.page .sub-page,
+.tab-page .sub-page {
+  padding-bottom: env(safe-area-inset-bottom) !important;
+  position: absolute;
+  z-index: 1;
+  width: 100vw;
+  height: 100vh;
+  background-color: #f0f0f0;
+}
 /* 公共样式 */
 * {
   touch-action: pan-y;
@@ -183,47 +191,6 @@ input {
 .bg-contain {
   background-size: contain !important;
 }
-/** 字体颜色 */
-.color-222222 {
-  color: #222222;
-}
-.color-333333 {
-  color: #333333;
-}
-.color-444444 {
-  color: #444444;
-}
-.color-555555 {
-  color: #555555;
-}
-.color-666666 {
-  color: #666666;
-}
-.color-777777 {
-  color: #777777;
-}
-.color-888888 {
-  color: #888888;
-}
-.color-999999 {
-  color: #999999;
-}
-.color-FFFFFF {
-  color: #FFFFFF;
-}
-/** 背景颜色 */
-.bg-F9F9F9 {
-  background-color: #F9F9F9;
-}
-.bg-EEEEEE {
-  background-color: #EEEEEE;
-}
-.bg-DEDEDE {
-  background-color: #DEDEDE;
-}
-.bg-FFFFFF {
-  background-color: #FFFFFF;
-}
 /** 尺寸相关 */
 .w-min-0 {
   min-width: 0;
@@ -1720,19 +1687,19 @@ input {
 }
 /* 边框 */
 .border {
-  border: 1px solid #ECECEC;
+  border: 1px solid #ececec;
 }
 .border-top {
-  border-top: 1px solid #ECECEC;
+  border-top: 1px solid #ececec;
 }
 .border-right {
-  border-right: 1px solid #ECECEC;
+  border-right: 1px solid #ececec;
 }
 .border-bottom {
-  border-bottom: 1px solid #ECECEC;
+  border-bottom: 1px solid #ececec;
 }
 .border-left {
-  border-left: 1px solid #ECECEC;
+  border-left: 1px solid #ececec;
 }
 .border-0 {
   border: none;
@@ -1756,28 +1723,28 @@ input {
   border-color: transparent;
 }
 .border-light {
-  border-color: #FFFFFF;
+  border-color: #ffffff;
 }
 .border-dark {
   border-color: #000000;
 }
 .border-FFCA2A {
-  border-color: #FFCA2A;
+  border-color: #ffca2a;
 }
 .border-F1F1F1 {
-  border-color: #F1F1F1;
+  border-color: #f1f1f1;
 }
 .border-F2F2F2 {
-  border-color: #F2F2F2;
+  border-color: #f2f2f2;
 }
 .border-DBDBDB {
-  border-color: #DBDBDB;
+  border-color: #dbdbdb;
 }
 .border-E5E5E5 {
-  border-color: #E5E5E5;
+  border-color: #e5e5e5;
 }
 .border-FFC689 {
-  border-color: #FFC689;
+  border-color: #ffc689;
 }
 /* 文本 */
 .text-center {
@@ -1950,10 +1917,10 @@ input {
 .bubble {
   display: inline-block;
   height: 14px;
-  background-color: #FF7E26;
+  background-color: #ff7e26;
   border-radius: 2px;
   padding: 0 4px;
-  color: #FFFFFF;
+  color: #ffffff;
   white-space: nowrap;
   font-size: 9px;
   text-align: center;
@@ -1961,12 +1928,12 @@ input {
   position: relative;
 }
 .bubble::after {
-  content: '';
+  content: "";
   display: block;
   width: 0;
   height: 0;
   border-style: solid;
-  border-color: #FF7E26 transparent transparent transparent;
+  border-color: #ff7e26 transparent transparent transparent;
   border-width: 5px 3px 0 3px;
   position: absolute;
   top: 100%;
@@ -2007,3 +1974,7 @@ input {
 .space-99 {
   height: 99px;
 }
+.van-cell-group--inset {
+  margin-left: 0;
+  margin-right: 0;
+}

+ 1 - 0
src/assets/styles/index.less

@@ -1,2 +1,3 @@
 @import "base.less";
 @import "main.less";
+@import "vant-custom.less";

+ 5 - 46
src/assets/styles/main.css

@@ -60,16 +60,16 @@
 }
 /* H5 */
 .tab-page {
-  padding-bottom: calc(var(--van-tabbar-height) + env(safe-area-inset-bottom));
+  padding-bottom: calc(var(--van-tabbar-height) + env(safe-area-inset-bottom)) !important;
 }
 .page {
-  padding-bottom: env(safe-area-inset-bottom);
+  padding-bottom: env(safe-area-inset-bottom) !important;
 }
 .page,
 .tab-page {
   width: 100%;
   min-height: 100vh;
-  background-color: #f9f9f9;
+  background-color: #f0f0f0;
   font-family: PingFangSC-Regular, PingFang SC;
   overflow: auto;
   -webkit-overflow-scrolling: touch;
@@ -79,12 +79,12 @@
 }
 .page .sub-page,
 .tab-page .sub-page {
-  padding-bottom: env(safe-area-inset-bottom);
+  padding-bottom: env(safe-area-inset-bottom) !important;
   position: absolute;
   z-index: 1;
   width: 100vw;
   height: 100vh;
-  background-color: #f9f9f9;
+  background-color: #f0f0f0;
 }
 /* 公共样式 */
 * {
@@ -122,47 +122,6 @@ input {
 .bg-contain {
   background-size: contain !important;
 }
-/** 字体颜色 */
-.color-222222 {
-  color: #222222;
-}
-.color-333333 {
-  color: #333333;
-}
-.color-444444 {
-  color: #444444;
-}
-.color-555555 {
-  color: #555555;
-}
-.color-666666 {
-  color: #666666;
-}
-.color-777777 {
-  color: #777777;
-}
-.color-888888 {
-  color: #888888;
-}
-.color-999999 {
-  color: #999999;
-}
-.color-FFFFFF {
-  color: #ffffff;
-}
-/** 背景颜色 */
-.bg-F9F9F9 {
-  background-color: #f9f9f9;
-}
-.bg-EEEEEE {
-  background-color: #eeeeee;
-}
-.bg-DEDEDE {
-  background-color: #dedede;
-}
-.bg-FFFFFF {
-  background-color: #ffffff;
-}
 /** 尺寸相关 */
 .w-min-0 {
   min-width: 0;

+ 7 - 49
src/assets/styles/main.less

@@ -62,16 +62,18 @@
 
 /* H5 */
 .tab-page {
-  padding-bottom: calc(var(--van-tabbar-height) + env(safe-area-inset-bottom));
+  padding-bottom: calc(
+    var(--van-tabbar-height) + env(safe-area-inset-bottom)
+  ) !important;
 }
 .page {
-  padding-bottom: env(safe-area-inset-bottom);
+  padding-bottom: env(safe-area-inset-bottom) !important;
 }
 .page,
 .tab-page {
   width: 100%;
   min-height: 100vh;
-  background-color: #f9f9f9;
+  background-color: #f0f0f0;
   font-family: PingFangSC-Regular, PingFang SC;
   overflow: auto;
   -webkit-overflow-scrolling: touch;
@@ -79,12 +81,12 @@
   flex-direction: column;
   justify-content: flex-start;
   .sub-page {
-    padding-bottom: env(safe-area-inset-bottom);
+    padding-bottom: env(safe-area-inset-bottom) !important;
     position: absolute;
     z-index: 1;
     width: 100vw;
     height: 100vh;
-    background-color: #f9f9f9;
+    background-color: #f0f0f0;
   }
 }
 
@@ -125,50 +127,6 @@ input {
   background-size: contain !important;
 }
 
-/** 字体颜色 */
-
-.color-222222 {
-  color: #222222;
-}
-.color-333333 {
-  color: #333333;
-}
-.color-444444 {
-  color: #444444;
-}
-.color-555555 {
-  color: #555555;
-}
-.color-666666 {
-  color: #666666;
-}
-.color-777777 {
-  color: #777777;
-}
-.color-888888 {
-  color: #888888;
-}
-.color-999999 {
-  color: #999999;
-}
-.color-FFFFFF {
-  color: #ffffff;
-}
-
-/** 背景颜色 */
-.bg-F9F9F9 {
-  background-color: #f9f9f9;
-}
-.bg-EEEEEE {
-  background-color: #eeeeee;
-}
-.bg-DEDEDE {
-  background-color: #dedede;
-}
-.bg-FFFFFF {
-  background-color: #ffffff;
-}
-
 /** 尺寸相关 */
 .w-min-0 {
   min-width: 0;

+ 4 - 0
src/assets/styles/vant-custom.css

@@ -0,0 +1,4 @@
+.van-cell-group--inset {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+}

+ 4 - 0
src/assets/styles/vant-custom.less

@@ -0,0 +1,4 @@
+.van-cell-group--inset {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+}

+ 54 - 14
src/components/applyItem.vue

@@ -1,17 +1,27 @@
 <template>
-  <div class="location-info">
-    <p>{{ item.categoryName }}</p>
-    <p>{{ item.examSiteName }}</p>
-    <p>{{ item.examSiteAddress }}</p>
-  </div>
-  <div class="time-info">
-    <div class="time-box flex-h-between">
-      <span>{{ $filters.dateFormat(item.timePeriodStart, "MM月dd日") }}</span>
-      <span
-        >{{ $filters.dateFormat(item.timePeriodStart, "HH:mm") }}-{{
-          $filters.dateFormat(item.timePeriodEnd, "HH:mm")
-        }}</span
-      >
+  <div class="card p-16">
+    <div class="location-info">
+      <div class="title flex-h-start">
+        <img src="../assets/imgs/icon_apply.png" />
+        <span>{{ item.categoryName }}</span>
+      </div>
+      <div class="info flex-h-start">
+        <span class="label">考点</span
+        ><span class="val">{{ item.examSiteName }}</span>
+      </div>
+      <div class="info flex-h-start">
+        <span class="label">地址</span
+        ><span class="val">{{ item.examSiteAddress }}</span>
+      </div>
+      <div class="info flex-h-start">
+        <span class="label">时间</span>
+        <span class="val"
+          >{{ $filters.dateFormat(item.timePeriodStart, "MM月dd日") }}
+          {{ $filters.dateFormat(item.timePeriodStart, "HH:mm") }}-{{
+            $filters.dateFormat(item.timePeriodEnd, "HH:mm")
+          }}</span
+        >
+      </div>
     </div>
     <div class="flex-h-end">
       <van-button
@@ -55,4 +65,34 @@ function seeTicket(item) {
   router.push({ name: "AdmissionCard", params: { applyId: item.applyId } });
 }
 </script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.card {
+  border-radius: 8px;
+  background-color: #fff;
+  margin-bottom: 16px;
+  .location-info {
+    margin-bottom: 15px;
+    .title {
+      span {
+        font-size: 16px;
+        color: #262626;
+        font-weight: bold;
+      }
+      img {
+        width: 22px;
+        margin-right: 8px;
+      }
+    }
+    .info {
+      margin-top: 15px;
+      .label {
+        color: #8c8c8c;
+        margin-right: 16px;
+      }
+      .val {
+        color: #262626;
+      }
+    }
+  }
+}
+</style>

+ 89 - 0
src/components/chooseSite.vue

@@ -0,0 +1,89 @@
+<template>
+  <div class="choose-site">
+    <div class="choose-box">
+      <div
+        v-for="item in siteList"
+        :key="item.examSiteId"
+        @click="setActive(item)"
+        class="site-item"
+      >
+        <div class="left">
+          <div class="title">{{ item.examSiteName }}</div>
+          <div class="sub-title">{{ item.examSiteAddress }}</div>
+        </div>
+        <img
+          v-if="activeItem.examSiteId == item.examSiteId"
+          class="radio-img"
+          src="../assets/imgs/radio_check.png"
+        />
+        <img v-else class="radio-img" src="../assets/imgs/radio_uncheck.png" />
+      </div>
+    </div>
+
+    <van-button
+      block
+      type="success"
+      @click="confirm"
+      class="confirm-btn"
+      :disabled="!activeItem.examSiteId"
+    >
+      确定
+    </van-button>
+  </div>
+</template>
+<script name="ChooseSite" setup>
+import { ref } from "vue";
+import { getSiteList } from "@/api/user";
+
+const props = defineProps({
+  siteList: { type: Array, default: () => [] },
+});
+const emit = defineEmits(["site-confirm"]);
+const activeItem = ref({});
+
+function setActive(item) {
+  activeItem.value = { ...item };
+}
+function confirm() {
+  emit("site-confirm", activeItem.value);
+}
+</script>
+<style lang="less" scoped>
+.choose-site {
+  padding: 10px;
+  max-height: 80vh;
+  display: flex;
+  flex-direction: column;
+  .confirm-btn {
+    margin-bottom: 8px;
+    margin-top: 15px;
+  }
+  .choose-box {
+    flex: 1;
+    overflow: auto;
+    -webkit-overflow-scrolling: touch;
+    .site-item {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 12px 8px;
+      &:active {
+        background-color: #f3f3f3;
+      }
+      .left {
+        .title {
+          color: #262626;
+          font-weight: bold;
+        }
+        .sub-title {
+          color: #8c8c8c;
+          margin-top: 4px;
+        }
+      }
+      .radio-img {
+        width: 22px;
+      }
+    }
+  }
+}
+</style>

+ 4 - 1
src/components/index.js

@@ -1,7 +1,10 @@
 import ApplyItem from "./applyItem.vue";
-
+import ChooseSite from "./chooseSite.vue";
+import NoData from "./noData.vue";
 export default {
   install(Vue) {
     Vue.component("ApplyItem", ApplyItem);
+    Vue.component("ChooseSite", ChooseSite);
+    Vue.component("NoData", NoData);
   },
 };

+ 21 - 0
src/components/noData.vue

@@ -0,0 +1,21 @@
+<template>
+  <div class="no-data">
+    <img src="../assets/imgs/no_data.png" />
+    <p>
+      <slot />
+    </p>
+  </div>
+</template>
+<script name="NoData" setup></script>
+<style lang="less" scoped>
+.no-data {
+  text-align: center;
+  img {
+    width: 62px;
+  }
+  p {
+    color: #8c8c8c;
+    margin-top: 8px;
+  }
+}
+</style>

+ 5 - 0
src/constants/dicts.js

@@ -0,0 +1,5 @@
+export const APPLY_STATUS = {
+  AVAILABLE: "可约",
+  FULL: "约满",
+  FINISHED: "已约",
+};

+ 46 - 9
src/pages/admissionCard.vue

@@ -1,10 +1,26 @@
 <template>
-  <div class="admission-card">
-    <div class="text-center title">准考证</div>
-    <div>姓名:{{ info.studentName }}</div>
-    <div>性别:{{ info.gender }}</div>
-    <div>身份证号:{{ info.identityNumber }}</div>
-    <div>考点名称:{{ info.examSiteName }}</div>
+  <div class="admission-card page p-16">
+    <div class="card p-16">
+      <div class="big-title">
+        {{ appStore.globalConfig.orgTitle
+        }}{{ appStore.globalConfig.taskTitle }}
+      </div>
+      <div class="text-center title">准考证</div>
+      <div class="media-box">
+        <img :src="appStore.globalConfig + info.photoPath" />
+        <div class="media-right">
+          <div><span class="label">姓名:</span>{{ info.studentName }}</div>
+          <div><span class="label">性别:</span>{{ info.gender }}</div>
+          <div>
+            <span class="label">身份证号:</span>{{ info.identityNumber }}
+          </div>
+          <div>
+            <span class="label">考点名称:</span>{{ info.examSiteName }}
+          </div>
+        </div>
+      </div>
+    </div>
+
     <div>
       考试时间:{{
         $filters.dateFormat(info.timePeriodStart, "yyyy年MM月dd日")
@@ -23,6 +39,9 @@
 import { ref } from "vue";
 import { useRoute } from "vue-router";
 import { getAdmissionInfo, getExamNotice, getRoomGuide } from "@/api/user";
+import { useAppStore } from "@/store";
+
+const appStore = useAppStore();
 const route = useRoute();
 const applyId = route.params.applyId;
 const info = ref({
@@ -72,9 +91,27 @@ function getWeek(time) {
 </script>
 <style lang="less" scoped>
 .admission-card {
-  .title {
-    font-weight: bold;
-    font-size: 20px;
+  .card {
+    border-radius: 8px;
+    background-color: #fff;
+    .big-title {
+      margin-top: 8px;
+      font-size: 16px;
+      color: #262626;
+      text-align: center;
+    }
+    .title {
+      font-weight: bold;
+      font-size: 20px;
+      color: #262626;
+      margin-top: 8px;
+    }
+    .media-box {
+      & > img {
+        height: 126px;
+        margin-right: 16px;
+      }
+    }
   }
 }
 </style>

+ 0 - 35
src/pages/chooseSite.vue

@@ -1,35 +0,0 @@
-<template>
-  <div class="choose-site sub-page">
-    <div v-for="item in siteList" :key="item.id" @click="setActive(item)">
-      <div>{{ item.examSiteName }}</div>
-      <div>{{ item.examSiteAddress }}</div>
-    </div>
-    <button @click="confirm">确定</button>
-  </div>
-</template>
-<script name="ChooseSite" setup>
-import { ref } from "vue";
-import { useRoute, useRouter } from "vue-router";
-import { getSiteList } from "@/api/user";
-import bus from "@/utils/bus";
-
-const router = useRouter();
-const route = useRoute();
-const siteList = ref([]);
-const activeItem = ref();
-function getExamSiteList() {
-  getSiteList({ categoryId: route.params.id }).then((res) => {
-    siteList.value = res;
-  });
-}
-getExamSiteList();
-
-function setActive(item) {
-  activeItem.value = { ...item };
-}
-function confirm() {
-  bus.emit("site-confirm", activeItem.value);
-  router.back();
-}
-</script>
-<style lang="less" scoped></style>

+ 46 - 14
src/pages/login.vue

@@ -1,28 +1,35 @@
 <template>
-  <div class="page login">
-    <van-form @submit="onSubmit">
+  <div class="login">
+    <div class="title">{{ appStore.globalConfig.orgTitle }}</div>
+    <div class="sub-title">{{ appStore.globalConfig.taskTitle }}</div>
+    <van-form @submit="onSubmit" class="login-form">
       <van-cell-group inset>
         <van-field
           v-model="account"
           name="学号"
-          label="学号"
+          label=""
           placeholder="请输入学号"
+          clearable
+          label-width="0px"
           :rules="[{ required: true, message: '请输入学号' }]"
         />
         <van-field
           v-model="password"
-          type="password"
+          :type="passwordShow ? 'text' : 'password'"
           name="密码"
-          label="密码"
+          label=""
           placeholder="请输入证件号后六位"
+          :right-icon="passwordShow ? 'closed-eye' : 'eye-o'"
+          @click-right-icon="passwordShow = !passwordShow"
+          label-width="0px"
           :rules="[{ required: true, message: '请输入证件号后六位' }]"
         />
+        <div style="margin: 16px">
+          <van-button block type="success" native-type="submit">
+            提交
+          </van-button>
+        </div>
       </van-cell-group>
-      <div style="margin: 16px">
-        <van-button block type="primary" native-type="submit">
-          提交
-        </van-button>
-      </div>
     </van-form>
   </div>
 </template>
@@ -30,16 +37,41 @@
 import { ref } from "vue";
 import { login } from "@/api/user";
 import { useRouter } from "vue-router";
-import { useUserStore } from "@/store";
+import { useUserStore, useAppStore } from "@/store";
 const router = useRouter();
+const appStore = useAppStore();
 const userStore = useUserStore();
 const account = ref("1509000120002");
 const password = ref("123456");
+const passwordShow = ref(false);
 const onSubmit = () => {
   login({ account: account.value, password: password.value }).then((res) => {
-    userStore.setLoginInfo(res);
-    router.push("/index");
+    if (res?.id) {
+      userStore.setLoginInfo(res);
+      router.push("/index");
+    }
   });
 };
 </script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.login {
+  background-color: #fff !important;
+  height: 100vh;
+  padding-top: 48px;
+  .title {
+    font-size: 24px;
+    font-weight: 600;
+    color: #262626;
+    padding-left: 24px;
+  }
+  .sub-title {
+    font-size: 16px;
+    color: #8c8c8c;
+    margin-top: 8px;
+    padding-left: 24px;
+  }
+  .login-form {
+    margin-top: 24px;
+  }
+}
+</style>

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

@@ -1,7 +1,18 @@
 <template>
-  <div class="index tab-page">
-    <div class="card" v-for="item in list" :key="item.applyId">
-      <ApplyItem :item="item" @update="_getIndexList"></ApplyItem>
+  <div class="index tab-page p-16">
+    <template v-if="list.length">
+      <ApplyItem
+        :item="item"
+        @update="_getIndexList"
+        v-for="item in list"
+        :key="item.applyId"
+      ></ApplyItem>
+    </template>
+    <div class="vh-100 flex-h-center" v-else>
+      <div>
+        <NoData>当前无考试预约订单</NoData>
+        <div class="cus-btn flex-h-center" @click="toReservation">去预约</div>
+      </div>
     </div>
   </div>
 </template>
@@ -10,7 +21,8 @@ import { ref } from "vue";
 
 import { useUserStore } from "@/store";
 import { getIndexList } from "@/api/user";
-
+import { useRouter } from "vue-router";
+const router = useRouter();
 const userStore = useUserStore();
 userStore.requestStuInfo();
 const list = ref([]);
@@ -20,14 +32,23 @@ function _getIndexList() {
   });
 }
 _getIndexList();
+
+function toReservation() {
+  router.push({ name: "Reservation" });
+}
 </script>
 <style lang="less" scoped>
 .index {
-  padding: 15px;
-  .card {
+  .cus-btn {
+    border-radius: 8px;
     background-color: #fff;
-    border-radius: 6px;
-    margin-bottom: 10px;
+    height: 44px;
+    margin-top: 36px;
+    color: #00b42a;
+    font-weight: bold;
+    &:active {
+      background-color: #f8f8f8;
+    }
   }
 }
 </style>

+ 125 - 23
src/pages/tab-pages/reservation.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="reservation tab-page position-relative">
+  <div class="reservation tab-page position-relative p-16">
     <van-cell-group inset>
       <van-field
         v-model="params.aaa.text"
@@ -74,25 +74,48 @@
       />
     </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>我的剩余可约时段:{{ unApplyNumber }}</div>
+      <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">
-          <div class="flex-h-between">
-            <div>
-              {{ $filters.dateFormat(item.timePeriodStart, "HH:mm") }}-{{
+        <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")
-              }}
-            </div>
-            <div>剩余{{ item.availableCount }}</div>
-            <van-button
-              type="primary"
-              size="small"
-              :disabled="item.status != '可约'"
-              @click="reservation(item)"
-              >{{ item.status }}</van-button
+              }}</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="full" v-else-if="item.status === 'FULL'">约满</span>
+          <span class="stop" v-else>停止</span>
         </div>
       </div>
     </div>
@@ -112,12 +135,19 @@ import {
   getDateList,
   getReservationList,
   reservationSave,
+  getSiteList,
 } from "@/api/user";
 import { useRouter } from "vue-router";
 import bus from "@/utils/bus";
+import { APPLY_STATUS } from "@/constants/dicts";
 
 const router = useRouter();
-
+const siteList = ref([]);
+function _getSiteList() {
+  getSiteList({ categoryId: params.bbb.value }).then((res) => {
+    siteList.value = res || [];
+  });
+}
 const resultList = ref([]);
 const params = reactive({
   aaa: { value: "", text: "" },
@@ -126,6 +156,7 @@ const params = reactive({
   date: { value: "", text: "" },
 });
 const treeData = ref([]);
+const showSites = ref(false);
 const cityStates = reactive({
   show: false,
   value: [],
@@ -155,6 +186,7 @@ const teachStates = reactive({
       params.examSiteId = { value: "", text: "" };
     }
     teachStates.show = false;
+    _getSiteList();
   },
 });
 
@@ -172,7 +204,8 @@ const dateStates = reactive({
 });
 
 const toChooseSite = () => {
-  router.push({ name: "ChooseSite", params: { id: params.bbb.value } });
+  // router.push({ name: "ChooseSite", params: { id: params.bbb.value } });
+  showSites.value = true;
 };
 
 function _getCategoryList() {
@@ -208,13 +241,13 @@ function _getReservationList() {
   });
 }
 
-function reservation(item) {
+function reservationHandle(item) {
   showConfirmDialog({
     // title: '标题',
     message: `${params.date.text} ${filters.dateFormat(
       item.timePeriodStart,
-      "mm:ss"
-    )}-${filters.dateFormat(item.timePeriodEnd, "mm:ss")}`,
+      "HH:mm"
+    )}-${filters.dateFormat(item.timePeriodEnd, "HH:mm")}`,
   }).then(() => {
     reservationSave({
       examSiteId: params.examSiteId.value,
@@ -231,8 +264,77 @@ watch([() => params.examSiteId, () => params.date], ([examSiteId, date]) => {
   }
 });
 
-bus.on("site-confirm", (obj) => {
+function siteConfirm(obj) {
   params.examSiteId = { text: obj.examSiteName, value: obj.examSiteId };
-});
+  showSites.value = false;
+}
 </script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.reservation {
+  .result-box {
+    .tip {
+      font-size: 14px;
+      color: #8c8c8c;
+      margin-top: 16px;
+    }
+    .sub-tip {
+      height: 32px;
+      background: #fff7eb;
+      border-radius: 6px;
+      padding-left: 8px;
+      display: flex;
+      align-items: center;
+      margin-top: 12px;
+      .txt {
+        color: #262626;
+        margin-left: 4px;
+      }
+    }
+    .list-box {
+      border-radius: var(--van-cell-group-inset-radius);
+      background: #fff;
+      padding: 0 var(--van-cell-horizontal-padding);
+      margin-top: 8px;
+      .list-item {
+        padding: var(--van-cell-vertical-padding) 0;
+        position: relative;
+        &:after {
+          position: absolute;
+          box-sizing: border-box;
+          content: " ";
+          pointer-events: none;
+          right: 0;
+          bottom: 0;
+          left: 0;
+          border-bottom: 0.02667rem solid var(--van-cell-border-color);
+          -webkit-transform: scaleY(0.5);
+          -ms-transform: scaleY(0.5);
+          transform: scaleY(0.5);
+        }
+        span.full {
+          color: #00b42a;
+          font-weight: bold;
+        }
+        span.stop {
+          color: #bfbfbf;
+          font-weight: bold;
+        }
+        .left {
+          .s1 {
+            font-size: 14px;
+            color: #262626;
+          }
+          .s2 {
+            color: #bfbfbf;
+            margin: 0 7px;
+          }
+          .s3 {
+            color: #165dff;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 34 - 0
src/pages/wxLogin.vue

@@ -0,0 +1,34 @@
+<template>
+  <div class="wx-login"></div>
+</template>
+<script name="WxLogin" setup>
+import { useRoute, useRouter } from "vue-router";
+import { useUserStore } from "@/store";
+import { wxLogin } from "@/api/user";
+const userStore = useUserStore();
+const route = useRoute();
+const router = useRouter();
+const code = route.query.code;
+async function _wxLogin() {
+  showLoadingToast({
+    forbidClick: true,
+    loadingType: "spinner",
+    duration: 0,
+  });
+  const openId = await userStore.requestOpenId(code);
+  if (openId) {
+    let res = await wxLogin(openId);
+    closeToast();
+    if (res?.id) {
+      userStore.setLoginInfo(res);
+      router.replace("/index");
+    } else {
+      router.replace("/login");
+    }
+  } else {
+    closeToast();
+    router.replace("/login");
+  }
+}
+</script>
+<style lang="less" scoped></style>

+ 3 - 2
src/router/index.js

@@ -5,7 +5,7 @@ import {
 } from "vue-router";
 import routes from "./routes";
 import LibForWeixin from "@/utils/LibForWeixin";
-import { useUserStore } from "@/store";
+import { useUserStore, useAppStore } from "@/store";
 
 const router = createRouter({
   // history: createWebHistory(import.meta.env.VITE_BASE),
@@ -17,8 +17,9 @@ const router = createRouter({
     behavior: "smooth",
   }),
 });
-const whiteList = ["Login"];
+const whiteList = ["Login", "WxLogin"];
 router.beforeEach(async (to, from, next) => {
+  const appStore = useAppStore();
   if (to.meta.jsApiList) {
     await LibForWeixin.initJSSDK(to.meta.jsApiList)
       .then(() => {

+ 8 - 8
src/router/routes.js

@@ -18,14 +18,6 @@ const routes = [
         name: "Reservation",
         component: () => import("@/pages/tab-pages/reservation.vue"),
         meta: { title: "预约考试" },
-        children: [
-          {
-            path: "chooseSite/:id",
-            name: "ChooseSite",
-            component: () => import("@/pages/chooseSite.vue"),
-            meta: { title: "选择考点" },
-          },
-        ],
       },
       {
         path: "/mine",
@@ -43,6 +35,14 @@ const routes = [
       title: "登录",
     },
   },
+  {
+    path: "/wxLogin",
+    name: "WxLogin",
+    component: () => import("@/pages/wxLogin.vue"),
+    meta: {
+      title: "",
+    },
+  },
   {
     path: "/admissionCard/:applyId",
     name: "AdmissionCard",

+ 13 - 2
src/store/modules/app.js

@@ -1,7 +1,18 @@
 import { defineStore } from "pinia";
+import { getProperties } from "@/api/common";
 
 const useAppStore = defineStore("app", {
-  state: () => ({}),
-  actions: {},
+  state: () => ({
+    globalConfig: {},
+  }),
+  actions: {
+    getGlobalConfig() {
+      getProperties().then((res) => {
+        if (Object.prototype.toString.call(res) === "[object Object]") {
+          this.globalConfig = res;
+        }
+      });
+    },
+  },
 });
 export default useAppStore;

+ 13 - 3
src/store/modules/user.js

@@ -1,12 +1,12 @@
 import { defineStore } from "pinia";
-import { getStuInfo } from "@/api/user";
+import { getStuInfo, fetchOpenId } from "@/api/user";
 
 const useUserStore = defineStore("user", {
   persist: {
     storage: localStorage,
-    paths: ["loginInfo", "stuInfo"],
+    paths: ["loginInfo", "stuInfo", "openId"],
   },
-  state: () => ({ loginInfo: null, stuInfo: null }),
+  state: () => ({ loginInfo: null, stuInfo: null, openId: "" }),
   actions: {
     setState(data) {
       this.$patch(data);
@@ -19,6 +19,16 @@ const useUserStore = defineStore("user", {
         this.stuInfo = res;
       });
     },
+    async requestOpenId(code) {
+      let res = await fetchOpenId(code).catch(() => {});
+      if (res) {
+        let openid = typeof res === "string" ? res : res?.openid;
+        this.openId = openid;
+        return openid;
+      } else {
+        return false;
+      }
+    },
     resetUser() {
       this.$reset();
     },

+ 2 - 1
src/utils/LibForWeixin.js

@@ -3,7 +3,8 @@ import { getJSSDKConfigs } from "@/api/common";
 class LibForWeixin {
   static auth() {
     const appId = import.meta.env.VITE_WEIXIN_APPID;
-    const scope = "snsapi_userinfo";
+    // const scope = "snsapi_userinfo";
+    const scope = "snsapi_base"; //只获取openid,不获取用户信息的话,就用这个
     let state = "";
     let redirect_uri = encodeURIComponent(window.location.href);
     window.location.replace(