Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4596af7c12 | ||
|
|
5332f8f0c5 |
@@ -19,6 +19,7 @@
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
/public/runtime-config.js
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
|
||||
@@ -1,48 +1,91 @@
|
||||
# my-refine-app
|
||||
# TJWaterFrontend_Refine 内部前端
|
||||
|
||||
<div align="center" style="margin: 30px;">
|
||||
<a href="https://refine.dev">
|
||||
<img alt="refine logo" src="https://refine.ams3.cdn.digitaloceanspaces.com/readme/refine-readme-banner.png">
|
||||
</a>
|
||||
</div>
|
||||
<br/>
|
||||
`TJWaterFrontend_Refine` 是 TJWater 内部 Web 前端,基于 Refine、Next.js、React 和 MUI 构建。它承载管网地图、业务管理、用户认证、智能体聊天、SCADA/历史数据查看和结果可视化等内部功能。
|
||||
|
||||
This [Refine](https://github.com/refinedev/refine) project was generated with [create refine-app](https://github.com/refinedev/refine/tree/master/packages/create-refine-app).
|
||||
## 技术栈
|
||||
|
||||
## Getting Started
|
||||
- Next.js 16
|
||||
- React 19
|
||||
- Refine 5
|
||||
- MUI 6 / MUI X
|
||||
- OpenLayers、deck.gl、Turf
|
||||
- Zustand、NextAuth、Jest
|
||||
|
||||
A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility ✨
|
||||
## 目录结构
|
||||
|
||||
Refine's hooks and components simplifies the development process and eliminates the repetitive tasks by providing industry-standard solutions for crucial aspects of a project, including authentication, access control, routing, networking, state management, and i18n.
|
||||
```text
|
||||
src/app/ Next.js App Router 页面
|
||||
src/components/ 复用 UI 组件
|
||||
src/providers/ Refine、认证、数据和主题 provider
|
||||
src/hooks/ 业务 hooks
|
||||
src/utils/ 通用工具
|
||||
public/ 静态资源
|
||||
scripts/ 运行时配置和辅助脚本
|
||||
Dockerfile 镜像构建文件
|
||||
docker-compose.yml 本地编排参考
|
||||
```
|
||||
|
||||
## Available Scripts
|
||||
新增功能应复用现有页面、组件、provider、地图和聊天结构,避免创建平行体系。
|
||||
|
||||
### Running the development server.
|
||||
## 本地开发
|
||||
|
||||
要求 Node.js 20 或更高版本:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Building for production.
|
||||
`npm run dev` 会先执行运行时配置生成,再启动 Next.js 开发服务。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm test
|
||||
npm run test:coverage
|
||||
npm run build
|
||||
npm run start
|
||||
docker build -t tjwater-frontend:local .
|
||||
```
|
||||
|
||||
- `npm run lint`:运行 ESLint。
|
||||
- `npm test`:运行 Jest。
|
||||
- `npm run test:coverage`:生成测试覆盖率。
|
||||
- `npm run build`:生成生产构建。
|
||||
- `npm run start`:启动生产模式服务。
|
||||
|
||||
## 配置说明
|
||||
|
||||
运行时配置由 `scripts/generate-runtime-config.mjs` 生成。API 地址、Agent 地址、Keycloak/认证参数、地图服务地址和其他环境差异配置应通过环境变量或部署配置注入。
|
||||
|
||||
只有允许暴露给浏览器的配置才应进入 public/runtime 配置;密钥和私有 token 不能进入前端构建产物。
|
||||
|
||||
## 开发规范
|
||||
|
||||
- React 组件文件使用 `PascalCase.tsx`。
|
||||
- 普通 TypeScript 模块、hooks、store、provider 和工具使用 `camelCase.ts`。
|
||||
- `src/app` 路由目录使用 `kebab-case`,保留 Next.js 路由组和动态段语法。
|
||||
- UI 优先沿用 MUI、Refine 和既有地图/聊天界面模式。
|
||||
- 与后端或 Agent 通信的字段保持接口原始格式,通常为 `snake_case`。
|
||||
|
||||
## 测试与发布
|
||||
|
||||
提交前建议运行:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm test
|
||||
```
|
||||
|
||||
发布镜像前运行:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Running the production server.
|
||||
Gitea 包工作流位于 `.gitea/workflows/package.yml`,通常由 tag 触发构建和推送镜像。
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
## 安全规则
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about **Refine**, please check out the [Documentation](https://refine.dev/docs)
|
||||
|
||||
- **REST Data Provider** [Docs](https://refine.dev/docs/core/providers/data-provider/#overview)
|
||||
- **Material UI** [Docs](https://refine.dev/docs/ui-frameworks/mui/tutorial/)
|
||||
- **Custom Auth Provider** [Docs](https://refine.dev/docs/core/providers/auth-provider/)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
不要提交 `.env`、`.next/`、`node_modules/`、本地缓存、私有地图/API token、客户数据或部署密钥。CI/CD 凭据应放在 Gitea secrets 中。
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_OPTIONS=--max_old_space_size=4096 next dev",
|
||||
"dev": "npm run runtime:config && cross-env NODE_OPTIONS=--max_old_space_size=4096 next dev",
|
||||
"runtime:config": "node scripts/generate-runtime-config.mjs",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import nextEnv from "@next/env";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const projectDir = process.cwd();
|
||||
const { loadEnvConfig } = nextEnv;
|
||||
|
||||
loadEnvConfig(projectDir, process.env.NODE_ENV !== "production");
|
||||
|
||||
const parseExtent = (value) => {
|
||||
if (!value) {
|
||||
return [13508849, 3608036, 13555781, 3633813];
|
||||
}
|
||||
|
||||
const extent = value.split(",").map(Number);
|
||||
return extent.length === 4 && extent.every(Number.isFinite)
|
||||
? extent
|
||||
: [13508849, 3608036, 13555781, 3633813];
|
||||
};
|
||||
|
||||
const config = {
|
||||
BACKEND_URL: process.env.BACKEND_URL || "http://127.0.0.1:8000",
|
||||
AGENT_URL: process.env.AGENT_URL || "http://127.0.0.1:8788",
|
||||
MAP_URL: process.env.MAP_URL || "http://127.0.0.1:8080/geoserver",
|
||||
MAP_WORKSPACE: process.env.MAP_WORKSPACE || "tjwater",
|
||||
MAP_EXTENT: parseExtent(process.env.MAP_EXTENT),
|
||||
NETWORK_NAME: process.env.NETWORK_NAME || "tjwater",
|
||||
MAPBOX_TOKEN: process.env.MAPBOX_TOKEN || "",
|
||||
TIANDITU_TOKEN: process.env.TIANDITU_TOKEN || "",
|
||||
};
|
||||
|
||||
const outputPath = path.join(projectDir, "public", "runtime-config.js");
|
||||
|
||||
fs.writeFileSync(
|
||||
outputPath,
|
||||
`window.__TJWATER_RUNTIME_CONFIG__ = ${JSON.stringify(config)};\n`,
|
||||
);
|
||||
|
||||
console.log(`Generated ${path.relative(projectDir, outputPath)}`);
|
||||
@@ -38,10 +38,15 @@ export default async function MainLayout({
|
||||
Title={Title}
|
||||
Sider={AppSider}
|
||||
childrenBoxProps={{
|
||||
sx: { height: "100vh", p: 0 },
|
||||
sx: {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
overflow: "auto",
|
||||
p: 0,
|
||||
},
|
||||
}}
|
||||
containerBoxProps={{
|
||||
sx: { height: "100%" },
|
||||
sx: { height: "100vh", overflow: "hidden" },
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={<MapSkeleton />}>
|
||||
|
||||
@@ -1138,12 +1138,12 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
}}
|
||||
>
|
||||
<MapContext.Provider value={map}>
|
||||
<div className="relative w-full h-full">
|
||||
<div className="flex w-full h-full">
|
||||
<div className="relative h-full w-full overflow-hidden">
|
||||
<div className="flex h-full min-h-0 w-full min-w-0 overflow-hidden">
|
||||
<div
|
||||
className={`relative h-full ${isCompareMode ? "w-1/2" : "w-full"}`}
|
||||
className={`relative h-full min-w-0 overflow-hidden ${isCompareMode ? "w-1/2" : "w-full"}`}
|
||||
>
|
||||
<div ref={mapRef} className="w-full h-full"></div>
|
||||
<div ref={mapRef} className="h-full w-full"></div>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="pointer-events-none absolute inset-0"
|
||||
@@ -1155,8 +1155,8 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
)}
|
||||
</div>
|
||||
{isCompareMode && (
|
||||
<div className="relative h-full w-1/2 border-l border-white/40">
|
||||
<div ref={compareMapRef} className="w-full h-full"></div>
|
||||
<div className="relative h-full min-w-0 w-1/2 overflow-hidden border-l border-white/40">
|
||||
<div ref={compareMapRef} className="h-full w-full"></div>
|
||||
<canvas
|
||||
ref={compareCanvasRef}
|
||||
className="pointer-events-none absolute inset-0"
|
||||
|
||||
Reference in New Issue
Block a user