Jelajahi Sumber

type: 需求修改

chenlei 2 tahun lalu
induk
melakukan
546466fe0e

+ 0 - 116
public/__config.js

@@ -1,116 +0,0 @@
-// 场馆 Record<场馆id, string>
-museum = { 3: "苏州博物馆(本馆),苏州博物馆(西馆)" };
-
-// 接口地址
-VUE_APP_BACKEND_URL = "http://192.168.20.245:8051";
-
-// 云游景区
-cloudScenicUrl =
-  "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area" +
-  "?T=" +
-  Date.now();
-
-// 单位
-company = "江苏省文化和旅游厅";
-
-// 预约场馆-预约时间段
-MUSEUM_LIST_TIME = {
-  1: ["上午", "下午"],
-  2: ["上午", "下午"],
-  3: ["上午", "下午", "晚上"],
-};
-
-// 云游景区 url 配置
-SCENIC_MUSEUM_POS = [
-  {
-    id: 37,
-    name: "徐州市",
-    top: 136,
-    left: 300,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320300",
-  },
-  {
-    id: 47,
-    name: "宿迁市",
-    left: 400,
-    top: 227,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321300",
-  },
-  {
-    id: 41,
-    name: "连云港市",
-    left: 550,
-    top: 60,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320700",
-  },
-  {
-    id: 42,
-    name: "淮安市",
-    left: 525,
-    top: 270,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320800",
-  },
-  {
-    id: 43,
-    name: "盐城市",
-    top: 300,
-    left: 710,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320900",
-  },
-  {
-    id: 44,
-    name: "扬州市",
-    left: 570,
-    top: 380,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321000",
-  },
-  {
-    id: 46,
-    name: "泰州市",
-    left: 700,
-    top: 585,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321200",
-  },
-  {
-    id: 35,
-    name: "南京市",
-    left: 404,
-    top: 677,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320100",
-  },
-  {
-    id: 45,
-    name: "镇江市",
-    left: 555,
-    top: 665,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321100",
-  },
-  {
-    id: 40,
-    name: "南通市",
-    left: 882,
-    top: 616,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320600",
-  },
-  {
-    id: 38,
-    name: "常州市",
-    left: 610,
-    top: 770,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320400",
-  },
-  {
-    id: 36,
-    name: "无锡市",
-    left: 750,
-    top: 800,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320200",
-  },
-  {
-    id: 39,
-    name: "苏州市",
-    left: 875,
-    top: 800,
-    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320500",
-  },
-];

+ 113 - 0
public/_config.js

@@ -0,0 +1,113 @@
+// 场馆 Record<场馆id, string>
+museum = { 3: "苏州博物馆(本馆),苏州博物馆(西馆)" };
+
+// 是否开启预约调试弹窗
+venueDebug = false;
+
+// 接口地址
+VUE_APP_BACKEND_URL = "http://114.217.51.225:7077";
+
+// 云游景区
+cloudScenicUrl =
+  "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area" +
+  "?T=" +
+  Date.now();
+
+// 单位
+company = "江苏省文化和旅游厅";
+companyId = 101;
+
+// 云游景区 url 配置
+SCENIC_MUSEUM_POS = [
+  {
+    id: 37,
+    name: "徐州市",
+    top: 133,
+    left: 317,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320300",
+  },
+  {
+    id: 47,
+    name: "宿迁市",
+    left: 416,
+    top: 251,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321300",
+  },
+  {
+    id: 41,
+    name: "连云港市",
+    left: 570,
+    top: 82,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320700",
+  },
+  {
+    id: 42,
+    name: "淮安市",
+    left: 531,
+    top: 302,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320800",
+  },
+  {
+    id: 43,
+    name: "盐城市",
+    top: 304,
+    left: 719,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320900",
+  },
+  {
+    id: 44,
+    name: "扬州市",
+    left: 587,
+    top: 392,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321000",
+  },
+  {
+    id: 46,
+    name: "泰州市",
+    left: 712,
+    top: 605,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321200",
+  },
+  {
+    id: 35,
+    name: "南京市",
+    left: 422,
+    top: 690,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320100",
+  },
+  {
+    id: 45,
+    name: "镇江市",
+    left: 583,
+    top: 675,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321100",
+  },
+  {
+    id: 40,
+    name: "南通市",
+    left: 896,
+    top: 647,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320600",
+  },
+  {
+    id: 38,
+    name: "常州市",
+    left: 633,
+    top: 800,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320400",
+  },
+  {
+    id: 36,
+    name: "无锡市",
+    left: 767,
+    top: 819,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320200",
+  },
+  {
+    id: 39,
+    name: "苏州市",
+    left: 888,
+    top: 859,
+    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320500",
+  },
+];

+ 45 - 48
public/config.js

@@ -4,116 +4,113 @@ museum = { 3: "苏州博物馆(本馆),苏州博物馆(西馆)" };
 // 是否开启预约调试弹窗
 venueDebug = false;
 
+// 是否开启云上博物接口请求
+openCloudApi = false;
+
 // 接口地址
-VUE_APP_BACKEND_URL = "http://114.217.51.225:7077";
+VUE_APP_BACKEND_URL = "http://192.168.20.245:8051";
 
 // 云游景区
 cloudScenicUrl =
-  "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area" +
+  "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area" +
   "?T=" +
   Date.now();
 
 // 单位
 company = "江苏省文化和旅游厅";
-
-// 预约场馆-预约时间段
-MUSEUM_LIST_TIME = {
-  1: ["上午", "下午"],
-  2: ["上午", "下午"],
-  3: ["上午", "下午", "晚上"],
-};
+companyId = 101;
 
 // 云游景区 url 配置
 SCENIC_MUSEUM_POS = [
   {
     id: 37,
     name: "徐州市",
-    top: 133,
-    left: 317,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320300",
+    top: 136,
+    left: 300,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320300",
   },
   {
     id: 47,
     name: "宿迁市",
-    left: 416,
-    top: 251,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321300",
+    left: 400,
+    top: 227,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321300",
   },
   {
     id: 41,
     name: "连云港市",
-    left: 570,
-    top: 82,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320700",
+    left: 550,
+    top: 60,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320700",
   },
   {
     id: 42,
     name: "淮安市",
-    left: 531,
-    top: 302,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320800",
+    left: 525,
+    top: 270,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320800",
   },
   {
     id: 43,
     name: "盐城市",
-    top: 304,
-    left: 719,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320900",
+    top: 300,
+    left: 710,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320900",
   },
   {
     id: 44,
     name: "扬州市",
-    left: 587,
-    top: 392,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321000",
+    left: 570,
+    top: 380,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321000",
   },
   {
     id: 46,
     name: "泰州市",
-    left: 712,
-    top: 605,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321200",
+    left: 700,
+    top: 585,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321200",
   },
   {
     id: 35,
     name: "南京市",
-    left: 422,
-    top: 690,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320100",
+    left: 404,
+    top: 677,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320100",
   },
   {
     id: 45,
     name: "镇江市",
-    left: 583,
-    top: 675,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321100",
+    left: 555,
+    top: 665,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=321100",
   },
   {
     id: 40,
     name: "南通市",
-    left: 896,
-    top: 647,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320600",
+    left: 882,
+    top: 616,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320600",
   },
   {
     id: 38,
     name: "常州市",
-    left: 633,
-    top: 800,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320400",
+    left: 610,
+    top: 770,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320400",
   },
   {
     id: 36,
     name: "无锡市",
-    left: 767,
-    top: 819,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320200",
+    left: 750,
+    top: 800,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320200",
   },
   {
     id: 39,
     name: "苏州市",
-    left: 888,
-    top: 859,
-    url: "http://127.0.0.1:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320500",
+    left: 875,
+    top: 800,
+    url: "http://192.168.9.78:18080/ms-mechanism-bs/default/on-cloud-scenic-area?areaCode=320500",
   },
 ];

TEMPAT SAMPAH
public/imgs/banner.jpg


+ 25 - 19
src/App.vue

@@ -30,24 +30,19 @@ import { GetScreenConfigApiResponse } from "./api/types";
 import { useStore } from "vuex";
 import { isOnlineFn } from "./utils";
 
+const DEFAULT_TIME = 60 * 10;
 const store = useStore();
 const showScreenSavers = ref(false);
-const showScreenTime = ref(60 * 10);
+const showScreenTime = ref(DEFAULT_TIME);
 const autoPlay = ref(false);
 const screenList = ref<GetScreenConfigApiResponse["img"]>([]);
 
 onMounted(() => {
   getOnline().then((state) => {
-    state && getScreenConfig();
+    getScreenConfig(state);
   });
 
   setInterval(() => {
-    getOnline().then((state) => {
-      state && getScreenConfig();
-    });
-  }, 5000);
-
-  setInterval(() => {
     store.commit("setTime", store.state.time + 1);
     if (store.state.time >= showScreenTime.value && !showScreenSavers.value) {
       showScreenSavers.value = true;
@@ -70,17 +65,28 @@ const closeScreen = () => {
   showScreenSavers.value = false;
 };
 
-const getScreenConfig = async () => {
-  const { data } = await getScreenConfigApi();
-  const c = JSON.parse(data.config.content);
-  autoPlay.value = c.autoPlay === "true";
-  showScreenTime.value = Number(c.time);
-  screenList.value = data.img
-    .filter((i) => i.display === 1)
-    .map((i) => ({
-      ...i,
-      thumb: window.VUE_APP_BACKEND_URL + i.thumb,
-    }));
+const getScreenConfig = async (isOnline: boolean) => {
+  if (isOnline) {
+    const { data } = await getScreenConfigApi();
+    const c = JSON.parse(data.config.content);
+    autoPlay.value = c.autoPlay === "true";
+    showScreenTime.value = Number(c.time);
+    screenList.value = data.img
+      .filter((i) => i.display === 1)
+      .map((i) => ({
+        ...i,
+        thumb: window.VUE_APP_BACKEND_URL + i.thumb,
+      }));
+  } else {
+    showScreenTime.value = Number(DEFAULT_TIME);
+    screenList.value = [
+      {
+        id: 1,
+        thumb: "/imgs/banner.jpg",
+        display: 1,
+      },
+    ];
+  }
 };
 </script>
 

+ 7 - 0
src/api/index.ts

@@ -5,6 +5,7 @@ import {
   GetCityMuseumListApiRequest,
   GetCityMuseumListApiResponse,
   GetExhibitListApiRequest,
+  GetExhibitionConfigApiResponse,
   GetScreenConfigApiResponse,
 } from "./types";
 
@@ -44,3 +45,9 @@ export const getVenueNumApi = (data: any) => {
 export const getScreenConfigApi = async () => {
   return service.get<GetScreenConfigApiResponse>("/api/show/screen/getConfig");
 };
+
+export const getExhibitionConfigApi = async (exhibitionId: number) => {
+  return service.get<GetExhibitionConfigApiResponse>(
+    `/api/show/book/info/${exhibitionId}`
+  );
+};

+ 9 - 0
src/api/types.ts

@@ -39,3 +39,12 @@ export interface GetScreenConfigApiResponse {
   config: { content: string };
   img: { id: number; thumb: string; display: 1 | 0 }[];
 }
+
+export interface GetExhibitionConfigApiResponse {
+  bookDesc: null | string;
+  date: {
+    timeScope: string;
+    weeks: string;
+  }[];
+  unableDate: string[];
+}

+ 2 - 0
src/global.d.ts

@@ -2,6 +2,7 @@ interface Window {
   museum: string;
   cloudScenicUrl: string;
   company: string;
+  companyId: number;
   SCENIC_MUSEUM_POS: {
     id: number;
     name: string;
@@ -11,5 +12,6 @@ interface Window {
   }[];
   VUE_APP_BACKEND_URL: string;
   venueDebug: boolean;
+  openCloudApi: boolean;
   MUSEUM_LIST_TIME: Record<number, string[]>;
 }

+ 10 - 0
src/utils/index.ts

@@ -40,3 +40,13 @@ export const isOnlineFn = async () => {
 };
 
 export * from "./date";
+
+export function checkTelephone(telephone: string) {
+  const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
+  return reg.test(telephone);
+}
+
+export function checkIdCard(idCard: string) {
+  const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
+  return reg.test(idCard);
+}

+ 109 - 0
src/utils/pass.ts

@@ -0,0 +1,109 @@
+function randomWord(randomFlag: boolean, min: number, max = 15) {
+  let str = "";
+  let range = min;
+  const arr = [
+    "0",
+    "1",
+    "2",
+    "3",
+    "4",
+    "5",
+    "6",
+    "7",
+    "8",
+    "9",
+    "a",
+    "b",
+    "c",
+    "d",
+    "e",
+    "f",
+    "g",
+    "h",
+    "i",
+    "j",
+    "k",
+    "l",
+    "m",
+    "n",
+    "o",
+    "p",
+    "q",
+    "r",
+    "s",
+    "t",
+    "u",
+    "v",
+    "w",
+    "x",
+    "y",
+    "z",
+    "A",
+    "B",
+    "C",
+    "D",
+    "E",
+    "F",
+    "G",
+    "H",
+    "I",
+    "J",
+    "K",
+    "L",
+    "M",
+    "N",
+    "O",
+    "P",
+    "Q",
+    "R",
+    "S",
+    "T",
+    "U",
+    "V",
+    "W",
+    "X",
+    "Y",
+    "Z",
+  ];
+  // 随机产生
+  if (randomFlag) {
+    range = Math.round(Math.random() * (max - min)) + min;
+  }
+  for (let i = 0; i < range; i++) {
+    const pos = Math.round(Math.random() * (arr.length - 1));
+    str += arr[pos];
+  }
+  return str;
+}
+
+const encodeStr = (str: string, strv = "") => {
+  const NUM = 2;
+  const front = randomWord(false, 8);
+  const middle = randomWord(false, 8);
+  const end = randomWord(false, 8);
+
+  const str1 = str.substring(0, NUM);
+  const str2 = str.substring(NUM);
+
+  if (strv) {
+    const strv1 = strv.substring(0, NUM);
+    const strv2 = strv.substring(NUM);
+    return [
+      front + str2 + middle + str1 + end,
+      front + strv2 + middle + strv1 + end,
+    ];
+  }
+
+  return front + str2 + middle + str1 + end;
+};
+
+export const decodeStr = (str: string) => {
+  const NUM = 2;
+  const str1 = str.substring(8);
+  const str2 = str1.substring(0, str1.length - 8);
+  const front = str2.slice(-NUM);
+  const end = str2.substring(0, str2.length - 8 - NUM);
+  return front + end;
+};
+
+export default encodeStr;

+ 1 - 1
src/views/cloud-museum/index.vue

@@ -170,7 +170,7 @@ const handleFocus = () => {
 };
 
 const getCityMuseumList = async (load?: boolean) => {
-  if (!store.state.isOnline) {
+  if (!window.openCloudApi || !store.state.isOnline) {
     console.log("===读取静态数据===");
     const regex = new RegExp(searchKey.value, "gi");
     let stack: CityMuseumItemType[] = [];

+ 8 - 5
src/views/venue-reservation/components/dialog.vue

@@ -35,8 +35,8 @@ const show = computed({
 
   &__close {
     position: absolute;
-    top: 0.875rem /* 70/80 */;
-    right: 0.75rem /* 60/80 */;
+    top: 1.125rem /* 90/80 */;
+    right: 0.625rem /* 50/80 */;
     width: 0.5375rem /* 43/80 */;
     height: 0.4875rem /* 39/80 */;
     background: url("../imgs/btm_cancel.png") no-repeat center / 100%;
@@ -48,20 +48,23 @@ const show = computed({
     align-items: center;
     justify-content: center;
     margin-top: 25%;
-    width: 5.8875rem /* 471/80 */;
-    height: 4.2375rem /* 339/80 */;
+    width: 6.8875rem /* 551/80 */;
+    height: 5.2375rem /* 419/80 */;
     box-shadow: none;
     transform: translateY(-50%);
-    background: url("../imgs/pop.png") no-repeat center / contain;
+    background: url("../imgs/pop.png") no-repeat center / cover;
 
     &__header {
       display: none;
     }
     &__body {
+      padding: 0.375rem /* 30/80 */ 0.625rem /* 50/80 */;
+      width: 100%;
       font-size: 0.375rem /* 30/80 */;
       text-align: center;
       color: #fffae9;
       line-height: 0.5125rem /* 41/80 */;
+      box-sizing: border-box;
       text-shadow: 0px 0px 0.1rem /* 8/80 */ #ffb525;
     }
   }

+ 9 - 0
src/views/venue-reservation/constants.ts

@@ -0,0 +1,9 @@
+export const DEFAULT_FORM1 = {
+  organ: window.company,
+  division: "",
+  leader: {
+    name: "",
+    phone: "",
+    idcard: "",
+  },
+};

+ 119 - 0
src/views/venue-reservation/form-1.vue

@@ -0,0 +1,119 @@
+<template>
+  <el-form
+    ref="formRef"
+    :rules="rules"
+    :model="form"
+    class="venue-reservation-form"
+  >
+    <el-form-item label="所在单位" prop="organ">
+      <div class="vr-input">
+        <el-input v-model="form.organ" readonly placeholder="请输入内容" />
+      </div>
+    </el-form-item>
+    <el-form-item label="所在部门" prop="division">
+      <div class="vr-input">
+        <el-input
+          v-model="form.division"
+          placeholder="请填写部门名称"
+          :maxlength="20"
+          @click="handleFocus"
+        />
+      </div>
+    </el-form-item>
+    <el-form-item label="预约人员" prop="leader">
+      <div class="vr-visitor">
+        <div class="vr-visitor__item no-id-card">
+          <div class="vr-input">
+            <el-input
+              v-model="form.leader.name"
+              placeholder="请输入姓名"
+              @click="handleFocus"
+            />
+          </div>
+          <div class="vr-input">
+            <el-input
+              v-model="form.leader.phone"
+              placeholder="请输入手机号"
+              type="number"
+              @click="handleFocus"
+            />
+          </div>
+          <!-- <div class="vr-input">
+            <el-input
+              v-model="form.leader.idcard"
+              placeholder="请输入身份证号"
+              @click="handleFocus"
+            />
+          </div> -->
+        </div>
+      </div>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { checkTelephone } from "@/utils";
+import { OpenVirtualKeyBoard } from "@/utils/open-keyboard";
+import { FormInstance, FormRules } from "element-plus";
+import { reactive, ref, watch } from "vue";
+import { DEFAULT_FORM1 } from "./constants";
+import { cloneDeep } from "lodash";
+
+const props = defineProps<{
+  formData: typeof DEFAULT_FORM1 | null;
+}>();
+const formRef = ref<FormInstance>();
+const form = reactive(cloneDeep(DEFAULT_FORM1));
+const rules = reactive<FormRules>({
+  division: [
+    { required: true, message: "请填写部门名称", trigger: "blur" },
+    {
+      validator: (rule, val, callback) => {
+        if (val.length < 2) {
+          callback(new Error("部门名称长度不能小于 2 个字符"));
+        }
+        callback();
+      },
+    },
+  ],
+  leader: [
+    {
+      validator: (rule, val, callback) => {
+        if (!val.name) {
+          callback(new Error("请输入姓名"));
+        } else if (!checkTelephone(val.phone)) {
+          callback(new Error("手机号输入有误"));
+        }
+        callback();
+      },
+      trigger: "submit",
+    },
+  ],
+});
+
+watch(
+  () => props.formData,
+  (val) => {
+    if (val) {
+      Object.assign(form, val);
+    }
+  },
+  {
+    immediate: true,
+  }
+);
+
+const handleFocus = () => {
+  OpenVirtualKeyBoard();
+};
+
+const next = async () => {
+  if (!formRef.value || !(await formRef.value.validate())) return;
+
+  return form;
+};
+
+defineExpose({
+  next,
+});
+</script>

+ 387 - 0
src/views/venue-reservation/form-2.vue

@@ -0,0 +1,387 @@
+<template>
+  <el-form
+    ref="formRef"
+    :rules="rules"
+    :model="form"
+    class="venue-reservation-form"
+  >
+    <el-form-item label="参观展馆" prop="exhibitionName">
+      <div class="vr-ex-flex">
+        <div
+          v-for="item in MUSEUM_LIST"
+          :key="item.id"
+          :class="[
+            'vr-ex-flex__item',
+            form.exhibitionName === item.name && 'active',
+          ]"
+          :style="{ backgroundImage: `url(${item.bg})` }"
+          @click="handleExhibition(item)"
+        >
+          <span>{{ item.name }}</span>
+        </div>
+      </div>
+    </el-form-item>
+    <el-form-item v-if="venues.length" label="选择场馆" prop="venues">
+      <div class="vr-flex exhibition">
+        <div
+          v-for="str in venues"
+          :key="str"
+          :class="['vr-flex__item', str === form.venues && 'active']"
+          @click="form.venues = str"
+        >
+          {{ str }}
+        </div>
+      </div>
+    </el-form-item>
+    <template v-if="form.exhibitionName">
+      <el-form-item label="预约日期" prop="bookDay">
+        <div class="vr-flex">
+          <div
+            v-for="item in dateList"
+            :key="item.value"
+            :class="[
+              'vr-flex__item',
+              item.value === form.bookDay && 'active',
+              (item.disabled ||
+                !hasTimeAreaWeeks.includes(item.week + '') ||
+                exhibitionConfig?.unableDate.includes(item.value)) &&
+                'disabled',
+            ]"
+            @click="handleDate(item)"
+          >
+            {{ item.label }}
+          </div>
+        </div>
+        <p v-if="exhibitionConfig?.bookDesc" class="bookDay-tips">
+          {{ exhibitionConfig?.bookDesc }}
+        </p>
+      </el-form-item>
+      <el-form-item
+        v-if="timeAreaList.length"
+        label="入馆时段"
+        prop="bootTimeScope"
+      >
+        <template v-if="maxVisitorNum > 0">
+          <div class="vr-flex" style="width: 100%">
+            <div
+              v-for="str in timeAreaList"
+              :key="str"
+              :class="['vr-flex__item', str === form.bootTimeScope && 'active']"
+              @click="form.bootTimeScope = str"
+            >
+              {{ str }}
+            </div>
+          </div>
+          <p v-if="maxVisitorNum < 5" class="bookDay-tips">
+            剩余可预约人数:{{ maxVisitorNum }}
+          </p>
+        </template>
+        <p v-else class="form-error-tips">该日期已被约满,请选择其他日期</p>
+      </el-form-item>
+
+      <el-form-item v-if="form.bookDay && maxVisitorNum > 0" label="参观人员">
+        <div class="vr-visitor">
+          <template v-if="maxVisitorNum > 0">
+            <div
+              v-for="(item, index) in visitorList"
+              :key="item.id"
+              class="vr-visitor__item"
+            >
+              <div class="vr-input">
+                <el-input
+                  v-model="item.name"
+                  placeholder="请输入姓名"
+                  @click="handleFocus"
+                />
+              </div>
+              <div class="vr-input">
+                <el-input
+                  v-model="item.phone"
+                  placeholder="请输入手机号"
+                  type="number"
+                  @click="handleFocus"
+                />
+              </div>
+              <div class="vr-input">
+                <el-input
+                  v-model="item.idcard"
+                  placeholder="请输入身份证号"
+                  @click="handleFocus"
+                />
+              </div>
+
+              <div
+                v-if="item.id !== 0"
+                class="vr-visitor__del"
+                @click="() => visitorList.splice(index, 1)"
+              />
+            </div>
+          </template>
+
+          <p
+            v-if="visitorList.length === maxVisitorNum || maxVisitorNum === 0"
+            class="form-error-tips"
+          >
+            已达最大可预约人数
+          </p>
+          <div
+            v-if="visitorList.length < maxVisitorNum"
+            class="vr-visitor__add"
+            @click="addVisitor"
+          />
+        </div>
+      </el-form-item>
+    </template>
+
+    <div
+      v-if="loading"
+      v-loading="true"
+      style="
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        z-index: 9999;
+      "
+    />
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { FormInstance, FormRules } from "element-plus";
+import { computed, onMounted, reactive, ref } from "vue";
+import NJImg from "./imgs/img_nanjing.jpg";
+import DYHImg from "./imgs/img_yangzhou.jpg";
+import { getExhibitionConfigApi, getVenueNumApi } from "@/api";
+import { GetExhibitionConfigApiResponse } from "@/api/types";
+import { checkIdCard, checkTelephone, formatDate } from "@/utils";
+import { OpenVirtualKeyBoard } from "@/utils/open-keyboard";
+// import SZImg from "./imgs/img_suzhou.jpg";
+
+type DateType = {
+  label: string;
+  value: string;
+  week: number;
+  disabled: boolean;
+};
+
+let visitorId = 0;
+const DEFAULT_FORM = {
+  venues: "",
+  bookDay: "",
+  bootTimeScope: "",
+  exhibitionName: "",
+};
+
+const loading = ref(false);
+const dateList = ref<DateType[]>([]);
+const formRef = ref<FormInstance>();
+const form = reactive({ ...DEFAULT_FORM });
+const maxVisitorNum = ref(0);
+const rules = reactive<FormRules>({
+  exhibitionName: [
+    { required: true, message: "请选择参观展馆", trigger: "blur" },
+  ],
+  venues: [{ required: true, message: "请选择场馆", trigger: "blur" }],
+  bookDay: [{ required: true, message: "请选择预约日期", trigger: "blur" }],
+  bootTimeScope: [
+    { required: true, message: "请选择预约时段", trigger: "blur" },
+  ],
+});
+const curExhibitionId = ref(0);
+const exhibitionConfig = ref<null | GetExhibitionConfigApiResponse>(null);
+const venues = computed(() => {
+  const str = window.museum[curExhibitionId.value];
+  return str ? str.split(",") : [];
+});
+const timeAreaList = computed(() => {
+  const date = dateList.value.find((i) => i.value === form.bookDay);
+  return exhibitionConfig.value && date
+    ? exhibitionConfig.value?.date
+        .filter((i) => i.weeks.split(",").includes(date.week + ""))
+        .map((i) => i.timeScope)
+    : [];
+});
+const hasTimeAreaWeeks = computed(() =>
+  Array.from(
+    new Set(
+      exhibitionConfig.value
+        ? exhibitionConfig.value.date.map((i) => i.weeks.split(",")).flat()
+        : []
+    )
+  )
+);
+const visitorList = reactive<
+  {
+    id: number;
+    name: string;
+    idcard: string;
+    phone: string;
+  }[]
+>([
+  {
+    id: visitorId++,
+    name: "",
+    idcard: "",
+    phone: "",
+  },
+]);
+
+const MUSEUM_LIST = [
+  {
+    id: 118,
+    name: "南京博物院",
+    bg: NJImg,
+  },
+  {
+    id: 114,
+    name: "扬州中国大运河博物馆",
+    bg: DYHImg,
+  },
+  // {
+  //   id: 3,
+  //   name: "苏州博物馆",
+  //   bg: SZImg,
+  // },
+];
+
+onMounted(() => {
+  initDate();
+});
+
+const next = async () => {
+  if (!formRef.value || !(await formRef.value.validate())) return;
+};
+
+const initDate = () => {
+  const stack: DateType[] = [];
+  const today = new Date();
+  const endDay = new Date();
+  let isTomorro = true;
+  endDay.setDate(today.getDate() + 7);
+
+  if (!endDay) return;
+
+  while (today.getTime() < endDay.getTime()) {
+    today.setDate(today.getDate() + 1);
+    const day = formatDate(today);
+    const week = today.getDay();
+    stack.push({
+      value: day,
+      label: `${isTomorro ? "明天" : ""}${formatDate(today, "MM月DD日")}`,
+      disabled: false,
+      week,
+    });
+    isTomorro = false;
+  }
+  dateList.value = stack;
+};
+
+const getVenueNum = async () => {
+  try {
+    loading.value = true;
+    const { data } = await getVenueNumApi({
+      bookDay: form.bookDay,
+      exhibitionId: curExhibitionId.value,
+      unitId: window.companyId,
+    });
+    // 需要算上报名人
+    maxVisitorNum.value = data.pcs > 10 ? 10 : data.pcs;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const getExhibitionConfig = async () => {
+  try {
+    loading.value = true;
+    const { data } = await getExhibitionConfigApi(curExhibitionId.value);
+    exhibitionConfig.value = data;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleExhibition = (item: (typeof MUSEUM_LIST)[0]) => {
+  if (item.id === curExhibitionId.value) return;
+
+  form.exhibitionName = item.name;
+  form.bookDay = "";
+  form.bootTimeScope = "";
+  maxVisitorNum.value = 0;
+  curExhibitionId.value = item.id;
+  getExhibitionConfig();
+};
+
+const handleFocus = () => {
+  OpenVirtualKeyBoard();
+};
+
+const handleDate = (item: DateType) => {
+  if (item.value === form.bookDay) return;
+
+  if (
+    !item.disabled &&
+    hasTimeAreaWeeks.value.includes(item.week + "") &&
+    !exhibitionConfig.value?.unableDate.includes(item.value)
+  ) {
+    form.bookDay = item.value;
+    form.bootTimeScope = "";
+
+    getVenueNum();
+  }
+};
+
+const addVisitor = () => {
+  visitorList.push({
+    id: ++visitorId,
+    name: "",
+    idcard: "",
+    phone: "",
+  });
+};
+
+const submit = async () => {
+  if (!formRef.value || !(await formRef.value.validate())) return;
+
+  if (visitorList.length > maxVisitorNum.value) {
+    return Promise.reject({ msg: "超过最大可预约人数" });
+  }
+
+  for (let i = 0; i < visitorList.length; i++) {
+    let errMsg = "";
+    const item = visitorList[i];
+
+    if (!item.name) {
+      errMsg = `第${i + 1}个参观人员姓名`;
+    } else if (!checkTelephone(item.phone)) {
+      errMsg = `第${i + 1}个参观人员手机号`;
+    } else if (!checkIdCard(item.idcard)) {
+      errMsg = `第${i + 1}个参观人员身份证号`;
+    }
+
+    if (errMsg) {
+      return Promise.reject({ msg: errMsg });
+    }
+  }
+
+  const params = {
+    ...form,
+    exhibitionName: `${form.exhibitionName}${
+      form.venues ? "-" + form.venues : ""
+    }`,
+    unitId: window.companyId,
+    exhibitionId: curExhibitionId.value,
+    visitorList: maxVisitorNum.value === 0 ? [] : visitorList,
+  };
+
+  return params;
+};
+
+defineExpose({
+  next,
+  submit,
+  maxVisitorNum,
+});
+</script>

TEMPAT SAMPAH
src/views/venue-reservation/imgs/btn_active.png


TEMPAT SAMPAH
src/views/venue-reservation/imgs/btn_cancel.png


TEMPAT SAMPAH
src/views/venue-reservation/imgs/btn_default.png


TEMPAT SAMPAH
src/views/venue-reservation/imgs/btn_submit@2x.png


TEMPAT SAMPAH
src/views/venue-reservation/imgs/img_circle.png


TEMPAT SAMPAH
src/views/venue-reservation/imgs/pop2.png


TEMPAT SAMPAH
src/views/venue-reservation/imgs/tab.png


+ 142 - 8
src/views/venue-reservation/index.scss

@@ -73,7 +73,6 @@
     .vr-visitor {
       display: flex;
       flex-direction: column;
-      align-items: center;
       position: relative;
       width: 100%;
 
@@ -83,12 +82,22 @@
         justify-content: space-between;
         width: 100%;
 
+        &.no-id-card {
+          justify-content: flex-start;
+
+          > div:first-child {
+            margin-right: 0.25rem /* 20/80 */;
+          }
+        }
         &:not(:first-child) {
           margin-top: 0.075rem /* 6/80 */;
         }
+
+        & + .vr-visitor__add {
+          margin-top: 0.1875rem /* 15/80 */;
+        }
       }
       &__add {
-        margin-top: 0.1875rem /* 15/80 */;
         width: 1.4625rem /* 117/80 */;
         height: 0.4375rem /* 35/80 */;
         cursor: pointer;
@@ -224,12 +233,6 @@
     font-size: 0.1875rem /* 15/80 */;
   }
 
-  .vr-submit {
-    margin: 0 auto 0.5rem /* 40/80 */;
-    width: 0.85rem /* 68/80 */;
-    height: 0.85rem /* 68/80 */;
-    background: url("./imgs/btn_submit@2x.png") no-repeat center / 100%;
-  }
   .el-form-item__label:before {
     display: none;
   }
@@ -237,4 +240,135 @@
     padding-top: 0.0625rem /* 5/80 */;
     font-size: 0.15rem /* 12/80 */;
   }
+
+  &__btns {
+    margin-top: 0.5625rem /* 45/80 */;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-weight: bold;
+    color: white;
+    font-size: 0.1875rem /* 15/80 */;
+
+    > div:not(:last-child) {
+      margin-right: 0.25rem /* 20/80 */;
+    }
+    &__next,
+    &__pre {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    &__next {
+      width: 1.70625rem /* 136.5/80 */;
+      height: 0.6625rem /* 53/80 */;
+      background: url("./imgs/btn_active.png") no-repeat center / 100% 100%;
+
+      &.disabled {
+        color: #858585 !important;
+        background-image: url("./imgs/btn_default.png");
+      }
+    }
+    &__pre {
+      width: 1.70625rem /* 136.5/80 */;
+      height: 0.6625rem /* 53/80 */;
+      color: #5c594b;
+      background: url("./imgs/btn_cancel.png") no-repeat center / 100% 100%;
+    }
+  }
+
+  .ticket {
+    margin: 0.625rem /* 50/80 */ auto 0;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    gap: 0.25rem /* 20/80 */;
+    padding: 0 1.1rem /* 88/80 */;
+    width: 11.58125rem /* 926.5/80 */;
+    height: 2.64375rem /* 211.5/80 */;
+    color: #fff3c9;
+    font-size: 0.25rem /* 20/80 */;
+    font-weight: bold;
+    box-sizing: border-box;
+    background: url("./imgs/tab.png") no-repeat center / cover;
+
+    p {
+      position: relative;
+    }
+    p:first-child {
+      margin-top: 0.5625rem /* 45/80 */;
+    }
+    p::before {
+      content: "";
+      position: absolute;
+      top: 50%;
+      left: -0.5rem /* 40/80 */;
+      width: 0.25rem /* 20/80 */;
+      height: 0.25rem /* 20/80 */;
+      transform: translateY(-50%);
+      background: url("./imgs/img_circle.png") no-repeat center / cover;
+    }
+    & + .venue-reservation-form {
+      margin-top: 0.475rem /* 38/80 */;
+    }
+  }
+}
+
+.bookDay-tips,
+.form-error-tips {
+  font-weight: 400;
+  font-size: 0.1875rem /* 15/80 */;
+}
+.bookDay-tips {
+  color: #5c594b;
+}
+.form-error-tips {
+  color: #d9574a;
+}
+
+.el-dialog.check-dialog {
+  width: 8.8375rem /* 707/80 */;
+  height: 6.09375rem /* 487.5/80 */;
+  background-image: url("./imgs/pop2.png");
+
+  .ven-popup__close {
+    display: none;
+  }
+  .el-dialog__body {
+    text-shadow: none;
+    text-align: left;
+    font-weight: bold;
+    padding: 0.375rem /* 30/80 */ 1.75rem /* 140/80 */;
+  }
+  .title {
+    margin-bottom: 0.125rem /* 10/80 */;
+    color: #fffae9;
+    text-align: center;
+    font-size: 0.375rem /* 30/80 */;
+    text-shadow: 0px 0px 8px #ffb525;
+  }
+  .venue-reservation__btns {
+    margin-top: 0.375rem /* 30/80 */;
+  }
+  .check-dialog__info {
+    overflow-y: auto;
+    height: 2.5rem /* 200/80 */;
+
+    &::-webkit-scrollbar {
+      width: 2px;
+    }
+    &::-webkit-scrollbar-track {
+      background-color: #eee;
+    }
+    &::-webkit-scrollbar-thumb {
+      background-color: #999;
+    }
+    &::-webkit-scrollbar-thumb:hover {
+      background-color: #666;
+    }
+    p {
+      font-size: 0.25rem /* 20/80 */;
+      color: #5c594b;
+    }
+  }
 }

+ 118 - 357
src/views/venue-reservation/index.vue

@@ -3,390 +3,153 @@
     <BackBtn />
 
     <div class="venue-reservation-container">
-      <div class="venue-reservation__title">场馆预约</div>
-
-      <el-form
-        ref="formRef"
-        :rules="rules"
-        :model="form"
-        class="venue-reservation-form"
-      >
-        <el-form-item label="参观展馆">
-          <div class="vr-ex-flex">
-            <div
-              v-for="item in MUSEUM_LIST"
-              :key="item.id"
-              :class="[
-                'vr-ex-flex__item',
-                form.exhibitionName === item.name && 'active',
-              ]"
-              :style="{ backgroundImage: `url(${item.bg})` }"
-              @click="handleExhibition(item)"
-            >
-              <span>{{ item.name }}</span>
-            </div>
-          </div>
-        </el-form-item>
-        <el-form-item v-if="venues.length" label="选择场馆">
-          <div class="vr-flex exhibition">
-            <div
-              v-for="str in venues"
-              :key="str"
-              :class="['vr-flex__item', str === form.venues && 'active']"
-              @click="form.venues = str"
-            >
-              {{ str }}
-            </div>
-          </div>
-        </el-form-item>
-        <el-form-item label="所在单位" prop="organ">
-          <div class="vr-input">
-            <el-input v-model="form.organ" readonly placeholder="请输入内容" />
-          </div>
-        </el-form-item>
-        <el-form-item label="参观人员">
-          <div class="vr-visitor">
-            <div
-              v-for="(item, index) in visitorList"
-              :key="item.id"
-              class="vr-visitor__item"
-            >
-              <div class="vr-input">
-                <el-input
-                  v-model="item.name"
-                  placeholder="请输入姓名"
-                  @click="handleFocus"
-                />
-              </div>
-              <div class="vr-input">
-                <el-input
-                  v-model="item.phone"
-                  placeholder="请输入手机号"
-                  type="number"
-                  @click="handleFocus"
-                />
-              </div>
-              <div class="vr-input">
-                <el-input
-                  v-model="item.idcard"
-                  placeholder="请输入身份证号"
-                  @click="handleFocus"
-                />
-              </div>
-
-              <div
-                v-if="item.id !== 0"
-                class="vr-visitor__del"
-                @click="() => visitorList.splice(index, 1)"
-              />
-            </div>
-
-            <div
-              v-if="visitorList.length < 5"
-              class="vr-visitor__add"
-              @click="addVisitor"
-            />
-          </div>
-        </el-form-item>
-        <el-form-item label="预约时间">
-          <div class="vr-flex">
-            <div
-              v-for="item in dateList"
-              :key="item.value"
-              :class="[
-                'vr-flex__item',
-                item.value === form.bookDay && 'active',
-                item.disabled && 'disabled',
-              ]"
-              @click="!item.disabled && (form.bookDay = item.value)"
-            >
-              {{ item.label }}
-            </div>
-
-            <!-- <div
-              :class="['vr-flex__item', bookDay === form.bookDay && 'active']"
-            >
-              <SvgIcon name="icon_date" />
-              <el-date-picker
-                v-model="bookDay"
-                format="MM月DD日"
-                placeholder="其他日期"
-                value-format="YYYY-MM-DD"
-                :clearable="false"
-                :disabled-date="disabledDate"
-                @focus="bookDay && (form.bookDay = bookDay)"
-                @change="form.bookDay = bookDay"
-              />
-            </div> -->
+      <div class="venue-reservation__title">
+        {{ step === 0 ? "身份核验" : "场馆预约" }}
+      </div>
+
+      <div v-if="step === 1 && form1" class="ticket">
+        <p>{{ form1.organ }} - {{ form1.division }}</p>
+        <p>{{ form1.leader.name }} - {{ form1.leader.phone }}</p>
+      </div>
+
+      <Form1 v-if="step === 0" ref="form1Ref" :form-data="form1" />
+      <Form2 v-else-if="step === 1" ref="form2Ref" :form1-data="form1" />
+
+      <div class="venue-reservation__btns">
+        <div
+          v-if="step === 0"
+          class="venue-reservation__btns__next"
+          @click="next"
+        >
+          下一步
+        </div>
+        <template v-else>
+          <div class="venue-reservation__btns__pre" @click="previous">
+            上一步
           </div>
-        </el-form-item>
-        <el-form-item label="预约时段">
-          <div class="vr-flex">
-            <div
-              v-for="str in timeAreaList"
-              :key="str"
-              :class="['vr-flex__item', str === form.bootTimeScope && 'active']"
-              @click="form.bootTimeScope = str"
-            >
-              {{ str }}
-            </div>
+          <div
+            :class="[
+              'venue-reservation__btns__next',
+              form2Ref?.maxVisitorNum === 0 && 'disabled',
+            ]"
+            @click="next"
+            style="color: #fff3c9"
+          >
+            提交
           </div>
-        </el-form-item>
-        <el-form-item label="备注">
-          <div class="vr-input textarea">
-            <el-input
-              v-model="form.description"
-              resize="none"
-              :rows="3"
-              :maxlength="200"
-              type="textarea"
-              placeholder="请输入备注"
-              @click="handleFocus"
-            />
-          </div>
-        </el-form-item>
-      </el-form>
-
-      <div class="vr-submit" @click="submit" />
+        </template>
+      </div>
     </div>
 
     <Dialog v-model:visible="dialogVisible">
       <p v-if="dialogErrType">请准确填写</p>
-      <p>{{ dialogText }}</p>
+      <p v-html="dialogText"></p>
+    </Dialog>
+
+    <Dialog v-if="form1" v-model:visible="checkVisible" class="check-dialog">
+      <p class="title">请确认预约信息</p>
+      <div class="check-dialog__info">
+        <p>{{ form1.organ }} - {{ form1.division }}</p>
+        <p v-for="(item, index) in visitList" :key="index">
+          {{ item.name }} - {{ item.phone }} - {{ item.idcard }}
+        </p>
+      </div>
+
+      <div class="venue-reservation__btns">
+        <div class="venue-reservation__btns__pre" @click="checkVisible = false">
+          取消
+        </div>
+        <div
+          class="venue-reservation__btns__next"
+          style="color: #fff3c9"
+          @click="submit2"
+        >
+          确认
+        </div>
+      </div>
     </Dialog>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { getVenueNumApi, reservationVenueApi } from "@/api";
 import { BackBtn } from "@/components";
-import { formatDate } from "@/utils";
 import Dialog from "./components/dialog.vue";
-import { FormInstance, FormRules } from "element-plus";
-import { computed, onActivated, onMounted, reactive, ref, watch } from "vue";
-import NJImg from "./imgs/img_nanjing.jpg";
-import DYHImg from "./imgs/img_yangzhou.jpg";
-// import SZImg from "./imgs/img_suzhou.jpg";
-import limitJson from "/public/limit.json";
+import { onActivated, ref } from "vue";
 import "./index.scss";
-import { OpenVirtualKeyBoard } from "@/utils/open-keyboard";
-
-type DateType = {
-  label: string;
-  value: string;
-  week: number;
-  disabled: boolean;
-};
-
-let visitorId = 0;
-const curExhibitionId = ref(0);
+import Form1 from "./form-1.vue";
+import Form2 from "./form-2.vue";
+import { reservationVenueApi } from "@/api";
+import { DEFAULT_FORM1 } from "./constants";
+import encodeStr from "@/utils/pass";
+import { cloneDeep } from "lodash";
+
+let saveParams: any = null;
+const step = ref(0);
+const checkVisible = ref(false);
 const dialogVisible = ref(false);
 const dialogText = ref("");
 const dialogErrType = ref(false);
-const venues = computed(() => {
-  const str = window.museum[curExhibitionId.value];
-  return str ? str.split(",") : [];
-});
-const timeAreaList = computed(
-  () => window.MUSEUM_LIST_TIME[curExhibitionId.value]
-);
-const DEFAULT_FORM = {
-  bookDay: "",
-  bootTimeScope: window.MUSEUM_LIST_TIME[1][0],
-  description: "",
-  organ: window.company,
-  venues: "",
-  exhibitionName: "",
-};
-const DEFAULT_VISITOR = {
-  id: visitorId,
-  name: "",
-  idcard: "",
-  phone: "",
-};
-const MUSEUM_LIST = [
-  {
-    id: 1,
-    name: "南京博物院",
-    bg: NJImg,
-  },
-  {
-    id: 2,
-    name: "扬州中国大运河博物馆",
-    bg: DYHImg,
-  },
-  // {
-  //   id: 3,
-  //   name: "苏州博物馆",
-  //   bg: SZImg,
-  // },
-];
-const visitorList = reactive<
-  {
-    id: number;
-    name: string;
-    idcard: string;
-    phone: string;
-  }[]
->([
-  {
-    ...DEFAULT_VISITOR,
-  },
-]);
-const dateList = ref<DateType[]>([]);
-const bookDay = ref("");
-const form = reactive({ ...DEFAULT_FORM });
-const rules = reactive<FormRules>({
-  organ: [{ required: true, message: "请填写单位", trigger: "blur" }],
-});
 const loading = ref(false);
-const formRef = ref<FormInstance>();
-
-onMounted(() => {
-  initDate();
-});
+const form1 = ref<typeof DEFAULT_FORM1 | null>(null);
+const visitList = ref<(typeof DEFAULT_FORM1)["leader"][]>([]);
+const form1Ref = ref();
+const form2Ref = ref();
 
 onActivated(() => {
   clear();
 });
 
-watch(venues, (list) => {
-  form.venues = list[0];
-});
-
-const handleFocus = () => {
-  OpenVirtualKeyBoard();
-};
-
-const clear = () => {
-  Object.assign(form, {
-    ...DEFAULT_FORM,
-    bookDay:
-      dateList.value[0].week !== 1
-        ? dateList.value[0].value
-        : dateList.value[1].value,
-    venues: venues.value[0],
-    exhibitionName: MUSEUM_LIST[0].name,
-  });
-  bookDay.value = "";
-  visitorList.length = 0;
-  visitorList.push({ ...DEFAULT_VISITOR });
-  curExhibitionId.value = MUSEUM_LIST[0].id;
-};
-
-// const disabledDate = (time: Date) => {
-//   const today = new Date();
-//   today.setDate(today.getDate() + 4);
-//   return time.getTime() < today.getTime();
-// };
-
-const addVisitor = () => {
-  visitorList.push({
-    id: ++visitorId,
-    name: "",
-    idcard: "",
-    phone: "",
-  });
-};
-
-const initDate = () => {
-  const stack: DateType[] = [];
-  const today = new Date();
-  const endDay = new Date();
-  let isTomorro = true;
-  endDay.setDate(today.getDate() + 6);
-
-  if (!endDay) return;
-
-  while (today.getTime() < endDay.getTime()) {
-    today.setDate(today.getDate() + 1);
-    const day = formatDate(today);
-    const week = today.getDay();
-    stack.push({
-      value: day,
-      label: `${isTomorro ? "明天" : ""}${formatDate(today, "MM月DD日")}`,
-      disabled: week === 1,
-      week,
-    });
-    isTomorro = false;
-  }
-  dateList.value = stack;
-};
-
-function checkTelephone(telephone: string) {
-  var reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
-  return reg.test(telephone);
-}
-
-function checkIdCard(idCard: string) {
-  var reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
-  return reg.test(idCard);
-}
-
-const submit = async () => {
-  if (!form.bookDay && !bookDay.value) {
-    dialogErrType.value = false;
-    dialogText.value = "请选择预约时间";
-    dialogVisible.value = true;
-    return;
-  }
-
-  if (!formRef.value || !(await formRef.value.validate())) return;
-
-  for (let i = 0; i < visitorList.length; i++) {
-    let errMsg = "";
-    const item = visitorList[i];
-
-    if (!item.name) {
-      errMsg = `第${i + 1}个参观人员姓名`;
-    } else if (!checkTelephone(item.phone)) {
-      errMsg = `第${i + 1}个参观人员手机号`;
-    } else if (!checkIdCard(item.idcard)) {
-      errMsg = `第${i + 1}个参观人员身份证号`;
+const next = async () => {
+  try {
+    if (step.value === 0) {
+      const params = await form1Ref.value.next();
+      form1.value = params;
+      step.value += 1;
+    } else if (form2Ref.value?.maxVisitorNum > 0) {
+      const { visitorList, ...form2Rest } = await form2Ref.value.submit();
+      const { leader, ...form1Rest } = form1.value || {};
+      const list = [leader, ...visitorList];
+      visitList.value = visitorList;
+
+      saveParams = {
+        ...form1Rest,
+        ...form2Rest,
+        contact: list
+          .map(
+            (item: any) =>
+              `${item.name}-${item.phone}${
+                item.idcard ? "-" + encodeStr(item.idcard) : ""
+              }`
+          )
+          .join(","),
+        pcs: visitorList.length,
+      };
+
+      checkVisible.value = true;
     }
-
-    if (errMsg) {
+  } catch (err: any) {
+    if (typeof err === "object" && err.msg) {
       dialogErrType.value = true;
-      dialogText.value = errMsg;
+      dialogText.value = err.msg;
       dialogVisible.value = true;
-      return;
-    } else {
-      dialogErrType.value = false;
     }
   }
+};
 
-  const params = {
-    ...form,
-    exhibitionName: `${form.exhibitionName}${
-      form.venues ? "-" + form.venues : ""
-    }`,
-    contact: visitorList
-      .map((item) => `${item.name}-${item.phone}-${item.idcard}`)
-      .join(","),
-    pcs: visitorList.length,
-  };
+const clear = () => {
+  step.value = 0;
+  saveParams = null;
+  form1.value = cloneDeep(DEFAULT_FORM1);
+  visitList.value = [];
+};
 
-  loading.value = true;
+const submit2 = async () => {
   try {
-    const { data } = await getVenueNumApi(params);
-
-    console.log(
-      (data ? data.pcs : 0) + visitorList.length,
-      limitJson[params.organ][params.exhibitionName][params.bookDay]
-    );
-
-    if (
-      (data ? data.pcs : 0) + visitorList.length >
-      limitJson[params.organ][params.exhibitionName][params.bookDay]
-    ) {
-      dialogText.value = "已超出可预约人数上限";
-      dialogVisible.value = true;
-      return;
-    }
-
-    await reservationVenueApi(params);
-
-    dialogText.value = "团队预约已接收!";
+    loading.value = true;
+    checkVisible.value = false;
+    await reservationVenueApi(saveParams);
+    dialogErrType.value = false;
+    dialogText.value = "预约已被接受<br/>请按照预约时间刷身份证原件入馆";
     dialogVisible.value = true;
 
     clear();
@@ -398,9 +161,7 @@ const submit = async () => {
   }
 };
 
-const handleExhibition = (item: (typeof MUSEUM_LIST)[0]) => {
-  form.exhibitionName = item.name;
-  curExhibitionId.value = item.id;
-  form.bootTimeScope = timeAreaList.value[0];
+const previous = () => {
+  step.value -= 1;
 };
 </script>

+ 9 - 2
src/views/ver-scroll-home/index.vue

@@ -81,8 +81,15 @@ const menuList = computed(() => [
   {
     icon: MuseumNormalImg,
     activeIcon: MuseumActiveImg,
-    event() {
-      router.push({ name: "cloudMuseum", params: { id: 34 } });
+    async event() {
+      try {
+        loading.value = true;
+        const online = await isOnlineFn();
+        store.commit("setIsOnline", online);
+        router.push({ name: "cloudMuseum", params: { id: 34 } });
+      } finally {
+        loading.value = false;
+      }
     },
   },
   {