Add static deployment build and guide
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-static
|
||||||
|
.next
|
||||||
|
.vinext
|
||||||
|
.wrangler
|
||||||
|
*.log
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,6 +35,7 @@ yarn-error.log*
|
|||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
/dist/
|
/dist/
|
||||||
|
/dist-static/
|
||||||
/.wrangler/
|
/.wrangler/
|
||||||
/outputs/
|
/outputs/
|
||||||
/work/
|
/work/
|
||||||
|
|||||||
75
DEPLOY_STATIC.md
Normal file
75
DEPLOY_STATIC.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# 高徳乐 AI 舵机选型助手:静态部署说明
|
||||||
|
|
||||||
|
本仓库已经提供纯静态构建方式。页面的产品数据、图片、选型计算、筛选和弹窗交互都在浏览器中运行,不依赖 Node.js、数据库、API 或 Cloudflare Worker 的运行时。
|
||||||
|
|
||||||
|
## 给 Git AI 的部署任务
|
||||||
|
|
||||||
|
可以直接把下面这段话发给仓库里的 AI:
|
||||||
|
|
||||||
|
> 请将本仓库部署为纯静态网站,不要启动 Node.js 后端、Vinext Server 或 Cloudflare Worker。先执行 `npm ci`,再执行 `npm run build:static`。把生成的 `dist-static/` 目录作为网站根目录发布到当前云服务器,并配置 Nginx 将未知路径回退到 `index.html`。如果服务器使用 Docker,请使用仓库中的 `Dockerfile.static` 构建并运行,容器端口为 80。部署完成后检查首页、`#selector`、`#products` 和 `#precision-gears` 页面区域,以及产品图片是否能正常加载。
|
||||||
|
|
||||||
|
## 方式一:Nginx / 静态文件服务器
|
||||||
|
|
||||||
|
在服务器项目目录执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.databerrypi.com/MJ-IT/gdm-Servo-model.git
|
||||||
|
cd gdm-Servo-model
|
||||||
|
npm ci
|
||||||
|
npm run build:static
|
||||||
|
```
|
||||||
|
|
||||||
|
将 `dist-static/` 下的全部文件复制到 Nginx 网站根目录,例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /var/www/gdm-servo
|
||||||
|
sudo rsync -a --delete dist-static/ /var/www/gdm-servo/
|
||||||
|
```
|
||||||
|
|
||||||
|
Nginx 的站点配置可以参考 [`deploy/nginx-static.conf`](deploy/nginx-static.conf)。其中最关键的是:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
root /var/www/gdm-servo;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
以后更新时,在仓库目录执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull --ff-only origin main
|
||||||
|
npm ci
|
||||||
|
npm run build:static
|
||||||
|
sudo rsync -a --delete dist-static/ /var/www/gdm-servo/
|
||||||
|
```
|
||||||
|
|
||||||
|
运行时只需要 Nginx,不需要保留 Node.js 进程。
|
||||||
|
|
||||||
|
## 方式二:Docker
|
||||||
|
|
||||||
|
仓库已经提供 [`Dockerfile.static`](Dockerfile.static):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -f Dockerfile.static -t gdm-servo-static:latest .
|
||||||
|
docker run -d --name gdm-servo-static --restart unless-stopped -p 80:80 gdm-servo-static:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
如果服务器已有 80 端口服务,可以改成 `-p 8080:80`,再由现有 Nginx 反向代理到 `127.0.0.1:8080`。
|
||||||
|
|
||||||
|
## 本地检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build:static
|
||||||
|
python3 -m http.server 8080 --directory dist-static
|
||||||
|
```
|
||||||
|
|
||||||
|
然后打开 `http://服务器IP:8080`。不要直接双击 `index.html`,因为浏览器会限制本地文件加载模块和图片资源。
|
||||||
|
|
||||||
|
## 部署注意事项
|
||||||
|
|
||||||
|
- 静态版没有服务端接口;当前“申请样品”和“联系我们”表单只在浏览器中生成演示编号,不会把线索写入 CRM 或数据库。
|
||||||
|
- 如果后续要真正保存客户线索,需要另行接入表单 API、企业微信、CRM 或数据库,不应把密钥写进前端代码。
|
||||||
|
- 页面使用根路径资源(例如 `/catalog-products/...`),因此建议部署在域名根路径。如果必须部署到 `/servo/` 子目录,需要同步调整 Vite 的 `base` 和图片资源路径。
|
||||||
|
- 每次产品数据或图片变更后,都要重新执行 `npm run build:static`,再同步整个 `dist-static/` 目录。
|
||||||
12
Dockerfile.static
Normal file
12
Dockerfile.static
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build:static
|
||||||
|
|
||||||
|
FROM nginx:1.27-alpine
|
||||||
|
COPY deploy/nginx-static.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=builder /app/dist-static /usr/share/nginx/html
|
||||||
|
EXPOSE 80
|
||||||
@@ -16,6 +16,10 @@ npm run dev
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Static Deployment
|
||||||
|
|
||||||
|
This project also supports a no-backend static build. Run `npm run build:static` and publish the generated `dist-static/` directory to Nginx, object storage, or another static file server. See [DEPLOY_STATIC.md](DEPLOY_STATIC.md) for instructions that can be handed directly to a Git AI deployment agent.
|
||||||
|
|
||||||
This starter does not use `wrangler.jsonc`.
|
This starter does not use `wrangler.jsonc`.
|
||||||
|
|
||||||
## Included Shape
|
## Included Shape
|
||||||
|
|||||||
@@ -1200,14 +1200,15 @@ export default function Home() {
|
|||||||
<div
|
<div
|
||||||
className="catalog-modal-backdrop"
|
className="catalog-modal-backdrop"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
onMouseDown={() => setSelectedCatalogProduct(null)}
|
onMouseDown={(event) => {
|
||||||
|
if (event.target === event.currentTarget) setSelectedCatalogProduct(null);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
className="catalog-modal"
|
className="catalog-modal"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="catalog-modal-title"
|
aria-labelledby="catalog-modal-title"
|
||||||
onMouseDown={(event) => event.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="catalog-modal-close"
|
className="catalog-modal-close"
|
||||||
|
|||||||
17
deploy/nginx-static.conf
Normal file
17
deploy/nginx-static.conf
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(?:js|css|png|jpg|jpeg|webp|gif|svg|ico|woff2)$ {
|
||||||
|
try_files $uri =404;
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public, max-age=86400";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext dev",
|
"dev": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext dev",
|
||||||
"build": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext build",
|
"build": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext build",
|
||||||
|
"build:static": "vite build --config vite.static.config.ts",
|
||||||
"start": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext start",
|
"start": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext start",
|
||||||
"test": "npm run build && node --test tests/rendered-html.test.mjs",
|
"test": "npm run build && node --test tests/rendered-html.test.mjs",
|
||||||
"lint": "eslint . --ignore-pattern dist --ignore-pattern .next",
|
"lint": "eslint . --ignore-pattern dist --ignore-pattern .next",
|
||||||
|
|||||||
16
static/entry.tsx
Normal file
16
static/entry.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import Home from "../app/page";
|
||||||
|
import "../app/globals.css";
|
||||||
|
|
||||||
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
throw new Error("Static app root was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
createRoot(root).render(
|
||||||
|
<StrictMode>
|
||||||
|
<Home />
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
14
static/index.html
Normal file
14
static/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#185ee8" />
|
||||||
|
<meta name="description" content="高徳乐 AI 舵机选型助手,支持产品目录浏览、参数筛选和 AI 初选。" />
|
||||||
|
<title>高徳乐 AI 舵机选型助手</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./entry.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
vite.static.config.ts
Normal file
16
vite.static.config.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { dirname, resolve } from "node:path";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
const projectRoot = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: resolve(projectRoot, "static"),
|
||||||
|
plugins: [react()],
|
||||||
|
publicDir: resolve(projectRoot, "public"),
|
||||||
|
build: {
|
||||||
|
outDir: resolve(projectRoot, "dist-static"),
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user