# 豆包智能服务的端能力 API 使用配方

本文件放高频豆包智能服务的端能力 API 的组合示例。这些 API 也称 Open API，代码统一从
`@doubao-dev/framework/api` 导入。完整参数和返回类型以 [豆包智能服务的端能力 API 目录](../doubao-agentic-service-api/groups.md)
和 IDE 类型提示为准；这里重点展示端能力如何和 Page / Widget、生命周期、事件处理、状态反馈组合使用。
Widget 示例只展示端能力和状态管理组合，卡片展示层使用 [Widget 模板库](../widget-templates/overview.md) 中的模板。

---

## 使用原则

- 豆包智能服务的端能力 API 从 `@doubao-dev/framework/api` 导入。
- 首次加载或随输入变化的调用放在 `useEffect` 或生命周期钩子中。
- 用户触发的调用放在事件处理函数中。
- Page / Widget 默认用 `useViewData<T>()` 声明输入类型。
- `request` 不支持泛型参数，不要写 `request<T>()`；业务返回类型在 `response.data` 上做类型收窄或断言。
- 异步调用提供 loading、success、error 等可见状态。

---

## 位置能力 Widget

`getLocation` 放在 `useEffect` 中调用，不要在 `render()` 中直接调用。

```tsx
import { useEffect, useState, useViewData } from '@doubao-dev/framework';
import { getLocation } from '@doubao-dev/framework/api';
import { ContentCard, type ContentCardItem } from '@doubao-dev/template';

interface LocationCardData {
  title?: string;
}

interface LocationInfo {
  latitude: number;
  longitude: number;
  accuracy: number;
  speed: number;
  timestamp: string;
}

export default function LocationCard() {
  const viewData = useViewData<LocationCardData>();
  const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
  const [location, setLocation] = useState<LocationInfo | null>(null);
  const [error, setError] = useState('');

  useEffect(() => {
    let active = true;

    const loadLocation = async () => {
      try {
        setStatus('loading');
        setError('');
        const result = await getLocation();
        if (!active) {
          return;
        }
        setLocation({
          latitude: result.latitude,
          longitude: result.longitude,
          accuracy: result.accuracy,
          speed: result.speed,
          timestamp: result.timestamp
        });
        setStatus('success');
      } catch (caughtError) {
        if (!active) {
          return;
        }
        setError(caughtError instanceof Error ? caughtError.message : '定位失败');
        setStatus('error');
      }
    };

    void loadLocation();

    return () => {
      active = false;
    };
  }, []);

  if (status === 'loading') {
    return <ContentCard items={[{ key: 'status', title: viewData.title || '位置能力', subtitle: '定位中...' }]} />;
  }

  if (status === 'error') {
    return <ContentCard items={[{ key: 'error', title: viewData.title || '位置能力', subtitle: error }]} />;
  }

  const items: ContentCardItem[] = location
    ? [
        { key: 'lat', title: '纬度', subtitle: location.latitude },
        { key: 'lng', title: '经度', subtitle: location.longitude }
      ]
    : [{ key: 'empty', title: viewData.title || '位置能力', subtitle: '暂无数据' }];

  return <ContentCard header={{ actionText: viewData.title || '位置能力' }} items={items} />;
}
```

---

## Storage 状态持久化 Widget

`getStorage` 放在 `useEffect` 中读取初始状态，`setStorage` 放在事件处理函数中保存用户操作。Storage 支持
JSON 可序列化数据，读取时用泛型声明 `result.data` 的业务类型。

```tsx
import { useEffect, useState, useViewData } from '@doubao-dev/framework';
import { getStorage, setStorage } from '@doubao-dev/framework/api';
import { ContentCard, type ContentCardItem } from '@doubao-dev/template';

interface StorageCounterData {
  title?: string;
  storageKey?: string;
  defaultValue?: number;
}

interface StoredCounter {
  value: number;
  updatedAt: string;
}

export default function StorageCounter() {
  const viewData = useViewData<StorageCounterData>();
  const storageKey = viewData.storageKey || 'counter';
  const defaultCount = viewData.defaultValue ?? 0;
  const [count, setCount] = useState(defaultCount);
  const [status, setStatus] = useState<'loading' | 'ready' | 'saving' | 'saved' | 'error'>('loading');
  const [error, setError] = useState('');

  useEffect(() => {
    let active = true;

    const loadStoredValue = async () => {
      try {
        setStatus('loading');
        setError('');
        const result = await getStorage<StoredCounter>({ key: storageKey });
        if (!active) {
          return;
        }
        setCount(typeof result.data.value === 'number' ? result.data.value : defaultCount);
        setStatus('ready');
      } catch (caughtError) {
        if (!active) {
          return;
        }
        setError(caughtError instanceof Error ? caughtError.message : '读取缓存失败，已使用默认值');
        setStatus('ready');
      }
    };

    void loadStoredValue();

    return () => {
      active = false;
    };
  }, [storageKey, defaultCount]);

  const handleSave = async () => {
    try {
      setStatus('saving');
      setError('');
      await setStorage<StoredCounter>({
        key: storageKey,
        data: {
          value: count,
          updatedAt: new Date().toISOString()
        }
      });
      setStatus('saved');
    } catch (caughtError) {
      setError(caughtError instanceof Error ? caughtError.message : '保存失败');
      setStatus('error');
    }
  };

  const items: ContentCardItem[] = [
    {
      key: 'status',
      title: viewData.title || '本地计数',
      subtitle: status === 'loading' ? '读取中...' : status === 'saving' ? '保存中...' : status === 'saved' ? '已保存' : '就绪'
    },
    ...(error ? [{ key: 'error', title: '错误', subtitle: error }] : []),
    { key: 'count', title: '当前计数', subtitle: count }
  ];

  return (
    <ContentCard
      items={items}
      footer={{
        secondaryActionButton: {
          text: '保存',
          loading: status === 'saving',
          disabled: status === 'loading',
          onClick: handleSave
        },
        primaryActionButton: {
          text: '增加',
          disabled: status === 'loading' || status === 'saving',
          onClick: () => setCount(current => current + 1)
        }
      }}
    />
  );
}
```
