2023年最新尚硅谷的尚医通项目(上)

2,325 阅读11分钟

image.png 尚医通是一个网上预约挂号系统,包含后台管理系统和前台用户系统,采用前后端分离开发模式。

尚医通是采用Vue3全家桶、TypeScript、Vite、Pinia、Element-plus等技术栈开发的在线医疗服务平台,集成了多家医院的挂号信息,提供全程跟踪服务,用户可以随时了解自己的挂号状态。

前端整体功能,已经学习完了,功能均已实现,在这里记录一下技术重点.....

gitee尚医通 gitee.com/wujingde/sh…

一、接口

服务器地址:syt.atguigu.cn

医院接口:http://139.198.34.216:8201/swagger-ui.html

公共数据接口: http://139.198.34.216:8202/swagger-ui.html

会员接口:http://139.198.34.216:8203/swagger-ui.html

短信验证码接口:http://139.198.34.216:8204/swagger-ui.html

订单接口:http://139.198.34.216:8206/swagger-ui.html

文件上传接口:http://139.198.34.216:8205/swagger-ui.html

后台用户接口:http://139.198.34.216:8212/swagger-ui.html

二、项目其他配置

2.1 浏览器自动打开 找到 package.json 配置文件!

"scripts": {
"dev": "vite --open",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},

2.2src 别名的配置

找到 vite.config.ts 配置文件。 如果红色语法提示请安装@types/node 是 TypeScript 的一个声明文件包,用于描述 Node.js 核心模块和常用的第三方库的类型信息

image.png 安装这份

image.png

image.png

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
}
})

找到 tsconfig.json 配置文件,找到配置项 compilerOptions 添加配置,这一步的作用是让 IDE 可以对路径进行智能提示

"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}

//微信开放平台官网地址 open.weixin.qq.com

//查看微信扫码登录文档 mp.weixin.qq.com/

项目当中全部路由:、 用户未登录可以访问的路由 /home /hospital/register /hospital/detail /hospital/notice /hospital/close / hospital/search 剩下其余的路由未登录不能访问的,如果用户登录了全部的路由都是可以访问的

引入清除默认样式

image.png

image.png

image.png

三、顶部、底部静态页面搭建

页面展示

image.png App.vue

<template>
  <div class="container">
    <!-- 顶部全局组件 -->
    <HospitalTop />
    <!-- 展示路由组件的区域 -->
    <div class="content">展示路由组件的区域</div>
    <!-- 底部全局组件 -->
    <HospitalBottom />
  </div>
</template>

<script setup lang="ts"></script>

<style scoped lang="scss">
.container {
  display: flex;
  /* 切换主轴方向 */
  flex-direction: column;
  /* justify-content: center; */
  align-items: center;
  .content {
    margin-top: 70px;
    width: 1200px;
    min-height: 700px;
  }
}
</style>

image.png

hospital_top.vue

<template>
  <div class="top">
    <div class="content">
      <!-- 左侧 -->
      <div class="left">
        <img src="../../assets/images/logo.png" alt="" />
        <p>尚医通 预约挂号统一平台</p>
      </div>
      <!-- 右侧 -->
      <div class="right">
        <div class="help">帮助中心</div>
        <div class="login">登录/注册</div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts"></script>

<style scoped lang="scss">
.top {
  /* 固定到顶部 */
  position: fixed;
  /* 层级 */
  z-index: 999;
  width: 100%;
  height: 70px;
  background: #fff;
  display: flex;
  /* 主轴上居中 */
  justify-content: center;
  .content {
    width: 1200px;
    height: 70px;
    background: white;
    display: flex;
    justify-content: space-between;
    .left {
      display: flex;
      /* 主轴侧轴都居中 */
      justify-content: center;
      align-items: center;
      img {
        width: 50px;
        height: 50px;
        margin-right: 10px;
      }
      p {
        font-size: 20px;
        color: #55a6fe;
      }
    }
    .right {
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 14px;
      color: #bbb;
      .help {
        margin-right: 10px;
      }
    }
  }
}
</style>

hospital_bottom.vue

<template>
  <div class="bottom">
    <div class="contont">
      <div class="left">蜀ICP备2021007923号</div>
      <div class="right">
        <span> 联系我们 </span>
        <span>合作伙伴</span>
        <span>隐私协议</span>
        <span>用户协议</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts"></script>

<style scoped lang="scss">
.bottom {
  width: 100%;
  height: 50px;
  background-color: #f0f2f5;
  display: flex;
  justify-content: center;
  .contont {
    width: 1200px;
    height: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 14px;
    .right {
      span {
        margin: 0px 5px;
      }
    }
  }
}
</style>

四、路由配置

1.1目录

image.png

1.2路由index文件

import { createRouter, createWebHistory } from "vue-router";
// createApp方法,用于创建路由器实例,可以管理多个路由
export default createRouter({
  // 路由模式的设置
  history: createWebHistory(),
  // 管理路由
  routes: [
    {
      path: "/home",
      //   路由懒加载模式
      component: () => import("@/pages/home/index.vue"),
    },
    {
      path: "/hospital",
      component: () => import("@/pages/hospital/index.vue"),
    },
    {
      path: "/",
      //   路由重定向到home
      redirect: "/home",
    },
  ],
  // 滚动行为:控制滚动条的位置
  scrollBehavior() {
    return {
      left: 0,
      top: 0,
    };
  },
});

image.png

image.png

1.3 路由出口

image.png

五、首页静态搭建banner

element-plus 的使用

image.png 搜索组件 image.png 效果图

image.png

六、首页等级与地区组件拆分与静态搭建

image.png level/index.vue

<template>
  <div class="level">
    <h1>医院</h1>
    <div class="content">
      <div class="left">等级:</div>
      <ul class="hospital">
        <li class="active">全部</li>
        <li>三级甲等</li>
        <li>三级甲等</li>
        <li>三级甲等</li>
        <li>三级甲等</li>
        <li>三级甲等</li>
        <li>三级甲等</li>
        <li>三级甲等</li>
      </ul>
    </div>
  </div>
</template>

<script setup lang="ts"></script>

<style scoped lang="scss">
.level {
  color: #7f7f7f;
  h1 {
    font-weight: 900;
    margin: 10px 0px;
  }
  .content {
    display: flex;
    .left {
      margin-right: 10px;
    }
    .hospital {
      display: flex;
      li {
        margin-right: 10px;
        &.active {
          color: #55a6fe;
        }
      }
      /* 鼠标进经过时变色 */
      li:hover {
        color: #55a6fe;
        /* 鼠标经过时出现小手 */
        cursor: pointer;
      }
    }
  }
}
</style>

region/index.vue

<template>
  <div class="region">
    <div class="content">
      <div class="left">地区:</div>
      <ul>
        <li class="active">全部</li>
        <li>昌平区</li>
        <li>昌平区</li>
        <li>昌平区</li>
        <li v-for="item in 20" :key="item">昌平区</li>
      </ul>
    </div>
  </div>
</template>

<script setup lang="ts"></script>

<style scoped lang="scss">
.region {
  color: #7f7f7f;
  margin-top: 10px;
  .content {
    display: flex;
    .left {
      margin-right: 10px;
      width: 50px;
    }
    ul {
      display: flex;
      flex-wrap: wrap;
      li {
        margin-right: 10px;
        margin-bottom: 10px;
        &.active {
          color: #55a6fe;
        }
      }
      li:hover {
        color: #55a6fe;
        /* 鼠标经过时出现小手 */
        cursor: pointer;
      }
    }
  }
}
</style>

七、分页搭建

image.png

八、网络axios二次封装

utils/request.ts

// 对于axios 函数库进行二次封装
// 你工作的时候是否axios进行二次封装?二次封装的目的是什么?
// 目的1:利用axios请求、响应拦截器功能
// 目的2:请求拦截器,一般可以在请求头中携带公共的参数:token
// 目的3:响应拦截器,可以简化服务器返回的数据,处理http网络错误
import axios from "axios";
import { ElMessage } from "element-plus";

// 利用axios.create方法创建一个axios实例:可以设置基础路径、超时的时间的设置
const request = axios.create({
  baseURL: "/api", //请求的基础路径的设置
  timeout: 5000, //超时时间的设置,超出5秒请求就失败
});

// 请求拦截器
request.interceptors.request.use((config) => {
  // config :请求拦截器回调注入的对象(配置对象),p配置对象的身上最重要的一件事情headers属性
  //   k可以通过请求头携带公共参数-token
  return config;
});
// 响应拦截器
request.interceptors.response.use(
  (response) => {
    // 响应拦截器成功地回调,一般会进行简化
    return response;
  },
  (error) => {
    // 处理http网络错误
    let status = error.response.status;
    switch (status) {
      case 404:
        // 错误提示信息
        ElMessage({
          type: "error",
          //   message: error.message,
          message: "请求失败路径,出现问题",
        });
        break;
      case 401:
        // 错误提示信息
        ElMessage({
          type: "error",
          //   message: error.message,
          message: "参数有误",
        });
        break;
      case 500 | 501 | 502 | 503 | 504 | 505:
        // 错误提示信息
        ElMessage({
          type: "error",
          //   message: error.message,
          message: "服务器挂了",
        });
        break;
    }
    return Promise.reject(new Error(error.message));
  }
);

// 务必对外暴露axios
export default request;


九、配置代理跨域

image.png

十、分页及获取数据的实现

10.1 获取数据接口配置
// 统一管理首页模块接口
import request from "@/utils/request";
// 通过枚举管理首页模块的接口地址
enum API {
  // 获取已有的医院的是数据接口地址
  HOSPITAL_URL = "/hosp/hospital/",
}
// 获取医院的数据
export const reqHospital = (page: number, limit: number) =>
  request.get(API.HOSPITAL_URL + `${page}/${limit}`);

image.png

10.2 获取首页数据

image.png

image.png

10. 分页功能

image.png

image.png

十一、等级和区域展示

11.1等级及高亮展示
接口类型

image.png

image.png

image.png

11.2获取等级数据

image.png

image.png

image.png

image.png

11.3获取医院地区数据

image.png

image.png

高亮显示

image.png

image.png

11.4首页根据等级与地区筛选医院展示

给组件绑定事件

image.png

image.png

image.png

image.png

image.png image.png

image.png 暂无数据

image.png

十二、首页根据关键字搜索医院及路由简单跳转

接口ts类型

image.png

数据接口

image.png

绑定事件

image.png

image.png 当用户搜索关键字时,会出现推荐数据项

image.png

image.png 因为官方文档中写的是需要返回value值,所以就弄了一个showData函数,让其返回一个对象(对象的话就会有吧value值)

image.png

路由跳转三步走

image.png

十三、首页常见科室搭建

image.png

image.png

image.png

十四、医院详情菜单与子路由

image.png

建五个文件

image.png

路由菜单

image.png

点击这些组件就会进行跳转

image.png

image.png

点击搜索框的推荐数据,也会进行跳转到详情页

image.png

image.png

十五、Pinia仓库存储医院详情数据

pnpm i pinia@2.1.3(注意不要安装最新版,否则会有冲突报错)

image.png

image.png

拿数据->取数据->存数据

image.png

十六、预约挂号业务1

image.png

image.png

image.png

base64码转图片

data:image/jpeg;base64,

 <img :src="`data:image/jpeg;base64,`+hospitalStore.hospitalInfo.hospital?.logoData" alt=""/>

16.2、医院详情业务

image.png

<template>
  <div class="detail">
    <!-- 医院名字与等级 -->
    <div class="top">
      <div class="name">{{ hospitalStore.hospitalInfo.hospital?.hosname }}</div>
      <div class="level">
        <svg
          t="1688813652495"
          class="icon"
          viewBox="0 0 1024 1024"
          version="1.1"
          xmlns="http://www.w3.org/2000/svg"
          p-id="9729"
          width="16"
          height="16"
        >
          <path
            d="M939.358251 423.424662c-23.01825-37.252439-62.924121-60.779272-107.019409-63.209624-2.755764-0.38681-5.510504-0.579191-8.347109-0.579191l-152.202471-0.121773c6.649444-28.975938 10.015098-58.653865 10.015098-88.657202 0-28.223808-3.213181-57.139372-9.556657-85.952604-0.447185-2.043542-1.098008-4.006244-1.932002-5.866614-15.820314-57.302077-67.37755-96.841605-127.282918-96.841605-72.827679 0-132.081201 59.254545-132.081201 132.081201 0 3.334955 0.132006 6.66991 0.406253 10.015098-2.196015 57.211003-32.108279 109.947088-80.269162 141.363611-14.447037 9.42465-18.524912 28.773324-9.099239 43.220361 9.414417 14.437827 28.752858 18.535145 43.220361 9.099239 65.811892-42.925648 106.429984-115.325585 108.656699-193.684234 0.030699-1.332345-0.010233-2.663666-0.14224-3.996011-0.203638-2.012843-0.304945-4.016477-0.304945-6.019087 0-38.381146 31.233352-69.614497 69.614497-69.614497 32.57593 0 60.474326 22.204721 67.824735 53.997821 0.356111 1.534959 0.823761 3.019777 1.402953 4.453429 4.696975 22.814612 7.076162 45.579081 7.076162 67.743894 0 37.485753-6.222725 74.352405-18.494213 109.592001-3.324722 9.546424-1.819438 20.111037 4.02671 28.345582 5.856381 8.245801 15.332197 13.146415 25.448602 13.156648l193.226816 0.101307c1.423419 0.264013 2.857071 0.426719 4.300956 0.477884 24.116257 0.9967 45.935192 13.614066 58.603723 34.090423 7.838525 12.31242 11.438517 26.800389 10.431583 41.939181-0.080841 0.945535-0.121773 1.911536-0.11154 2.877537 0 0.854461 0.040932 1.697665 0.11154 2.53166 0.010233 0.335644-0.030699 0.661056-0.11154 0.976234-0.101307 0.376577-0.193405 0.772596-0.284479 1.159406l-74.972529 330.391802c-0.914836 1.281179-1.738597 2.6432-2.449795 4.046153-5.937223 11.762905-14.660908 21.48329-25.346271 28.172643-10.746762 6.812149-23.059182 10.614755-35.757388 11.06194-0.854461-0.061398-513.766226-0.224104-513.766226-0.224104-0.467651-0.020466-0.935302-0.030699-1.402953-0.030699 0 0-111.01542 0.172939-112.718201 0.457418-1.932002 0-3.446495-1.50426-3.446495-3.415796l0.299829-416.334173c0-1.901303 1.545192-3.446495 3.01466-3.456728l1.245364 0.121773c1.174756 0.132006 2.653433 0.284479 3.52836 0.193405l83.82822-0.222057 0 339.367221c0 17.253966 13.979386 31.233352 31.233352 31.233352s31.233352-13.979386 31.233352-31.233352L281.009092 435.350273c0-1.778506 0-8.631588 0-10.411117 0-17.253966-13.979386-30.928407-31.233352-30.928407-1.50426 0-117.547183 0.304945-119.402437 0.304945-36.34272 0-65.913199 29.566386-65.913199 65.893756l-0.299829 416.334173c0 36.337603 29.571503 65.902966 65.913199 65.902966 2.541893 0 111.406323-0.457418 111.406323-0.457418 0.457418 0.020466 0.925069 0.030699 1.382487 0.030699 0 0 511.458671 0.274246 512.505513 0.274246 25.469068 0 50.296523-7.197936 71.647807-20.73116 19.612687-12.281721 35.777855-29.881564 46.839795-50.967812 3.660366-5.622044 6.435573-11.875468 8.256034-18.615986 0.11154-0.416486 0.213871-0.823761 0.304945-1.240247l74.881454-330.340637c1.596358-6.212492 2.257413-12.586666 2.00261-18.992563C960.892707 473.366098 953.948551 446.331371 939.358251 423.424662z"
            fill="#d81e06"
            p-id="9730"
          ></path>
          <path
            d="M450.027553 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C481.260905 532.629853 467.281519 518.650467 450.027553 518.650467z"
            fill="#d81e06"
            p-id="9731"
          ></path>
          <path
            d="M693.805696 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C725.039048 532.629853 711.058638 518.650467 693.805696 518.650467z"
            fill="#d81e06"
            p-id="9732"
          ></path>
          <path
            d="M648.940882 660.09492c-14.304797-9.393951-33.592073-5.398964-43.159986 8.763594-0.132006 0.193405-13.614066 19.754926-34.171264 19.754926-19.98824 0-32.423457-18.09717-33.266661-19.368116-9.17087-14.427594-28.254507-18.809391-42.834574-9.770528-14.650675 9.109472-19.155269 28.366048-10.055007 43.016723 11.214413 18.047028 41.96988 48.588625 86.156242 48.588625 43.962258 0 75.104535-30.318516 86.572728-48.222281C667.404396 688.461991 663.216004 669.500127 648.940882 660.09492z"
            fill="#d81e06"
            p-id="9733"
          ></path>
        </svg>
        <span>{{
          hospitalStore.hospitalInfo.hospital?.param.hostypeString
        }}</span>
      </div>
    </div>
    <!-- 医院logo与路线 -->
    <div class="logo">
      <div class="left">
        <img
          :src="
            `data:image/jpeg;base64,` +
            hospitalStore.hospitalInfo.hospital?.logoData
          "
          alt=""
        />
      </div>
      <div class="right">
        <div class="address">
          <svg
            t="1688828004474"
            class="icon"
            viewBox="0 0 1024 1024"
            version="1.1"
            xmlns="http://www.w3.org/2000/svg"
            p-id="10870"
            width="16"
            height="16"
          >
            <path
              d="M512 512a136.533333 136.533333 0 1 1 136.533333-136.533333 136.533333 136.533333 0 0 1-136.533333 136.533333z m0-219.272533a81.92 81.92 0 1 0 81.92 81.92 81.92 81.92 0 0 0-81.92-81.92z"
              fill="#0073FF"
              p-id="10871"
            ></path>
            <path
              d="M512 831.214933a27.306667 27.306667 0 0 1-19.2512-8.055466l-214.493867-214.357334a330.5472 330.5472 0 1 1 467.490134 0l-214.357334 214.357334a27.306667 27.306667 0 0 1-19.387733 8.055466z m0-732.091733a275.933867 275.933867 0 0 0-195.106133 471.04L512 765.269333l195.106133-195.106133A275.933867 275.933867 0 0 0 512 99.1232z"
              fill="#0073FF"
              p-id="10872"
            ></path>
            <path
              d="M514.321067 979.490133c-147.456 0-306.107733-37.000533-306.107734-118.3744 0-45.602133 51.746133-81.92 145.681067-102.4a27.306667 27.306667 0 1 1 11.605333 53.384534c-78.370133 17.066667-102.673067 41.915733-102.673066 49.015466 0 18.432 88.064 63.761067 251.4944 63.761067s251.4944-45.192533 251.4944-63.761067c0-7.3728-25.258667-32.768-106.496-49.834666a27.306667 27.306667 0 1 1 11.195733-53.384534c96.6656 20.343467 150.186667 56.9344 150.186667 103.2192-0.273067 80.964267-158.9248 118.3744-306.3808 118.3744z"
              fill="#0073FF"
              p-id="10873"
            ></path>
          </svg>
          <span
            >具体位置:{{
              hospitalStore.hospitalInfo.hospital?.param?.fullAddress
            }}</span
          >
        </div>
        <div class="route">
          规划路线:{{ hospitalStore.hospitalInfo.hospital?.route }}
        </div>
      </div>
    </div>
    <!-- 医院的介绍 -->
    <div class="footer">
      <h1>医院介绍</h1>
      <p>
        {{ hospitalStore.hospitalInfo.hospital?.intro }}
      </p>
    </div>
  </div>
</template>

<script setup lang="ts">
// 获取医院详情仓库数据进行展示
import useDetailStore from "@/store/modules/hospitalDetail";
let hospitalStore = useDetailStore();
</script>

<style scoped lang="scss">
.detail {
  .top {
    display: flex;
    align-items: center;
    .name {
      font-size: 30px;
      font-weight: 900;
    }
    .level {
      color: #7f7f7f;
      margin-left: 10px;
      span {
        margin-left: 5px;
      }
    }
  }
  .logo {
    display: flex;
    margin: 10px 0px;
    .left {
      img {
        width: 80px;
        height: 80px;
        border-radius: 50px;
      }
    }
    .right {
      color: #7f7f7f;
      margin-left: 10px;
      div {
        margin: 10px 0px;
      }
    }
  }
  .footer {
    color: #7f7f7f;
    p {
      margin-top: 10px;
      line-height: 30px;
    }
  }
}
</style>

16.3 小bug

第一次进去本来是能够显示的,而且带有编号,但是但切换菜单时,编号就消失了

image.png

image.png

16.4做科室时遇见的错误

image.png

image.png 错误原因:在箭头后面加对象符号{},但是却没有return 回去,导致打印出来的result是undefine。

解决办法:1、不加{},因为箭头函数如果只有一行表达式,可以不加括号,箭头函数自带return。 2、 或者在箭头后面加{return xxxxx},不要忘记return,不然会出现错误

image.png

16.5、医院科室业务

效果

image.png

左侧导航栏及高亮效果

image.png image.png

image.png

右侧信息及点击哪个导航就定位到对应的科室

image.png

image.png

完整代码:

<template>
  <div class="register">
    <div class="top">
      <div class="title">
        {{ hospitalStore.hospitalInfo.hospital?.hosname }}
      </div>
      <div class="level">
        <svg
          t="1688813652495"
          class="icon"
          viewBox="0 0 1024 1024"
          version="1.1"
          xmlns="http://www.w3.org/2000/svg"
          p-id="9729"
          width="16"
          height="16"
        >
          <path
            d="M939.358251 423.424662c-23.01825-37.252439-62.924121-60.779272-107.019409-63.209624-2.755764-0.38681-5.510504-0.579191-8.347109-0.579191l-152.202471-0.121773c6.649444-28.975938 10.015098-58.653865 10.015098-88.657202 0-28.223808-3.213181-57.139372-9.556657-85.952604-0.447185-2.043542-1.098008-4.006244-1.932002-5.866614-15.820314-57.302077-67.37755-96.841605-127.282918-96.841605-72.827679 0-132.081201 59.254545-132.081201 132.081201 0 3.334955 0.132006 6.66991 0.406253 10.015098-2.196015 57.211003-32.108279 109.947088-80.269162 141.363611-14.447037 9.42465-18.524912 28.773324-9.099239 43.220361 9.414417 14.437827 28.752858 18.535145 43.220361 9.099239 65.811892-42.925648 106.429984-115.325585 108.656699-193.684234 0.030699-1.332345-0.010233-2.663666-0.14224-3.996011-0.203638-2.012843-0.304945-4.016477-0.304945-6.019087 0-38.381146 31.233352-69.614497 69.614497-69.614497 32.57593 0 60.474326 22.204721 67.824735 53.997821 0.356111 1.534959 0.823761 3.019777 1.402953 4.453429 4.696975 22.814612 7.076162 45.579081 7.076162 67.743894 0 37.485753-6.222725 74.352405-18.494213 109.592001-3.324722 9.546424-1.819438 20.111037 4.02671 28.345582 5.856381 8.245801 15.332197 13.146415 25.448602 13.156648l193.226816 0.101307c1.423419 0.264013 2.857071 0.426719 4.300956 0.477884 24.116257 0.9967 45.935192 13.614066 58.603723 34.090423 7.838525 12.31242 11.438517 26.800389 10.431583 41.939181-0.080841 0.945535-0.121773 1.911536-0.11154 2.877537 0 0.854461 0.040932 1.697665 0.11154 2.53166 0.010233 0.335644-0.030699 0.661056-0.11154 0.976234-0.101307 0.376577-0.193405 0.772596-0.284479 1.159406l-74.972529 330.391802c-0.914836 1.281179-1.738597 2.6432-2.449795 4.046153-5.937223 11.762905-14.660908 21.48329-25.346271 28.172643-10.746762 6.812149-23.059182 10.614755-35.757388 11.06194-0.854461-0.061398-513.766226-0.224104-513.766226-0.224104-0.467651-0.020466-0.935302-0.030699-1.402953-0.030699 0 0-111.01542 0.172939-112.718201 0.457418-1.932002 0-3.446495-1.50426-3.446495-3.415796l0.299829-416.334173c0-1.901303 1.545192-3.446495 3.01466-3.456728l1.245364 0.121773c1.174756 0.132006 2.653433 0.284479 3.52836 0.193405l83.82822-0.222057 0 339.367221c0 17.253966 13.979386 31.233352 31.233352 31.233352s31.233352-13.979386 31.233352-31.233352L281.009092 435.350273c0-1.778506 0-8.631588 0-10.411117 0-17.253966-13.979386-30.928407-31.233352-30.928407-1.50426 0-117.547183 0.304945-119.402437 0.304945-36.34272 0-65.913199 29.566386-65.913199 65.893756l-0.299829 416.334173c0 36.337603 29.571503 65.902966 65.913199 65.902966 2.541893 0 111.406323-0.457418 111.406323-0.457418 0.457418 0.020466 0.925069 0.030699 1.382487 0.030699 0 0 511.458671 0.274246 512.505513 0.274246 25.469068 0 50.296523-7.197936 71.647807-20.73116 19.612687-12.281721 35.777855-29.881564 46.839795-50.967812 3.660366-5.622044 6.435573-11.875468 8.256034-18.615986 0.11154-0.416486 0.213871-0.823761 0.304945-1.240247l74.881454-330.340637c1.596358-6.212492 2.257413-12.586666 2.00261-18.992563C960.892707 473.366098 953.948551 446.331371 939.358251 423.424662z"
            fill="#d81e06"
            p-id="9730"
          ></path>
          <path
            d="M450.027553 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C481.260905 532.629853 467.281519 518.650467 450.027553 518.650467z"
            fill="#d81e06"
            p-id="9731"
          ></path>
          <path
            d="M693.805696 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C725.039048 532.629853 711.058638 518.650467 693.805696 518.650467z"
            fill="#d81e06"
            p-id="9732"
          ></path>
          <path
            d="M648.940882 660.09492c-14.304797-9.393951-33.592073-5.398964-43.159986 8.763594-0.132006 0.193405-13.614066 19.754926-34.171264 19.754926-19.98824 0-32.423457-18.09717-33.266661-19.368116-9.17087-14.427594-28.254507-18.809391-42.834574-9.770528-14.650675 9.109472-19.155269 28.366048-10.055007 43.016723 11.214413 18.047028 41.96988 48.588625 86.156242 48.588625 43.962258 0 75.104535-30.318516 86.572728-48.222281C667.404396 688.461991 663.216004 669.500127 648.940882 660.09492z"
            fill="#d81e06"
            p-id="9733"
          ></path>
        </svg>
        <span>{{
          hospitalStore.hospitalInfo.hospital?.param.hostypeString
        }}</span>
      </div>
    </div>
    <!-- 展示内容的区域 -->
    <div class="content">
      <div class="left">
        <img
          :src="
            `data:image/jpeg;base64,` +
            hospitalStore.hospitalInfo.hospital?.logoData
          "
          alt=""
        />
      </div>
      <div class="right">
        <div>挂号规则</div>
        <div class="time">
          预约周期:10天 放号时间:{{
            hospitalStore.hospitalInfo.bookingRule?.releaseTime
          }}
          停挂时间:{{ hospitalStore.hospitalInfo.bookingRule?.stopTime }}
        </div>
        <div class="address">
          具体位置:{{ hospitalStore.hospitalInfo.hospital?.param.fullAddress }}
        </div>
        <div class="route">
          规划路线:{{ hospitalStore.hospitalInfo.hospital?.route }}
        </div>
        <div class="releasetime">
          退号时间:就诊前一工作日{{
            hospitalStore.hospitalInfo.bookingRule?.quitTime
          }}前取消
        </div>
        <div class="rule">预约挂号规则</div>
        <div
          class="ruleInfo"
          v-for="(item, index) in hospitalStore.hospitalInfo.bookingRule?.rule"
          :key="index"
        >
          {{ item }}
        </div>
      </div>
    </div>
    <!-- 放置每一个医院的科室数据 -->
    <div class="deparment">
      <div class="leftNav">
        <ul>
          <li
            v-for="(deparment, index) in hospitalStore.deparmentArr"
            :class="{ active: index == currentIndex }"
            @click="changeIndex(index)"
            :key="deparment.depcode"
          >
            {{ deparment.depname }}
          </li>
        </ul>
      </div>
      <div class="deparmentInfo">
        <!-- 用一个div代表:大科室与小科室 -->
        <div
          class="showDeparment"
          v-for="deparment in hospitalStore.deparmentArr"
          :key="deparment.depcode"
        >
          <h1 class="cur">{{ deparment.depname }}</h1>
          <!-- 每一个大的科室下小科室 -->
          <ul>
            <li v-for="item in deparment.children" :key="item.depcode">
              {{ item.depname }}
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
// 引入医院详情仓库的数据
import useDetailStore from "@/store/modules/hospitalDetail";
let hospitalStore = useDetailStore();
// 控制科室高亮的响应式数据
let currentIndex = ref<number>(0);
// 左侧大的科室点击的事件
const changeIndex = (index: number) => {
  currentIndex.value = index;
  // 点击导航获取右侧科室(大的科室好标题)
  let allH1 = document.querySelectorAll(".cur");
  // console.log(allH1);
  // 滚动到对应科室的位置
  allH1[currentIndex.value].scrollIntoView({
    behavior: "smooth",//过渡动画效果
    block:'start',//滚动到的位置,默认是起始位置
  });
};
</script>

<style scoped lang="scss">
.register {
  .top {
    display: flex;
    .title {
      font-size: 30px;
      font-weight: 900;
    }
    .level {
      color: #7f7f7f;
      margin-left: 10px;
      /* 文字居中 */
      height: 40px;
      text-align: center;
      line-height: 40px;
      span {
        margin-left: 5px;
      }
    }
  }
  .content {
    display: flex;
    margin-top: 20px;
    .left {
      width: 80px;
      img {
        width: 80px;
        height: 80px;
        boder-radius: 50%;
      }
    }
    .right {
      /* 左边占了80px,剩下的空间给右边 */
      flex: 1;
      font-size: 14px;
      margin-left: 20px;
      .time,
      .route,
      .address,
      .releasetime,
      .ruleInfo {
        margin-top: 10px;
        color: #7f7f7f;
      }
      .rule {
        margin: 10px 0;
      }
    }
  }
  .deparment {
    width: 100%;
    height: 500px;
    display: flex;
    margin-top: 20px;
    /* background: pink; */
    .leftNav {
      width: 80px;
      height: 100%;
      ul {
        width: 100%;
        height: 100%;
        background: rgb(248, 248, 248);
        display: flex;
        flex-direction: column;
        li {
          flex: 1;
          text-align: center;
          color: #7f7f7f;
          font-size: 14px;
          line-height: 30px;
          /* 高亮效果 */
          &.active {
            border-left: 1px solid red;
            color: red;
            background: white;
          }
        }
      }
    }
    .deparmentInfo {
      /* 除了 leftNav占了80px,其他都是deparmentInfo*/
      flex: 1;
      margin-left: 20px;
      height: 100%;
      overflow: auto;
      /* 清除滚动条 */
      &::-webkit-scrollbar {
        display: none;
      }
      .showDeparment {
        h1 {
          background: rgb(248, 248, 248);
          color: #7f7f7f;
        }
        ul {
          display: flex;
          flex-wrap: wrap;
          li {
            width: 33%;
            color: #7f7f7f;
            line-height: 30px;
          }
        }
      }
    }
  }
}
</style>

16.6、登录静态搭建

注册为全局组件main.ts

image.png

image.png

image.png

完整代码

<template>
  <div class="login-container">
    <!-- title:对话框左上角的标题  v-model :控制对话框的显示与隐藏 -->
    <el-dialog v-model="userStore.visiable" title="用户登录">
      <!-- 对话框身体部分结构 -->
      <div class="content">
        <el-row>
          <!-- 左侧结构:收集号码登录、微信扫一扫登录 -->
          <el-col :span="12">
            <div class="login">
              <div v-show="scene == 0">
                <el-form>
                  <el-form-item>
                    <el-input
                      placeholder="请你输入手机号码"
                      :prefix-icon="User"
                    ></el-input>
                  </el-form-item>
                  <el-form-item>
                    <el-input
                      placeholder="请你输入手机验证码"
                      :prefix-icon="Lock"
                    ></el-input>
                  </el-form-item>
                  <el-form-item>
                    <el-button>获取验证码</el-button>
                  </el-form-item>
                </el-form>
                <el-button type="primary" style="width: 100%"
                  >用户登陆</el-button
                >
                <div class="bottom" @click="changeScene">
                  <p>微信扫码登录</p>
                  <svg
                    t="1688921974790"
                    class="icon"
                    viewBox="0 0 1024 1024"
                    version="1.1"
                    xmlns="http://www.w3.org/2000/svg"
                    p-id="15061"
                    width="32"
                    height="32"
                  >
                    <path
                      d="M1023.162182 641.303273c0-143.243636-143.243636-260.910545-306.944-260.910546-168.820364 0-306.944 117.666909-306.944 260.910546s133.003636 260.910545 306.944 260.910545c35.816727 0 71.633455-10.24 107.426909-20.456727l97.210182 56.273454-25.576728-92.090181c71.633455-56.273455 127.883636-127.906909 127.883637-204.637091z m-404.130909-46.056728c-20.456727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727-0.023273 15.36-20.48 35.816727-46.056727 35.816727z m194.397091 0c-15.336727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727 0 15.36-15.36 35.816727-46.056727 35.816727z m0 0"
                      fill="#04AE0F"
                      p-id="15062"
                    ></path>
                    <path
                      d="M358.097455 104.122182c-199.517091 0-358.120727 133.026909-358.120728 306.967273 0 97.210182 56.273455 179.060364 143.243637 240.453818L107.426909 758.970182l127.906909-61.393455c46.033455 10.24 81.850182 20.456727 127.906909 20.456728h35.816728c-5.12-25.576727-10.216727-51.153455-10.216728-76.730182 0-158.580364 133.003636-286.510545 306.944-286.510546h35.816728C690.641455 211.549091 537.157818 104.122182 358.097455 104.122182z m-112.54691 240.453818c-25.576727 0-56.273455-15.36-56.273454-46.056727 0-25.6 25.6-46.056727 56.273454-46.056728 25.576727 0 46.056727 15.36 46.056728 46.056728-5.12 30.696727-20.456727 46.056727-46.056728 46.056727z m250.693819 0c-25.576727 0-56.273455-15.36-56.273455-46.056727 0-25.6 25.576727-46.056727 56.273455-46.056728 25.576727 0 46.056727 15.36 46.056727 46.056728-0.023273 30.696727-20.48 46.056727-46.056727 46.056727z m0 0"
                      fill="#04AE0F"
                      p-id="15063"
                    ></path>
                  </svg>
                </div>
              </div>
              <div class="webchat" v-show="scene == 1">微信扫码登录</div>
            </div>
          </el-col>
          <el-col :span="12">
            <div class="leftContent">
              <div class="top">
                <div class="item">
                  <img src="../../assets/images/code_app.png" alt="" />
                  <svg
                    t="1688921974790"
                    class="icon"
                    viewBox="0 0 1024 1024"
                    version="1.1"
                    xmlns="http://www.w3.org/2000/svg"
                    p-id="15061"
                    width="16"
                    height="16"
                  >
                    <path
                      d="M1023.162182 641.303273c0-143.243636-143.243636-260.910545-306.944-260.910546-168.820364 0-306.944 117.666909-306.944 260.910546s133.003636 260.910545 306.944 260.910545c35.816727 0 71.633455-10.24 107.426909-20.456727l97.210182 56.273454-25.576728-92.090181c71.633455-56.273455 127.883636-127.906909 127.883637-204.637091z m-404.130909-46.056728c-20.456727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727-0.023273 15.36-20.48 35.816727-46.056727 35.816727z m194.397091 0c-15.336727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727 0 15.36-15.36 35.816727-46.056727 35.816727z m0 0"
                      fill="#515151"
                      p-id="15062"
                    ></path>
                    <path
                      d="M358.097455 104.122182c-199.517091 0-358.120727 133.026909-358.120728 306.967273 0 97.210182 56.273455 179.060364 143.243637 240.453818L107.426909 758.970182l127.906909-61.393455c46.033455 10.24 81.850182 20.456727 127.906909 20.456728h35.816728c-5.12-25.576727-10.216727-51.153455-10.216728-76.730182 0-158.580364 133.003636-286.510545 306.944-286.510546h35.816728C690.641455 211.549091 537.157818 104.122182 358.097455 104.122182z m-112.54691 240.453818c-25.576727 0-56.273455-15.36-56.273454-46.056727 0-25.6 25.6-46.056727 56.273454-46.056728 25.576727 0 46.056727 15.36 46.056728 46.056728-5.12 30.696727-20.456727 46.056727-46.056728 46.056727z m250.693819 0c-25.576727 0-56.273455-15.36-56.273455-46.056727 0-25.6 25.576727-46.056727 56.273455-46.056728 25.576727 0 46.056727 15.36 46.056727 46.056728-0.023273 30.696727-20.48 46.056727-46.056727 46.056727z m0 0"
                      fill="#515151"
                      p-id="15063"
                    ></path>
                  </svg>
                  <p>微信扫一扫关注</p>
                  <p>“快速预约挂号”</p>
                </div>
                <div class="item">
                  <img src="../../assets/images/code_login_wechat.png" alt="" />
                  <svg
                    t="1688923491762"
                    class="icon"
                    viewBox="0 0 1024 1024"
                    version="1.1"
                    xmlns="http://www.w3.org/2000/svg"
                    p-id="16931"
                    width="16"
                    height="16"
                  >
                    <path
                      d="M766.003899 974.097466H282.947368a47.906433 47.906433 0 0 1-47.906432-47.906433V83.836257a47.906433 47.906433 0 0 1 47.906432-47.906432h483.056531a47.906433 47.906433 0 0 1 47.906432 47.906432v842.354776a47.906433 47.906433 0 0 1-47.906432 47.906433z m-241.528265-57.88694a61.879142 61.879142 0 1 0-61.879143-61.879142 61.879142 61.879142 0 0 0 61.879143 61.879142z m202.604288-791.454191H321.871345v598.83041h405.208577V124.756335z"
                      fill="#E61D15"
                      p-id="16932"
                    ></path>
                  </svg>
                  <p>扫一扫下载</p>
                  <p>“预约挂号”APP</p>
                </div>
              </div>
            </div>
            <p class="tip">尚医通官方指定平台</p>
            <p class="tip">快速挂号 安全放心</p>
          </el-col>
        </el-row>
      </div>
      <template #footer>
        <el-button type="primary" size="default">关闭窗口</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { User, Lock } from "@element-plus/icons-vue";
// 获取user仓库的数据visiable,可以控制login组件的对话框显示与隐藏
// import useUserStore from '@/store/mod'
import useUserStore from "@/store/modules/user";
import { ref } from "vue";
let userStore = useUserStore();
let scene = ref<number>(0); //0代表手机号码登录,1代表微信扫码登录

// 点击微信扫码登录|微信小图标切换为微信扫码登录
const changeScene = () => {
  scene.value = 1;
};
</script>

<script lang="ts">
// 用vue2写个别名,方便vue插件寻找数据
export default {
  name: "Login",
};
</script>

<style scoped lang="scss">
.login-container {
  /* 要修改组件内部的样式,就要用深度选择器 */
  :deep(.el-dialog__body) {
    border-top: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
  }
  .login {
    padding: 20px;
    border: 1px solid #ccc;
  }
  .bottom {
    display: flex;
    flex-direction: column;
    align-items: center;
    p {
      margin: 10px 0px;
    }
  }
  .leftContent {
    .top {
      display: flex;
      justify-content: space-around;
      .item {
        display: flex;
        flex-direction: column;
        align-items: center;
        img {
          width: 130px;
          height: 130px;
        }
        p {
          margin: 5px 0px;
        }
      }
    }
  }
  .tip {
    text-align: center;
    margin: 20px 0px;
    font-size: 20px;
    font-weight: 900;
  }
}
</style>

16.7、登录组件获取验证码

接口设置

image.png 输入框验证规则

image.png image.png

image.png

获取验证码的方法

image.png 放在仓库中

image.png

16.8获取验证码倒计时业务

将倒计时组件进行封装

image.png

用v-if v-else 判断展示哪个组件,当flag为真时,倒计时组件开启,否则关闭。

还有就是当你输入的手机号码不符合规定时或者当开启倒计时后,获取验证码的按钮将会被禁用

image.png

image.png

image.png

十七、登录业务功能

登录相关的ts类型

image.png

登录接口

image.png

登录请求 image.png

image.png

登录逻辑

发送登录请求

登录请求成功:顶部组件需要展示用户名字/对话框关闭

登录失败:弹出对应登录失败的错误信息

image.png

image.png

17.1、普通表单校验规则(一般不用)

image.png

image.png

17.2、自定义表单校验

image.png

image.png

image.png

image.png

image.png

17.3、关闭对话框时表单清空

image.png

image.png

方法二: 当对话框关闭时,直接销毁Login组件

APP.vuew image.png

17.4、退出登录

image.png

image.png

清除本地存储和仓库存储的数据

image.png

image.png

17.5、微信扫码登录之二维码嵌套在login内部

image.png

image.png

image.png

image.png

image.png

17.6 微信扫码登录

image.png

image.png

image.png

image.png

image.png

image.png

十八、预约挂号

18.1 小错误

image.png

错误原因:workData刚开始有可能是空对象,所以{}.hosname 肯定报错undefined

image.png image.png

修改: 加个?变成可选参数 image.png

18.1 页面展示

点击某个科室进入

image.png image.png

image.png

image.png

image.png

接口:

image.png

image.png

接口类型

image.png

image.png

渲染页面

image.png

切换分页获取数据 image.png

完整代码

<template>
  <div class="wrap">
    <!-- 顶部结构 -->
    <div class="top">
      <div class="hosname">{{ workData.baseMap?.hosname }}</div>
      <div class="line">|</div>
      <div>{{ workData.baseMap?.bigname }}</div>
      <div class="dot">.</div>
      <div class="hosdeparment">{{ workData.baseMap?.depname }}</div>
    </div>
    <div class="center">
      <h1 class="time">{{ workData.baseMap?.workDateString }}</h1>
      <div class="container">
        <div
          class="item"
          :class="{ active: item.status == -1 || item.availableNumber == -1 }"
          v-for="item in workData.bookingScheduleList"
          :key="item"
        >
          <div class="top2">{{ item.workDate }}-{{ item.dayOfWeek }}</div>
          <div class="bottom">
            <div v-if="item.status == -1">停止挂号</div>
            <div v-if="item.status == 0">
              {{
                item.availableNumber == -1
                  ? `约满了`
                  : `有号(${item.availableNumber})`
              }}
            </div>
            <div v-if="item.status == 1">即将放号</div>
          </div>
        </div>
      </div>
      <el-pagination
        v-model:current-page="pageNo"
        layout="prev, pager, next"
        :total="workData.total"
        @current-change="fetchWorkData"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";

import { reqHospitalWork } from "@/api/hospital";

import type { HospitalWorkData } from "@/api/hospital/type";

import { useRoute } from "vue-router";
// 获取路由对象
let $route = useRoute();

// 分页器当前页码
let pageNo = ref<number>(1);
// 每一页展示几条数据
let limit = ref<number>(6);

// 存储医院科室挂号的数据
let workData = ref<any>({});

// 组件挂载完毕发一次请求
onMounted(() => {
  fetchWorkData();
});

// 获取挂号的数据
const fetchWorkData = async () => {
  let result: HospitalWorkData = await reqHospitalWork(
    pageNo.value,
    limit.value,
    $route.query.hoscode as string,
    $route.query.depcode as string
  );
  console.log(result);
  if (result.code == 200) {
    workData.value = result.data;
  }
};
</script>

<style scoped lang="scss">
.wrap {
  .top {
    display: flex;
    color: #7f7f7f;
    .line {
      width: 5px;
      height: 20px;
      background: skyblue;
      margin: 0px 5px;
    }
    .dot {
      margin: 0px 5px;
      color: skyblue;
    }
  }
  .center {
    margin: 20px 0px;
    display: flex;
    flex-direction: column;
    align-items: center;
    .time {
      font-weight: 900;
    }
    .container {
      width: 100%;
      display: flex;
      margin: 30px 0px;
      .item {
        flex: 1;
        width: 100%;
        border: 1px solid skyblue;
        margin: 0px 5px;
        &.active {
          border: 1px solid #cccc;
          color: #7f7f7f;
          .top2 {
            background: #ccc;
          }
        }

        .top2 {
          background: #e8f2ff;
          /* margin: 5px 0px; */
          width: 100%;
          height: 30px;
          text-align: center;
          line-height: 30px;
        }
        .bottom {
          width: 100%;
          height: 60px;
          text-align: center;
          line-height: 60px;
        }
      }
    }
  }
}
</style>

18.2 预约挂号底部医生排班业务

ts数据类型

image.png

image.png

获取医生信息的逻辑

image.png

展示数据

image.png

image.png

image.png

image.png image.png

完整代码:

<template>
  <div class="wrap">
    <!-- 顶部结构 -->
    <div class="top">
      <div class="hosname">{{ workData.baseMap?.hosname }}</div>
      <div class="line">|</div>
      <div>{{ workData.baseMap?.bigname }}</div>
      <div class="dot">.</div>
      <div class="hosdeparment">{{ workData.baseMap?.depname }}</div>
    </div>
    <!-- 中间展示日期的结构 -->
    <div class="center">
      <h1 class="time">{{ workData.baseMap?.workDateString }}</h1>
      <div class="container">
        <div
          class="item"
          :class="{
            active: item.status == -1 || item.availableNumber == -1,
            cur: item.workDate == workTime.workDate,
          }"
          v-for="item in workData.bookingScheduleList"
          :key="item"
          @click="changeTime(item)"
        >
          <div class="top2">{{ item.workDate }}-{{ item.dayOfWeek }}</div>
          <div class="bottom">
            <div v-if="item.status == -1">停止挂号</div>
            <div v-if="item.status == 0">
              {{
                item.availableNumber == -1
                  ? `约满了`
                  : `有号(${item.availableNumber})`
              }}
            </div>
            <div v-if="item.status == 1">即将放号</div>
          </div>
        </div>
      </div>
      <el-pagination
        v-model:current-page="pageNo"
        layout="prev, pager, next"
        :total="workData.total"
        @current-change="fetchWorkData"
      />
    </div>
    <!-- 底部展示医生的结构 -->
    <div class="bottom1">
      <!-- 展示即将放号的时间 -->
      <div class="will" v-if="workTime.status == 1">
        <span class="time">2023年{{ workTime.workDateMd }}</span>
        <span class="willtext">放号</span>
      </div>
      <!-- 展示医生的结构 :上午、下午-->
      <div class="doctor" v-else>
        <div class="morning">
          <!-- 顶部文字提示 -->
          <div class="tip1">
            <svg
              t="1689336807398"
              class="icon"
              viewBox="0 0 1024 1024"
              version="1.1"
              xmlns="http://www.w3.org/2000/svg"
              p-id="2545"
              width="32"
              height="32"
            >
              <path
                d="M304.5 691.8c0-145.3 117.8-263 263-263s263 117.8 263 263"
                fill="#FDDA09"
                p-id="2546"
              ></path>
              <path
                d="M567.5 428.8c-16.7 0-32.9 1.7-48.7 4.7 122 22.9 214.3 129.7 214.3 258.3h97.4c0.1-145.2-117.7-263-263-263z"
                fill="#FDA906"
                p-id="2547"
              ></path>
              <path
                d="M772.1 687.3c-8.3 0-15-6.7-15-15 0-66.2-25.8-128.5-72.6-175.4-46.8-46.8-109.1-72.6-175.4-72.6-66.2 0-128.5 25.8-175.4 72.6-46.8 46.8-72.6 109.1-72.6 175.4 0 8.3-6.7 15-15 15s-15-6.7-15-15c0-74.3 28.9-144.1 81.4-196.6 52.5-52.5 122.3-81.4 196.6-81.4s144.1 28.9 196.6 81.4c52.5 52.5 81.4 122.3 81.4 196.6 0 8.3-6.7 15-15 15z"
                fill=""
                p-id="2548"
              ></path>
              <path
                d="M914.1 704.5H120c-8.3 0-15-6.7-15-15s6.7-15 15-15h794.1c8.3 0 15 6.7 15 15s-6.7 15-15 15zM755.2 777.4H278.8c-8.3 0-15-6.7-15-15s6.7-15 15-15h476.5c8.3 0 15 6.7 15 15s-6.8 15-15.1 15zM605.6 858.1H428.5c-8.3 0-15-6.7-15-15s6.7-15 15-15h177.1c8.3 0 15 6.7 15 15s-6.8 15-15 15zM179.4 641h-63.5c-8.3 0-15-6.7-15-15s6.7-15 15-15h63.5c8.3 0 15 6.7 15 15s-6.7 15-15 15zM515.9 323.4c-8.3 0-15-6.7-15-15V181.3c0-8.3 6.7-15 15-15s15 6.7 15 15v127.1c0 8.3-6.7 15-15 15zM271 422.9c-3.8 0-7.7-1.5-10.6-4.4l-56.2-56.2c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l56.2 56.2c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.8 4.4-10.6 4.4zM754.8 422.9c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l56.2-56.2c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-56.2 56.2c-2.9 3-6.7 4.4-10.6 4.4z"
                fill=""
                p-id="2549"
              ></path>
              <path
                d="M441.2 544.2a18.8 17.8 0 1 0 37.6 0 18.8 17.8 0 1 0-37.6 0Z"
                fill="#050400"
                p-id="2550"
              ></path>
              <path
                d="M553.9 544.2a18.8 17.8 0 1 0 37.6 0 18.8 17.8 0 1 0-37.6 0Z"
                fill="#050400"
                p-id="2551"
              ></path>
              <path
                d="M491.3 577.5c0 13.1 11.2 23.7 25 23.7s25-10.6 25-23.7h-50z"
                fill="#050400"
                p-id="2552"
              ></path>
              <path
                d="M406.9 588.6a21.9 20.7 0 1 0 43.8 0 21.9 20.7 0 1 0-43.8 0Z"
                fill="#FDA906"
                p-id="2553"
              ></path>
              <path
                d="M582.1 588.6a21.9 20.7 0 1 0 43.8 0 21.9 20.7 0 1 0-43.8 0Z"
                fill="#FDA906"
                p-id="2554"
              ></path>
            </svg>
            <span class="text">上午号源</span>
          </div>
          <!-- 每一个医生的信息 -->
          <div class="doc_info" v-for="doctor in morningArr" :key="doctor.id">
            <!-- 展示医生的名字|技能 -->
            <div class="left">
              <div class="info">
                <span>{{ doctor.title }}</span>
                <span>|</span>
                <span>{{ doctor.docname }}</span>
              </div>
              <div class="skill">{{ doctor.skill }}</div>
            </div>
            <!-- 右侧展示挂号的钱数 -->
            <div class="right">
              <div class="money">¥{{ doctor.amount }}元</div>
              <el-button type="primary">{{ doctor.availableNumber }}</el-button>
            </div>
          </div>
        </div>
        <div class="morning">
          <!-- 顶部文字提示 -->
          <div class="tip1">
            <svg
              t="1689338507838"
              class="icon"
              viewBox="0 0 1024 1024"
              version="1.1"
              xmlns="http://www.w3.org/2000/svg"
              p-id="5411"
              width="32"
              height="32"
            >
              <path
                d="M369.86 594.04c45.34-78.5 145.73-105.38 224.23-60.04a164.171 164.171 0 0 1 60.04 60.04H369.86z m306.23 82.05z m-492.26 0h656.35c22.66 0 41.02 18.36 41.02 41.02 0 22.66-18.36 41.02-41.02 41.02H183.83c-22.66 0-41.02-18.36-41.02-41.02-0.01-22.66 18.36-41.02 41.02-41.02z"
                fill="#1296db"
                p-id="5412"
              ></path>
              <path
                d="M678.58 383.66l43.51-43.51c24.03-24.03 62.99-24.03 87.02 0 24.03 24.03 24.03 62.99 0 87.02l-43.51 43.51c-24.14 23.92-63.1 23.74-87.02-0.39-23.77-23.99-23.77-62.65 0-86.63zM512 204.34c33.98 0 61.53 27.55 61.53 61.53v61.53c-0.14 33.98-27.8 61.42-61.78 61.28-33.79-0.14-61.15-27.49-61.28-61.28v-61.53c0-33.98 27.55-61.53 61.53-61.53zM217.08 340.15c24.03-24.03 62.99-24.03 87.02 0l43.51 43.51c24.03 24.03 24.03 62.99 0 87.02-24.03 24.03-62.99 24.03-87.02 0l-43.51-43.51c-24.03-24.03-24.03-62.99 0-87.02z"
                fill="#1296db"
                opacity=".3"
                p-id="5413"
              ></path>
            </svg>
            <span class="text">下午号源</span>
          </div>
          <!-- 每一个医生的信息 -->
          <div class="doc_info" v-for="doctor in afterArr" :key="doctor.id">
            <!-- 展示医生的名字|技能 -->
            <div class="left">
              <div class="info">
                <span>{{ doctor.title }}</span>
                <span>|</span>
                <span>{{ doctor.docname }}</span>
              </div>
              <div class="skill">{{ doctor.skill }}</div>
            </div>
            <!-- 右侧展示挂号的钱数 -->
            <div class="right">
              <div class="money">¥{{ doctor.amount }}元</div>
              <el-button type="primary">{{ doctor.availableNumber }}</el-button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref, computed } from "vue";

import { reqHospitalWork, reqHospitalDoctor } from "@/api/hospital";

import type {
  HospitalWorkData,
  DoctorResponseData,
  DocArr,
  Doctor,
} from "@/api/hospital/type";

import { useRoute } from "vue-router";
// 获取路由对象
let $route = useRoute();

// 分页器当前页码
let pageNo = ref<number>(1);
// 每一页展示几条数据
let limit = ref<number>(6);

// 存储医院科室挂号的数据
let workData = ref<any>({});
// 存储当前排班日期:当前数据的第一个
let workTime: any = ref({});
// 存储排班医生的数据
let docArr = ref<DocArr>([]);

// 组件挂载完毕发一次请求
onMounted(() => {
  fetchWorkData();
});

// 获取挂号的数据
const fetchWorkData = async () => {
  let result: HospitalWorkData = await reqHospitalWork(
    pageNo.value,
    limit.value,
    $route.query.hoscode as string,
    $route.query.depcode as string
  );
  // console.log(result);
  if (result.code == 200) {
    workData.value = result.data;
    // 存储第一天日期的数据
    // workTime.value=workData.bookingScheduleList[0]
    workTime.value = workData.value.bookingScheduleList[0];

    // 获取一次医生的数据
    getDoctorWorkData();
  }
};

// 获取医生排班信息
const getDoctorWorkData = async () => {
  // 医院的编号
  let hoscode: string = $route.query.hoscode as string;
  // 科室的编号
  let depcode: string = $route.query.depcode as string;
  // 时间
  let workDate: string = workTime.value.workDate;
  // 获取排班医生的数据
  let result: DoctorResponseData = await reqHospitalDoctor(
    hoscode,
    depcode,
    workDate
  );
  // console.log(result);
  if (result.code == 200) {
    docArr.value = result.data;
  }
};

// 点击顶部某一天的时候触发回调
const changeTime = (item: any) => {
  // console.log(item);
  // 存储用户选择那一天的数据
  workTime.value = item;
  // 再发一次获取医生的排班的数据
  getDoctorWorkData();
};

// 计算出上午排班的医生数据
let morningArr = computed(() => {
  return docArr.value.filter((doc: Doctor) => {
    return doc.workTime == 0;
  });
});
// 计算出下午排班的医生数据
let afterArr = computed(() => {
  return docArr.value.filter((doc: Doctor) => {
    return doc.workTime == 1;
  });
});
</script>

<style scoped lang="scss">
.wrap {
  .top {
    display: flex;
    color: #7f7f7f;
    .line {
      width: 5px;
      height: 20px;
      background: skyblue;
      margin: 0px 5px;
    }
    .dot {
      margin: 0px 5px;
      color: skyblue;
    }
  }
  .center {
    margin: 20px 0px;
    display: flex;
    flex-direction: column;
    align-items: center;
    transition: all 0.5s;
    .time {
      font-weight: 900;
    }
    .container {
      width: 100%;
      display: flex;
      margin: 30px 0px;
      .item {
        flex: 1;
        width: 100%;
        border: 1px solid skyblue;
        margin: 0px 5px;
        &.active {
          border: 1px solid #cccc;
          color: #7f7f7f;
          .top2 {
            background: #ccc;
          }
        }
        &.cur {
          transform: scale(1.1);
        }

        .top2 {
          background: #e8f2ff;
          /* margin: 5px 0px; */
          width: 100%;
          height: 30px;
          text-align: center;
          line-height: 30px;
        }
        .bottom {
          width: 100%;
          height: 60px;
          text-align: center;
          line-height: 60px;
        }
      }
    }
  }
  .bottom1 {
    .will {
      text-align: center;
      font-size: 30px;
      font-weight: 900;
      .time {
        color: red;
      }
      .willtext {
        color: skyblue;
      }
    }

    .doctor {
      .morning {
        .tip1 {
          display: flex;
          align-items: center;
          .text {
            color: #7f7f7f;
            font-weight: 900;
          }
        }
        .doc_info {
          display: flex;
          justify-content: space-between;
          margin: 10px 0px;
          border-bottom: 1px solid #ccc;
          .left {
            .info {
              color: skyblue;
              margin: 10px 0px;
              span {
                margin: 0px 5px;
                font-size: 18px;
                font-weight: 900;
              }
            }
            .skill {
              margin: 10px 0px;
              color: #7f7f7f;
            }
          }
          .right {
            display: flex;
            width: 150px;
            justify-content: space-between;
            align-items: center;
            .money {
              color: #7f7f7f;
              font-weight: 900;
            }
          }
        }
      }
    }
  }
}
</style>

18.3、确认挂号信息

静态页面搭建

image.png

引入路由

image.png 创建文件

image.png

点击跳转到这个页面

image.png

使用路由

image.png

点击跳转,记得带上医生id

image.png

register_step2.vue

<template>
  <div class="container">
    <h1 class="tip">确认挂号信息</h1>
    <!-- 卡片:展示就诊人信息 -->
    <el-card class="box-card">
      <!-- 卡片的头部 -->
      <template #header>
        <div class="card-header">
          <span>请选择就诊人</span>
          <el-button class="button" type="primary" size="default" :icon="User"
            >添加就诊人</el-button
          >
        </div>
      </template>
      <!-- 卡片的身体部分展示就诊人信息 -->
      <div class="user">
        <!-- :user="user"       :index="index"    :currenIndex="index"    父组件传数据给子组件 -->
        <Visitor
          v-for="(user, index) in userArr"
          :key="user.id"
          class="item"
          :user="user"
          @click="changeIndex(index)"
          :index="index"
          :currentIndex="currentIndex"
        />
      </div>
    </el-card>
    <!-- 底部展示医生的信息 -->
    <el-card class="box-card">
      <!-- 卡片头部 -->
      <template #header>
        <div class="card-header">
          <span>挂号信息</span>
        </div>
      </template>
      <!-- 卡片的身体部分:展示医生的信息-->
      <el-descriptions class="margin-top" :column="2" border>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">就诊日期:</div>
          </template>
          {{ docInfo.workDate }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">就诊医院:</div>
          </template>
          {{ docInfo.param?.hosname }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">就诊科室:</div>
          </template>
          {{ docInfo.param?.depname }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生姓名:</div>
          </template>
          {{ docInfo?.docname }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生职称:</div>
          </template>
          {{ docInfo.title }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生专长:</div>
          </template>
          {{ docInfo.skill }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生服务费:</div>
          </template>
          <span style="color: red">{{ docInfo.amount }}</span>
        </el-descriptions-item>
      </el-descriptions>
    </el-card>

    <!-- 确定挂号按钮 -->
    <div class="btn">
      <el-button
        type="primary"
        size="default"
        :disabled="currentIndex == -1 ? true : false"
        >确定挂号</el-button
      >
    </div>
  </div>
</template>

<script setup lang="ts">
import { User } from "@element-plus/icons-vue";

// 引入获取就诊人的信息接口
import { reqGetUser, reqDoctorInfo } from "@/api/hospital";
import { onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import type {
  UserResponseData,
  UserArr,
  DoctorInfoData,
} from "@/api/hospital/type";
// 就诊人组件
import Visitor from "./visitor.vue";

// 获取路由对象
let $route = useRoute();
// 存储全部就诊人信息
let userArr = ref<UserArr>([]);

// 存储医生的信息
let docInfo = ref<any>({});

// 存储用户确认就诊人索引值
let currentIndex = ref<number>(-1);
// 组件挂载完毕获取数据
onMounted(() => {
  // 获取就诊人信息
  fetchUserData();
  //   获取医生信息
  fetchInfo();
});

// 获取就诊人信息
const fetchUserData = async () => {
  let result: UserResponseData = await reqGetUser();
  //   console.log(result);
  if (result.code == 200) {
    userArr.value = result.data;
  }
};

// 获取医生信息
const fetchInfo = async () => {
  let result: DoctorInfoData = await reqDoctorInfo(
    $route.query.docId as string
  );
  if (result.code == 200) {
    docInfo.value = result.data;
  }
};

// 点击就诊人子组件的回调
const changeIndex = (index: number) => {
  //   console.log(index);
  // 存储当前用户选中就诊人信息索引值
  currentIndex.value = index;
};
</script>

<style scoped lang="scss">
.container {
  .tip {
    font-weight: 900;
    color: #7f7f7f;
    font-size: 20px;
  }
  .box-card {
    margin: 20px 0px;
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .user {
      display: flex;
      flex-wrap: wrap;
      .item {
        width: 32%;
        margin: 5px;
      }
    }
  }
  .btn {
    display: flex;
    justify-content: center;
    margin-bottom: 20px;
  }
}
</style>


visitor.vue

<template>
  <div class="visitor">
    <div class="top">
      <div class="left">
        <span class="free">{{ user.isInsure == 1 ? "医保" : "自费" }}</span>
        <span class="username">{{ user.name }}</span>
      </div>
      <div class="right">
        <el-button circle type="primary" :icon="Edit"></el-button>
      </div>
    </div>
    <div class="bottom">
      <p>证件类型:{{ user.param.certificatesTypeString }}</p>
      <p>证件号码:{{ user.certificatesNo }}</p>
      <p>用户性别:{{ user.sex == 0 ? "女生" : "男生" }}</p>
      <p>出生日期:{{ user.birthdate }}</p>
      <p>手机号码:{{ user.phone }}</p>
      <p>婚姻状况:{{ user.isMarry == 1 ? "已婚" : "未婚" }}</p>
      <p>当前住址:{{ user.param.cityString }}</p>
      <p>详细住址:{{ user.paramfullAddress }}</p>
      <!-- 红色已选择的盒子 -->
      <transition name="confirm">
        <div class="confirm" v-if="index === currentIndex">已选择</div>
      </transition>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Edit } from "@element-plus/icons-vue";

// 接受父组件传递过来的就诊人信息
defineProps(["user", "index", "currentIndex"]);
</script>

<style scoped lang="scss">
.visitor {
  box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
  .top {
    height: 60px;
    background: #e5e5e5;
    display: flex;
    justify-content: space-around;
    align-items: center;
    .left {
      .free {
        background: white;
        padding: 5px;
        font-size: 12px;
        margin-right: 5px;
        border-radius: 10px;
      }
      .username {
        color: #7f7f7f;
      }
    }
  }
  .bottom {
    position: relative;
    padding-left: 50px;
    p {
      line-height: 40px;
    }
    .confirm {
      position: absolute;
      width: 200px;
      height: 200px;
      color: red;
      border-radius: 50%;
      border: 1px dashed red;
      text-align: center;
      line-height: 200px;
      left: 20%;
      top: 20%;
      opacity: 0.5;
      transform: rotate(35deg);
      font-weight: 900;
    }
    .confirm-enter-from {
      transform: scale(1);
    }
    .confirm-enter-active {
      transition: all 0.3s;
    }
    .confirm-enter-to {
      transform: scale(1.3);
    }
  }
}
</style>



18.4、医生挂号信息展示

接口

image.png image.png

image.png

image.png

展示医生信息

image.png

18.5、已选择图的完成

image.png

image.png

利用子绝父相的方法

image.png

18.6、确定就诊人业务

image.png

image.png

image.png

image.png

当选择有东西时,才可以确定挂号

image.png

十九、会员中心

19.1小错误

里面应该用单引号,不能用双引号 image.png

19.2 会员中心路由跳转

image.png

image.png

image.png

19.3 确认挂号(接口似乎挂了,一直数据异常)

接口

image.png

image.png

确定挂号

image.png

image.png

完整代码

<template>
  <div class="container">
    <h1 class="tip">确认挂号信息</h1>
    <!-- 卡片:展示就诊人信息 -->
    <el-card class="box-card">
      <!-- 卡片的头部 -->
      <template #header>
        <div class="card-header">
          <span>请选择就诊人</span>
          <el-button class="button" type="primary" size="default" :icon="User"
            >添加就诊人</el-button
          >
        </div>
      </template>
      <!-- 卡片的身体部分展示就诊人信息 -->
      <div class="user">
        <!-- :user="user"       :index="index"    :currenIndex="index"    父组件传数据给子组件 -->
        <Visitor
          v-for="(user, index) in userArr"
          :key="user.id"
          class="item"
          :user="user"
          @click="changeIndex(index)"
          :index="index"
          :currentIndex="currentIndex"
        />
      </div>
    </el-card>
    <!-- 底部展示医生的信息 -->
    <el-card class="box-card">
      <!-- 卡片头部 -->
      <template #header>
        <div class="card-header">
          <span>挂号信息</span>
        </div>
      </template>
      <!-- 卡片的身体部分:展示医生的信息-->
      <el-descriptions class="margin-top" :column="2" border>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">就诊日期:</div>
          </template>
          {{ docInfo.workDate }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">就诊医院:</div>
          </template>
          {{ docInfo.param?.hosname }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">就诊科室:</div>
          </template>
          {{ docInfo.param?.depname }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生姓名:</div>
          </template>
          {{ docInfo?.docname }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生职称:</div>
          </template>
          {{ docInfo.title }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生专长:</div>
          </template>
          {{ docInfo.skill }}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">医生服务费:</div>
          </template>
          <span style="color: red">{{ docInfo.amount }}</span>
        </el-descriptions-item>
      </el-descriptions>
    </el-card>

    <!-- 确定挂号按钮 -->
    <div class="btn">
      <el-button
        type="primary"
        size="default"
        :disabled="currentIndex == -1 ? true : false"
        @click="submitOrder"
        >确定挂号</el-button
      >
    </div>
  </div>
</template>

<script setup lang="ts">
import { User } from "@element-plus/icons-vue";

// 引入获取就诊人的信息接口
import { reqGetUser, reqDoctorInfo } from "@/api/hospital";
import { reqSubmitOrder } from "@/api/user";
import type { SubmitOrder } from "@/api/user/type";
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import type {
  UserResponseData,
  UserArr,
  DoctorInfoData,
} from "@/api/hospital/type";
import { ElMessage } from "element-plus";
// 就诊人组件
import Visitor from "./visitor.vue";

// 获取路由对象
let $route = useRoute();
// 获取路由器对象
let $router = useRouter();
// 存储全部就诊人信息
let userArr = ref<UserArr>([]);

// 存储医生的信息
let docInfo = ref<any>({});

// 存储用户确认就诊人索引值
let currentIndex = ref<number>(-1);
// 组件挂载完毕获取数据
onMounted(() => {
  // 获取就诊人信息
  fetchUserData();
  //   获取医生信息
  fetchInfo();
});

// 获取就诊人信息
const fetchUserData = async () => {
  let result: UserResponseData = await reqGetUser();
  //   console.log(result);
  if (result.code == 200) {
    userArr.value = result.data;
  }
};

// 获取医生信息
const fetchInfo = async () => {
  let result: DoctorInfoData = await reqDoctorInfo(
    $route.query.docId as string
  );
  if (result.code == 200) {
    docInfo.value = result.data;
  }
};

// 点击就诊人子组件的回调
const changeIndex = (index: number) => {
  //   console.log(index);
  // 存储当前用户选中就诊人信息索引值
  currentIndex.value = index;
};

//确定挂号按钮的回调
const submitOrder = async () => {
  //医院编号
  let hoscode = docInfo.value.hoscode;
  //医生的ID
  let scheduleId = docInfo.value.id;
  //就诊人的ID
  let patientId = userArr.value[currentIndex.value].id;
  //提交订单
  let result: SubmitOrder = await reqSubmitOrder(
    hoscode,
    scheduleId,
    patientId
  );
  // console.log(result);

  //提交订单成功
  if (result.code == 200) {
    $router.push({ path: "/user/order", query: { orderId: result.data } });
  } else {
    ElMessage({
      type: "error",
      message: result.message,
    });
  }
};
</script>

<style scoped lang="scss">
.container {
  .tip {
    font-weight: 900;
    color: #7f7f7f;
    font-size: 20px;
  }
  .box-card {
    margin: 20px 0px;
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .user {
      display: flex;
      flex-wrap: wrap;
      .item {
        width: 32%;
        margin: 5px;
      }
    }
  }
  .btn {
    display: flex;
    justify-content: center;
    margin-bottom: 20px;
  }
}
</style>

19.4 订单详情

image.png

接口

image.png

image.png

19.5 取消预约

image.png

取消预约

image.png

image.png

image.png

19.6 支付二维码静态搭建

image.png

image.png

image.png

19.7 支付二维码获取

将字符串变为二维码插件---npm官网找qrcode插件

image.png

获取二维码

image.png

image.png

image.png

动态绑定图片 image.png

19.8 订单支付业务

接口

image.png

image.png

image.png 当二维码出现时,每隔两秒都要向服务器询问是否支付成功,当支付成功后,还要将定时器清除 image.png

清除定时器(窗口和右上角的叉子)

image.png image.png

二十、实名认证静态搭建

image.png 完整代码

<template>
  <!-- 实名认证结构 -->
  <el-card class="box-card">
    <!-- 卡片的头部 -->
    <template #header>
      <div class="card-header">
        <h1>实名信息</h1>
      </div>
    </template>
    <!-- 实名认证结构的提示部分 -->
    <div class="tip" style="color: #7f7f7f">
      <p>
        <el-icon><InfoFilled /></el-icon>
        完成实名认证后才能添加就诊人,正常进行挂号,为了不影响后续步骤,建议提前实名认证。
      </p>
    </div>
    <!-- 卡片身体的底部:认证成功的结构、认证未成功的结构 -->
    <el-descriptions
      v-if="true"
      class="margin-top"
      :column="1"
      border
      style="margin: 20px auto"
      size="small"
    >
      <el-descriptions-item label-align="center" :width="20">
        <template #label>
          <div class="cell-item">用户姓名</div>
        </template>
        kooriookami
      </el-descriptions-item>
      <el-descriptions-item label-align="center" :width="20">
        <template #label>
          <div class="cell-item">证件类型</div>
        </template>
        kooriookami
      </el-descriptions-item>
      <el-descriptions-item label-align="center" :width="20">
        <template #label>
          <div class="cell-item">证件号码</div>
        </template>
        kooriookami
      </el-descriptions-item>
    </el-descriptions>
    <!-- 用户未认证的结构 -->
    <el-form style="width: 60%; margin: 20px auto" label-width="80">
      <el-form-item label="用户姓名">
        <el-input placeholder="请输入用户的姓名"></el-input>
      </el-form-item>
      <el-form-item label="证件类型">
        <el-select placeholder="请选择证件类型" style="width: 100%">
          <el-option label="身份证"></el-option>
          <el-option label="户口本"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="证件号码">
        <el-input placeholder="请输入证件号码"></el-input>
      </el-form-item>
      <el-form-item label="上传证件">
        <el-upload list-type="picture-card">
          <img
            src="../../../assets/images/auth_example.png"
            alt=""
            style="width: 100%; height: 80%"
          />
        </el-upload>

        <el-dialog>
          <img w-full alt="Preview Image" />
        </el-dialog>
      </el-form-item>
      <el-form-item>
        <el-button type="primary"> 提交</el-button>
        <el-button> 重写</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</template>

<script setup lang="ts">
import { InfoFilled } from "@element-plus/icons-vue";
</script>

<style scoped lang="scss">
.box-card {
  .tip {
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
</style>

20.1 获取用户模块

image.png 接口

image.png

image.png

image.png

20.2 获取表单数据

上传图片功能

小错误

image.png 原因:没有判断是否有图片,也可能上传的是空数组

功能实现:

image.png

image.png

image.png

image.png

重写:

image.png

image.png

image.png

image.png

提交:

image.png

image.png image.png

image.png

20.3 实名认证表单自定义校验

小bug:上传图片成功了,但是校验规则还在 image.png

解决:用了element plus自带的方法

image.png

自定义表单校验

image.png

image.png

校验函数;

// 自定义校验姓名规则
const validatorName = (rule: any, value: any, callBack: any) => {
  // rule即为当前校验字段的校验规则对象   value即为当前校验字段的校验数据data
  // console.log(rule);
  // console.log(value);
  const reg =
    /^[\u00B7\u3007\u3400-\u4DBF\u4E00-\u9FFF\uE000-\uF8FF\uD840-\uD8C0\uDC00-\uDFFF\uF900-\uFAFF]+$/;
  if (reg.test(value)) {
    callBack();
  } else {
    callBack(new Error("请输入正确的中国人的名字"));
  }
};

// 证件类型校验方法
const validatorType = (rule: any, value: any, callBack: any) => {
  // console.log(value);
  if (value == "10" || value == "20") {
    callBack();
  } else {
    callBack(new Error("请选择证件类型"));
  }
};

// 身份证号码
const validatorNo = (rule: any, value: any, callBack: any) => {
  // console.log(111);

  const reg =
    /^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
  const hkb = /^\d{9}$/;
  if (reg.test(value) || hkb.test(value)) {
    callBack();
  } else {
    callBack(new Error("请输入正确的身份证号码或者户口本号码"));
  }
};

// 证件照图片
const validatorUrl = (rule: any, value: any, callBack: any) => {
  if (value.length) {
    callBack();
  } else {
    callBack(new Error("请上传证件照"));
  }
};