一、项目介绍:
这是一个医疗类的小程序,主要用于新冠疫苗预约,HPV疫苗预约,核酸预约,和咨询等,主要作用就是方便快捷,可以在手机上进行预约挂号,和一些健康自测的功能,还有一些医生专家讲座,分享一些疾病宣传及预防
二、项目使用技术栈:
✨脚手架工具:uniapp 和 高效、快速的 Vite
🔥前端框架:眼下最时髦的 Vue3
🍍状态管理器:vue3新秀 Pinia,犹如 react zustand般的体验,友好的api和异步处理
🏆开发语言 TypeScript
三、项目模块:

项目目录:
com-components:
| 文件名 | 页面属性 | 
| point.vue | 页面没有数据,为空的时候显示该页面 | 
| st-member.vue | 模态框,点击显示模态框,选择就诊人,提价数据 | 
pages:
| 文件夹名 | 页面属性 | 
| login-page | 登录授权 | 
| index | 首页 | 
| registered | 挂号 | 
| video | 医师课堂 | 
| mine | 我的 | 
| xinguan-vaccine | 新冠接种疫苗接种预约 | 
| hpv-vaccine | hpv页面和详情页 | 
| hesuan-vaccine | 核酸检测预约 | 
| graphics | 图文咨询 | 
| phy-exam | 体检套餐列表页面和体检详情页 | 
| my-service | 存放的是每个页面的二级三级....页面 | 
my-service:
| 文件夹名 | 页面属性 | 
| hesuan | 核酸预约列表 | 
| hpv-view | hpv 预约列表 | 
| my-patient | 我的就诊人和添加就诊人 | 
| xinguan | 新冠疫苗预约订单 | 
| phy-view | 体检预约订单 | 
public:
| 文件名 | 页面属性 | 
| request.ts | 接口封装 | 
| decl-type.d.ts | 类型声明文件 .d.ts文件 不能执行逻辑操作 只能用来声明 不会转化成js文件 | 
| misc.ts | 上传图片的方法 | 
四、 项目开发前期准备工作:
1、打开uniapp官网下载项目框架

这次做的是uniapp,vue3,ts的项目,以下图片可根据需求下载,点击gitee到git码云仓库下载

2、获取Appid
重定项目Appid
打开src/manifest.json文件,微信小程序配置里,更改为你自己的小程序APPID

3,下载依赖
npm install
4、运行
在命令窗口输入: npm run dev:mp-weixin ,用于解析代码转换小程序原生代码
运行完之后会生成一个dist文件夹,

5、生成dist文件包,因为本项目用到vue3+ts语法,所以选用vs code编译器,但是不能主动调去微信开发者工具。所以手动拖动项目到微信开发者平台
五、封装接口
1、在src下创建public文件夹,在这个文件夹加下面创建request.ts文件
2、在这个页面下定义一个对象,用于接口管理,然后用export导出
3、登录授权,下载js-base64,封装一个方法用于获取本地存储的token,在请求接口request()里的header使用这个方法
//公用请求
const baseUrl = 'https://meituan.thexxdd.cn/api'
//获取token npm install -- save js-base64
import { Base64 } from 'js-base64'
import {
    Rescovidapi,
    Wxloginapi,
    Covidcancelapi,
    Hpvorder,
    Resnuataapi,
    AddPatient,
    Graphics,
    FilterData,
    PhyRes
} from "@/public/decl-type"
function getToken(): string {
    const token = uni.getStorageSync('wxuser').user_Token || ''
    //后端和前端的约定 
    const base64_token = Base64.encode(token + ':')
    return 'Basic ' + base64_token
}
//请求
function request(url: string, method: 'GET' | 'POST', data: string | object | ArrayBuffer) {
    return new Promise((resolve, reject) => {
        uni.request({
            url: baseUrl + url,
            method,
            data,
            header: {
                Authorization: getToken()
            },
            success: (res: any) => {
                if (res.statusCode == 200) {
                    resolve(res)
                } else if (res.statusCode == 401) {
                    //权限过期
                    // 没有权限访问接口:跳转到登陆界面
                    uni.navigateTo({ url: '/pages/login-page/index' });
                    uni.showToast({
                        title: "信息过期",
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                } else if (res.statusCode == 400) {
                    uni.showToast({
                        title: '开发者某个字段或参数填写不对',
                        icon: 'none',
                        duration: 1000,
                    });
                    reject(res);
                } else if (res.statusCode == 500) {
                    uni.showToast({
                        title: "服务器错误",
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                } else if (res.statusCode == 202) {
                    uni.showToast({
                        title: res.data.msg,
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                } else {
                    uni.showToast({
                        title: "服务器错误",
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                }
            },
            fail: (err: any) => {
                uni.showToast({
                    title: "服务器错误",
                    icon: 'none',
                    duration: 1000
                })
                reject(err)
            }
        })
    })
}
//接口管理
const RequestApi = {
    //首页数据
    Frontpage: () => request('/frontpage', 'GET', {}),
    //新冠疫苗预约时段
    NewappTime: () => request('/newapptime', 'GET', {}),
}
//上传图片
let IMAGEURL = baseUrl + "/upload_picture"
//上传图片,身份证信息
let AICARD = baseUrl + "/ai_card";
export { RequestApi, IMAGEURL, AICARD }登录逻辑:
1、下载js-base64 下载方法:npm install -- save js-base64

2、在封住的request里面写请求头

3、请求小程序登录接口,我这个使用ts写的蓝色的单词是ts的规范接口类型,用js的把这个删了就行了

4、在登录页面登录按钮添加点击事件、当点击事件触发的时候进行的操作
在登录页面调用封装好的登录接口
 
5、 登录写一个方法用于调用api接口登录

6、点击登录

7、在访问一些需要登录的页面,没有登录或者登录过期的时候后台会返回一个失败信息,这个失败信息的statusCode是401,所以需要在请求接口的时候判断,如果返回的是401,需要跳转到登录页面,重新登陆。

代码:
const baseUrl = 'https://meituan.thexxdd.cn/api'
 
//获取token npm install -- save js-base64
import { Base64 } from 'js-base64'
function getToken(): string {
    const token = uni.getStorageSync('wxuser').user_Token || ''
    //后端和前端的约定 
    const base64_token = Base64.encode(token + ':')
    return 'Basic ' + base64_token
}
 
//请求
function request(url: string, method: 'GET' | 'POST', data: string | object | ArrayBuffer) {
    return new Promise((resolve, reject) => {
        uni.request({
            url: baseUrl + url,
            method,
            data,
            header: {
                Authorization: getToken()
            },
            success: (res: any) => {
                if (res.statusCode == 200) {
                    resolve(res)
                } else if (res.statusCode == 401) {
                    //权限过期
                    // 没有权限访问接口:跳转到登陆界面
                    uni.navigateTo({ url: '/pages/login-page/index' });
                    uni.showToast({
                        title: "信息过期",
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                } else if (res.statusCode == 400) {
                    uni.showToast({
                        title: '开发者某个字段或参数填写不对',
                        icon: 'none',
                        duration: 1000,
                    });
                    reject(res);
                } else if (res.statusCode == 500) {
                    uni.showToast({
                        title: "服务器错误",
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                } else if (res.statusCode == 202) {
                    uni.showToast({
                        title: res.data.msg,
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                } else {
                    uni.showToast({
                        title: "服务器错误",
                        icon: 'none',
                        duration: 1000
                    })
                    reject(res)
                }
            },
            fail: (err: any) => {
                uni.showToast({
                    title: "服务器错误",
                    icon: 'none',
                    duration: 1000
                })
                reject(err)
            }
        })
    })
 
}
//接口管理
const RequestApi = {
    //首页数据
    Frontpage: () => request('/frontpage', 'GET', {}),
    //新冠疫苗预约时段
    NewappTime: () => request('/newapptime', 'GET', {}),
    // 新冠疫苗提交预约
    RescoVid: (data: Rescovidapi) => request('/rescovid', 'POST', data),
    // 小程序登陆
    WxLogin: (data: Wxloginapi) => request('/wx_login', 'POST', data),
}
export { RequestApi}首页代码:
<template>
  <view class="Login-page">
    <image
      mode="aspectFill"
      src="https://diancan-1252107261.cos.accelerate.myqcloud.com/yiliao/denglu-yemian.jpg"
    ></image>
    <button @click="Login">授权登陆</button>
  </view>
</template>
 
<script setup lang="ts">
import { ref, onMounted, reactive, toRefs } from "vue";
import { RequestApi } from "@/public/request";
// 授权登陆
function Login() {
  uni.getUserInfo({
    // desc: "获取个人信息",
    withCredentials: false,
    lang: "zh_CN",
    timeout: 10000,
    success: (res) => {
      let { avatarUrl, nickName } = res.userInfo;
      // 获取code
      uni.login({
        success: (code) => {
          console.log(code);
 
          uni.showLoading({ title: "登陆中", mask: true });
          ApiLogin(avatarUrl, nickName, code.code);
        },
        fail: (err) => {
          uni.showToast({ title: "登录失败", icon: "none", duration: 1000 });
        },
      });
    },
    fail: (err) => {
      uni.showToast({ title: "登录失败", icon: "none", duration: 1000 });
    },
  });
}
 
// 调用api接口登陆
async function ApiLogin(avatarUrl: string, nickName: string, code: string) {
  try {
    let obj = {
      appid: "",//id
      secret: "",//秘钥
      avatarUrl,
      nickName,
      code,
    };
    let res: any = await RequestApi.WxLogin(obj);
    // 存储用户信息到本地缓存,然后返回上一页
    uni.setStorageSync("wxuser", res.data.data);
    setTimeout(() => {
      uni.navigateBack({ delta: 1 });
      uni.hideLoading();
    }, 600);
  } catch (error) {
    uni.showToast({ title: "登录失败", icon: "none", duration: 1000 });
  }
}
</script>六、首页
效果图:

因为我们每个人的手机型号不一样,尺寸不一样,所以要做到没个手机型号和尺寸的自适应一样。
逻辑:
在app.vue页面一开始加载的时候需要获取按钮高度,这里用到onLaunch()生命周期

在首页页面挂载钱获取按钮高度

在style里面用v-bind()绑定获取到的高度

剩下的就是调取接口,获取数据,渲染数据,样式布局,以及点击响应的区域跳转到响应的模块
七、新冠疫苗(亮点:选择时段)
效果图:
 
                            
这个页面的输入框是小程序自带的,他的需要就是绑定所有数据,调取提交接口
比较难的业务逻辑就是上午时段和下午时段只能选择一个

然后用这些下标用三元表达式绑定动态class,这样能实现效果图的效果了
点击提交预约,发送后台需要的字段,调取接口
字段:

 
提交成功后,跳转到订单页面
 
渲染数据,点击取消预约调用接口,传递ID,就成功取消预约
八、HPV疫苗(亮点:Top切换)
效果图:
 
                                      
列表页:
1、点击获取下标,用三元表达式的方法绑定动态class
2、每一个分类他的商品都有相同的id,用id进行筛选显示相应的数据
3、将获取到的数据设置两个变量。一个用于筛选,一个用于展示全部数据

点击去预约需要把id,套餐名,价格,描述用字符串的形式传给页面

预约页:
跟新冠疫苗页面相似,也渲染数据,调取接口,跳转页面,提交。
HPV订单页面:
提交成功后跳转到HPV订单页面
效果图:

九、核酸检测(亮点:电话)
核酸检测预约效果图:

亮点逻辑:

写完之后,需要去微信公众平台绑定接口路径


下滑找到一下区域 这个接口不需要在后面拼接/api

逻辑写完后跳转到订单页面
核酸检测订单:
 
十、首页——图文咨询(重点:亮点是上传图片):
以下是图文咨询的页面效果,每个红色的盒子是一个模块,方便后期遍历数据
 
                
当前页面的重点逻辑:
1、上传图片:在这个项目中我们有很多地方会用到长传图片的这个功能,所以把这个方法封装了一下,方便后期复用。
操作如下:
在public问价夹创建misc.ts文件,把上传图片的方法放在这个文件夹里。
在request.ts写自己的接口地址


 
   
代码:
function uploadImage(url: string, su_title: string, err_title: string) {
  return new Promise((reactive, reject) => {
    uni.chooseMedia({
      count: 1, //每次可选择几张图片
      mediaType: ["image"], //文件类型,图片、视频
      sizeType: ["compressed"], //压缩图片
      success(res) {
        console.log(res);
        
        uni.showLoading({
          title: su_title,
          mask: true
        })
        uni.uploadFile({
          url, //自己的接口地址
          filePath: res.tempFiles[0].tempFilePath, //请求成功返回的临时地址
          name: "file",
          success(res: any) {
            reactive(res)
            uni.hideLoading()
          },
          fail(err: any) {
            uni.showToast({
              title: err_title,
              icon: "error",
              duration: 1000
            })
            reject(err)
          }
        });
      },
    });
  });
}
export { uploadImage };在图文咨询页面引入这个方法


控制台输出:
 
 
代码:
const unload = async () => {
  const res: any = await uploadImage(IMAGEURL, "上传中", "上传失败");
  const res_data = JSON.parse(res.data);
  submitData.ins_report.push(res_data);
};添加就诊人需要跳转到就诊人页面,如果需要添加就诊人 要调到添加就诊人页面
 
                                    
这个两个页面也是比较简单,主要考验css功底,和渲染数据,比较难的就是身份证识别,但这个功能是上传图片后台识别后返回身份证信息的数据,然后绑定数据就行了,方法跟上面一致。



这有一个bug,看一下效果图
 
                
想要实现我们需要的效果把那段注释的代码解开
代码:
const AIcardupload = async () => {
  const res: any = (await uploadImage(AICARD, "识别中", "识别失败")) as {
    statusCode: number;
  };
  const res_data = JSON.parse(res.data).data;
  if (res.statusCode == 200) {
    submitData.name = res_data.name;
    submitData.sex = res_data.sex;
    submitData.born = res_data.born;
    submitData.id_card = res_data.id_card;
    console.log(submitData.born);
    submitData.born = submitData.born.slice(0, 4)+"-" +submitData.born.slice(4, 6)+"-"+submitData.born.slice(6, 8)
  }
  console.log(submitData, "submitData");
};存储到pinia仓库
在选择就诊人页面选择就诊人保存到pinia,因为在上一个页面(图文咨询)需要用到name和ID
npm install pinia --save 下载
在src下面添加store文件夹,再添加一个index.ts文件(可以去参考pinia官网)
pinia仓库代码:
import { defineStore } from "pinia";
interface patient {
  name:string;
  id:string
}
export const myData = defineStore('my_data', {
  state: () => {
    return {
      patient: {
        name: "",
        id: ""
      } as patient
    }
  },
  actions:{
    addPatient(value :patient){
      this.patient = value
    }
  }
})就诊人页面代码:
//引入pinia仓库
import { myData } from "@/store/index";
let store = myData();
//点击事件触发向pinia发送数据
const checkedData = (name: string, id: string) => {
  store.addPatient({ name, id });
};图文咨询页面:
 
 

提交数据:
添加就诊人提交接口提交参数


图文咨询提交接口提交参数


十一、健康体检(亮点:点击全部点击相应的套餐显示响应的数据、销量排序、价格排序,uniapp模态框二次封装,方便后期复用)
体检预约和体检详情页面效果:
 
                             
 
页面逻辑:
全部、销量、价格 这个盒子是定位在头部的,数据上划的时候需要显示在头部
它们都需要绑定一个点击事件,用来触发后续的筛选,和调用接口
html和逻辑代码:
1,样式结构

控制台输出

2、用于存放数据

3、请求接口方法

4、点击触发 全部、销量、价格的筛选方法

5、点击全部打开模态框,点击相应的筛选数据进行筛选

6、套餐数据请求渲染完之后,点击需要的套菜,携带id到体检详情页面
体检详情:
效果图:
 
     
   
这个页面的时间选择 使用uniapp的组件进行实现的,调一下css的样式就行了
 
    
二次封装模态框:
1、在子组件文件夹在新建一个vue文件,用来当做子组件
2、在体检详情页面注册这个子组件,并在子组件标签上绑定ref获取子组件实例
3、点击通过子组件标签上的ref获取到的实例,调取子组件的方法传递数据
父组件:
 
    
子组件:

接收到的数据也是用来提交预约触发了调取接口发送后台的
点击选择成员跳转到就诊人页面,这个页面之前就已经写好了,只需要跳转过去就行
点击提交成功后跳转到体检预约订单
效果图:

十二、健康自测
 
   
   
1、首页点击测评,判断点击的是哪一个测评模块

2、跳转到测评页面后,做完页面的样式进行的业务逻辑
2.1 在进入页面的时候接受上个页面传递过来的数据,进行下一步的请求接口的判断
2.2 在页面也开始加载的时候处理接口请求,看首页点击的是哪个自测进行判断,进行相应的接口请求

2.3 再点击选项的时候触发相应的业务逻辑

2.4 页面优化 跳转页面














![[附源码]计算机毕业设计基于Springboot物品捎带系统](https://img-blog.csdnimg.cn/a192c9500a184a6ba2d028d985ce44f5.png)






