# 全局规则
YYYY-MM-DD)用纯比较运算符 a > b ? 1 : a < b ? -1 : 0,不用 localeCompare —— 后者走 ICU 规则,与字典序不严格等价observer 必须跟随 export,而非组件定义:
// ✅ 正确:定义与导出分离
const MyComponent = () => { ... };
export default observer(MyComponent);
// ✅ 正确:定义与导出合一时
export const MyComponent = observer(() => { ... });
// ❌ 错误:observer 包裹定义,单独导出
const MyComponent = observer(() => { ... });
export default MyComponent;
生成 commit message 前,必须过滤非实质性变更:
git diff --ignore-all-space --stat 检查是否有实质性变更git diff --ignore-cr-at-eol 忽略行尾符(CRLF/LF)变化git diff --word-diff 检查单词级别的实质性变更当用户要求"了解项目"、"熟悉规范"或类似指令时:
/root/.config/opencode/AGENTS.md,即本文件)和项目级 AGENTS.md(/workspace/AGENTS.md,位于项目根目录)中的所有规范"已阅读全局和项目级 AGENTS.md,项目采用 [技术栈],包含 [模块数量] 个模块。 命名规范:[简述]。代码模板已就绪,可以开始开发。"
适用范围说明: 以下原则仅适用于前端 React 项目开发。对于后端项目(Java、Python 等)或其他类型项目,这些规则不适用。
分层计算原则
响应式数据流
语义优先,避免冗余
compare.scale.daily)逻辑与展示分离
全局与局部分离
渐进式改进
一致性原则
以最细粒度实体为基础
确定性 Mock
import {action, computed, observable} from "mobx";
import {LocalStore} from "./local.store"; // 如需本地存储继承
import {XxxApi} from "../api";
export class XxxStore extends LocalStore {
@observable
list = []
@observable
detail = null
@computed
get derivedData() {
// 计算衍生数据
return this.list.map(item => ({
...item,
computedField: item.field * 2
}));
}
get storeName() {
return "xxx"; // 本地存储 key
}
@action
fetchData = async (params) => {
this.list = await XxxApi.getList(params);
}
@action
fetchDetail = async (id) => {
this.detail = await XxxApi.getDetail(id);
}
@action
reset = () => {
this.list = [];
this.detail = null;
}
}
export default new XxxStore();
import request from "../utils/request";
import qs from "query-string";
// GET 请求
export const getList = async (query) => {
return request.get(qs.stringifyUrl({
url: '/xxx', query
}, {skipNull: true, skipEmptyString: true}));
}
// GET 详情
export const getDetail = async (id) => {
return request.get(`/xxx/${id}`);
}
// POST 创建
export const create = async (data) => {
return request.post('/xxx', {data});
}
// PUT 更新
export const update = async (id, data) => {
return request.put(`/xxx/${id}`, {data});
}
// DELETE 删除
export const remove = async (id) => {
return request.delete(`/xxx/${id}`);
}
import React, {useEffect} from "react";
import {observer} from 'mobx-react';
import {PageContainer} from "@ant-design/pro-layout";
import {useStores} from "../../hooks";
const XxxPage = () => {
const {xxxStore} = useStores();
useEffect(() => {
xxxStore.fetchData();
}, []);
return (
<PageContainer>
{/* 页面内容 */}
</PageContainer>
);
}
export default observer(XxxPage);
import {useState, useEffect, useCallback} from "react";
export const useXxx = (initialValue) => {
const [value, setValue] = useState(initialValue);
const doSomething = useCallback(() => {
// 逻辑
}, []);
return {value, setValue, doSomething};
}
// mock/xxx.js
module.exports = {
_apis: {
getList: "GET /api/v1.0/xxx",
getDetail: "GET /api/v1.0/xxx/:id",
create: "POST /api/v1.0/xxx",
},
"GET /api/v1.0/xxx": (req, res) => {
res.send({
status: "success",
body: {
content: [
{id: 1, name: "示例1", field: "value1"},
{id: 2, name: "示例2", field: "value2"},
],
total: 2,
page: 0,
size: 20,
last: true
},
success: true
});
},
"GET /api/v1.0/xxx/:id": {
status: "success",
body: {id: 1, name: "示例", field: "value"},
success: true
},
"POST /api/v1.0/xxx": {
status: "success",
body: {id: 3},
success: true
}
}
模板约定说明:
_apis:别名 → 路由字符串的映射,供环境变量细粒度屏蔽指定接口(如 REACT_APP_MOCK_IGNORE=xxx.create)。别名必须与 API 层的导出方法名完全一致,聚合入口依赖此对应关系做过滤_getData():返回模块内数据数组,供其他 mock 模块交叉引用(如创建时校验关联实体是否存在)。仅在模块间有引用需求时提供:id 会吞掉字面量路径(如 /xxx/settings 必须先于 /xxx/:id)mock/index.js)合并多模块,展开时必须剔除非路由键(_apis、_getData),否则同名键会被后注册模块覆盖;模块间交叉引用应直接 require 目标模块,不经聚合对象import React from "react";
import {observer} from 'mobx-react';
import {Line, Column, Pie} from "@ant-design/charts";
import {Card, Row, Col, Statistic} from "antd";
import {useStores} from "../../hooks";
const XxxChart = () => {
const {xxxStore} = useStores();
const {chartData, summary} = xxxStore;
// 折线图配置
const lineConfig = {
data: chartData.lineData,
xField: 'date',
yField: 'value',
seriesField: 'type',
};
// 饼图配置
const pieConfig = {
data: chartData.pieData,
angleField: 'value',
colorField: 'type',
};
return (
<Row gutter={16}>
<Col span={8}>
<Card>
<Statistic title="指标1" value={summary.value1} />
</Card>
</Col>
<Col span={16}>
<Card title="趋势图">
<Line {...lineConfig} />
</Card>
</Col>
<Col span={24}>
<Card title="分布图">
<Pie {...pieConfig} />
</Card>
</Col>
</Row>
);
}
export default observer(XxxChart);
项目使用 MobX 5,需要以下 Babel 配置:
// craco.config.js
babel: {
plugins: [
['@babel/plugin-proposal-decorators', {legacy: true}],
["@babel/plugin-proposal-class-properties", {loose: true}],
]
}
常用装饰器:
@observable - 声明可观察状态@action - 声明修改状态的方法@computed - 声明计算属性xxx.scss 或 xxx.module.scsssrc/ 和 @chaosoft/ 下的 .scss 文件// component.scss
.container {
padding: 16px;
}
.title-text {
font-size: 16px;
}
// component.js
import styles from './component.scss';
const Component = () => (
<div className={styles.container}>
<span className={styles.titleText}>标题</span>
</div>
);
项目根目录:{project-root}/VERSION.md
采用语义化版本(Semantic Versioning):MAJOR.MINOR.PATCH
| 部分 | 说明 | 示例 |
|---|---|---|
| MAJOR | 不兼容的 API 变更 | 1.0.0 → 2.0.0 |
| MINOR | 向下兼容的功能新增 | 1.0.0 → 1.1.0 |
| PATCH | 向下兼容的问题修复 | 1.0.0 → 1.0.1 |
# 项目版本记录 ## 当前版本 v1.0.0 (2026-09-22) ## 版本历史 | 版本 | 日期 | 说明 | |------|------|------| | v1.0.0 | 2026-09-22 | 初始发布版本 | ## 版本明细 ### 待发布 **发布日期:** 待定 **新增功能:** - 无 **修复问题:** - 无 **变更说明:** - 无 --- ### v1.0.0 **发布日期:** 2026-09-22 **新增功能:** - 客户管理模块(CRUD) - 租约管理模块(CRUD) **修复问题:** - 无 **变更说明:** - 无
**发布日期:** 待定 + 新增功能 / 修复问题 / 变更说明,以 --- 与下一章节分隔### v1.0.0)