pgx.ts
16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
import { defineStore } from 'pinia'
export type UserRole = 'sampler' | 'checkPointer'
export type OrderStatus = 'draft' | 'submitted' | 'received' | 'testing' | 'reported' | 'failed'
export type ProductType =
// 孕前系列
| 'ECS' | 'ECS-Pro' | 'AZF' | 'Y-Poly'
// 胚胎植入前系列
| 'PGT-M' | 'PGT-A' | 'PGT-A-4M' | 'PGT-SR' | 'PGT-MA' | 'PGT-HLA' | 'Array-PGT'
// 产前系列
| 'NIPT' | 'NIPT-Plus' | 'CNV-Prenatal' | 'Karyotype' | 'WES-Prenatal' | 'WGS-Prenatal' | 'Array-CGH'
// 遗传诊断系列
| 'WES' | 'WGS' | 'BRCA' | 'Panel' | 'CNV-seq' | 'CMA' | 'Methylation' | 'GTV'
export interface Sample {
id: string
sampleNo: string
sampleType: string
relation: string
barcode: string
samplingDate: string
detectionPlatform?: string
libraryDate?: string
sequenceDate?: string
q30?: number
duplicate?: number
readsM?: number
mapRatio?: number
}
export interface ReportFile {
id: string
name: string
url: string
date: string
source?: string
reviewStatus?: 'pending' | 'approved' | 'rejected'
geneticNote?: string
finalNote?: string
archivedPath?: string
emailSent?: boolean
}
export interface Order {
id: string
orderNo: string
productType: ProductType
patientName: string
hospitalName: string
submitter: string
phone: string
submitDate: string
status: OrderStatus
editable: boolean
hasReport: boolean
failed: boolean
failReason?: string
urgency: boolean
urgencyReason?: string
limsStatus: 'pending' | 'pushed' | 'failed'
samples: Sample[]
expressNo?: string
reportFiles?: ReportFile[]
specialNeeds?: string
// PGT-M 专属
femaleAge?: number
maleAge?: number
disease?: string
gene?: string
inheritMode?: string
embryoCount?: number
// 报告邮寄地址
reportRecipient?: string
reportPhone?: string
reportAddress?: string
reportEmail?: string
}
export interface BioinfoRun {
id: string
groupName: string
appType: 'PGS' | 'PGD' | 'AZF' | 'ECS' | 'Array-PGT'
status: 'ready' | 'running' | 'success' | 'failed'
createDate: string
orderId: string
notifyLims?: boolean
results?: {
chromosome?: string
cnvConclusion?: string
aneuploidyConclusion?: string
q30?: number
clusterPf?: number
duplicate?: number
}
}
export interface Patient {
id: string
name: string
idCard?: string
phone?: string
gender?: string
hospitalName?: string
// 夫妻信息(PGT系列)
spouseName?: string
femaleAge?: number
maleAge?: number
// 遗传背景(PGT-M复用)
disease?: string
gene?: string
inheritMode?: string
knownVariant?: string
childStatus?: string
hpoTerms?: string[]
// 历史
orderIds: string[]
lastOrderDate?: string
lastProductType?: string
createdAt: string
}
export interface BatchOrder {
id: string
name: string
productType: ProductType
hospital: string
region: string
createDate: string
status: 'draft' | 'submitted' | 'reviewed'
orderCount: number
}
export const usePGXStore = defineStore('pgx', () => {
const currentRole = ref<UserRole>('sampler')
const orders = ref<Order[]>([
{
id: '1', orderNo: 'JB2026030001', productType: 'PGT-M',
patientName: '张晓华', hospitalName: '北京协和医院',
submitter: '王医生', phone: '13800138001',
submitDate: '2026-03-01', status: 'reported',
editable: false, hasReport: true, failed: false, urgency: false,
limsStatus: 'pushed', femaleAge: 32, maleAge: 35,
disease: '脊髓性肌萎缩症(SMA)', gene: 'SMN1',
inheritMode: '常染色体隐性遗传', embryoCount: 4,
samples: [
{ id: 's1', sampleNo: 'S001', sampleType: '外周血', relation: '女方', barcode: 'BC2026030001', samplingDate: '2026-02-28', detectionPlatform: 'Illumina', q30: 91.2, duplicate: 12.3, readsM: 15.6, mapRatio: 98.5 },
{ id: 's2', sampleNo: 'S002', sampleType: '外周血', relation: '男方', barcode: 'BC2026030002', samplingDate: '2026-02-28', detectionPlatform: 'Illumina', q30: 92.1, duplicate: 11.8, readsM: 14.9, mapRatio: 99.1 },
{ id: 's3', sampleNo: 'S003', sampleType: '活检样本', relation: '胚胎1', barcode: 'BC2026030003', samplingDate: '2026-02-28', q30: 88.5, duplicate: 15.2, readsM: 10.2, mapRatio: 97.3 },
{ id: 's4', sampleNo: 'S004', sampleType: '活检样本', relation: '胚胎2', barcode: 'BC2026030004', samplingDate: '2026-02-28', q30: 89.3, duplicate: 14.1, readsM: 11.1, mapRatio: 98.0 },
],
expressNo: 'SF1234567890',
reportFiles: [
{ id: 'r1', name: 'PGT-M报告_张晓华_v1.pdf', url: '#', date: '2026-03-25', source: '生信平台', reviewStatus: 'approved', emailSent: true, archivedPath: '/reports/2026/03/JB2026030001.pdf' }
],
reportRecipient: '王医生', reportPhone: '13800138001', reportEmail: 'wang@pumch.cn'
},
{
id: '2', orderNo: 'JB2026030002', productType: 'PGT-A',
patientName: '李美丽', hospitalName: '上海妇产科医院',
submitter: '陈医生', phone: '13900139002',
submitDate: '2026-03-05', status: 'testing',
editable: false, hasReport: false, failed: false, urgency: true,
urgencyReason: '移植周期临近,请加急处理',
limsStatus: 'pushed', femaleAge: 38,
samples: [
{ id: 's5', sampleNo: 'S005', sampleType: '活检样本', relation: '胚胎1', barcode: 'BC2026030005', samplingDate: '2026-03-04', detectionPlatform: 'Illumina', q30: 87.2, duplicate: 16.5, readsM: 9.8, mapRatio: 96.8 },
{ id: 's6', sampleNo: 'S006', sampleType: '活检样本', relation: '胚胎2', barcode: 'BC2026030006', samplingDate: '2026-03-04', q30: 90.1, duplicate: 13.2, readsM: 12.3, mapRatio: 98.2 },
{ id: 's7', sampleNo: 'S007', sampleType: '活检样本', relation: '胚胎3', barcode: 'BC2026030007', samplingDate: '2026-03-04' },
]
},
{
id: '3', orderNo: 'JB2026030003', productType: 'ECS',
patientName: '刘婷婷', hospitalName: '广州市妇女儿童医疗中心',
submitter: '刘医生', phone: '13700137003',
submitDate: '2026-03-10', status: 'received',
editable: false, hasReport: false, failed: false, urgency: false,
limsStatus: 'pushed',
samples: [
{ id: 's8', sampleNo: 'S008', sampleType: '外周血', relation: '女方', barcode: 'BC2026030008', samplingDate: '2026-03-09' },
]
},
{
id: '4', orderNo: 'JB2026030004', productType: 'WES',
patientName: '赵小明', hospitalName: '北京儿童医院',
submitter: '赵医生', phone: '13600136004',
submitDate: '2026-03-15', status: 'failed',
editable: false, hasReport: false, failed: true,
failReason: '样本量不足,DNA提取失败,请重新采集样本',
urgency: false,
limsStatus: 'pushed',
samples: [
{ id: 's9', sampleNo: 'S009', sampleType: '外周血', relation: '先证者', barcode: 'BC2026030009', samplingDate: '2026-03-14' },
]
},
{
id: '5', orderNo: 'JB2026030005', productType: 'PGT-M',
patientName: '孙雅丽', hospitalName: '复旦大学附属妇产科医院',
submitter: '孙医生', phone: '13500135005',
submitDate: '2026-03-20', status: 'submitted',
editable: false, hasReport: false, failed: false, urgency: true,
urgencyReason: '患者情况特殊,请优先处理',
limsStatus: 'pending',
samples: [
{ id: 's10', sampleNo: 'S010', sampleType: '外周血', relation: '女方', barcode: 'BC2026030010', samplingDate: '2026-03-19' },
{ id: 's11', sampleNo: 'S011', sampleType: '外周血', relation: '男方', barcode: 'BC2026030011', samplingDate: '2026-03-19' },
]
},
{
id: '6', orderNo: 'JB2026030006', productType: 'AZF',
patientName: '周建国', hospitalName: '中山大学附属第一医院',
submitter: '周医生', phone: '13400134006',
submitDate: '2026-03-25', status: 'draft',
editable: true, hasReport: false, failed: false, urgency: false,
limsStatus: 'pending',
samples: []
},
{
id: '7', orderNo: 'JB2026030007', productType: 'BRCA',
patientName: '吴小红', hospitalName: '中国医学科学院肿瘤医院',
submitter: '吴医生', phone: '13300133007',
submitDate: '2026-03-28', status: 'submitted',
editable: false, hasReport: false, failed: false, urgency: false,
limsStatus: 'pushed',
samples: [
{ id: 's12', sampleNo: 'S012', sampleType: '外周血', relation: '先证者', barcode: 'BC2026030012', samplingDate: '2026-03-27' },
]
},
])
const bioinfoRuns = ref<BioinfoRun[]>([
{ id: 'b1', groupName: '张晓华-PGT-M-20260302', appType: 'PGD', status: 'success', createDate: '2026-03-02', orderId: '1', notifyLims: true, results: { cnvConclusion: '胚胎1:整倍体,胚胎2:整倍体', q30: 91.2, clusterPf: 95.3, duplicate: 12.3 } },
{ id: 'b2', groupName: '李美丽-PGT-A-20260306', appType: 'PGS', status: 'running', createDate: '2026-03-06', orderId: '2', results: { q30: 87.2, clusterPf: 93.1, duplicate: 16.5 } },
{ id: 'b3', groupName: '刘婷婷-ECS-20260311', appType: 'ECS', status: 'ready', createDate: '2026-03-11', orderId: '3' },
{ id: 'b4', groupName: '赵小明-WES-20260316', appType: 'PGS', status: 'failed', createDate: '2026-03-16', orderId: '4' },
])
const patients = ref<Patient[]>([
{
id: 'p1', name: '张晓华', phone: '13800138001', gender: '女', hospitalName: '北京协和医院',
femaleAge: 32, maleAge: 35, spouseName: '张某某',
disease: '脊髓性肌萎缩症(SMA)', gene: 'SMN1', inheritMode: '常染色体隐性遗传',
knownVariant: 'del exon 7-8', childStatus: '有先证者在世',
hpoTerms: ['HP:0002011', 'HP:0001324'],
orderIds: ['1'], lastOrderDate: '2026-03-01', lastProductType: 'PGT-M',
createdAt: '2026-03-01',
},
{
id: 'p2', name: '李美丽', phone: '13900139002', gender: '女', hospitalName: '上海妇产科医院',
femaleAge: 38, maleAge: 40,
orderIds: ['2'], lastOrderDate: '2026-03-05', lastProductType: 'PGT-A',
createdAt: '2026-03-05',
},
{
id: 'p3', name: '刘婷婷', phone: '13700137003', gender: '女', hospitalName: '广州市妇女儿童医疗中心',
femaleAge: 29,
orderIds: ['3'], lastOrderDate: '2026-03-10', lastProductType: 'ECS',
createdAt: '2026-03-10',
},
{
id: 'p4', name: '孙雅丽', phone: '13500135005', gender: '女', hospitalName: '复旦大学附属妇产科医院',
femaleAge: 33, maleAge: 36,
disease: '地中海贫血', gene: 'HBB', inheritMode: '常染色体隐性遗传',
orderIds: ['5'], lastOrderDate: '2026-03-20', lastProductType: 'PGT-M',
createdAt: '2026-03-20',
},
{
id: 'p5', name: '吴小红', phone: '13300133007', gender: '女', hospitalName: '中国医学科学院肿瘤医院',
femaleAge: 42,
orderIds: ['7'], lastOrderDate: '2026-03-28', lastProductType: 'BRCA',
createdAt: '2026-03-28',
},
])
const batchOrders = ref<BatchOrder[]>([
{ id: 'bt1', name: '协和医院-3月批次', productType: 'PGT-A', hospital: '北京协和医院', region: '华北', createDate: '2026-03-01', status: 'reviewed', orderCount: 12 },
{ id: 'bt2', name: '上海妇产-PGT-M批次', productType: 'PGT-M', hospital: '上海妇产科医院', region: '华东', createDate: '2026-03-10', status: 'submitted', orderCount: 8 },
{ id: 'bt3', name: '广州妇儿-ECS筛查', productType: 'ECS', hospital: '广州市妇女儿童医疗中心', region: '华南', createDate: '2026-03-20', status: 'draft', orderCount: 5 },
])
// 计算属性
const draftOrders = computed(() => orders.value.filter(o => o.status === 'draft'))
const submittedOrders = computed(() => orders.value.filter(o => o.status === 'submitted'))
const receivedOrders = computed(() => orders.value.filter(o => o.status === 'received'))
const testingOrders = computed(() => orders.value.filter(o => o.status === 'testing'))
const reportedOrders = computed(() => orders.value.filter(o => o.status === 'reported' && o.hasReport))
const failedOrders = computed(() => orders.value.filter(o => o.failed))
const stats = computed(() => ({
total: orders.value.length,
draft: draftOrders.value.length,
submitted: submittedOrders.value.length,
received: receivedOrders.value.length,
testing: testingOrders.value.length,
reported: reportedOrders.value.length,
failed: failedOrders.value.length,
limsError: orders.value.filter(o => o.limsStatus === 'failed').length,
}))
function switchRole(role: UserRole) { currentRole.value = role }
function addOrder(order: Omit<Order, 'id'>) {
const newOrder = { ...order, id: Date.now().toString() }
orders.value.unshift(newOrder as Order)
return newOrder
}
function updateOrder(id: string, updates: Partial<Order>) {
const idx = orders.value.findIndex(o => o.id === id)
if (idx !== -1) orders.value[idx] = { ...orders.value[idx], ...updates }
}
function submitOrder(id: string) {
updateOrder(id, { status: 'submitted', editable: false, limsStatus: 'pushed' })
}
function receiveOrder(id: string) {
updateOrder(id, { status: 'received' })
}
function deleteOrder(id: string) {
const idx = orders.value.findIndex(o => o.id === id)
if (idx !== -1) orders.value.splice(idx, 1)
}
function addBioRun(run: Omit<BioinfoRun, 'id'>) {
const newRun = { ...run, id: 'b' + Date.now() }
bioinfoRuns.value.unshift(newRun as BioinfoRun)
return newRun
}
function updateBioRun(id: string, updates: Partial<BioinfoRun>) {
const idx = bioinfoRuns.value.findIndex(r => r.id === id)
if (idx !== -1) bioinfoRuns.value[idx] = { ...bioinfoRuns.value[idx], ...updates }
}
function getOrderById(id: string) {
return orders.value.find(o => o.id === id)
}
function searchPatients(keyword: string): Patient[] {
if (!keyword) return patients.value.slice(0, 8)
const kw = keyword.toLowerCase()
return patients.value.filter(p =>
p.name.includes(keyword) ||
p.phone?.includes(keyword) ||
p.idCard?.includes(keyword)
).slice(0, 10)
}
function upsertPatient(data: Partial<Patient> & { name: string }, orderId: string) {
const existing = patients.value.find(p => p.name === data.name && p.phone === data.phone)
if (existing) {
existing.orderIds.push(orderId)
existing.lastOrderDate = data.lastOrderDate || new Date().toISOString().split('T')[0]
existing.lastProductType = data.lastProductType
// 更新可能变化的字段
if (data.femaleAge) existing.femaleAge = data.femaleAge
if (data.hospitalName) existing.hospitalName = data.hospitalName
if (data.disease) existing.disease = data.disease
if (data.gene) existing.gene = data.gene
} else {
patients.value.unshift({
id: 'p' + Date.now(),
name: data.name,
phone: data.phone,
gender: data.gender,
hospitalName: data.hospitalName,
femaleAge: data.femaleAge,
maleAge: data.maleAge,
spouseName: data.spouseName,
disease: data.disease,
gene: data.gene,
inheritMode: data.inheritMode,
knownVariant: data.knownVariant,
childStatus: data.childStatus,
hpoTerms: data.hpoTerms,
orderIds: [orderId],
lastOrderDate: new Date().toISOString().split('T')[0],
lastProductType: data.lastProductType,
createdAt: new Date().toISOString().split('T')[0],
})
}
}
function addReportFile(orderId: string, file: ReportFile) {
const order = getOrderById(orderId)
if (order) {
if (!order.reportFiles) order.reportFiles = []
order.reportFiles.push(file)
updateOrder(orderId, { hasReport: true, status: 'reported', reportFiles: order.reportFiles })
}
}
return {
currentRole, orders, bioinfoRuns, batchOrders,
draftOrders, submittedOrders, receivedOrders, testingOrders, reportedOrders, failedOrders, stats,
switchRole, addOrder, updateOrder, submitOrder, receiveOrder, deleteOrder,
addBioRun, updateBioRun, getOrderById, addReportFile,
patients, searchPatients, upsertPatient
}
})