全栈项目(golang+vue)门诊系统

theme: awesome-green

全栈项目(golang+vue)门诊系统

接一项目 主要实现的功能是,处理牙医门诊的预约管理。拖拖拽拽就能管理客户的预约。最近在练手golang, 就使用golang自己实现一遍后端接口,并加入自己用的登录验证, 图片存储使用的七牛存储。为了除去敏感信息,现在展示出来的前端只保留预约面板的主逻辑,其他页面简单重写实现 。(正经门诊不可能用迪迦做背景板吧🥲)

开源地址

应小伙伴感兴趣,现补上前端项目地址。有不好的望指正,有能帮到大家的希望大家喜欢。

前端:https://github.com/luqiangbo/vite-vue-demo

后端:gin-vue-admin 参考学习的gin-vue-admin

登录页

image.png
预约面板(vue-grid-layout)

项目预约使用的的是vue-grid-layout,插件文档Vue Grid Layout -️ 适用Vue.js的栅格布局系统

image.png

前端(vite + vue3 + pinia + element-plus + vue-grid-layout)

主要使用的插件

  • unplugin-auto-import/vite 按需自动导入api
  • unplugin-vue-router/vite 根据文件自动生成路由
  • vite-plugin-vue-layouts 搭配unplugin-vue-router/vite
  • vite-plugin-html 修改html

主要使用的函数

接口请求封装

// request.js
import axios from 'axios'
import { ElMessage, ElLoading } from 'element-plus'
import { useAdminStore } from '@/store/admin'

import { to } from '@/utils'

const baseUrl = import.meta.env.VITE_APP_PROXY_URL

let loading = null

const instance = axios.create({
  baseURL: baseUrl,
  timeout: 60 * 1000,
})

// 请求拦截
instance.interceptors.request.use(
  (config) => {
    loading = ElLoading.service({
      lock: true,
      loading: false,
    })
    return config
  },
  (error) => {
    return Promise.reject(error)
  },
)
// 相应拦截
instance.interceptors.response.use(
  (response) => {
    const { status, data, request } = response
    loading.close()
    if (status !== 200) {
      return Promise.reject(status)
    } else {
      if (data.code !== 0) {
        ElMessage({
          message: `${data.msg}`,
          type: 'error',
          grouping: true,
          offset: 200,
        })
        return Promise.reject(data)
      } else {
        return Promise.resolve(data.data)
      }
    }
  },
  (error) => {
    loading.close()
    return Promise.reject(error)
  },
)

export const request = (req) => {
  const adminStore = useAdminStore()
  const token = adminStore.token
  let data = {}
  if (req?.method === 'get') {
    data = { params: data }
  } else {
    data = { data: data }
  }
  let headers = {}
  if (req.headers) {
    headers = req.reqders
  }
  if (token) {
    headers = {
      ...req.headers,
      'x-token': token,
    }
  }

  const good = {
    method: 'post',
    url: req.url,
    headers,
    ...data,
  }

  return to(instance(good))
}

async await 接口处理, 避免promise面条链式调用

export const to = (promise) => promise.then((res) => [res, null]).catch((err) => [null, err])
举例说明
const onfetchClinicUserLogin = async () => {
const [resOrder,errOrder] = await  onApiOrder()
const [resDetail,errDetail] = await  onApiDetail({id:resOrder.id})
}

预约面板处理,需要支持随意拖动到固定位置,改变大小。 开发使用的是vue3-grid-layout-next, 以支持vue3

userStore 为使用pinia,组件间通信使用全局store,避免数据多级传递混乱

<grid-layout
  v-if="userStore.layout.length"
  v-model:layout="userStore.layout"
  :margin="[0, 0]"
  :col-num="getColNum()"
  :row-height="30"
  :is-draggable="true"
  :is-resizable="true"
  :responsive="false"
  :vertical-compact="false"
  :prevent-collision="true"
  :use-css-transforms="true"
>
  <grid-item
    v-for="item in userStore.layout"
    v-show="isShowOrder(item.data)"
    :key="item.i"
    :x="item.x"
    :y="item.y"
    :w="item.w"
    :h="item.h"
    :i="item.i"
    :max-w="item.w"
    :min-w="item.w"
    @moved="onMoved"
    @move="onMove"
    @resized="onResize"
  >
    <ComShowPopover :data="item.data"></ComShowPopover>
  </grid-item>
</grid-layout>

后端(gin + gorm + postgressql + redis + qiniu )

主要使用的插件

  • gin api服务
  • chai2010/webp 图片转webp 节省空间大小
  • go-redis/redis redis
  • uuid/v5 数据库id使用uuid
  • golang-jwt/jwt/v4 登录
  • gorm  PostgreSQL
  • viper 环境配置

主要使用到的函数

gorm创建数据库

gorm创建表,gen生成crud,完美

package main

import (
    "fmt"
    "goo/config"
    "goo/table/abc"
    "goo/table/clinic"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/schema"
)

func main() {
    var db, err = gorm.Open(postgres.Open(config.SqlDsn), &gorm.Config{
        NamingStrategy: schema.NamingStrategy{
            SingularTable: true,
        },
    })
    if err != nil {
        fmt.Println("连接数据库失败:")
    }

    tables := []interface{}{
        abc.TUser{},
        abc.TShop{},
        clinic.CUser{},
        clinic.COrder{},
    }
    for _, t := range tables {
        _ = db.AutoMigrate(&t)
        // 视图 authority_menu 会被当成表来创建,引发冲突错误(更新版本的gorm似乎不会)
        // 由于 AutoMigrate() 基本无需考虑错误,因此显式忽略
    }
}

gen生成数据库相应的crud

package main

import (
    "gorm.io/driver/postgres"
    "gorm.io/gen"
    "gorm.io/gorm"

    "goo/config"
)

func main() {

    db, _ := gorm.Open(postgres.Open(config.SqlDsn), &gorm.Config{})
    g := gen.NewGenerator(gen.Config{
        OutPath: "../../dal/query",
        Mode:    gen.WithDefaultQuery | gen.WithoutContext | gen.WithQueryInterface,
    })
    g.UseDB(db)
    g.ApplyBasic(g.GenerateAllTable()...)
    g.Execute()
}

数据库设计

// 店铺
type CShop struct {
    global.GVA_MODEL
    Name   string `json:"name" gorm:"comment:名称"`                         // 名称
    Image  string `json:"image" gorm:"comment:图片"`                        // 图片
    Status int    `json:"status" gorm:"default:0;comment:状态 0新店未启用 1启用 "` //状态 0新店未启用 1启用
    Sort   int    `json:"sort" gorm:"default:0;comment:排序"`               //排序

    //
    Users     []CUser     `json:"users"`
}
type CUser struct {
    global.GVA_MODEL
    Name      string `json:"name" gorm:"comment:名称"`                // 名称
    OpenId    string `json:"open_id" gorm:"comment:微信id"`           // 微信id
    Email     string `json:"email" gorm:"comment:邮箱"`               // 邮箱
    Password  string `json:"password" gorm:"comment:密码"`            // 密码
    Authority string `json:"authority" gorm:"comment:权限;default:3"` // 权限

    CShopID string `json:"-"`
    CShop   CShop  `json:"-"`
}

jwt中间件

package middleware

import (
    "context"
    "errors"
    "goo/global"
    "goo/response"
    "goo/utils"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v4"
)

func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.Request.Header.Get("x-token")
        if token == "" {
            response.FailWithMessage("auth", c)
            c.Abort()
            return
        } else {

            j := utils.NewJWT()
            claims, err := j.ParseToken(token)
            if err != nil {
                if errors.Is(err, utils.ErrTokenExpired) {
                    response.FailWithMessage("token授权已过期", c)
                    c.Abort()
                    return
                }
                response.FailWithMessage(err.Error(), c)
                c.Abort()
                return
            }

            resRedis, errRedis := global.GVA_REDIS.Get(context.Background(), claims.BaseClaims.ID).Result()
            if errRedis != nil {
                response.FailWithMessage("redis时效过期, 请重新登录", c)
                c.Abort()
                return
            }

            if resRedis != token {
                response.FailWithMessage("您已在其他地方登录, 请重新登录", c)
                c.Abort()
                return
            }

            time11 := claims.ExpiresAt.Unix() - time.Now().Unix()
            if time11 < claims.BufferTime {
                ep, _ := utils.ParseDuration(global.GVA_VP.GetString("jwt.expires-time"))
                claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(ep))
                newToken, _ := j.CreateTokenByOldToken(token, *claims)
                // c.Header("new-token", newToken)
                errRedis := global.GVA_REDIS.Set(context.Background(), claims.BaseClaims.ID, newToken, ep).Err()
                if errRedis != nil {
                    response.FailWithMessage("err redis set", c)
                    c.Abort()
                    return
                }
            }
            //  验证jwt剩余时间
            c.Set("token_user_id", claims.BaseClaims.ID)
            c.Next()
        }
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇