示例 PRO

import { Button, List, Navigation, NavigationStack, Script, Section, Text, useState } from "scripting"

// 把一次服药事件格式化成一行可读文本。
function describeDose(dose: HealthMedicationDoseEvent): string {
  const parts: string[] = [dose.logStatus]
  if (dose.doseQuantity != null) {
    parts.push(`${dose.doseQuantity} ${dose.unit.unitString}`)
  }
  parts.push(dose.startDate.toLocaleString())
  return parts.join(" · ")
}

// 服药事件详情页:用某个用药的 identifier 只取它的 dose events,演示两个 API 的串联。
function DoseEventsView({
  medication,
}: {
  medication: HealthUserAnnotatedMedication
}) {
  const dismiss = Navigation.useDismiss()
  const [doses, setDoses] = useState<HealthMedicationDoseEvent[] | null>(null)
  const [error, setError] = useState<string | null>(null)

  const load = async () => {
    setError(null)
    try {
      const end = new Date()
      const start = new Date(end.getTime() - 30 * 86400 * 1000) // 近 30 天
      const result = await Health.queryMedicationDoseEvents({
        startDate: start,
        endDate: end,
        medicationConceptIdentifiers: [medication.medication.identifier],
        sortDescriptors: [{ key: "startDate", order: "reverse" }],
      })
      setDoses(result)
    } catch (e) {
      setError(String(e))
    }
  }

  return <NavigationStack>
    <List
      navigationTitle={medication.nickname ?? medication.medication.displayText}
      navigationBarTitleDisplayMode={"inline"}
      toolbar={{
        cancellationAction: <Button title={"Done"} action={dismiss} />,
        confirmationAction: <Button title={"Load"} action={load} />,
      }}
    >
      {error != null
        ? <Text foregroundStyle={"systemRed"}>{error}</Text>
        : doses == null
          ? <Text foregroundStyle={"secondaryLabel"}>Tap “Load” to read the dose events from the last 30 days.</Text>
          : doses.length === 0
            ? <Text foregroundStyle={"secondaryLabel"}>No dose events found.</Text>
            : <Section header={<Text>{`${doses.length} dose event(s)`}</Text>}>
                {doses.map(dose => <Text key={dose.uuid}>{describeDose(dose)}</Text>)}
              </Section>}
    </List>
  </NavigationStack>
}

function Example() {
  const [medications, setMedications] = useState<HealthUserAnnotatedMedication[] | null>(null)
  const [error, setError] = useState<string | null>(null)

  // 查询用户在「健康」中跟踪的用药。首次调用会触发 per-object 授权(系统弹「选择药物」)。
  const loadMedications = async () => {
    if (!Health.isHealthDataAvailable) {
      await Dialog.alert({ message: "Health data is not available on this device." })
      return
    }
    setError(null)
    try {
      const result = await Health.queryMedications({ isArchived: false })
      setMedications(result)
    } catch (e) {
      // iOS 26 以下会在这里 reject。
      setError(String(e))
    }
  }

  const openDoses = (medication: HealthUserAnnotatedMedication) => {
    Navigation.present({ element: <DoseEventsView medication={medication} /> })
  }

  return <NavigationStack>
    <List
      navigationTitle={"Medications"}
      navigationBarTitleDisplayMode={"inline"}
    >
      <Text font={"footnote"} foregroundStyle={"secondaryLabel"}>
        Reads the medications tracked in the Health app and their logged doses. Requires iOS 26 and grants access to the medications you choose.
      </Text>

      <Button title={"Load medications"} action={loadMedications} />

      {error != null
        ? <Text foregroundStyle={"systemRed"}>{error}</Text>
        : medications == null
          ? null
          : medications.length === 0
            ? <Text foregroundStyle={"secondaryLabel"}>No medications found.</Text>
            : <Section header={<Text>{`${medications.length} medication(s)`}</Text>}>
                {medications.map(item => <Button
                  key={item.medication.identifier}
                  action={() => openDoses(item)}
                >
                  <Text>{item.nickname ?? item.medication.displayText}</Text>
                  <Text font={"footnote"} foregroundStyle={"secondaryLabel"}>
                    {`${item.medication.generalForm}${item.hasSchedule ? " · scheduled" : ""}`}
                  </Text>
                </Button>)}
              </Section>}
    </List>
  </NavigationStack>
}

async function run() {
  await Navigation.present({
    element: <Example />
  })

  Script.exit()
}

run()