Skip to content

[refactor] migrate NGO models to BI Table API based on MobX-Lark 2.8#63

Merged
TechQuery merged 1 commit intomainfrom
NGO-Lark-API
Apr 5, 2026
Merged

[refactor] migrate NGO models to BI Table API based on MobX-Lark 2.8#63
TechQuery merged 1 commit intomainfrom
NGO-Lark-API

Conversation

@TechQuery
Copy link
Copy Markdown
Member

@TechQuery TechQuery commented Apr 5, 2026

PR-63 PR-63 PR-63 Powered by Pull Request Badge

  1. [refactor] migrate NGO models to BI Table API based on MobX-Lark 2.8 (resolve 中国公益数据库 2.0 Strapi API 替换为飞书多维表格 #62)
  2. [optimize] update Upstream packages

Summary by CodeRabbit

发布说明

  • 新功能

    • 添加城市级统计数据可视化,在地图上展示组织分布。
  • 改进

    • 更新统计图表以显示城市分布数据。
    • 改进组织卡片和详情页面的信息展示和数据处理。
    • 优化数据获取和搜索性能。

Copilot AI review requested due to automatic review settings April 5, 2026 18:43
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

将数据源从 Strapi API 迁移至飞书多维表格。核心改动:Organization 模型层从 MobX-Strapi 替换为 Lark/koajax,统计维度从 coverageArea 改为 city,相应更新组件和页面层的数据依赖和查询参数。

Changes

Cohort / File(s) Summary
数据模型层重构
models/Organization.ts
核心迁移:从 StrapiListModel 改为 BiDataTable<Organization>,引入 ngoLarkClient 和环境常量;OrganizationStatistic 统计维度从 coverageArea|locale 改为 city|year;新增 OrganizationYearStatisticModelOrganizationCityStatisticModelOrganizationTypeStatisticModelSearchOrganizationModel 抽象;方法签名移除 Base 约束,适配 Lark 表格 TableCellValue 类型体系。
系统模型适配
models/System.ts
searchMap 中 NGO 条目从 OrganizationModel 改为 SearchOrganizationModel
城市统计地图与图表
components/Map/CityStatisticMap.tsx, components/Organization/Charts.tsx
属性接收从 coverageArea 改为 city;城市坐标查询时需对城市名称进行后缀规范化处理(去掉 市|自治州|特别行政区)。
组织卡片与景观展示
components/Organization/Card.tsx, components/Organization/Landscape.tsx
数据来源调整:从 internetContact 改为直接从 observedProps 读取 website/wechatPublic;增强类型容错性,使用 toString()+ '' 强制字符串转换;服务徽章从对象映射改为字符串数组映射;导入调整以支持新的 Organization 类型。
页面层查询适配
pages/NGO/[year]/index.tsx, pages/NGO/[year]/landscape.tsx
getServerSideProps 查询参数从 establishedDate 改为 startYear;传递给 CityStatisticMap 的数据字段从 statistic.coverageArea 改为 statistic.city
Lark API 路由简化
pages/api/Lark/bitable/v1/[...slug].ts
移除自定义 Koa 路由器和字段过滤逻辑,统一采用 withSafeKoa(proxyLarkAll) 委托代理实现。

Sequence Diagram(s)

sequenceDiagram
    participant Page as Pages/NGO/[year]
    participant Model as OrganizationModel
    participant Lark as Lark API
    participant Store as systemStore
    participant Map as CityStatisticMap

    Page->>Model: getStatistic({startYear})
    Model->>Lark: 查询数据表记录
    Lark-->>Model: 返回 city 维度数据
    Model->>Model: extractFields() 规范化 city/entityType
    Model-->>Page: OrganizationStatistic['city']
    
    Page->>Map: 传递 statistic.city
    Map->>Map: Object.entries(city) 遍历
    Note over Map: 城市名称规范化<br/>(去掉市|自治州|特别行政区)
    Map->>Store: 查询 cityCoordinate[normalizedCity]
    Store-->>Map: 返回坐标数据
    Map->>Map: 生成地图标记并渲染
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

关键审查点:

  1. 类型系统一致性 - Organization 从外部库类型改为 TableCellValue 基础的内部定义,需验证所有数据流中的类型转换安全
  2. 统计维度迁移 - coverageAreacity 涉及多个文件,需确保坐标查询时的城市名称规范化逻辑(regex 后缀匹配)正确
  3. 查询参数一致 - establishedDatestartYear 的页面层改动需与模型层 getStatistic/groupAllByType 签名对应
  4. 数据容错 - Card.tsx 中多处 ?.toString()+ '' 强制转换,需评估 Lark 返回值的类型不确定性是否充分应对
  5. API 路由变更 - 移除字段过滤后是否会导致下游数据结构变化,需确认 proxyLarkAll 的处理行为

Suggested labels

enhancement, migration


飞书万物,多维表格展新颜 📊
Strapi 旧日已成烟,API 迁移稳如山 🏔️
城市坐标巧规范,数据流转更明晰 ✨
TypeScript 强类型守护,React 组件优雅绽放 🌸

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确概括了核心改动:将NGO模型从Strapi迁移至Lark BI Table API,符合MobX-Lark 2.8要求。
Linked Issues check ✅ Passed 所有代码改动完整满足issue #62要求:成功迁移Strapi API至Lark BI Table API,引入MobX-Lark 2.8的BiDataTable/BiSearch,调整组织统计维度从coverageArea迁移至city。
Out of Scope Changes check ✅ Passed 所有改动均在issue #62范畴内,聚焦API迁移;API代理简化、类型调整和组件更新都是直接支撑迁移的必要改动,无超范围变更。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch NGO-Lark-API

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@TechQuery TechQuery added enhancement Some improvements feature New feature or request and removed enhancement Some improvements feature New feature or request labels Apr 5, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the NGO data layer to use Feishu/Lark Bitable (via MobX-Lark 2.8) instead of the previous Strapi/package-based model, and updates upstream dependencies accordingly.

Changes:

  • Replaced the NGO OrganizationModel implementation with BiDataTable/BiSearch based models and added Bitable-backed statistic table models.
  • Updated NGO pages/components to use the new field names/statistic shapes (startYear, city, etc.).
  • Updated dependencies (Next.js, MobX-Lark, tooling) and adjusted project setup docs/config (pnpm, registry cleanup, new env vars).

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
README.md Updates local dev commands to pnpm.
pnpm-lock.yaml Locks upgraded framework/tooling/library versions.
package.json Bumps Next.js/MobX-Lark/etc.; removes GitHub Packages dependency.
.npmrc Removes GitHub Packages registry configuration (no longer needed).
.env Points NGO data source to Lark proxy + adds NGO Bitable IDs.
pages/api/Lark/bitable/v1/[...slug].ts Simplifies Bitable proxy API route to a single safe Koa handler.
models/Organization.ts Migrates NGO model + statistics models to MobX-Lark BI Table API.
models/System.ts Switches NGO search model mapping to SearchOrganizationModel.
pages/NGO/[year]/index.tsx Updates stats query/filter and map input to new city statistic.
pages/NGO/[year]/landscape.tsx Updates grouping filter to startYear.
components/Organization/Charts.tsx Updates chart inputs from coverageArea to city.
components/Organization/Card.tsx Adapts card fields to new model shape (website/wechatPublic/services).
components/Organization/Landscape.tsx Adapts type import + string-safety for name slicing.
components/Map/CityStatisticMap.tsx Adapts prop type and normalizes city names for coordinate lookup.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

...fields,
city: normalizeTextArray(city as TableCellText[]),
entityType: (entityType as TableCellRelation[])?.map(normalizeText),
tags: (tags as TableCellRelation[])?.map(normalizeText).toString().split(','),
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tags normalization can throw when tags is missing/empty: optional chaining only applies to .map(), but .toString() is then called on the (possibly) undefined result. Consider defaulting to an empty array (or chaining ?. through) before calling toString()/split, so records without tags don’t crash SSR/client rendering.

Suggested change
tags: (tags as TableCellRelation[])?.map(normalizeText).toString().split(','),
tags: ((tags as TableCellRelation[])?.map(normalizeText) ?? []).toString().split(','),

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +116
const years = await yearStore.getAll();

Object.assign(organizationStore, { sort: { establishedDate: 'desc' } });

const [{ establishedDate: end } = {}] = await organizationStore.getList({}, 1, 1);

const startYear = new Date(start || now).getFullYear(),
endYear = new Date(end || now).getFullYear();

return [startYear, endYear] as const;
const [{ name: start } = {}] = years.sort(
({ name: a }, { name: b }) => (a as number) - (b as number),
);
const [{ name: end } = {}] = years.sort(
({ name: a }, { name: b }) => (b as number) - (a as number),
);
return [start, end] as [number, number];
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getYearRange() assumes name is a number ((a as number) - (b as number) and returns [start, end] as [number, number]), but TableCellValue is often a string. This can yield NaN sorting and return non-numeric years. Consider coercing via Number(name) (or parseInt) and computing min/max without mutating/sorting the whole array.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to 46
'year' | 'city' | 'entityType' | 'serviceCategory',
Record<string, number>
>;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrganizationStatistic is defined to always include a year distribution, but getStatistic() only populates city, entityType, and serviceCategory and then type-casts. This makes the type misleading and encourages unsafe casts; either add a real year aggregation or remove year from the required keys in the type.

Suggested change
'year' | 'city' | 'entityType' | 'serviceCategory',
Record<string, number>
>;
'city' | 'entityType' | 'serviceCategory',
Record<string, number>
> & {
year?: Record<string, number>;
};

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +164
const group = list.map(({ name, [key]: count }) => count && [name, count]).filter(Boolean) as [
string,
number,
][];

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

countAll() drops valid zero counts because it filters with count && .... Also name/count are TableCellValue, so returning them as [string, number] without coercion can leak strings into the result. Consider filtering on count != null (or typeof count === 'number') and converting name/count to the expected primitive types.

Suggested change
const group = list.map(({ name, [key]: count }) => count && [name, count]).filter(Boolean) as [
string,
number,
][];
const group = list.flatMap(({ name, [key]: count }) => {
const normalizedCount =
typeof count === 'number'
? count
: typeof count === 'string' && count.trim()
? Number(count)
: NaN;
if (Number.isNaN(normalizedCount)) return [];
return [[String(name), normalizedCount] as [string, number]];
});

Copilot uses AI. Check for mistakes.
Comment on lines 22 to 27
## 开始

```bash
npm install
npm run dev
# or
yarn install
yarn dev
pnpm install
pnpm dev
```
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated deps/tooling (e.g., Next.js 16.2.2 / ESLint 10.2.x) have stricter Node engine requirements than just “Node 20+” in some cases. It would help to document the minimum supported Node.js version here (and that pnpm is required), so pnpm install doesn’t fail unexpectedly for contributors on older 20.x minors.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/Organization/Card.tsx (1)

38-50: ⚠️ Potential issue | 🟡 Minor

用户可见文本应使用 i18n t() 函数

title="WWW"title="WeChat" 是硬编码字符串,违反了国际化要求。所有用户可见的文本必须使用 t() 函数。

🌐 建议修改
        {website && (
-         <Button title="WWW" size="sm" target="_blank" href={website?.toString()}>
+         <Button title={t('www')} size="sm" target="_blank" href={website?.toString()}>
            <Icon name="globe2" />
          </Button>
        )}
        {wechatPublic && (
          <Button
-           title="WeChat"
+           title={t('wechat')}
            size="sm"
            variant="success"
            onClick={() => (this.showQRC = !this.showQRC)}
          >

需要在 render() 方法中从 context 获取 t 函数,并在翻译文件中添加相应的键值。

As per coding guidelines: "All user-facing text MUST use the i18n t() function (no hardcoded strings)"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Organization/Card.tsx` around lines 38 - 50, Replace the hardcoded
button titles with i18n keys by using the t() function from context inside the
component's render() method: obtain t (e.g., const { t } = this.context or from
props) and set title={t('organization.www')} and
title={t('organization.wechat')} for the Button instances referenced in Card.tsx
(the Button with Icon "globe2" and the Button with Icon "chat-fill" that toggles
this.showQRC); also add corresponding keys ("organization.www" and
"organization.wechat") to the translation files.
🧹 Nitpick comments (3)
components/Organization/Card.tsx (2)

28-28: 在非响应式方法中应使用 this.props 而非 this.observedProps

根据 MobX observer 组件的最佳实践,renderIcon() 是从 render() 调用的普通方法,不是响应式上下文(如 computed getter 或 observable 方法)。在此处使用 this.observedProps 可能导致不必要的重新渲染或响应式追踪问题。

♻️ 建议修改
  renderIcon() {
-   const { website, wechatPublic } = this.observedProps;
+   const { website, wechatPublic } = this.props;

Based on learnings: "In MobX observer components that extend ObservedComponent, only access this.observedProps inside reactive contexts (e.g., computed getters, observable methods). In non-reactive methods such as render(), prefer using this.props."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Organization/Card.tsx` at line 28, renderIcon is a plain
(non-reactive) method but reads props via this.observedProps; change it to use
this.props instead to avoid unintended MobX reactive tracking and extra
re-renders—locate the renderIcon method in the Organization Card component and
replace any uses of this.observedProps (e.g., this.observedProps.website,
this.observedProps.wechatPublic) with this.props.website and
this.props.wechatPublic (or destructure const { website, wechatPublic } =
this.props) so only reactive contexts use observedProps.

81-85: services as string[] 类型断言存在潜在风险

根据 Organization 类型定义,services 的类型是 TableCellValue(联合类型),而非显式的 string[]。虽然此写法与代码库中其他地方的模式一致(如 getStatistic 方法),但如果 API 返回非数组类型,.map() 调用会抛出 TypeError。

建议增加类型守卫或使用更安全的写法:

🛡️ 可选的防御性写法
          {services && (
            <BadgeBar
              className="justify-content-end"
-             list={(services as string[]).map(text => ({ text }))}
+             list={Array.isArray(services) ? services.map(text => ({ text: String(text) })) : []}
            />
          )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Organization/Card.tsx` around lines 81 - 85, The code casts
services to string[] and calls .map on it which can throw if services (from the
Organization TableCellValue union) is not an array; in the Card component add a
defensive type guard before mapping (e.g. use Array.isArray(services) and ensure
items are strings or coerce via String(item)), produce an empty list fallback
when not an array, and pass that safe array to BadgeBar (referencing the
services prop and BadgeBar usage in Card.tsx); this mirrors safer handling used
elsewhere (e.g., getStatistic) and prevents runtime TypeError.
models/Organization.ts (1)

17-19: 补上 ./Base 的模型层初始化。

这个 model 已经成了新的数据入口,但当前没有先 import './Base'。这样基础配置会依赖别的文件是否碰巧先加载,后面很容易和其他 model 的 client 行为分叉。

♻️ 建议修正
+import './Base';
 import { STRAPI_API_HOST } from './configuration';
 import { SearchableFilter } from './System';
 import { observable } from 'mobx';

As per coding guidelines: models/**/*.ts: Import './Base' in model files to ensure proper GitHub client configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/Organization.ts` around lines 17 - 19, The Organization model is
missing the mandatory model-layer initialization import, so ensure you add the
side-effect import for Base at the top of models/Organization.ts by importing
'./Base' before any other model-specific imports (so the Base initialization
runs and configures the GitHub/client behavior); place the import('./Base')
above or before uses of STRAPI_API_HOST, SearchableFilter, or observable to
guarantee consistent client setup across models.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/Organization/Card.tsx`:
- Around line 77-78: Replace the unsafe string coercion using "+ ''" for name
and entityType with a null-safe fallback like the nullish coalescing used
elsewhere (e.g., use name ?? '' and entityType ?? '') so that the display and
the BadgeBar list item do not render literal "undefined" or "null"; update the
occurrences around the JSX where name and the BadgeBar list use entityType to
use the null-safe fallback.

In `@models/Organization.ts`:
- Around line 43-46: The exported OrganizationStatistic type currently requires
'year' but getStatistic() only returns city, entityType and serviceCategory,
causing a mismatched type and a misleading assertion; update the
OrganizationStatistic declaration to match the actual return shape (e.g.,
Record<'city' | 'entityType' | 'serviceCategory', Record<string, number>>) or
alternatively implement the missing year aggregation inside getStatistic()
(refer to the OrganizationStatistic type and the getStatistic() function to
apply the chosen fix).
- Around line 68-80: The keyMap object is missing mappings for email, codeLink,
and tags which are referenced elsewhere; update the keyMap (the keyMap variable
in models/Organization.ts) to include Chinese labels for "email", "codeLink",
and "tags" so Feishu header mapping and validation succeed; ensure the labels
align with existing style (e.g., short Chinese descriptors) and verify
extractFields() and searchKeys still find their corresponding keys after the
additions.

---

Outside diff comments:
In `@components/Organization/Card.tsx`:
- Around line 38-50: Replace the hardcoded button titles with i18n keys by using
the t() function from context inside the component's render() method: obtain t
(e.g., const { t } = this.context or from props) and set
title={t('organization.www')} and title={t('organization.wechat')} for the
Button instances referenced in Card.tsx (the Button with Icon "globe2" and the
Button with Icon "chat-fill" that toggles this.showQRC); also add corresponding
keys ("organization.www" and "organization.wechat") to the translation files.

---

Nitpick comments:
In `@components/Organization/Card.tsx`:
- Line 28: renderIcon is a plain (non-reactive) method but reads props via
this.observedProps; change it to use this.props instead to avoid unintended MobX
reactive tracking and extra re-renders—locate the renderIcon method in the
Organization Card component and replace any uses of this.observedProps (e.g.,
this.observedProps.website, this.observedProps.wechatPublic) with
this.props.website and this.props.wechatPublic (or destructure const { website,
wechatPublic } = this.props) so only reactive contexts use observedProps.
- Around line 81-85: The code casts services to string[] and calls .map on it
which can throw if services (from the Organization TableCellValue union) is not
an array; in the Card component add a defensive type guard before mapping (e.g.
use Array.isArray(services) and ensure items are strings or coerce via
String(item)), produce an empty list fallback when not an array, and pass that
safe array to BadgeBar (referencing the services prop and BadgeBar usage in
Card.tsx); this mirrors safer handling used elsewhere (e.g., getStatistic) and
prevents runtime TypeError.

In `@models/Organization.ts`:
- Around line 17-19: The Organization model is missing the mandatory model-layer
initialization import, so ensure you add the side-effect import for Base at the
top of models/Organization.ts by importing './Base' before any other
model-specific imports (so the Base initialization runs and configures the
GitHub/client behavior); place the import('./Base') above or before uses of
STRAPI_API_HOST, SearchableFilter, or observable to guarantee consistent client
setup across models.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8174f2ee-3d2c-4aa0-b284-da35d213acf8

📥 Commits

Reviewing files that changed from the base of the PR and between fc0e541 and 5030491.

⛔ Files ignored due to path filters (5)
  • .env is excluded by none and included by none
  • .npmrc is excluded by none and included by none
  • README.md is excluded by !**/*.md and included by none
  • package.json is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml and included by none
📒 Files selected for processing (9)
  • components/Map/CityStatisticMap.tsx
  • components/Organization/Card.tsx
  • components/Organization/Charts.tsx
  • components/Organization/Landscape.tsx
  • models/Organization.ts
  • models/System.ts
  • pages/NGO/[year]/index.tsx
  • pages/NGO/[year]/landscape.tsx
  • pages/api/Lark/bitable/v1/[...slug].ts

Comment on lines +77 to +78
{name + ''}
<BadgeBar className="ms-2" list={[{ text: entityType + '' }]} />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

字符串强制转换可能产生 "undefined" 或 "null" 文本

使用 + '' 进行字符串转换时,如果 nameentityTypeundefinednull,会渲染出字面量字符串 "undefined""null",这对用户体验不友好。

建议使用与第 91 行一致的安全写法:

🛡️ 建议修改
          <Card.Title>
-           {name + ''}
-           <BadgeBar className="ms-2" list={[{ text: entityType + '' }]} />
+           {name?.toString() ?? ''}
+           <BadgeBar className="ms-2" list={[{ text: entityType?.toString() ?? '' }]} />
          </Card.Title>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{name + ''}
<BadgeBar className="ms-2" list={[{ text: entityType + '' }]} />
<Card.Title>
{name?.toString() ?? ''}
<BadgeBar className="ms-2" list={[{ text: entityType?.toString() ?? '' }]} />
</Card.Title>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Organization/Card.tsx` around lines 77 - 78, Replace the unsafe
string coercion using "+ ''" for name and entityType with a null-safe fallback
like the nullish coalescing used elsewhere (e.g., use name ?? '' and entityType
?? '') so that the display and the BadgeBar list item do not render literal
"undefined" or "null"; update the occurrences around the JSX where name and the
BadgeBar list use entityType to use the null-safe fallback.

Comment on lines 43 to 46
export type OrganizationStatistic = Record<
'coverageArea' | 'locale' | 'entityType' | 'serviceCategory',
'year' | 'city' | 'entityType' | 'serviceCategory',
Record<string, number>
>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the Organization.ts file
find . -name "Organization.ts" -type f

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 115


🏁 Script executed:

# Let's search for the OrganizationStatistic type and getStatistic method
rg -n "OrganizationStatistic|getStatistic" --type ts

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 1730


🏁 Script executed:

# Read the Organization.ts file around the relevant lines
cat -n models/Organization.ts | head -150 | tail -110

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 4234


🏁 Script executed:

# Check how getStatistic is used and if .year is accessed
rg -B2 -A2 "getStatistic\(\)" --type ts --type tsx

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 118


🏁 Script executed:

# Check if year field is accessed on the returned statistic
rg "\.year" --type ts --type tsx | grep -i statistic

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 118


🏁 Script executed:

# Check OrganizationYearStatisticModel for context
cat -n models/Organization.ts | sed -n '169,185p'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 667


🏁 Script executed:

# Search for getStatistic usages correctly
rg "getStatistic" -A 3 -B 1

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 679


🏁 Script executed:

# Search for access to .year on statistic variable
rg "statistic\.year" -B 2 -A 2

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Check the Charts.tsx component to see how OrganizationStatistic is used
cat -n components/Organization/Charts.tsx | head -20

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 1038


OrganizationStatistic 的导出类型与实际返回值不匹配。

getStatistic() 方法(第 119-132 行)仅构造了 cityentityTypeserviceCategory 三个字段,但第 43-46 行的类型声明要求 'year' | 'city' | 'entityType' | 'serviceCategory' 四个字段都存在。第 132 行使用 as OrganizationStatistic 的类型断言掩盖了这个不一致。

调用方(如 components/Organization/Charts.tsx)只解构访问 { entityType, serviceCategory, city},从未使用 year 字段,而运行时其值为 undefined。建议将类型收窄为实际返回的形状:

Record<'city' | 'entityType' | 'serviceCategory', Record<string, number>>

或在 getStatistic() 中补上 year 聚合逻辑(如果业务需要)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/Organization.ts` around lines 43 - 46, The exported
OrganizationStatistic type currently requires 'year' but getStatistic() only
returns city, entityType and serviceCategory, causing a mismatched type and a
misleading assertion; update the OrganizationStatistic declaration to match the
actual return shape (e.g., Record<'city' | 'entityType' | 'serviceCategory',
Record<string, number>>) or alternatively implement the missing year aggregation
inside getStatistic() (refer to the OrganizationStatistic type and the
getStatistic() function to apply the chosen fix).

Comment on lines +68 to +80
keyMap = {
name: '常用名称',
code: '机构信用代码',
entityType: '实体类型',
startDate: '成立时间',
startYear: '成立年份',
city: '城市',
website: '机构官网',
wechatPublic: '机构微信公众号',
description: '机构/项目简介',
coverageArea: '机构/项目辐射范围',
services: '关于行业类服务对象',
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate the Organization.ts file
find . -name "Organization.ts" -type f

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 115


🏁 Script executed:

# Let's examine the Organization.ts file to verify the review comment
wc -l ./models/Organization.ts

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 119


🏁 Script executed:

# Read the file sections mentioned in the review
cat -n ./models/Organization.ts | sed -n '60,220p'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 5528


🏁 Script executed:

# Let's search for how keyMap is used in the codebase
rg "keyMap" --type ts --type tsx -B 2 -A 2

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 118


🏁 Script executed:

# Let's search for how keyMap is used, using ts filetype only
rg "keyMap" -t ts -B 2 -A 2

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 807


🏁 Script executed:

# Search for how keyMap is being accessed or used in the codebase
rg "\.keyMap|keyMap\[" -t ts -B 3 -A 3

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Let's check the parent class BiDataTable to understand keyMap usage
fd "BiDataTable" -t f

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Let's check what BiDataTable is and where it comes from
rg "BiDataTable" -t ts -B 2 -A 2 | head -50

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 1764


🏁 Script executed:

# Let's find where BiDataTable is imported from
rg "from.*BiDataTable|import.*BiDataTable" -t ts -B 1 -A 1 | head -30

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 210


🏁 Script executed:

# Let's check the Organization.ts imports to confirm BiDataTable source
head -20 ./models/Organization.ts

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 552


🏁 Script executed:

# Let's search for any usage of extractFields or searchKeys to understand the data flow
rg "extractFields" -t ts -B 3 -A 3

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 4233


🏁 Script executed:

# Let's check if there are any other similar model files to see the pattern of keyMap usage
cat -n ./models/Project.ts | sed -n '50,130p'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 455


🏁 Script executed:

# Let's see the Project.ts keyMap
cat -n ./models/Project.ts | sed -n '1,50p'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 1556


🏁 Script executed:

# Search for keyMap in Project.ts
grep -n "keyMap" ./models/Project.ts

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Let's check Activity.ts to see if it has a keyMap
cat -n ./models/Activity.ts | sed -n '1,100p'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 3301


🏁 Script executed:

# Let's search for how the missing fields (email, tags, codeLink) are defined in the Organization type
cat -n ./models/Organization.ts | head -60 | tail -20

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 822


补齐 keyMap 缺失的字段映射

keyMap 中缺少 emailcodeLinktags 三个字段的中文标签映射。这些字段已被代码活跃消费,但缺少对应的飞书表头映射可能导致字段同步失效或表结构验证问题:

  • codeLink:在 extractFields() (90-100 行) 和 searchKeys (209 行) 中使用
  • tags:在 extractFields() (98 行) 中使用
  • email:在 searchKeys (207 行) 中使用

需要为这三个字段添加对应的中文标签到 keyMap 中。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/Organization.ts` around lines 68 - 80, The keyMap object is missing
mappings for email, codeLink, and tags which are referenced elsewhere; update
the keyMap (the keyMap variable in models/Organization.ts) to include Chinese
labels for "email", "codeLink", and "tags" so Feishu header mapping and
validation succeed; ensure the labels align with existing style (e.g., short
Chinese descriptors) and verify extractFields() and searchKeys still find their
corresponding keys after the additions.

@TechQuery TechQuery merged commit dbefb17 into main Apr 5, 2026
11 checks passed
@TechQuery TechQuery deleted the NGO-Lark-API branch April 5, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Some improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

中国公益数据库 2.0 Strapi API 替换为飞书多维表格

2 participants