09d4cb8054
- 修复选择审批人后再选择抄送人导致状态丢失的问题 - 使用独立的 sessionStorage key (selectedApprovers, selectedCc) - 组件每次挂载时从 sessionStorage 恢复状态 - 只在提交/保存成功后清除 sessionStorage - 优化人员选择页面布局和样式 - 更新 CLAUDE.md 文档记录问题和解决方案 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.3 KiB
9.3 KiB
X-Server Mobile 项目文档
项目概述
这是一个基于飞书的企业内部H5移动工具平台,采用多租户架构,支持审批流程引擎。
关键特性:
- 支持5种语言:中文、英文、俄语、西语、法语
- 多租户架构(用户全局唯一,关联多个公司)
- 审批流程引擎(单人审批、会签、或签、通知)
- 飞书免登集成
技术栈:
- 前端:React 18.3.1 + TypeScript + Vite + Ant Design Mobile
- 后端:PHP 8.5 + Webman 2.x + MySQL + Redis
- 部署:Docker
国际化 (i18n) - 重要!
前端
- 配置:
frontend/src/i18n/ - 翻译文件:
frontend/src/i18n/locales/{zh,en,ru,es,fr}.json - 语言切换:
- 首页右上角国旗按钮
- 个人中心 -> 语言设置
- 存储位置:localStorage
i18nextLng
使用方式:
import { useTranslation } from 'react-i18next'
const { t } = useTranslation()
<div>{t('common.confirm')}</div>
后端
- 翻译文件:
backend/lang/{zh,en,ru,es,fr}.php - 工具类:
backend/app/Util/Lang.php
使用方式:
use App\Util\Lang;
$message = Lang::get('common.confirm');
Lang::setLocale('en');
目录结构
/www/x-server-mobile/
├── frontend/ # React 前端 (运行在 5173)
│ ├── src/
│ │ ├── i18n/ # 国际化配置
│ │ ├── components/ # 组件
│ │ ├── pages/ # 页面
│ │ ├── modules/ # 业务模块 (approval等)
│ │ └── router/ # 路由
│ └── package.json
├── backend/ # PHP 后端 (运行在 8787)
│ ├── app/
│ │ ├── Controller/ # 控制器 (PascalCase命名)
│ │ ├── Model/ # 模型 (PascalCase命名)
│ │ ├── Service/ # 服务层
│ │ ├── Util/ # 工具类
│ │ └── Middleware/ # 中间件
│ ├── config/ # 配置
│ ├── lang/ # 语言文件
│ └── composer.json
├── docker-compose.yml # Docker 编排
├── nginx/ # OpenResty 配置
├── frpc/ # frpc 配置
├── PROJECT.md # 项目详细文档
└── IMPLEMENTATION_PLAN.md # 实现计划
环境配置
端口
- Frontend (Vite): 5173
- Backend (Webman): 8787
- OpenResty: 8080
- MySQL: 3306
- Redis: 6379
数据库
- 数据库名:x_server_mobile
- 密码:mysql_xEsdPa
- 网络:1panel-network
Docker 容器
- 后端容器:x-server-backend
- 使用 Dockerfile:
backend/Dockerfile - PHP 版本:8.5
关键文件路径
前端关键文件
- 入口:
frontend/src/main.tsx - 路由:
frontend/src/router/index.tsx - 布局:
frontend/src/components/layout/ - 状态管理:
frontend/src/stores/ - rem 适配:
frontend/src/utils/rem.ts
后端关键文件
- 入口:
backend/start.php - 路由:
backend/config/route.php - 数据库配置:
backend/config/database.php - Redis 配置:
backend/config/redis.php - 容器配置:
backend/config/container.php
重要配置文件
- Docker 编排:
docker-compose.yml - OpenResty 配置:
nginx/x-server-api.conf - frpc 配置:
frpc/frpc-dev.ini
审批流程引擎
节点类型
single_approve- 单人审批and_sign- 会签(所有人通过)or_sign- 或签(任一人通过)notify- 通知节点
状态
- pending: 待审批
- approved: 已通过
- rejected: 已拒绝
- cancelled: 已撤销
关键文件
- 服务:
backend/app/Service/ApprovalService.php - 模型:
backend/app/Model/ApprovalOrder.php - 前端模块:
frontend/src/modules/approval/
飞书集成
免登接口
- 回调:
POST /api/v1/auth/feishu-login
环境变量
FEISHU_APP_ID=cli_xxxxxxxxxxxxx
FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxx
FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxxxxxxxx
FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxxxxxxxx
常用命令
前端
cd /www/x-server-mobile/frontend
npm install
npm run dev
npm run build
后端
cd /www/x-server-mobile/backend
composer install
php start.php start
php start.php stop
Docker
cd /www/x-server-mobile
docker compose up -d
docker compose restart backend
docker logs x-server-backend -f
反向代理配置(开发环境)
用户已有 frpc 和 OpenResty,配置用于调试飞书登录。
- OpenResty 监听 8080,代理到 8787
- frpc 将 8080 暴露到公网
- 飞书回调地址配置为公网域名
已知问题和解决方案
页面间状态丢失问题 ⚠️ 重要!
问题1:navigate(-1) 不传递 state
现象: 使用 navigate(-1, { state }) 返回上一页时,location.state 为 null
原因: React Router 在使用 navigate(-1) 时,state 参数不可靠
解决方案: 使用 sessionStorage 传递数据
问题2:多次选择导致状态丢失
现象:
- 选择审批人后,再选择抄送人,审批人状态丢失
- 先选择抄送人,再选择审批人,抄送人状态丢失
原因:
- 每次导航时,目标页面组件会重新挂载
- 重新挂载时,所有 useState 都会重置为初始值
- sessionStorage 在第一次读取后被清除,第二次挂载时无数据可恢复
解决方案:
- 使用独立的 sessionStorage key(不要共用一个 key)
- 不在 useEffect 中立即清除 sessionStorage
- 每次挂载时从 sessionStorage 恢复状态
- 只在业务操作完成后才清除 sessionStorage(提交、保存等)
正确实现:
// 选择页面 - 使用独立的 key
const handleConfirm = () => {
if (type === 'approver') {
sessionStorage.setItem('selectedApprovers', JSON.stringify(selectedUsers))
} else if (type === 'cc') {
sessionStorage.setItem('selectedCc', JSON.stringify(selectedUsers))
}
navigate(-1)
}
// 申请页面 - 每次挂载时恢复状态
useEffect(() => {
const approversData = sessionStorage.getItem('selectedApprovers')
const ccData = sessionStorage.getItem('selectedCc')
if (approversData) {
setApprovers(JSON.parse(approversData))
}
if (ccData) {
setCcUsers(JSON.parse(ccData))
}
}, [])
// 提交/保存成功后清除
const handleSubmit = async () => {
// ... 提交逻辑 ...
sessionStorage.removeItem('selectedApprovers')
sessionStorage.removeItem('selectedCc')
navigate('/business-trip')
}
关键点:
- ✅ 不同类型数据使用独立的 sessionStorage key
- ✅ 不在 useEffect 中清除 sessionStorage
- ✅ 每次挂载都恢复状态,确保组件重新挂载时状态不丢失
- ✅ 只在业务操作完成后清除 sessionStorage
相关文件:
frontend/src/pages/business-trip/select-approver.tsxfrontend/src/pages/business-trip/select-cc.tsxfrontend/src/pages/business-trip/apply.tsx
Ant Design Mobile DatePicker 点击无反应
问题: DatePicker 组件使用 children render prop 方式时,点击无法弹出选择框
原因: antd-mobile 5.x 的 DatePicker children render prop 点击事件绑定存在问题
解决方案: 使用 visible 属性手动控制弹框显示
// ❌ 错误用法 - 点击无反应
<DatePicker value={value} onConfirm={setValue}>
{(value) => <button>{value ? '已选择' : '请选择'}</button>}
</DatePicker>
// ✅ 正确用法 - 使用 visible 属性
const [visible, setVisible] = useState(false)
<div onClick={() => setVisible(true)}>
{value ? '已选择' : '请选择'}
</div>
<DatePicker
visible={visible}
onConfirm={(val) => {
setValue(val)
setVisible(false)
}}
onClose={() => setVisible(false)}
onCancel={() => setVisible(false)}
/>
相关文件:
frontend/src/main.tsx- 需要导入import 'antd-mobile/es/global'frontend/src/pages/business-trip/apply.tsx- 正确使用示例
Ant Design Mobile 全局样式导入
必须导入全局样式:
import 'antd-mobile/es/global'
否则弹框、遮罩等组件将无法正常显示。
Selector 组件对号图标隐藏
问题: Selector 组件选中时显示对号图标和背景色,想要只保留背景色
解决方案: 使用 showCheckMark 属性
<Selector showCheckMark={false} options={options} />
PSR-4 自动加载
- 目录名必须是 PascalCase(Controller、Model、Service、Util)
- 不能用小写(controller、model、service、util)
React 版本
- 必须使用 React 18.3.1(不能用19)
- @types/react 和 @types/react-dom 也要用 v18
数据库连接
- 使用自定义 Db 类 (
backend/app/Util/Db.php) - 不使用
support\Db(Webman 2.x 没有)
Bootstrap 接口
- 方法签名:
public static function start(?\Workerman\Worker $worker): void - 需要添加
use Workerman\Worker;
Docker 容器重启
- CMD 使用
php start.php start(不要用-d) - 前台运行,避免容器退出
语言切换位置
- 首页 - 右上角显示当前语言国旗,点击切换
- 个人中心 -> 语言设置
当前开发状态
- ✅ 前端基础框架搭建完成
- ✅ 后端 API 框架搭建完成
- ✅ 5语言国际化支持完成
- ✅ Docker 开发环境配置完成
- ✅ 审批流程引擎基础框架完成
- 🚧 出差申请功能开发中
下一步工作
- 完成出差申请前端页面
- 实现审批列表和详情页
- 集成飞书免登
- 实现公司切换功能
- 完善权限控制