Skip to content

实现 孕期与预产期计算 工具

分类:general | 标签:孕期、预产期、产检 计算预产期孕周与产检时间轴

2.1 功能说明

计算预产期孕周与产检时间轴

2.1.1 使用指南

功能说明

根据末次月经第一天(LMP)推算预产期、当前孕周孕天,并生成一份完整的产检时间轴, 标出每一项检查的建议时间窗与当前状态。也支持用 B 超测得的孕周反推,适合月经周期不规律或记不清末次月经的情况。 全部计算在浏览器本地完成,不上传任何信息。

计算规则

预产期采用国际通用的内格勒规则(Naegele's rule):预产期 = 末次月经第一天 + 280 天(即 40 周)。 这一规则默认月经周期为 28 天、排卵发生在第 14 天。

民间常说的「月份加 9(或减 3)、日期加 7」是同一规则的口算简化版。由于各月天数不同, 两种算法在部分月份会相差 1–3 天,本工具统一采用更精确的 280 天计法。

若你的周期不是 28 天,工具会自动做周期校正:预产期 = 末次月经 + 280 天 +(实际周期 − 28)。 例如周期 35 天,排卵推迟约 7 天,预产期相应顺延 7 天。周期短于 28 天则提前。

孕周按「满周 + 天」表示,孕 0 周 0 天即末次月经当天。临床上说的「怀孕 40 周」是从末次月经算起, 比实际受精时间早约 2 周,这是产科通用口径,不是计算错误。

怎么用

1. 「按末次月经」页签:选择末次月经第一天,填入平时的月经周期天数(默认 28 天);
2. 「按 B 超反推」页签:填入做 B 超的日期和当时报告上的孕周孕天,工具会倒推出等效末次月经与预产期;
3. 「计算基准日」默认是今天,也可以改成任意日期,用来查看某一天的孕周;
4. 下方时间轴列出各阶段产检项目,已过期、进行中、未开始分别用不同颜色标记;
5. 结果和时间轴都可以一键复制或下载为 TXT。

关于产检时间轴

时间轴依据国内常规产检安排整理,共 {{ CHECKUPS.length }} 个节点,覆盖建卡、NT、唐筛、无创、大排畸、 糖耐、小排畸到足月前的每周复查。常规产检频率为:孕 28 周前每 4 周一次,28–36 周每 2 周一次, 36 周后每周一次,总计约 10–13 次。

重要提醒

预产期只是一个统计上的参考日,真正在预产期当天分娩的比例不足 5%, 孕 37–42 周之间分娩都属于足月,属正常范围。

本工具仅供参考,不能替代医生诊断。各医院的产检项目名称、时间窗和具体要求存在差异, 双胎、多胎、高龄、既往病史等情况的产检方案与本表差别较大,请一切以产科医生和《母子健康手册》的安排为准。 如出现腹痛、出血、胎动异常等情况,请立即就医。

2.2 代码实现

2.2.1 组件结构

本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-inputel-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。

2.2.2 核心逻辑概览

<script setup> 中定义的主要函数/方法:

  • todayStr()
  • fmtISO()
  • parseDate()
  • addDays()
  • diffDays()
  • fmtCN()
  • weekdayCN()
  • resetAll()
  • summaryText()
  • copySummary()
  • downloadTxt()

2.2.3 关键实现代码

以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。

vue
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import ToolPageShell from '@/components/ToolPageShell.vue'

const MS_DAY = 86400000

// 产检时间轴:startDay / endDay 为距末次月经的天数
const CHECKUPS = [
  { key: 'confirm', name: '确认怀孕', w: [4, 5], desc: '停经后可用早孕试纸或抽血查 HCG 确认怀孕,同时开始补充叶酸(每日 0.4–0.8 mg)。' },
  { key: 'early-us', name: '首次阴道 B 超', w: [6, 8], desc: '确认宫内孕、排除宫外孕,观察胎心胎芽与孕囊数量,核对孕周。' },
  { key: 'card', name: '建档建卡(首次产检)', w: [9, 12], desc: '在计划分娩的医院建立围产保健档案,完成血常规、尿常规、肝肾功能、血型、传染病筛查、心电图等基础检查。' },
  { key: 'nt', name: 'NT 检查', w: [11, 13], desc: '超声测量胎儿颈项透明层厚度,筛查染色体异常风险。时间窗较严格(11 周 0 天至 13 周 6 天),务必提前预约。' },
  { key: 'nipt', name: '无创 DNA 产前检测(NIPT)', w: [12, 22], desc: '抽取孕妇外周血检测胎儿染色体非整倍体,对 21/18/13 三体检出率高,最佳时间为 12–22 周。可与唐筛二选一。' },
  { key: 'down', name: '唐氏筛查(中期血清学筛查)', w: [15, 20], desc: '抽血评估 21 三体、18 三体和神经管缺陷风险。若已做无创 DNA 可不再重复。高风险需进一步做羊水穿刺。' },
  { key: 'amnio', name: '羊水穿刺(如需要)', w: [16, 22], desc: '仅在唐筛或无创提示高风险、高龄、既往异常孕产史等情况下进行,属有创检查,需医生评估后决定。' },
  { key: 'anomaly', name: '大排畸(系统超声筛查)', w: [20, 24], desc: '系统性筛查胎儿结构畸形,检查时间较长,建议提前预约。此阶段羊水量适中、胎儿结构清晰,是最佳观察窗口。' },
  { key: 'ogtt', name: '糖耐量试验(OGTT)', w: [24, 28], desc: '筛查妊娠期糖尿病。检查前需空腹 8–12 小时,喝糖水后分别测空腹、1 小时、2 小时血糖。' },
  { key: 'anemia', name: '血常规复查与补铁评估', w: [24, 28], desc: '孕中晚期血容量增加,易出现生理性贫血,需复查血红蛋白,必要时补铁。' },
  { key: 'small-anomaly', name: '小排畸(生长发育超声)', w: [28, 32], desc: '评估胎儿生长发育、羊水量、胎盘位置与成熟度,复查部分晚显性结构异常。' },
  { key: 'gbs', name: 'B 族链球菌(GBS)筛查', w: [35, 37], desc: '取阴道及直肠分泌物培养,阳性者分娩时需预防性使用抗生素,降低新生儿感染风险。' },
  { key: 'nst', name: '胎心监护(NST)', w: [32, 40], desc: '通常从 32–34 周开始,之后每次产检都做,评估胎儿宫内状态。孕晚期需每日自数胎动。' },
  { key: 'weekly', name: '每周产检', w: [36, 40], desc: '36 周后每周一次,检查宫高腹围、血压体重、胎位与胎心,评估分娩方式,做好待产准备。' },
  { key: 'term', name: '进入足月', w: [37, 42], desc: '孕 37 周起即为足月,随时可能临产。出现规律宫缩、见红或破水应立即前往医院。' },
  { key: 'overdue', name: '预产期后监测', w: [40, 42], desc: '超过预产期需加密监测胎心与羊水,通常满 41 周仍未临产会考虑引产,超过 42 周为过期妊娠。' }
]

const mode = ref('lmp')
const lmpDate = ref(todayStr())
const cycleDays = ref(28)
const scanDate = ref(todayStr())
const scanWeeks = ref(8)
const scanDays = ref(0)
const baseDate = ref(todayStr())
const timelineFilter = ref('all')

function todayStr () {
  const d = new Date()
  return fmtISO(d)
}

function fmtISO (d) {
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  return y + '-' + m + '-' + day
}

// 以本地时区正午构造日期,避免夏令时与时区造成的整天偏移
function parseDate (s) {
  if (!s) return null
  const [y, m, d] = s.split('-').map(Number)
  if (!y || !m || !d) return null
  return new Date(y, m - 1, d, 12, 0, 0, 0)
}

function addDays (date, n) {
  const d = new Date(date.getTime())
  d.setDate(d.getDate() + n)
  return d
}

function diffDays (a, b) {
  return Math.round((a.getTime() - b.getTime()) / MS_DAY)
}

function fmtCN (d) {
  return d.getFullYear() + ' 年 ' + (d.getMonth() + 1) + ' 月 ' + d.getDate() + ' 日'
}

function weekdayCN (d) {
  return '星期' + '日一二三四五六'.charAt(d.getDay())
}

const emptyHint = computed(() =>
    mode.value === 'lmp' ? '请选择末次月经第一天' : '请填写 B 超日期与当时孕周')

// 有效末次月经(B 超模式下为反推值)
const effectiveLmp = computed(() => {
  if (mode.value === 'scan') {
    const sd = parseDate(scanDate.value)
    if (!sd) return null
    return addDays(sd, -(scanWeeks.value * 7 + scanDays.value))
  }
  return parseDate(lmpDate.value)
})

const res = computed(() => {
  const lmp = effectiveLmp.value
  const base = parseDate(baseDate.value)
  if (!lmp || !base) return null

  // 预产期:LMP + 280 天,并按月经周期校正(B 超反推时不再校正)
  const adjust = mode.value === 'scan' ? 0 : cycleDays.value - 28
  const edd = addDays(lmp, 280 + adjust)
  // 孕龄同样以校正后的口径计算,保证「孕 40 周 = 预产期当天」
  const daysPregnant = diffDays(base, lmp) - adjust
  const weeks = Math.floor(daysPregnant / 7)
  const days = ((daysPregnant % 7) + 7) % 7
  const remain = diffDays(edd, base)

  let warn = ''
  let warnType = 'info'
  if (daysPregnant < 0) {
    warn = '计算基准日早于末次月经,请检查日期是否填反'
    warnType = 'error'
  } else if (daysPregnant > 300) {
    warn = '按此日期已超过 42 周(过期妊娠范围),请确认末次月经或基准日是否正确'
    warnType = 'warning'
  } else if (weeks >= 42) {
    warn = '已超过 42 周,属过期妊娠,请及时就医'
    warnType = 'warning'
  } else if (weeks >= 37) {
    warn = '已进入足月,随时可能临产,请做好待产准备'
    warnType = 'success'
  }

  let trimester = '孕早期'
  if (weeks >= 28) trimester = '孕晚期'
  else if (weeks >= 14) trimester = '孕中期'
  if (daysPregnant < 0) trimester = '尚未开始'

  const percent = Math.max(0, Math.min(100, Math.round(daysPregnant / 280 * 100)))

  return {
    lmp,
    edd,
    base,
    daysPregnant: Math.max(0, daysPregnant),
    weeks,
    days,
    percent,
    trimester,
    warn,
    warnType,
    gaText: daysPregnant < 0 ? '尚未开始' : weeks + ' 周 ' + days + ' 天',
    remainText: remain > 0 ? '还有 ' + remain + ' 天' : remain === 0 ? '就是今天' : '已超过 ' + (-remain) + ' 天',
    eddText: fmtCN(edd),
    eddWeekday: weekdayCN(edd),
    lmpText: fmtCN(lmp),
    baseText: fmtCN(base) + '(' + weekdayCN(base) + ')',
    conceptionText: fmtCN(addDays(lmp, 14 + adjust)) + ' 前后',
    t1EndText: fmtCN(addDays(lmp, 13 * 7 + 6 + adjust)),
    t2EndText: fmtCN(addDays(lmp, 27 * 7 + 6 + adjust)),
    termText: fmtCN(addDays(lmp, 37 * 7 + adjust))
  }
})

const progressColor = computed(() => {
  if (!res.value) return '#3883fa'
  if (res.value.weeks >= 37) return '#67c23a'
  if (res.value.weeks >= 28) return '#e6a23c'
  return '#3883fa'
})

const timeline = computed(() => {
  if (!res.value) return []
  const adjust = mode.value === 'scan' ? 0 : cycleDays.value - 28
  const cur = res.value.daysPregnant

  return CHECKUPS.map(c => {
    const startDay = c.w[0] * 7
    const endDay = c.w[1] * 7 + 6
    const startDate = addDays(res.value.lmp, startDay + adjust)
    const endDate = addDays(res.value.lmp, endDay + adjust)

    let status = 'future'
    if (cur > endDay) status = 'past'
    else if (cur >= startDay) status = 'current'

    return {
      key: c.key,
      name: c.name,
      desc: c.desc,
      weekText: c.w[0] + '–' + c.w[1] + ' 周',
      rangeText: fmtCN(startDate) + ' 至 ' + fmtCN(endDate),
      status,
      statusText: status === 'past' ? '已过期' : status === 'current' ? '正当时' : '未开始',
      type: status === 'past' ? 'info' : status === 'current' ? 'success' : 'primary'
    }
  })
})

const visibleTimeline = computed(() =>
    timelineFilter.value === 'all' ? timeline.value : timeline.value.filter(t => t.status !== 'past'))

function resetAll () {
  mode.value = 'lmp'
  lmpDate.value = todayStr()
  cycleDays.value = 28
  scanDate.value = todayStr()
  scanWeeks.value = 8
  scanDays.value = 0
  baseDate.value = todayStr()
  timelineFilter.value = 'all'
}

function summaryText () {
  const r = res.value
  if (!r) return ''
  const lines = [
    '【孕期计算结果】',
    '末次月经:' + r.lmpText + (mode.value === 'scan' ? '(由 B 超孕周反推)' : ''),
    mode.value === 'lmp' ? '月经周期:' + cycleDays.value + ' 天' : 'B 超日期:' + fmtCN(parseDate(scanDate.value)) + ',当时 ' + scanWeeks.value + ' 周 ' + scanDays.value + ' 天',
    '预产期:' + r.eddText + '(' + r.eddWeekday + ')',
    '计算基准日:' + r.baseText,
    '当前孕周:' + r.gaText + '(' + r.trimester + ')',
    '距预产期:' + r.remainText,
    '受精时间约:' + r.conceptionText,
    '',
    '【产检时间轴】'
  ]
  for (const t of timeline.value) {
    lines.push('· ' + t.name + '(孕 ' + t.weekText + ',' + t.rangeText + ')[' + t.statusText + ']')
    lines.push('  ' + t.desc)
  }
  lines.push('')
  lines.push('提示:预产期为参考日,孕 37–42 周分娩均属足月。本结果仅供参考,请以产科医生的安排为准。')
  return lines.join('\n')
}

async function copySummary () {
  try {
    await navigator.clipboard.writeText(summaryText())
    ElMessage.success('已复制计算结果与产检时间轴')
  } catch {
    ElMessage.warning('复制失败,请手动选择复制')
  }
}

function downloadTxt () {
  const blob = new Blob([summaryText()], { type: 'text/plain;charset=utf-8' })
  const a = document.createElement('a')
  a.href = URL.createObjectURL(blob)
  a.download = '孕期计算与产检时间轴.txt'
  a.click()
  URL.revokeObjectURL(a.href)
}

2.3 效果截图

孕期与预产期计算 效果截图

工具访问地址:https://www.i91tools.com/tools/pregnancy