diff --git a/.env b/.env new file mode 100644 index 0000000..9cf3b4f --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PROXY_PORT=1081 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a58136 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +.idea +.vscode + +# dependencies +.cache +node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +~$* + diff --git a/yarn/Dockerfile b/yarn/Dockerfile new file mode 100644 index 0000000..a568e24 --- /dev/null +++ b/yarn/Dockerfile @@ -0,0 +1,11 @@ +# 1. 指定基础镜像 +FROM ghcr.io/anomalyco/opencode:1.14.20 + +# 2. 设置非交互式安装,更新包列表并安装 Python3 和 pip +# 使用 && 连接命令以减少镜像层数 +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk update && \ + apk add --no-cache python3 py3-pip docker-cli curl bash git yarn + +# 3. (可选)创建一个工作目录 +WORKDIR /workspace \ No newline at end of file diff --git a/yarn/config/AGENTS.md b/yarn/config/AGENTS.md new file mode 100644 index 0000000..84a2480 --- /dev/null +++ b/yarn/config/AGENTS.md @@ -0,0 +1,501 @@ + + # 全局规则 + +## 交互语言 +- 所有交互一律使用中文(包括问题回答、代码解释、错误说明等) +- 代码注释使用中文 +- 变量名、函数名等代码标识符使用英文(遵循编程惯例) + +## 代码风格 +- 保持代码简洁、易读 +- 遵循项目现有的代码规范和约定 +- 不要添加不必要的注释或解释 +- 字符串排序(如日期 `YYYY-MM-DD`)用纯比较运算符 `a > b ? 1 : a < b ? -1 : 0`,不用 `localeCompare` —— 后者走 ICU 规则,与字典序不严格等价 + +### observer 使用规范 + +`observer` 必须跟随 `export`,而非组件定义: + +```javascript +// ✅ 正确:定义与导出分离 +const MyComponent = () => { ... }; +export default observer(MyComponent); + +// ✅ 正确:定义与导出合一时 +export const MyComponent = observer(() => { ... }); + +// ❌ 错误:observer 包裹定义,单独导出 +const MyComponent = observer(() => { ... }); +export default MyComponent; +``` + +## 工作原则 +- 优先理解用户意图,必要时主动提问澄清 +- 修改代码前先了解项目结构和现有模式 +- 完成修改后运行测试验证(如果项目有测试) +- 除非用户明确要求,否则不要提交代码变更 +- **代码查阅确认机制**:当需要阅读代码来确认 API 细节或具体用法时,必须先向用户说明: + 1. 为什么要阅读代码(目的) + 2. 准备阅读哪些代码(范围) + 3. 等待用户决定: + - 同意按方案继续阅读 + - 直接告知具体用法 + - 修正阅读范围,指定应该阅读哪些代码 + +## 代码查阅原则 + +- 当 AGENTS.md 中已提供了完整的代码模板和规范时(如 Entity、Dao、Service、MyBatis XML 等模板), + 必须直接按照模板和规范编写代码,**不得**先去阅读现有代码来"学习风格" +- 只有在以下情况才允许阅读现有代码: + 1. 确认某个框架 API 的具体用法或参数 + 2. 了解某个模块的具体业务逻辑或实现细节 + 3. 模板和规范中未覆盖的特殊场景 +- 简单说:规范已覆盖的内容 → 直接按规范写;规范未覆盖的内容 → 再去看代码 +- **禁止在规划阶段启动 agent 或工具去阅读现有代码**。 + 规划必须仅基于两份 AGENTS.md 中的信息进行。 + 只有在进入实现阶段、且需要确认模板未覆盖的框架 API 细节时, + 才允许按「代码查阅确认机制」经用户同意后阅读代码。 + +## 项目初始化规则 + +当用户要求"了解项目"、"熟悉规范"或类似指令时: + +### 必须做的: +1. 阅读并理解**全局 AGENTS.md**(`/root/.config/opencode/AGENTS.md`,即本文件)和**项目级 AGENTS.md**(`/workspace/AGENTS.md`,位于项目根目录)中的所有规范 +2. 基于两份 AGENTS.md 中的信息回答项目相关问题 +3. 直接使用两份 AGENTS.md 中的代码模板和规范进行开发 + +### 禁止做的: +1. 不要主动阅读实际代码文件来"学习风格" +2. 不要查看现有模块来"理解代码组织" +3. 不要假设 AGENTS.md 中的信息不够用 +4. 不要只阅读其中一份 AGENTS.md 而忽略另一份——两份都必须阅读 + +### 标准回复: +"已阅读全局和项目级 AGENTS.md,项目采用 [技术栈],包含 [模块数量] 个模块。 +命名规范:[简述]。代码模板已就绪,可以开始开发。" + +## 前端项目开发原则(仅限 React 项目) + +> **适用范围说明:** 以下原则仅适用于前端 React 项目开发。对于后端项目(Java、Python 等)或其他类型项目,这些规则不适用。 + +### 1. 架构思维 + +**分层计算原则** +- 数据计算按层级划分:原始数据 → 单实体衍生 → 跨实体汇总 → 展示 +- 每一层只做自己该做的事,不越界 +- 体现"关注点分离"的核心思想 + +**响应式数据流** +- 状态变化自动触发相关更新,而非手动调用 +- 使用观察者模式(如 autorun)实现自动响应 +- 体现"声明式编程"优于"命令式编程"的理念 + +### 2. 命名哲学 + +**语义优先,避免冗余** +- 命名要清晰表达含义,但不重复上下文 +- 在已知上下文中,省略冗余前缀 +- 嵌套结构表达关联关系(如 `compare.scale.daily`) +- 体现"代码即文档"的自解释原则 + +### 3. 职责分离 + +**逻辑与展示分离** +- 业务逻辑集中在状态管理层 +- 组件只负责展示和 UI 配置 +- 体现"单一职责原则" + +**全局与局部分离** +- 全局参数集中管理(日期、汇率等) +- 业务数据按领域分离 +- 体现"高内聚、低耦合"的设计原则 + +### 4. 工程实践 + +**渐进式改进** +- 逐步消除技术债务(如硬编码数据) +- 暂时不处理的部分明确标注,不阻塞整体进度 +- 体现"持续重构"的工程思维 + +**一致性原则** +- 相关文件集中组织(Store、API、Mock 一一对应) +- 统一的命名规范和文件结构 +- 体现"约定优于配置"的理念 + +### 5. 数据驱动 + +**以最细粒度实体为基础** +- 所有数据围绕最小业务单元组织 +- 汇总数据从细粒度数据派生 +- 体现"数据规范化"的思想 + +**确定性 Mock** +- Mock 数据基于种子生成,保证可重复性 +- 体现"可测试性"和"可预测性"的要求 + +## 代码模板 + +### Store 模板 + +```javascript +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(); +``` + +### API 模板 + +```javascript +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}`); +} +``` + +### Page 组件模板 + +```javascript +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 ( + + {/* 页面内容 */} + + ); +} + +export default observer(XxxPage); +``` + +### Hook 模板 + +```javascript +import {useState, useEffect, useCallback} from "react"; + +export const useXxx = (initialValue) => { + const [value, setValue] = useState(initialValue); + + const doSomething = useCallback(() => { + // 逻辑 + }, []); + + return {value, setValue, doSomething}; +} +``` + +### Mock 数据模板 + +```javascript +// 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` 目标模块,不经聚合对象 + +### 图表组件模板 + +```javascript +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 ( + + + + + + + + + + + + + + + + + + ); +} + +export default observer(XxxChart); +``` + +## 装饰器语法 + +项目使用 MobX 5,需要以下 Babel 配置: + +```javascript +// craco.config.js +babel: { + plugins: [ + ['@babel/plugin-proposal-decorators', {legacy: true}], + ["@babel/plugin-proposal-class-properties", {loose: true}], + ] +} +``` + +常用装饰器: +- `@observable` - 声明可观察状态 +- `@action` - 声明修改状态的方法 +- `@computed` - 声明计算属性 + +## 样式规范 + +### CSS Modules +- 文件命名:`xxx.scss` 或 `xxx.module.scss` +- 自动启用:`src/` 和 `@chaosoft/` 下的 `.scss` 文件 +- 类名转换:kebab-case → camelCase + +```scss +// component.scss +.container { + padding: 16px; +} + +.title-text { + font-size: 16px; +} +``` + +```javascript +// component.js +import styles from './component.scss'; + +const Component = () => ( +
+ 标题 +
+); +``` + +## VERSION.md 编写规范 + +### 文件位置 +项目根目录:`{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 | + +### 内容结构 + +```markdown +# 项目版本记录 + +## 当前版本 +v1.0.0 (2026-09-22) + +## 版本历史 + +| 版本 | 日期 | 说明 | +|------|------|------| +| v1.0.0 | 2026-09-22 | 初始发布版本 | + +## 版本明细 + +### 待发布 + +**发布日期:** 待定 + +**新增功能:** +- 无 + +**修复问题:** +- 无 + +**变更说明:** +- 无 + +--- + +### v1.0.0 + +**发布日期:** 2026-09-22 + +**新增功能:** +- 客户管理模块(CRUD) +- 租约管理模块(CRUD) + +**修复问题:** +- 无 + +**变更说明:** +- 无 +``` + +### 待发布章节 + +- **位置**:固定位于「版本明细」最上面,在所有已发布版本章节之上;全文有且仅有一个 +- **结构**:与版本章节同构 —— `**发布日期:** 待定` + 新增功能 / 修复问题 / 变更说明,以 `---` 与下一章节分隔 +- **用途**:累积当前未发布周期内用户可感知的净变化,同受「记录范围(产品视角)」与「对比基线(净变化原则)」约束 +- **发版迁移**:发版时将其内容整体转为新版本章节(标题改为版本号、发布日期改为实际日期,并同步「当前版本」与「版本历史」表格),随后在其上方重建一个空的「待发布」章节,保证其恒在最上面 +- **常驻保留**:即使本周期无待记录内容,也保留空的「待发布」章节(三项均填「无」),不得删除 + +### 编写要求 + +1. **当前版本**:始终显示最新版本号和发布日期 +2. **版本历史表格**:按时间倒序排列(最新版本在最上面) +3. **版本明细**: + - 每个版本一个章节,标题为版本号(如 `### v1.0.0`) + - 包含:发布日期、新增功能、修复问题、变更说明 + - 按时间倒序排列(最新版本在最上面) + - 「待发布」章节恒置于最上面(在所有已发布版本章节之上),详见「待发布章节」 +4. **更新时机**:每次发布新版本时更新此文件 +5. **记录范围(产品视角)**:只记录用户可感知的产品功能变化,按最终功能归类为新增 / 修复 / 变更 + - ✅ 记录:新增页面与菜单、新增图表与指标卡、功能行为与数据口径变化、界面呈现调整、缺陷修复、交付形态变更 + - ❌ 不记录:技术实现细节(Store / API / 组件拆分、状态管理调整)、代码重构与规范修正、 + Mock 数据与辅助文档(AGENTS.md、docs/)调整、构建脚本与依赖变更、 + API 数据结构变更(仅后端可感知的字段调整、接口重构等) + - 判断标准:以最终用户(产品使用者)是否可感知为唯一标准,后端开发者的感知不纳入记录范围 + - 描述以功能名称与用户可见效果表述,不堆砌文件路径、类名、行数等技术标识 +6. **对比基线(净变化原则)**:所有条目必须是「当前状态 vs 上一个已发布版本」的净变化 + - 同一未发布周期内先新增、后又修复或调整的内容,不单独记录修复/变更,只记录最终功能形态 + - 判断方法:若某缺陷或行为在上一发布版本中不存在(属本周期新增功能自带),则不写入「修复问题」「变更说明」 + - 「新增功能」与「变更说明」不重复描述同一动作 + - 发版前按上一版本 tag 重新逐条核对 diff --git a/yarn/config/opencode.json b/yarn/config/opencode.json new file mode 100644 index 0000000..c6aea2c --- /dev/null +++ b/yarn/config/opencode.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://opencode.ai/config.json", + "model": "alibaba-cn/qwen3.8-max", + "provider": { + "bailian-payg": { + "npm": "@ai-sdk/anthropic", + "name": "Alibaba Cloud Model Studio", + "options": { + "baseURL": "https://llm-725mrgwyn1rzglgb.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1", + "apiKey": "{env:ALIBABA_API_KEY}" + }, + "models": { + "qwen3.7-max": { + "name": "Qwen3.7 Max", + "options": { + "thinking": { + "type": "enabled", + "budgetTokens": 8192 + } + } + }, + "qwen3.7-plus": { + "name": "Qwen3.7 Plus", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "options": { + "thinking": { + "type": "enabled", + "budgetTokens": 8192 + } + } + }, + "qwen3.6-plus": { + "name": "Qwen3.6 Plus", + "modalities": { + "input": ["text", "image"], + "output": ["text"] + }, + "options": { + "thinking": { + "type": "enabled", + "budgetTokens": 8192 + } + } + } + } + }, + "alibaba-cn": { + "options": { + "apiKey": "{env:ALIBABA_API_KEY}", + "baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1" + } + } + }, + "mcp": { + "chrome-devtools": { + "type": "local", + "command": [ + "docker", "run", "--rm", "-i", + "-v", "E:/Work/jftech/findash/srcs/findash-admin:/workspace", + "chrome-devtools-mcp:node24-v1.1.1", + "--browserUrl=http://192.168.3.236:9222" + ], + "enabled": false + } + }, + "server": { + "port": 4096, + "hostname": "0.0.0.0" + }, + "permission": { + "external_directory": { + "/var/store/**": "allow" + }, + "edit": { + "/var/store/**": "deny" + } + } +} diff --git a/yarn/docker-compose.yml b/yarn/docker-compose.yml new file mode 100644 index 0000000..98a9fbb --- /dev/null +++ b/yarn/docker-compose.yml @@ -0,0 +1,23 @@ +services: + opencode-yarn: + image: opencode:yarn1-1.14.20 + container_name: opencode-yarn + environment: + - TZ=Asia/Shanghai + - ALIBABA_API_KEY=sk-6026a1b171b84c2985c316d063136105 + - HTTP_PROXY=http://host.docker.internal:${PROXY_PORT} + - HTTPS_PROXY=http://host.docker.internal:${PROXY_PORT} + - NO_PROXY=localhost,127.0.0.1,host.docker.internal + volumes: + - ./config:/root/.config/opencode + - /var/run/docker.sock:/var/run/docker.sock + - ./node/.cache:/usr/local/share/.cache + - ./node/.yarnrc:/usr/local/share/.yarnrc + - ./projects/${PROJECT_NAME}/node_modules:/workspace/node_modules + - ${SRC_PATH}:/workspace + working_dir: /workspace + networks: + - "opencode-net" +networks: + opencode-net: + driver: bridge diff --git a/yarn/node/.yarnrc b/yarn/node/.yarnrc new file mode 100644 index 0000000..cbb24d8 --- /dev/null +++ b/yarn/node/.yarnrc @@ -0,0 +1,9 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +registry "https://registry.npmmirror.com" +"@chaosoft:registry" "https://nexus.chaosoft.com.cn/repository/npm-public/" +lastUpdateCheck 1793555433346 +sass_binary_site "https://npmmirror.com/mirrors/node-sass/" +strict-ssl false diff --git a/yarn/projects/biz-office-rent-web/.env b/yarn/projects/biz-office-rent-web/.env new file mode 100644 index 0000000..ce8e3a1 --- /dev/null +++ b/yarn/projects/biz-office-rent-web/.env @@ -0,0 +1 @@ +PROJECT_NAME=biz-office-rent-web \ No newline at end of file diff --git a/yarn/projects/biz-office-rent-web/startup.bat b/yarn/projects/biz-office-rent-web/startup.bat new file mode 100644 index 0000000..794cc02 --- /dev/null +++ b/yarn/projects/biz-office-rent-web/startup.bat @@ -0,0 +1,36 @@ +@echo off +chcp 65001 >nul +setlocal enabledelayedexpansion + +set "COMPOSE_CMD=docker compose" + +set "PROJECT_ROOT=%~dp0" +set "PROJECT_ROOT=%PROJECT_ROOT:~0,-1%" +for %%i in ("%PROJECT_ROOT%\..\..") do set "YARN_ROOT=%%~fi" +for %%i in ("%YARN_ROOT%\..") do set "SYSTEM_ROOT=%%~fi" + +echo %PROJECT_ROOT% +echo %YARN_ROOT% +echo %SYSTEM_ROOT% + +for %%R in ("%SYSTEM_ROOT%" "%YARN_ROOT%" "%PROJECT_ROOT%") do ( + if exist "%%R\.env" ( + set "COMPOSE_CMD=!COMPOSE_CMD! --env-file %%R\.env" + echo [FOUND] %%R\.env + ) + if exist "%%R\.env.local" ( + set "COMPOSE_CMD=!COMPOSE_CMD! --env-file %%R\.env.local" + echo [FOUND] %%R\.env.local + ) +) + +echo. + +cd %YARN_ROOT% + +::端口跳转,为了使用chrome mcp +::netsh interface portproxy add v4tov4 listenport=9222 listenaddress=0.0.0.0 connectport=9222 connectaddress=127.0.0.1 + +echo 执行命令: !COMPOSE_CMD! run --rm opencode-yarn +!COMPOSE_CMD! run --rm opencode-yarn +