第一篇我们已经搭建了脚手架,并且通过浏览器可以看到shadcn的组件库的一个效果。有了脚手架下一步就是建表,过去我们用低代码开发直接创建数据模型,我们用pg的话就用命令来管理表的创建。

1 安装 ORM 核心套件

输入如下命令来完成初始化

npm install prisma --save-dev
npm install @prisma/client

在这里插入图片描述
安装完成后,初始化 Prisma:

npx prisma init

在这里插入图片描述

2. 配置数据库连接

打开项目根目录下的 .env 文件。添加我们真实的访问地址

DATABASE_URL="postgresql://postgres:123456@localhost:5432/hospital_db"

在这里插入图片描述

3. 编写数据模型

一套管理系统的地基是组织和用户,这两个数据是所有业务的出发点,我们先来创建机构表和用户表,设计如下表结构

// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
}

// ==========================================
// 1. 枚举定义 (字典管理)
// ==========================================

// 人员生命周期状态
enum EmployeeStatus {
  PRE_ENTRY   // 待入职 (新考录)
  ACTIVE      // 在职 (正常)
  PROBATION   // 试用期
  SECONDED    // 借调/外派 (关系在医院,人出去了)
  LEAVE       // 长假 (产假/病假)
  RETIRED     // 退休 (但可能返聘)
  RESIGNED    // 离职 (完全脱离)
  DISMISSED   // 开除
}

// 科室/部门类型
enum DeptType {
  CLINICAL    // 临床科室 (心内科、骨科)
  MEDICAL_TECH // 医技科室 (影像科、检验科)
  ADMIN       // 职能处室 (人事处、医务处)
  RESEARCH    // 科研机构 (实验室、研究所)
  TEACHING    // 教学机构 (教研室)
  LOGISTICS   // 后勤保障
}

// 聘用形式
enum EmploymentType {
  ESTABLISHED // 在编 (正式编制)
  CONTRACT    // 合同制
  TEMPORARY   // 临时工/劳务派遣
  INTERN      // 实习生/规培生
}

// ==========================================
// 2. 组织架构模型 (Organization)
// ==========================================

model Department {
  id        String   @id @default(uuid())
  name      String   // 科室名称,如 "心血管内科"
  code      String?  @unique // 科室编码 (HIS系统对接用)
  type      DeptType @default(CLINICAL)
  
  // 树状结构设计:上级部门
  parentId  String?
  parent    Department?   @relation("DeptHierarchy", fields: [parentId], references: [id])
  children  Department[]  @relation("DeptHierarchy")

  // 部门包含的人员任职记录
  placements Placement[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("departments")
}

// ==========================================
// 3. 人员核心模型 (Personnel)
// ==========================================

model Employee {
  id          String   @id @default(uuid())
  
  // --- 基础信息 ---
  name        String   // 真实姓名
  employeeId  String   @unique // 工号 (全院唯一标识)
  idCard      String   @unique // 身份证号 (用于唯一性校验)
  phone       String?
  avatar      String?

  // --- 账号信息 (可分离,此处为简化合并) ---
  username    String?  @unique // 登录账号 (部分后勤人员可能没账号)
  password    String?          // 加密密码
  
  // --- 状态与属性 ---
  status      EmployeeStatus @default(PRE_ENTRY) // 生命周期状态
  hireType    EmploymentType @default(CONTRACT) // 编制类型
  joinDate    DateTime?      // 入职时间
  
  // --- 关系 ---
  // 一个员工可以有多个任职 (主职+兼职)
  placements  Placement[]
  
  // 生命周期变动日志 (升职、调岗、退休记录)
  logs        EmployeeLog[]

  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@map("employees")
}

// ==========================================
// 4. 任职关系模型 (The Link) - 解决多部门兼职
// ==========================================

model Placement {
  id           String   @id @default(uuid())
  
  employeeId   String
  departmentId String
  
  title        String   // 在该部门的职务 (如: 主任、主治医师、干事)
  isPrimary    Boolean  @default(false) // 是否为主部门 (一个人只能有一个主部门)
  
  startDate    DateTime @default(now()) // 任职开始时间
  endDate      DateTime?                // 任职结束时间 (空代表至今)

  employee     Employee   @relation(fields: [employeeId], references: [id], onDelete: Cascade)
  department   Department @relation(fields: [departmentId], references: [id])

  // 复合唯一键:防止同一人在同一部门重复添加活跃任职
  @@unique([employeeId, departmentId]) 
  @@map("placements")
}

// ==========================================
// 5. 生命周期变动日志 (Audit Trail)
// ==========================================

enum ChangeType {
  ENTRY       // 入职
  TRANSFER    // 转岗/调动
  PROMOTION   // 晋升
  SECONDMENT  // 借调
  RETIREMENT  // 退休
  EXIT        // 离职
  REHIRE      // 返聘
}

model EmployeeLog {
  id          String     @id @default(uuid())
  employeeId  String
  type        ChangeType
  
  reason      String?    // 变动原因
  operator    String?    // 操作人 (HR的账号)
  
  createdAt   DateTime   @default(now()) // 变动发生时间

  employee    Employee   @relation(fields: [employeeId], references: [id])

  @@map("employee_logs")
}

在这里插入图片描述

4. 执行“同步”魔法

现在代码写好了,我们要把它变成真正的数据库表。 回到终端,运行以下命令:

npx prisma migrate dev --name init

在这里插入图片描述
执行成功后可以到我们的pgAdmin查看相关的表结构
在这里插入图片描述

5. 封装数据库单例

在 Next.js 开发环境中(npm run dev),由于热更新机制,如果直接在代码里 new PrismaClient(),每次文件修改都会新建连接,导致数据库连接数瞬间耗尽报错。

我们需要创建一个全局单例来管理连接。

新建文件 src/lib/db.ts:

import { PrismaClient } from "@prisma/client";

// 声明全局变量类型,防止 TS 报错
const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

// 如果已有实例则复用,否则新建
export const db = globalForPrisma.prisma ?? new PrismaClient();

// 非生产环境下,将实例保存到全局变量中
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;

6. 启动可视化管理

运行如下命令

npx prisma studio

可以在浏览器里看到我们创建的表
在这里插入图片描述

总结

本篇我们介绍了安装数据库软件,利用ORM工具来完成数据库表的创建,后续有了数据就可以开发我们的业务逻辑了。

更多推荐