This commit is contained in:
EL 2026-01-22 14:06:39 +08:00
parent 1352d62a5d
commit 6e144fc657
7 changed files with 691 additions and 16 deletions

View File

@ -0,0 +1,67 @@
CREATE TABLE "department" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"hospital" uuid NOT NULL,
"description" varchar,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "doctor" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"username" varchar NOT NULL,
"password" varchar NOT NULL,
"phone" varchar NOT NULL,
"hospitalId" uuid NOT NULL,
"departmentId" uuid,
"groupId" uuid,
"roleId" uuid NOT NULL,
"isDoctor" boolean DEFAULT true NOT NULL,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "doctor_username_unique" UNIQUE("username")
);
--> statement-breakpoint
CREATE TABLE "group" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"departmentId" uuid NOT NULL,
"description" varchar,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "hospital" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"description" text,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patient" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"chiefDoctorId" uuid NOT NULL,
"sharedWith" text,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "role" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"code" varchar NOT NULL,
"description" text,
"permissions" text,
"isActive" boolean DEFAULT true NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "role_name_unique" UNIQUE("name"),
CONSTRAINT "role_code_unique" UNIQUE("code")
);

View File

@ -0,0 +1,426 @@
{
"id": "89cec236-6382-4cab-b163-f1fbd0cfdddb",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.department": {
"name": "department",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"hospital": {
"name": "hospital",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.doctor": {
"name": "doctor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"phone": {
"name": "phone",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"hospitalId": {
"name": "hospitalId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"departmentId": {
"name": "departmentId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"groupId": {
"name": "groupId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"roleId": {
"name": "roleId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"isDoctor": {
"name": "isDoctor",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"doctor_username_unique": {
"name": "doctor_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.group": {
"name": "group",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"departmentId": {
"name": "departmentId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.hospital": {
"name": "hospital",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.patient": {
"name": "patient",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"chiefDoctorId": {
"name": "chiefDoctorId",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"sharedWith": {
"name": "sharedWith",
"type": "text",
"primaryKey": false,
"notNull": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.role": {
"name": "role",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"permissions": {
"name": "permissions",
"type": "text",
"primaryKey": false,
"notNull": false
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"role_name_unique": {
"name": "role_name_unique",
"nullsNotDistinct": false,
"columns": [
"name"
]
},
"role_code_unique": {
"name": "role_code_unique",
"nullsNotDistinct": false,
"columns": [
"code"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1768760286048,
"tag": "0000_stormy_falcon",
"breakpoints": true
}
]
}

View File

@ -1,4 +1,4 @@
import { eq, inArray } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "../index"; import { db } from "../index";
import { patientTable } from "../modules"; import { patientTable } from "../modules";
@ -47,7 +47,7 @@ export const list = async ({ doctorId }: { doctorId?: string } = {}) => {
return patients; return patients;
} }
// 科室主任可以看到本科室所有医生的患者 // 科室主任可以看到本科室所有医生的患者 + 分享给自己的患者
if (currentDoctor.role?.code === "DIRECTOR" && currentDoctor.departmentId) { if (currentDoctor.role?.code === "DIRECTOR" && currentDoctor.departmentId) {
// 获取本科室所有医生 // 获取本科室所有医生
const departmentDoctors = await db.query.doctorTable.findMany({ const departmentDoctors = await db.query.doctorTable.findMany({
@ -57,7 +57,8 @@ export const list = async ({ doctorId }: { doctorId?: string } = {}) => {
if (doctorIds.length === 0) return []; if (doctorIds.length === 0) return [];
const patients = await db.query.patientTable.findMany({ // 获取本科室所有医生创建的患者
const allPatients = await db.query.patientTable.findMany({
with: { with: {
chiefDoctor: { chiefDoctor: {
with: { with: {
@ -65,13 +66,39 @@ export const list = async ({ doctorId }: { doctorId?: string } = {}) => {
}, },
}, },
}, },
where: (table, { inArray }) => inArray(table.chiefDoctorId, doctorIds),
orderBy: (table, { asc }) => asc(table.createdAt), orderBy: (table, { asc }) => asc(table.createdAt),
}); });
return patients;
// 过滤出本科室医生创建的患者 + 分享给自己的患者
const filteredPatients = allPatients.filter((patient: any) => {
// 本科室医生创建的患者
if (doctorIds.includes(patient.chiefDoctorId)) return true;
// 分享给自己的患者
if (patient.sharedWith) {
try {
const sharedWith = JSON.parse(patient.sharedWith);
return sharedWith.includes(doctorId);
} catch (error) {
console.error("解析 sharedWith 失败:", patient.sharedWith, error);
return false;
}
}
return false;
});
console.log("科室主任看到的患者数量:", {
total: allPatients.length,
filtered: filteredPatients.length,
doctorId,
departmentId: currentDoctor.departmentId,
});
return filteredPatients;
} }
// 组长可以看到本小组所有医生的患者 // 组长可以看到本小组所有医生的患者 + 分享给自己的患者
if (currentDoctor.role?.code === "GROUP_LEADER" && currentDoctor.groupId) { if (currentDoctor.role?.code === "GROUP_LEADER" && currentDoctor.groupId) {
// 获取本小组所有医生 // 获取本小组所有医生
const groupDoctors = await db.query.doctorTable.findMany({ const groupDoctors = await db.query.doctorTable.findMany({
@ -81,7 +108,8 @@ export const list = async ({ doctorId }: { doctorId?: string } = {}) => {
if (doctorIds.length === 0) return []; if (doctorIds.length === 0) return [];
const patients = await db.query.patientTable.findMany({ // 获取本小组所有医生创建的患者
const allPatients = await db.query.patientTable.findMany({
with: { with: {
chiefDoctor: { chiefDoctor: {
with: { with: {
@ -89,14 +117,40 @@ export const list = async ({ doctorId }: { doctorId?: string } = {}) => {
}, },
}, },
}, },
where: (table, { inArray }) => inArray(table.chiefDoctorId, doctorIds),
orderBy: (table, { asc }) => asc(table.createdAt), orderBy: (table, { asc }) => asc(table.createdAt),
}); });
return patients;
// 过滤出本小组医生创建的患者 + 分享给自己的患者
const filteredPatients = allPatients.filter((patient: any) => {
// 本小组医生创建的患者
if (doctorIds.includes(patient.chiefDoctorId)) return true;
// 分享给自己的患者
if (patient.sharedWith) {
try {
const sharedWith = JSON.parse(patient.sharedWith);
return sharedWith.includes(doctorId);
} catch (error) {
console.error("解析 sharedWith 失败:", patient.sharedWith, error);
return false;
}
}
return false;
});
console.log("组长看到的患者数量:", {
total: allPatients.length,
filtered: filteredPatients.length,
doctorId,
groupId: currentDoctor.groupId,
});
return filteredPatients;
} }
// 普通医生只能看到自己的患者 // 普通医生只能看到自己的患者 + 分享给自己的患者
const patients = await db.query.patientTable.findMany({ const allPatients = await db.query.patientTable.findMany({
with: { with: {
chiefDoctor: { chiefDoctor: {
with: { with: {
@ -104,10 +158,35 @@ export const list = async ({ doctorId }: { doctorId?: string } = {}) => {
}, },
}, },
}, },
where: (table, { eq }) => eq(table.chiefDoctorId, doctorId),
orderBy: (table, { asc }) => asc(table.createdAt), orderBy: (table, { asc }) => asc(table.createdAt),
}); });
return patients;
// 过滤出分享给自己的患者
const filteredPatients = allPatients.filter((patient: any) => {
// 自己创建的患者
if (patient.chiefDoctorId === doctorId) return true;
// 分享给自己的患者
if (patient.sharedWith) {
try {
const sharedWith = JSON.parse(patient.sharedWith);
return sharedWith.includes(doctorId);
} catch (error) {
console.error("解析 sharedWith 失败:", patient.sharedWith, error);
return false;
}
}
return false;
});
console.log("普通医生看到的患者数量:", {
total: allPatients.length,
filtered: filteredPatients.length,
doctorId,
});
return filteredPatients;
}; };
// 获取单个患者 // 获取单个患者
@ -192,3 +271,65 @@ export const remove = async ({ params }: { params: { id: string } }) => {
} }
return patient[0]; return patient[0];
}; };
// 分享患者给其他医生
export const share = async ({
params,
body,
currentDoctorId,
}: {
params: { id: string };
body: { sharedWith: string[] };
currentDoctorId?: string;
}) => {
// 获取患者信息
const patient = await db.query.patientTable.findFirst({
where: (table, { eq }) => eq(table.id, params.id),
with: {
chiefDoctor: true,
},
});
if (!patient) {
throw new Error("患者不存在");
}
// 验证权限:只有患者的主刀医生才能分享
if (patient.chiefDoctorId !== currentDoctorId) {
throw new Error("无权限分享此患者");
}
// 验证:只能分享给同医院的医生
const currentDoctor = await db.query.doctorTable.findFirst({
where: (table, { eq }) => eq(table.id, currentDoctorId!),
});
if (!currentDoctor) {
throw new Error("当前医生不存在");
}
// 验证所有要分享的医生都在同一家医院
if (body.sharedWith && body.sharedWith.length > 0) {
const targetDoctors = await db.query.doctorTable.findMany({
where: (table, { inArray }) => inArray(table.id, body.sharedWith),
});
for (const targetDoctor of targetDoctors) {
if (targetDoctor.hospitalId !== currentDoctor.hospitalId) {
throw new Error(`只能分享给同医院的医生:${targetDoctor.name} 不在同一家医院`);
}
}
}
// 将分享的医生ID列表转换为JSON字符串存储
const updatedPatient = await db
.update(patientTable)
.set({
sharedWith: JSON.stringify(body.sharedWith),
updatedAt: new Date(),
})
.where(eq(patientTable.id, params.id))
.returning();
return updatedPatient[0];
};

View File

@ -1,9 +1,10 @@
import { uuid, varchar, timestamp } from "drizzle-orm/pg-core"; import { uuid, varchar, timestamp, text } from "drizzle-orm/pg-core";
export const Patient = { export const Patient = {
id: uuid().primaryKey().defaultRandom(), // 主键UUID id: uuid().primaryKey().defaultRandom(), // 主键UUID
name: varchar().notNull(), // 患者姓名 name: varchar().notNull(), // 患者姓名
chiefDoctorId: uuid().notNull(), // 主刀医生ID虚拟外键 chiefDoctorId: uuid().notNull(), // 主刀医生ID虚拟外键
sharedWith: text(), // 分享给其他医生的ID列表JSON数组格式存储
createdAt: timestamp().notNull().defaultNow(), // 创建时间 createdAt: timestamp().notNull().defaultNow(), // 创建时间
updatedAt: timestamp().notNull().defaultNow(), // 更新时间 updatedAt: timestamp().notNull().defaultNow(), // 更新时间
}; };

View File

@ -45,4 +45,31 @@ export const patientRoutes = new Elysia({ prefix: "/patient" })
}), }),
}, },
) )
.delete("/:id", ({ params }) => patientController.remove({ params })); .delete("/:id", ({ params }) => patientController.remove({ params }))
.post(
"/:id/share",
async ({ params, body, headers, jwt }) => {
// 从 Authorization header 获取 token
const authHeader = headers.authorization;
const token = authHeader?.replace("Bearer ", "");
let currentDoctorId;
if (token) {
try {
const payload = await jwt.verify(token);
if (payload && typeof payload === "object" && "userId" in payload) {
currentDoctorId = payload.userId as string;
}
} catch (error) {
throw new Error("无效的token");
}
}
return patientController.share({ params, body, currentDoctorId });
},
{
body: t.Object({
sharedWith: t.Array(t.String()),
}),
}
);

View File

@ -6,7 +6,7 @@ const client = treaty<Api>("localhost:3000");
const res = await client.api.patient.get({ const res = await client.api.patient.get({
headers: { headers: {
Authorization: Authorization:
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJiMzcwMzgwNi1jNDRjLTRkMTEtYWMzYy03NDAwYzViODYyNmEiLCJyb2xlSWQiOiIzOTA0YmQ0OC01MzdhLTQ0MzgtODE4Yi01NDJlYjcyNjA4YmUiLCJyb2xlQ29kZSI6IkdST1VQX0xFQURFUiIsImlhdCI6MTc2ODc1OTM1Mn0.IkoYXCQy44HFG2Y7dZWHGJAmieYEuCSqAZE0oG46z40", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI3MDE5OGMyZS1lM2EyLTRhZTUtOTdjMC01YWJlNGE0MmU3ZWQiLCJyb2xlSWQiOiI5MWU4MDE4Mi05MTcwLTRjMDEtYmYxNC1hNDQwM2FjZTAyMGIiLCJyb2xlQ29kZSI6IkRPQ1RPUiIsImlhdCI6MTc2ODc2MDY5MH0.TAjbfbP5nszzw9-keufpBcjbI45UnZm5hHJp5sHStRg",
}, },
}); });