From de412297fb6f710247d54a25f5bb5b4a3d783cb5 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sat, 19 Sep 2026 00:52:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=20/=20=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E5=8F=8C=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 14 - .env.development | 40 ++ .env.example | 55 ++ .env.production | 33 ++ .gitignore | 6 + .workbuddy/memory/2026-09-08.md | 2 +- README.md | 59 ++- database/template-switch-verification.md | 2 +- nuxt.config.ts | 6 +- outputs/template-07-home-with-news.html | 2 +- .../汇吉采-标书购买-前端清单与后端接口清单.md | 2 +- package.json | 10 +- scripts/cms_fill_image.py | 2 +- scripts/publish-cms-content.mjs | 2 +- scripts/with-env.mjs | 102 ++++ 单页管理-导航关联-对接说明.md | 77 --- 开发计划.md | 480 ------------------ 17 files changed, 303 insertions(+), 591 deletions(-) delete mode 100644 .env create mode 100644 .env.development create mode 100644 .env.example create mode 100644 .env.production create mode 100644 scripts/with-env.mjs delete mode 100644 单页管理-导航关联-对接说明.md delete mode 100644 开发计划.md diff --git a/.env b/.env deleted file mode 100644 index 4041bb9..0000000 --- a/.env +++ /dev/null @@ -1,14 +0,0 @@ -# 租户 / 应用配置 -# 默认租户 ID(本地开发或演示站点用,生产环境通过域名自动识别) -NUXT_PUBLIC_TENANT_ID=10626 - -# 默认模板 ID -NUXT_PUBLIC_TEMPLATE_ID=template-07 - -# 本地开发:CMS / 内容接口指向本地后端(mp-java) -# 生产默认值见 nuxt.config.ts:https://cms-api.websoft.top/api -# 上线/联调远程时,把这两行注释掉或删掉即可 -NUXT_PUBLIC_MODULES_API_BASE=http://127.0.0.1:9500/api -# 若本地后端同时提供图片/文件,可一并打开(默认走 server.websoft.top) -# NUXT_PUBLIC_FILE_SERVER_BASE=http://127.0.0.1:9500 - NUXT_PUBLIC_MODULES_API_BASE=http://127.0.0.1:9200/api diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..6dcc9f8 --- /dev/null +++ b/.env.development @@ -0,0 +1,40 @@ +# ============================================================ +# 本地开发配置(npm run dev) +# ------------------------------------------------------------ +# 该文件会被提交到 git,供所有开发者共享;个人差异请写进 +# .env.local(不提交,优先级更高)。 +# ============================================================ + +# ---------- 租户 / 模板 ---------- +# 本地开发用固定租户,生产环境通过域名自动识别租户 +NUXT_PUBLIC_TENANT_ID=10626 +NUXT_PUBLIC_APP_ID=website +NUXT_PUBLIC_TEMPLATE_ID=template-07 + +# 本地调试用:强制指定模板,优先级高于应用/站点绑定。留空表示不覆盖 +NUXT_PUBLIC_FORCE_TEMPLATE_ID= + +# ---------- 后端接口 ---------- +# 本地后端(hjc-java)默认端口 9200。 +# 若要联调远程环境,把下面两行注释掉即可,nuxt.config.ts 会自动回落到生产地址 +NUXT_PUBLIC_MODULES_API_BASE=http://127.0.0.1:9200/api +# 本地后端同时提供图片/文件时打开(默认走 https://server.websoft.top) +# NUXT_PUBLIC_FILE_SERVER_BASE=http://127.0.0.1:9200 + +# 其余接口本地开发直接走远程(一般无需修改) +NUXT_PUBLIC_SERVER_API_BASE=https://server.websoft.top/api +NUXT_PUBLIC_APP_API_BASE=https://websopy-api.websoft.top + +# ---------- 站点 / 多租户域名 ---------- +NUXT_PUBLIC_BASE_DOMAIN=shoplnk.cn +NUXT_PUBLIC_BASE_DOMAINS=shoplnk.cn,sitelink.cn,wsdns.cn +NUXT_PUBLIC_SUBDOMAIN_PREFIXES=site,shop,store,mp,app,oa + +# ---------- 可选覆盖(留空 = 由后台配置决定) ---------- +NUXT_PUBLIC_SITE_NAME= +NUXT_PUBLIC_PHONE= +NUXT_PUBLIC_WX_QRCODE= +NUXT_PUBLIC_CONTACT_CAPTCHA= + +# 订阅状态缓存秒数(仅服务端可见) +NUXT_SUBSCRIPTION_CACHE_TTL=60 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..010fe66 --- /dev/null +++ b/.env.example @@ -0,0 +1,55 @@ +# ============================================================ +# 环境变量总览(参考模板) +# ------------------------------------------------------------ +# 本项目的配置分三层,优先级从低到高: +# 1. .env.development / .env.production ← 提交到 git 的共享配置 +# 2. .env ← 个人本地覆盖,已从 git 移除(不提交) +# 3. .env.local ← 优先级最高的个人覆盖(不提交) +# +# 常用命令: +# npm run dev # 开发,读取 .env.development +# npm run build:prod # 生产打包,读取 .env.production +# +# ⚠️ 所有 NUXT_PUBLIC_* 变量都会暴露给浏览器,不要放任何密钥。 +# ============================================================ + +# ---------- 租户 / 应用 / 模板 ---------- +# 固定租户 ID。生产环境留空,改由域名自动识别 +NUXT_PUBLIC_TENANT_ID= +NUXT_PUBLIC_APP_ID=website +# 默认模板 ID +NUXT_PUBLIC_TEMPLATE_ID= +# 强制模板 ID:设置后优先级最高,覆盖应用/站点绑定(仅本地调试用) +NUXT_PUBLIC_FORCE_TEMPLATE_ID= + +# ---------- 后端接口地址 ---------- +# 内容/CMS 接口。生产为 https://hjc-api.websoft.top/api +NUXT_PUBLIC_MODULES_API_BASE= +# 主服务接口。生产为 https://server.websoft.top/api +NUXT_PUBLIC_SERVER_API_BASE= +# 应用/用户接口。生产为 https://websopy-api.websoft.top +NUXT_PUBLIC_APP_API_BASE= +# 图片/文件服务器。留空则从 SERVER_API_BASE 自动推导 +NUXT_PUBLIC_FILE_SERVER_BASE= + +# ---------- 多租户域名 ---------- +# 兼容保留,实际取 BASE_DOMAINS 的第一个 +NUXT_PUBLIC_BASE_DOMAIN= +# 多主域清单,逗号分隔 +NUXT_PUBLIC_BASE_DOMAINS= +# 子域名前缀白名单(这些前缀 + 数字租户号 + 主域 才被识别为平台子域租户) +NUXT_PUBLIC_SUBDOMAIN_PREFIXES= + +# ---------- 展示覆盖(留空 = 由后台配置决定) ---------- +# 站点名称覆盖,留空则用后端 AppProduct / CMS 返回值 +NUXT_PUBLIC_SITE_NAME= +# 联系电话覆盖,用于后端 phone 被脱敏时兜底 +NUXT_PUBLIC_PHONE= +# 微信二维码覆盖。⚠️ 不要写死图片地址,留空让 Footer 从后台上传读取 +NUXT_PUBLIC_WX_QRCODE= +# 留言验证码开关:true / false / 留空(由后台 setting.requireCaptcha 决定) +NUXT_PUBLIC_CONTACT_CAPTCHA= + +# ---------- 服务端专用(不会暴露给浏览器) ---------- +# 订阅状态缓存秒数 +NUXT_SUBSCRIPTION_CACHE_TTL=60 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..de25f0d --- /dev/null +++ b/.env.production @@ -0,0 +1,33 @@ +# ============================================================ +# 生产打包配置(npm run build:prod) +# ------------------------------------------------------------ +# ⚠️ 该文件会被提交到 git,只放「非敏感、稳定」的生产参数。 +# 任何密钥类内容都不要写在这里。 +# ============================================================ + +# ---------- 接口(生产必须指向线上) ---------- +# 内容/CMS 接口统一使用 hjc-api 域名(原 cms-api.websoft.top 的 /api/* 已返回 404) +NUXT_PUBLIC_MODULES_API_BASE=https://hjc-api.websoft.top/api +NUXT_PUBLIC_SERVER_API_BASE=https://server.websoft.top/api +NUXT_PUBLIC_APP_API_BASE=https://websopy-api.websoft.top +# 图片/文件服务器。留空则由 nuxt.config.ts 从 SERVER_API_BASE 推导出 +# https://server.websoft.top,无需手写 +NUXT_PUBLIC_FILE_SERVER_BASE= + +# ---------- 站点 / 多租户域名 ---------- +# 生产通过域名自动识别租户,因此不设置 TENANT_ID / TEMPLATE_ID, +# 也不设置 FORCE_TEMPLATE_ID,避免写死租户导致所有域名串号 +NUXT_PUBLIC_BASE_DOMAIN=shoplnk.cn +NUXT_PUBLIC_BASE_DOMAINS=shoplnk.cn,sitelink.cn,wsdns.cn +NUXT_PUBLIC_SUBDOMAIN_PREFIXES=site,shop,store,mp,app,oa + +# ---------- 可选覆盖(留空 = 由后台配置决定) ---------- +# 站名/电话/二维码一律留空,从后台 CMS 读取,便于运营自行修改 +NUXT_PUBLIC_SITE_NAME= +NUXT_PUBLIC_PHONE= +NUXT_PUBLIC_WX_QRCODE= +# 留言验证码:留空表示由后台「网站设置」的 requireCaptcha 决定 +NUXT_PUBLIC_CONTACT_CAPTCHA= + +# 订阅状态缓存秒数(仅服务端可见) +NUXT_SUBSCRIPTION_CACHE_TTL=60 diff --git a/.gitignore b/.gitignore index b63b02c..840614d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ node_modules .output .data dist + +# 本地个人覆盖配置,不进版本库 +# 注意:.env.development / .env.production / .env.example 需要提交,故不在此忽略 +.env +.env.local +.env.*.local diff --git a/.workbuddy/memory/2026-09-08.md b/.workbuddy/memory/2026-09-08.md index 250362d..7d334e6 100644 --- a/.workbuddy/memory/2026-09-08.md +++ b/.workbuddy/memory/2026-09-08.md @@ -1,7 +1,7 @@ # 2026-09-08 ## 本地开发接口切换(cms-api → 127.0.0.1:9500) -- 需求:把 CMS/内容接口从 `https://cms-api.websoft.top/api` 切到本地 `http://127.0.0.1:9500/api`。 +- 需求:把 CMS/内容接口从 `https://hjc-api.websoft.top/api` 切到本地 `http://127.0.0.1:9500/api`。 - 改法:只动 `.env`,未改 `nuxt.config.ts` 默认值(保持生产配置不变)。新增 `NUXT_PUBLIC_MODULES_API_BASE=http://127.0.0.1:9500/api`;NUXT_ 前缀环境变量会覆盖 `runtimeConfig.public.modulesApiBase`。 - 关联:`modulesApiBase` 被 server/api 下 site/info、page/*、article/*、product/*、case/*、banner、form/submit、sitemap、domain/resolve 共用;文件/图片走另一个变量 `fileServerBase`(默认 origin(serverApiBase)=server.websoft.top),已在 .env 留注释开关。 - 顺带修复:原 `.env` 中 `NUXT_PUBLIC_TENANT_ID` / `NUXT_PUBLIC_TEMPLATE_ID` 两行有前导空格,dotenv 无法解析 → 配置一直没生效。已去除空格(tenant 10626 / template-07 将真正生效,若之前靠默认 template-01 调试需注意行为变化)。 diff --git a/README.md b/README.md index bac537e..54a8466 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,41 @@ npm run dev 默认访问:http://localhost:3000 +## 环境变量与打包 + +项目已按环境拆分配置文件,**打生产包不再需要手改 `.env`**: + +| 命令 | 用途 | 读取的配置 | +|---|---|---| +| `npm run dev` | 本地开发 | `.env.development` | +| `npm run build:prod` | 生产打包 | `.env.production` | +| `npm run build` | 等同 `build:prod` | `.env.production` | +| `npm run build:dev` | 用开发配置打包(排查用) | `.env.development` | +| `npm run dev:prod` | 以生产配置本地起服务(排查用) | `.env.production` | + +配置文件说明: + +| 文件 | 是否提交 | 说明 | +|---|---|---| +| `.env.development` | ✅ | 团队共享的开发配置,接口指向本地后端 | +| `.env.production` | ✅ | 团队共享的生产配置,接口指向线上 | +| `.env.example` | ✅ | 变量总览模板,新增变量时同步更新 | +| `.env.local` | ❌ | 个人通用覆盖 | +| `.env.<环境>.local` | ❌ | 个人针对某环境的覆盖,优先级最高 | +| `.env` | ❌ | 历史遗留的个人配置,优先级最低 | + +优先级(高 → 低): +**进程环境变量 > `.env.<环境>.local` > `.env.<环境>` > `.env.local` > `.env`** + +只有「变量尚未定义」时才会被下一层填充,因此高优先级不会被低优先级顶掉, +`.env` 里残留的本地地址也不会再污染生产包。 + +发布前可自查产物是否干净: + +```bash +grep -rl "127.0.0.1" .output/ || echo "产物干净,无本地地址" +``` + ## 模板系统 模板存放在 `app/templates/` 目录下,每个模板独立目录,包含: @@ -56,20 +91,30 @@ npm run dev - `template-01`:科技蓝企业模板(完整) - `template-02` ~ `template-04`:占位模板,后续根据设计稿实现 -## 环境变量 +## 环境变量清单 -复制 `.env.example` 为 `.env`,按需修改: +完整变量说明见 `.env.example`。核心变量: ```bash -NUXT_PUBLIC_TENANT_ID=10398 +# 租户 / 模板(生产留空,由域名自动识别) +NUXT_PUBLIC_TENANT_ID= NUXT_PUBLIC_APP_ID=website -NUXT_PUBLIC_TEMPLATE_ID=template-01 +NUXT_PUBLIC_TEMPLATE_ID= + +# 接口地址 NUXT_PUBLIC_SERVER_API_BASE=https://server.websoft.top/api -NUXT_PUBLIC_MODULES_API_BASE=https://cms-api.websoft.top/api -NUXT_PUBLIC_APP_API_BASE=https://cms-api.websoft.top -NUXT_PUBLIC_BASE_DOMAIN=shoplnk.cn +NUXT_PUBLIC_MODULES_API_BASE=https://hjc-api.websoft.top/api +NUXT_PUBLIC_APP_API_BASE=https://websopy-api.websoft.top + +# 多租户域名 +NUXT_PUBLIC_BASE_DOMAINS=shoplnk.cn,sitelink.cn,wsdns.cn ``` +> ℹ️ `nuxt.config.ts` 内置的 `NUXT_PUBLIC_MODULES_API_BASE` 默认值已统一为 +> `https://hjc-api.websoft.top/api`(原 `cms-api.websoft.top` 域名 `/api/*` 已返回 404, +> 故全面切换到 `hjc-api`)。`.env.production` 中也显式指定同一地址。 +> 若将来再次更换域名,请同步修改 `nuxt.config.ts`、`.env.production` 与 `.env.example`。 + ## 接口说明 本项目通过 `server/api/` 代理转发 SaaS 后端接口,前端不直接调用后端地址。 diff --git a/database/template-switch-verification.md b/database/template-switch-verification.md index 4459a26..cc65fe7 100644 --- a/database/template-switch-verification.md +++ b/database/template-switch-verification.md @@ -8,7 +8,7 @@ 后台 website-admin `_websopy` 代理 → `websopy-api.websoft.top` = **websopy-java**。 `CmsTemplateController.use()` 按 `loginUser.getTenantId()` 写 `cms_website.template_id`。 - **读路径** `GET /cms/cms-website/getSiteInfo`: - 公开站 `modulesApiBase` 与 后台 `_cms` 代理 → `cms-api.websoft.top` = **mp-java**。 + 公开站 `modulesApiBase` 与 后台 `_cms` 代理 → `hjc-api.websoft.top` = **mp-java**。 返回 `ShopVo`(站点信息),被 Redis 缓存 1 天(key `SiteInfo:{tenantId}`)。 - 两后端**共享同一 Redis**(prod `1Panel-redis-Q1LE`;dev `47.119.165.234`,同密码), 故 websopy-java 的 `use()` 清缓存能清掉 mp-java 读的 `SiteInfo:{tenantId}`。 diff --git a/nuxt.config.ts b/nuxt.config.ts index 8f72d7a..5bcc77f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -28,7 +28,7 @@ const serverApiBase = 'https://server.websoft.top/api' const modulesApiBase = process.env.NUXT_PUBLIC_MODULES_API_BASE || - 'https://cms-api.websoft.top/api' + 'https://hjc-api.websoft.top/api' const appApiBase = process.env.NUXT_PUBLIC_APP_API_BASE || 'https://websopy-api.websoft.top' @@ -81,9 +81,9 @@ export default defineNuxtConfig({ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, // 提前与内容/接口源站建立连接,降低首字节与图片加载延迟 { rel: 'preconnect', href: 'https://server.websoft.top', crossorigin: '' }, - { rel: 'preconnect', href: 'https://cms-api.websoft.top', crossorigin: '' }, + { rel: 'preconnect', href: 'https://hjc-api.websoft.top', crossorigin: '' }, { rel: 'dns-prefetch', href: 'https://server.websoft.top' }, - { rel: 'dns-prefetch', href: 'https://cms-api.websoft.top' }, + { rel: 'dns-prefetch', href: 'https://hjc-api.websoft.top' }, // 思源黑体(Noto Sans SC,SIL 开源许可,版权无忧)网页字体,全站统一字体 { rel: 'preconnect', href: 'https://fonts.googleapis.cn' }, { rel: 'preconnect', href: 'https://fonts.gstatic.cn', crossorigin: '' }, diff --git a/outputs/template-07-home-with-news.html b/outputs/template-07-home-with-news.html index 7bda305..a2aa7b5 100644 --- a/outputs/template-07-home-with-news.html +++ b/outputs/template-07-home-with-news.html @@ -7,4 +7,4 @@ if (!window.__NUXT_DEVTOOLS_TIME_METRIC__) { }) } window.__NUXT_DEVTOOLS_TIME_METRIC__.appInit = Date.now() -
首页幻灯片

四大核心业务

覆盖政府采购、工程咨询、投融资服务及 AI 数智化全链条

政府采购代理

提供招标投标、政府采购全流程代理服务,专业合规、高效透明。

工程咨询服务

工程造价、工程监理、项目管理等一站式工程咨询服务,助力项目顺利落地。

投融资服务

项目投融资策划与对接,为政府及企业提供资金解决方案与财务顾问支持。

AI 数智化

运用人工智能与大数据技术,推动政务与企业数字化转型,提升管理效能。

关于我们

广西汇吉采咨询有限公司成立于2017年,是一家专业化的全过程工程咨询服务商,业务涵盖招投标代理、政府采购代理、工程造价咨询、工程管理服务及建设工程监理等领域。目前已在柳州、崇左、北海、梧州、百色、贺州等地设立分支机构,服务网络覆盖全广西。我们始终以诚信为本,以专业立身,致力于为各级政企客户提供高效、可信赖的工程咨询解决方案。

汇吉采官网

准备好开始了吗?

立即联系我们,获取专属企业官网解决方案

\ No newline at end of file +
首页幻灯片

四大核心业务

覆盖政府采购、工程咨询、投融资服务及 AI 数智化全链条

政府采购代理

提供招标投标、政府采购全流程代理服务,专业合规、高效透明。

工程咨询服务

工程造价、工程监理、项目管理等一站式工程咨询服务,助力项目顺利落地。

投融资服务

项目投融资策划与对接,为政府及企业提供资金解决方案与财务顾问支持。

AI 数智化

运用人工智能与大数据技术,推动政务与企业数字化转型,提升管理效能。

关于我们

广西汇吉采咨询有限公司成立于2017年,是一家专业化的全过程工程咨询服务商,业务涵盖招投标代理、政府采购代理、工程造价咨询、工程管理服务及建设工程监理等领域。目前已在柳州、崇左、北海、梧州、百色、贺州等地设立分支机构,服务网络覆盖全广西。我们始终以诚信为本,以专业立身,致力于为各级政企客户提供高效、可信赖的工程咨询解决方案。

汇吉采官网

准备好开始了吗?

立即联系我们,获取专属企业官网解决方案

\ No newline at end of file diff --git a/outputs/汇吉采-标书购买-前端清单与后端接口清单.md b/outputs/汇吉采-标书购买-前端清单与后端接口清单.md index 9910c52..5f3d08b 100644 --- a/outputs/汇吉采-标书购买-前端清单与后端接口清单.md +++ b/outputs/汇吉采-标书购买-前端清单与后端接口清单.md @@ -43,7 +43,7 @@ | `server/api/payment/query.get.ts` | `tenderApiBase/payment/query` | 支付状态查询(复用现有支付查询) | | `server/api/supplier/login.post.ts` 等 | `tenderApiBase/...` | 仅当采用账号密码注册登录时新增 | -> **双后端兼容**:所有 `tenderApiBase` 走统一 runtimeConfig(`NUXT_TENDER_API_BASE`),默认 `https://cms-api.websoft.top/api`;若确认交易走 guilixu-java,改 env 即可,前端代码不变。租户头 `TenantId`、JWT 透传沿用现有中间件。 +> **双后端兼容**:所有 `tenderApiBase` 走统一 runtimeConfig(`NUXT_TENDER_API_BASE`),默认 `https://hjc-api.websoft.top/api`;若确认交易走 guilixu-java,改 env 即可,前端代码不变。租户头 `TenantId`、JWT 透传沿用现有中间件。 --- diff --git a/package.json b/package.json index b2c27de..bd74c23 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,12 @@ "private": true, "packageManager": "pnpm@9.15.9", "scripts": { - "build": "node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs build", - "build:staging": "dotenv -e .env.staging -- node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs build", - "dev": "node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs dev", - "dev:staging": "dotenv -e .env.staging -- node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs dev", + "build": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs build --env-name=production", + "build:prod": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs build --env-name=production", + "build:dev": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs build --env-name=development", + "dev": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs dev --env-name=development", + "dev:prod": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs dev --env-name=production", + "preview:prod": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs preview --env-name=production", "generate": "node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs generate", "lint": "eslint .", "lint:fix": "eslint . --fix", diff --git a/scripts/cms_fill_image.py b/scripts/cms_fill_image.py index a2a83f0..7f6b76f 100644 --- a/scripts/cms_fill_image.py +++ b/scripts/cms_fill_image.py @@ -7,7 +7,7 @@ import os, json, sys, subprocess, time, urllib.request, urllib.error TOKEN = os.environ.get("TOKEN", "") UPLOAD = "https://server.websoft.top/api/file/upload" -CMS = "https://cms-api.websoft.top/api" +CMS = "https://hjc-api.websoft.top/api" FILE_BASE = "https://file.websoft.top/api/file/" HDR = {"TenantId": "10626", "Authorization": "Bearer " + TOKEN} diff --git a/scripts/publish-cms-content.mjs b/scripts/publish-cms-content.mjs index 4d47dd5..f4db164 100644 --- a/scripts/publish-cms-content.mjs +++ b/scripts/publish-cms-content.mjs @@ -25,7 +25,7 @@ * ============================================================================ */ -const API_BASE = process.env.CMS_API_BASE || 'https://cms-api.websoft.top/api' +const API_BASE = process.env.CMS_API_BASE || 'https://hjc-api.websoft.top/api' const TOKEN_RAW = process.env.ADMIN_TOKEN || '' const TENANT_ID = process.env.TENANT_ID || '10546' const APPLY = process.env.APPLY === '1' diff --git a/scripts/with-env.mjs b/scripts/with-env.mjs new file mode 100644 index 0000000..6d5d3aa --- /dev/null +++ b/scripts/with-env.mjs @@ -0,0 +1,102 @@ +/** + * 按环境加载 .env 文件,然后启动 Nuxt。 + * + * 用法(推荐,跨平台): + * node --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs dev --env-name=development + * node --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs build --env-name=production + * + * 为什么不用 `NUXT_ENV=production node ...` 这种内联赋值: + * npm 在 Windows 上通过 cmd.exe 执行脚本,`FOO=bar cmd` 不是合法语法, + * 会报 "'FOO' is not recognized as an internal or external command"。 + * 也不要写成 `--import ./with-env.mjs .env.production --`:Node 会把 + * `--import` 之后、`--` 之前的每个裸参数都当成要加载的模块, + * `.env.production` 会触发 ERR_UNKNOWN_FILE_EXTENSION。 + * 因此改用命令行参数 `--env-name=` 传参,平台无关。 + * + * 加载顺序(先加载的优先级更高,已存在的变量不会被后续文件覆盖): + * 1. 进程已有的环境变量 ← 最高(CI / 命令行传入) + * 2. .env..local ← 个人针对某环境的覆盖 + * 3. .env.(development / production) ← 团队共享配置 + * 4. .env.local ← 个人通用覆盖 + * 5. .env ← 最低(历史遗留的个人配置) + * + * 说明:Nuxt 内部通过 c12 读取 .env,而 c12 只在变量「尚未存在」时才写入 + * process.env,因此这里先注入的值会被保留,不会被打包过程顶掉。 + */ +import { existsSync, readFileSync } from 'node:fs' +import { resolve } from 'node:path' + +/** 解析 .env 文本,返回键值对。支持 export 前缀、引号与 # 注释。 */ +function parseEnv (text) { + const out = {} + for (const rawLine of text.split(/\r?\n/)) { + const line = rawLine.trim() + if (!line || line.startsWith('#')) continue + + const eq = line.indexOf('=') + if (eq === -1) continue + + const key = line.slice(0, eq).trim().replace(/^export\s+/, '') + if (!key) continue + + let value = line.slice(eq + 1).trim() + // 去掉成对的引号(单引号内不做转义处理) + if ( + (value.startsWith('"') && value.endsWith('"') && value.length >= 2) || + (value.startsWith("'") && value.endsWith("'") && value.length >= 2) + ) { + const quote = value[0] + value = value.slice(1, -1) + if (quote === '"') { + value = value + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\t/g, '\t') + } + } else { + // 未加引号时,行尾注释按 dotenv 规则去掉(# 前需有空白) + value = value.replace(/\s+#.*$/, '').trim() + } + out[key] = value + } + return out +} + +/** 读取文件并注入 process.env,已存在的键不覆盖。 */ +function apply (file) { + const abs = resolve(process.cwd(), file) + if (!existsSync(abs)) return false + + for (const [key, value] of Object.entries(parseEnv(readFileSync(abs, 'utf8')))) { + if (process.env[key] === undefined) process.env[key] = value + } + return true +} + +// 从命令行参数中取 --env-name= +const envArgIndex = process.argv.findIndex(a => a.startsWith('--env-name=')) +const envName = envArgIndex === -1 + ? '' + : process.argv[envArgIndex].slice('--env-name='.length) + +// 读完后从 argv 中移除,避免把这个自定义参数透传给 Nuxt(nuxi 只认 --envName) +if (envArgIndex !== -1) process.argv.splice(envArgIndex, 1) + +// 高优先级在前:由于「已存在则不覆盖」,先加载者胜出。 +// 环境专属文件必须排在 .env 之前,否则历史遗留的 .env 会顶掉生产配置。 +const files = [] +if (envName) files.push(`.env.${envName}.local`) +if (envName) files.push(`.env.${envName}`) +files.push('.env.local') +files.push('.env') + +files.forEach(apply) + +if (envName && !existsSync(resolve(process.cwd(), `.env.${envName}`))) { + console.warn( + `[with-env] 警告:未找到 .env.${envName},将使用 nuxt.config.ts 内置默认值` + ) +} + +// 本模块只负责准备环境变量;Nuxt 由 Node 在加载本文件后继续执行, +// 两者同进程,因此 process.env 的修改对 Nuxt 可见。 diff --git a/单页管理-导航关联-对接说明.md b/单页管理-导航关联-对接说明.md deleted file mode 100644 index a49677a..0000000 --- a/单页管理-导航关联-对接说明.md +++ /dev/null @@ -1,77 +0,0 @@ -# 单页管理(cms_page)与导航(cms_navigation)关联对接说明 - -> 面向后端 / website-admin「单页管理」模块负责人。 -> 目标:把"菜单结构"与"单页内容"解耦又关联——导航只管菜单层级,单页管理只管正文,二者用 URL(slug)挂钩。 - ---- - -## 1. 约定结论(已与前端确认) - -| 项 | 约定 | -|---|---| -| **内容唯一源** | 新建的单页内容一律建在「单页管理」(`cms_page`),按 `slug` 存取;导航节点不再存放单页正文(`design.content` 仅保留给旧租户兼容)。 | -| **规范 URL** | 单页对外地址统一为 `/page/{slug}`,例如 `/page/about-us`。 | -| **About / Contact** | `/about`、`/contact` 为独立富组件路由,是这两个页面的**规范地址**;`/page/about`、`/page/contact` 仅作兼容入口,前端已 301 跳到顶层(见 §4)。 | -| **导航关联方式** | 后台建菜单关联单页时,把 `nav.path` 写成 `/page/{slug}`(前端已支持,无需额外接口)。`CmsNavigation.pageId` 作为后端对账键保留。 | -| **旧数据兼容** | `/page/{数字}`(数字 navigationId)仍走旧 `cms_navigation + design.content`,不破坏旧租户。 | - ---- - -## 2. 前端已具备的能力(后端无需改动即可生效) - -- `getNavLink(nav)`:当 `nav.model === 'page'` 或 `nav.path` 以 `/page/` 开头时,直接返回该 path,**不会追加 `?navId=`**,单页菜单 URL 干净。 -- `app/pages/page/[id].vue`: - - 非数字 slug → 调 `/api/page/detail?path={slug}` → 取 `cms_page` 已发布单页正文。 - - slug 为 `about` / `contact` → 301 收敛到 `/about`、`/contact`。 -- 列表/详情类栏目(article/product/case)维持原 `?navId=` 行为,不受影响。 - ---- - -## 3. 后台需要配合的 3 件事 - -### 3.1 单页管理创建页面时生成 slug -- `cms_page.slug` 必须**全局唯一、SEO 友好**(建议英文小写 + 连字符,如 `about-us`、`privacy-policy`)。 -- 同一站点内 slug 不可重复;应做唯一性校验并报错提示。 -- 不建议用中文或含空格的 slug(不利于 URL 与 SEO)。 - -### 3.2 导航菜单关联单页时填 path -两种方式任选其一(推荐方式 A,零成本): - -- **方式 A(推荐)**:在「单页管理」与导航建立关联时,把菜单 `nav.path` 直接写为 `/page/{slug}`。前端拿到即可跳转,无需新增接口。 -- **方式 B**:只填 `nav.pageId = cms_page.id`,由后台在返回导航树时**回写** `nav.path = /page/{slug}`(需后台保证返回前已解析)。前端不感知 pageId。 - -> 无论哪种方式,最终下发给前端的 `nav.path` 必须是 `/page/{slug}` 形态。 - -### 3.3 关联/解绑时的数据一致性 -- 单页被删除或 slug 变更时,应同步清理或更新引用它的导航 `nav.path`,避免产生死链(前端遇到无效 slug 会渲染空状态,但死链对 SEO 不友好)。 -- 建议后台在单页管理列表页提供"已绑定菜单"反查,便于排查。 - ---- - -## 4. 路由收敛规则(前端已落实,供后端理解) - -| 访问地址 | 行为 | -|---|---| -| `/page/{slug}` | 正常渲染 cms_page 单页内容(slug 非 about/contact) | -| `/page/{数字}` | 旧逻辑:按 navigationId 取 cms_navigation 正文(兼容旧租户) | -| `/page/about` | 301 → `/about`(独立富组件,规范地址) | -| `/page/contact` | 301 → `/contact`(独立富组件,规范地址) | -| `/about`、`/contact` | 直接渲染独立富组件 | - ---- - -## 5. 待办(需前后端共同确认,前端暂未做) - -- **动态 sitemap**:当前站点地图未纳入 `cms_page` 的 slug。需后台提供"当前站点全部已发布单页 slug 列表"接口(或 site info 携带),前端再补充到 sitemap。 -- **顶层兜底层清理**:`app/pages/[slug].vue` 旧兜底层可能与 `/page/{slug}` 产生 SEO 重复,后续建议收窄或移除(低优先级,不影响当前功能)。 - ---- - -## 6. 对接自查清单 - -- [ ] 单页管理创建页面 → 生成唯一 SEO slug ✅ -- [ ] 导航关联单页 → `nav.path` 输出为 `/page/{slug}` ✅ -- [ ] slug 删除/变更 → 同步更新导航 path ✅ -- [ ] 单页访问 → `/page/{slug}` 正常出内容 ✅ -- [ ] about/contact 访问 → `/about`、`/contact`(非 `/page/about`)✅ -- [ ] sitemap 是否需纳入 cms_page(待排期)⬜ diff --git a/开发计划.md b/开发计划.md deleted file mode 100644 index 84eb710..0000000 --- a/开发计划.md +++ /dev/null @@ -1,480 +0,0 @@ -# 通用企业官网程序开发计划 - -> 项目目标:基于 PC 端 + H5 响应式,开发一套可复用于多租户 SaaS 的企业官网**纯前端展示系统**,支持模板切换、租户隔离、独立域名绑定、订阅过期校验等能力。 -> **项目定位**:本项目仅为官网前台展示,不含任何后端管理端代码。所有数据与业务能力均通过调用自研 SaaS 后端 API 实现。 -> 当前阶段:方案规划(暂不写代码) - ---- - -## 一、项目背景与现状分析 - -### 1.1 项目定位 - -本项目是一个**纯前端展示项目**: - -- ✅ 本项目负责:官网页面渲染、模板展示、SEO/SSR、H5 响应式、域名路由 -- ❌ 本项目不含:后端管理端、CMS 内容管理后台、租户/应用管理控制台 -- 📡 数据来源:统一调用自研 SaaS 后端(Java 多租户系统)提供的 API 接口 - -管理端能力(模板选择、内容编辑、域名绑定、订阅管理等)由 SaaS 后端的管理后台负责,本项目只消费其 API。 - -### 1.2 现有资源 - -| 资源 | 路径 | 说明 | -| ------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | -| 模板预览 | `/Users/gxwebsoft/VUE/website/templates/cloud-website/template-01.png` ~ `template-04.png` | 4 套企业官网模板效果图(静态设计稿) | -| 参考项目 | `/Users/gxwebsoft/VUE/websopy-pc` | 基于 Nuxt 4 + Vue 3 + TypeScript 的项目,可参考其技术栈、代理模式、租户 header 传递机制 | -| SaaS 后端 | 自研 Java 多租户系统 | 提供租户、应用、CMS 站点数据、订阅管理等 API 接口 | - -### 1.3 参考项目 `websopy-pc` 技术栈评估 - -| 维度 | 现状 | 是否适合参考 | -| ------ | -------------------------------- | ----------------------------- | -| 框架 | Nuxt 4.2 + Vue 3 + TS | ✅ 适合:Nuxt 原生 SSR,对 SEO/GEO 友好 | -| UI 库 | Ant Design Vue | ⚠️ 偏后台,官网前台不使用,模板内自定义样式 | -| 用途 | 管理控制台、开发者中心、租户后台 | ❌ 本项目不包含管理端,仅参考技术栈与代理思路 | -| 已有能力 | 多租户 header、CMS 站点 API、订阅 API、代理层 | ✅ 参考其接口调用方式和代理模式 | -| SEO 支持 | 已有 `usePageSeo` composable,可扩展 | ✅ 参考其 SEO 实现思路 | - -**结论**:本项目采用与 `websopy-pc` 相同的 Nuxt 4 + Vue 3 + TS 技术栈,但作为**独立的纯前端展示项目**开发。参考 `websopy-pc` 的 Server API 代理模式、`runtimeConfig` 配置、租户 header 传递机制,但不复用其管理端代码。 - -### 1.3 核心需求拆解 - -1. **模板切换**:多套官网模板可配置切换,不同租户/应用使用不同模板。 -2. **多租户接入**:通过租户 ID(TenantId)和应用 ID(AppId)从 SaaS 后端拉取站点配置、页面数据、菜单、内容等。 -3. **SEO + GEO**:服务端渲染(SSR)、动态 TDK、结构化数据(JSON-LD)、语义化 HTML、站点地图(Sitemap)、robots 等。 -4. **H5 支持**:响应式布局,一套代码同时适配 PC 和 H5。 -5. **独立域名绑定**: - - 默认二级域名:`https://site-[租户ID].shoplnk.cn` - - 支持反向代理绑定顶级域名(如 `www.example.com`)。 -6. **订阅过期校验**:通过应用订阅接口判断网站是否已过期,过期后引导续费。 - ---- - -## 二、技术栈选择 - -| 层级 | 技术 | 选型理由 | -| ----- | ------------------------------ | ------------------------------------------------ | -| 前端框架 | **Nuxt 4**(Vue 3 + TypeScript) | 原生 SSR/SSG,SEO/GEO 友好;同 `websopy-pc` 技术栈,团队学习成本低 | -| 样式方案 | **Tailwind CSS** | 与 `websopy-pc` 一致,响应式能力强,适合多模板隔离 | -| UI 组件 | 模板内自定义 + **Shadcn/Vue** 或轻量组件库 | 官网模板风格差异大,避免强绑定单一组件库 | -| 状态管理 | Pinia / Nuxt 内置 `useState` | 简单场景用 `useState`,复杂用 Pinia | -| 请求库 | `ofetch`(Nuxt 内置) | 与 `websopy-pc` 的 `server/api` 代理模式一致 | -| 后端代理 | Nuxt **Server / Nitro** | 隐藏真实 API 域名,统一处理租户 header、缓存、鉴权 | -| 部署 | Node.js + Nginx 反向代理 | 支持自定义域名、SSL、多租户路由分发 | - ---- - -## 三、整体架构设计 - -``` -┌──────────────────────────────────────────────────────────────────────┐ -│ 用户访问层 │ -│ 默认域名: https://site-[tenantId].shoplnk.cn │ -│ 自定义域名: https://www.example.com → Nginx 反向代理 │ -└──────────────────────────────────┬─────────────────────────────────┘ - │ - ▼ -┌──────────────────────────────────────────────────────────────────────┐ -│ Nginx 网关层 │ -│ • 根据 Host 解析租户 / 应用 ID │ -│ • 自定义域名 → 查询域名-租户映射表 → 转发到 Nuxt 服务 │ -│ • SSL 证书管理(通配符或单域名) │ -└──────────────────────────────────┬─────────────────────────────────┘ - │ - ▼ -┌──────────────────────────────────────────────────────────────────────┐ -│ Nuxt 官网前台服务(Nitro) │ -│ • Middleware:根据 Host/Header 识别当前租户与应用 │ -│ • Server API:代理 SaaS 后端接口(站点信息、页面、订阅、文件等) │ -│ • SSR 渲染:动态生成页面 HTML + SEO Meta + JSON-LD │ -│ • 模板引擎:根据模板 ID 加载对应模板组件与样式 │ -└──────────────────────────────────┬─────────────────────────────────┘ - │ - ▼ -┌──────────────────────────────────────────────────────────────────────┐ -│ 自研 SaaS 后端(Java) │ -│ • 租户管理 / 应用管理 / 订阅管理 │ -│ • CMS 站点数据:站点配置、页面、栏目、文章、产品、案例等 │ -│ • 订阅接口:判断应用是否过期、续费引导 │ -└──────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 四、目录结构规划 - -本项目即为 `/Users/gxwebsoft/VUE/website`,纯前端展示项目,结构如下: - -``` -website/ -├── app/ -│ ├── components/ # 公共组件(SEO、Header、Footer、Loading、Renewal) -│ ├── composables/ # 组合式函数 -│ │ ├── useSite.ts # 获取当前站点信息 -│ │ ├── useTenant.ts # 租户/应用识别 -│ │ ├── usePageSeo.ts # SEO Meta 设置 -│ │ ├── useSubscription.ts # 订阅状态校验 -│ │ └── useTemplate.ts # 模板加载与切换 -│ ├── layouts/ # 布局(default、blank、renewal) -│ ├── pages/ # 页面路由 -│ │ ├── index.vue # 首页 -│ │ ├── [slug].vue # 动态 CMS 页面(关于我们、产品中心...) -│ │ ├── news/ # 新闻资讯列表/详情 -│ │ ├── products/ -│ │ ├── cases/ -│ │ └── renewal.vue # 续费引导页 -│ ├── templates/ # 模板集合(核心) -│ │ ├── template-01/ # 模板 1:科技感蓝 -│ │ │ ├── index.vue # 模板首页 -│ │ │ ├── components/ # 模板私有组件 -│ │ │ ├── pages/ # 模板私有页面布局 -│ │ │ ├── config.ts # 模板配置(名称、预览图、支持模块) -│ │ │ └── theme.css # 模板主题变量 -│ │ ├── template-02/ -│ │ ├── template-03/ -│ │ └── template-04/ -│ ├── api/ # 前端业务请求(可选,也可直接调 server/api) -│ ├── types/ # TS 类型定义 -│ ├── utils/ # 工具函数 -│ ├── plugins/ # 插件 -│ ├── app.vue # 根入口 -│ └── error.vue # 错误页 -├── server/ -│ ├── api/ # 服务端代理 API -│ │ ├── site/ # 站点信息 -│ │ ├── page/ # 页面内容 -│ │ ├── subscription/ # 订阅状态 -│ │ ├── file/ # 文件代理 -│ │ └── sitemap.xml.ts # 动态站点地图 -│ ├── middleware/ # 服务端中间件 -│ │ └── tenant.ts # 租户识别、域名解析、订阅校验 -│ └── utils/ # 服务端工具函数 -├── public/ # 静态资源 -├── nuxt.config.ts # Nuxt 配置 -├── tailwind.config.cjs # Tailwind 配置 -├── package.json -├── .env.example # 环境变量示例 -└── README.md -``` - ---- - -## 五、关键模块设计方案 - -### 5.1 模板切换机制 - -模板是核心竞争力,需要设计为“可插拔”结构: - -1. **模板注册表**:每个模板在 `templates/[template-id]/config.ts` 中声明: - - `id`、`name`、`description`、`preview`(预览图路径) - - `supportedModules`:该模板支持的功能模块(首页、关于、产品、案例、新闻、联系、留言) - - `themeConfig`:主题色、字体、间距等变量 - - `layouts`:模板提供的布局组件 -2. **模板加载**:根据后端返回的 `templateId` 动态 `import()` 对应模板组件。 -3. **模板隔离**: - - CSS 使用 `data-template-id` 命名空间或 CSS Modules 隔离,避免模板间样式冲突。 - - 模板组件只负责渲染,数据由 `useSite()` 统一提供。 -4. **预览与切换**:模板预览和切换能力由 SaaS 后端管理后台提供,本项目根据后端返回的 `templateId` 动态渲染即可。 - -### 5.2 多租户识别策略 - -Nuxt Server Middleware 中统一识别当前租户,优先级: - -1. **自定义域名**:Host 不是 `*.shoplnk.cn` 时,查询 SaaS 后端“域名绑定表”获取 `tenantId` + `appId`。 -2. **默认二级域名**:`site-[tenantId].shoplnk.cn` → 从 Host 解析 `tenantId`。 -3. **环境变量默认**:用于本地开发或默认演示站点。 -4. **Header 兜底**:`TenantId` / `AppId` Header(用于调试或特殊场景)。 - -识别结果写入 `event.context.tenant` 和 `event.context.app`,供 SSR 渲染和 Server API 使用。 - -### 5.3 SaaS 后端接口对接 - -本项目作为纯前端展示层,所有数据均通过调用 SaaS 后端 API 获取。参考 `websopy-pc` 的代理模式,在 Nuxt Server 层统一转发,隐藏真实后端地址: - -| 能力 | 参考来源 | 本项目实现 | -| ---- | ---------------------------------------------------------- | ------------------------------------------ | -| 站点信息 | `websopy-pc/server/api/cms/cms-website/getSiteInfo.get.ts` | `server/api/site/info.get.ts` 代理 | -| 页面列表 | `websopy-pc/server/api/cms/cms-website/pageAll.get.ts` | `server/api/page/all.get.ts` 代理 | -| 订阅状态 | SaaS 后端订阅接口 | `server/api/subscription/status.get.ts` 代理 | -| 域名映射 | SaaS 后端域名绑定查询 | `server/api/domain/resolve.get.ts` 代理 | -| 文件代理 | `websopy-pc/server/api/_file/[...path].ts` | `server/api/file/[...path].ts` 代理 | -| 表单提交 | SaaS 后端留言/表单接口 | `server/api/form/submit.post.ts` 代理 | - -统一在服务端转发 `TenantId` 和必要的鉴权信息,避免前端暴露真实后端地址和鉴权细节。 - -### 5.4 SEO + GEO 方案 - -1. **SSR 渲染**:Nuxt 默认 SSR,确保搜索引擎和 AI 爬虫能拿到完整 HTML。 -2. **动态 TDK**:每页根据 CMS 数据设置 `title`、`description`、`keywords`。 -3. **Open Graph / Twitter Card**:每页动态设置 `og:title`、`og:description`、`og:image`、`og:url`。 -4. **Canonical URL**:避免重复内容,每个页面设置 canonical 链接。 -5. **结构化数据(JSON-LD)**: - - `Organization` 企业信息 - - `WebSite` 站点信息 - - `WebPage` 页面信息 - - `Product`、`Article`、`BreadcrumbList` 等 -6. **Sitemap**:`server/api/sitemap.xml.ts` 根据站点页面动态生成。 -7. **Robots**:根据环境配置 `robots` meta,开发/测试环境禁止抓取。 -8. **语义化 HTML**:模板组件使用 `
`、`