开发 / 生产双配置
This commit is contained in:
@@ -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
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -3,3 +3,9 @@ node_modules
|
|||||||
.output
|
.output
|
||||||
.data
|
.data
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
# 本地个人覆盖配置,不进版本库
|
||||||
|
# 注意:.env.development / .env.production / .env.example 需要提交,故不在此忽略
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 2026-09-08
|
# 2026-09-08
|
||||||
|
|
||||||
## 本地开发接口切换(cms-api → 127.0.0.1:9500)
|
## 本地开发接口切换(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`。
|
- 改法:只动 `.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 留注释开关。
|
- 关联:`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 调试需注意行为变化)。
|
- 顺带修复:原 `.env` 中 `NUXT_PUBLIC_TENANT_ID` / `NUXT_PUBLIC_TEMPLATE_ID` 两行有前导空格,dotenv 无法解析 → 配置一直没生效。已去除空格(tenant 10626 / template-07 将真正生效,若之前靠默认 template-01 调试需注意行为变化)。
|
||||||
|
|||||||
@@ -43,6 +43,41 @@ npm run dev
|
|||||||
|
|
||||||
默认访问:http://localhost:3000
|
默认访问: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/` 目录下,每个模板独立目录,包含:
|
模板存放在 `app/templates/` 目录下,每个模板独立目录,包含:
|
||||||
@@ -56,20 +91,30 @@ npm run dev
|
|||||||
- `template-01`:科技蓝企业模板(完整)
|
- `template-01`:科技蓝企业模板(完整)
|
||||||
- `template-02` ~ `template-04`:占位模板,后续根据设计稿实现
|
- `template-02` ~ `template-04`:占位模板,后续根据设计稿实现
|
||||||
|
|
||||||
## 环境变量
|
## 环境变量清单
|
||||||
|
|
||||||
复制 `.env.example` 为 `.env`,按需修改:
|
完整变量说明见 `.env.example`。核心变量:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
NUXT_PUBLIC_TENANT_ID=10398
|
# 租户 / 模板(生产留空,由域名自动识别)
|
||||||
|
NUXT_PUBLIC_TENANT_ID=
|
||||||
NUXT_PUBLIC_APP_ID=website
|
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_SERVER_API_BASE=https://server.websoft.top/api
|
||||||
NUXT_PUBLIC_MODULES_API_BASE=https://cms-api.websoft.top/api
|
NUXT_PUBLIC_MODULES_API_BASE=https://hjc-api.websoft.top/api
|
||||||
NUXT_PUBLIC_APP_API_BASE=https://cms-api.websoft.top
|
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.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 后端接口,前端不直接调用后端地址。
|
本项目通过 `server/api/` 代理转发 SaaS 后端接口,前端不直接调用后端地址。
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
后台 website-admin `_websopy` 代理 → `websopy-api.websoft.top` = **websopy-java**。
|
后台 website-admin `_websopy` 代理 → `websopy-api.websoft.top` = **websopy-java**。
|
||||||
`CmsTemplateController.use()` 按 `loginUser.getTenantId()` 写 `cms_website.template_id`。
|
`CmsTemplateController.use()` 按 `loginUser.getTenantId()` 写 `cms_website.template_id`。
|
||||||
- **读路径** `GET /cms/cms-website/getSiteInfo`:
|
- **读路径** `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}`)。
|
返回 `ShopVo`(站点信息),被 Redis 缓存 1 天(key `SiteInfo:{tenantId}`)。
|
||||||
- 两后端**共享同一 Redis**(prod `1Panel-redis-Q1LE`;dev `47.119.165.234`,同密码),
|
- 两后端**共享同一 Redis**(prod `1Panel-redis-Q1LE`;dev `47.119.165.234`,同密码),
|
||||||
故 websopy-java 的 `use()` 清缓存能清掉 mp-java 读的 `SiteInfo:{tenantId}`。
|
故 websopy-java 的 `use()` 清缓存能清掉 mp-java 读的 `SiteInfo:{tenantId}`。
|
||||||
|
|||||||
+3
-3
@@ -28,7 +28,7 @@ const serverApiBase =
|
|||||||
'https://server.websoft.top/api'
|
'https://server.websoft.top/api'
|
||||||
const modulesApiBase =
|
const modulesApiBase =
|
||||||
process.env.NUXT_PUBLIC_MODULES_API_BASE ||
|
process.env.NUXT_PUBLIC_MODULES_API_BASE ||
|
||||||
'https://cms-api.websoft.top/api'
|
'https://hjc-api.websoft.top/api'
|
||||||
const appApiBase =
|
const appApiBase =
|
||||||
process.env.NUXT_PUBLIC_APP_API_BASE ||
|
process.env.NUXT_PUBLIC_APP_API_BASE ||
|
||||||
'https://websopy-api.websoft.top'
|
'https://websopy-api.websoft.top'
|
||||||
@@ -81,9 +81,9 @@ export default defineNuxtConfig({
|
|||||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||||
// 提前与内容/接口源站建立连接,降低首字节与图片加载延迟
|
// 提前与内容/接口源站建立连接,降低首字节与图片加载延迟
|
||||||
{ rel: 'preconnect', href: 'https://server.websoft.top', crossorigin: '' },
|
{ 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://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 开源许可,版权无忧)网页字体,全站统一字体
|
// 思源黑体(Noto Sans SC,SIL 开源许可,版权无忧)网页字体,全站统一字体
|
||||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.cn' },
|
{ rel: 'preconnect', href: 'https://fonts.googleapis.cn' },
|
||||||
{ rel: 'preconnect', href: 'https://fonts.gstatic.cn', crossorigin: '' },
|
{ rel: 'preconnect', href: 'https://fonts.gstatic.cn', crossorigin: '' },
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -43,7 +43,7 @@
|
|||||||
| `server/api/payment/query.get.ts` | `tenderApiBase/payment/query` | 支付状态查询(复用现有支付查询) |
|
| `server/api/payment/query.get.ts` | `tenderApiBase/payment/query` | 支付状态查询(复用现有支付查询) |
|
||||||
| `server/api/supplier/login.post.ts` 等 | `tenderApiBase/...` | 仅当采用账号密码注册登录时新增 |
|
| `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 透传沿用现有中间件。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -4,10 +4,12 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.15.9",
|
"packageManager": "pnpm@9.15.9",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs build",
|
"build": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs build --env-name=production",
|
||||||
"build:staging": "dotenv -e .env.staging -- node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs build",
|
"build:prod": "node --import ./scripts/crypto-hash-polyfill.mjs --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs build --env-name=production",
|
||||||
"dev": "node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs dev",
|
"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:staging": "dotenv -e .env.staging -- node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs dev",
|
"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",
|
"generate": "node --import ./scripts/crypto-hash-polyfill.mjs ./node_modules/nuxt/bin/nuxt.mjs generate",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import os, json, sys, subprocess, time, urllib.request, urllib.error
|
|||||||
|
|
||||||
TOKEN = os.environ.get("TOKEN", "")
|
TOKEN = os.environ.get("TOKEN", "")
|
||||||
UPLOAD = "https://server.websoft.top/api/file/upload"
|
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/"
|
FILE_BASE = "https://file.websoft.top/api/file/"
|
||||||
HDR = {"TenantId": "10626", "Authorization": "Bearer " + TOKEN}
|
HDR = {"TenantId": "10626", "Authorization": "Bearer " + TOKEN}
|
||||||
|
|
||||||
|
|||||||
@@ -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 TOKEN_RAW = process.env.ADMIN_TOKEN || ''
|
||||||
const TENANT_ID = process.env.TENANT_ID || '10546'
|
const TENANT_ID = process.env.TENANT_ID || '10546'
|
||||||
const APPLY = process.env.APPLY === '1'
|
const APPLY = process.env.APPLY === '1'
|
||||||
|
|||||||
@@ -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=<name>` 传参,平台无关。
|
||||||
|
*
|
||||||
|
* 加载顺序(先加载的优先级更高,已存在的变量不会被后续文件覆盖):
|
||||||
|
* 1. 进程已有的环境变量 ← 最高(CI / 命令行传入)
|
||||||
|
* 2. .env.<name>.local ← 个人针对某环境的覆盖
|
||||||
|
* 3. .env.<name>(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=<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 可见。
|
||||||
@@ -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(待排期)⬜
|
|
||||||
@@ -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**:模板组件使用 `<header>`、`<nav>`、`<main>`、`<article>`、`<footer>` 等标签。
|
|
||||||
9. **URL 设计**:简洁语义化,如 `/about`、`/products`、`/news/[id]`。
|
|
||||||
|
|
||||||
### 5.5 H5 响应式
|
|
||||||
|
|
||||||
- 模板设计采用 **Mobile First** 原则。
|
|
||||||
- 使用 Tailwind 断点:`sm: md: lg: xl:`。
|
|
||||||
- 导航组件在 H5 下自动切换为汉堡菜单。
|
|
||||||
- 图片、视频使用响应式尺寸,避免移动端加载过大资源。
|
|
||||||
- 针对 H5 独立优化首屏加载性能(Lazy loading、资源预加载)。
|
|
||||||
|
|
||||||
### 5.6 独立域名绑定
|
|
||||||
|
|
||||||
#### 5.6.1 默认二级域名
|
|
||||||
|
|
||||||
- 格式:`https://site-[tenantId].shoplnk.cn`
|
|
||||||
- 在 Nginx 配置泛域名解析:`*.shoplnk.cn` → Nuxt 服务。
|
|
||||||
- Server Middleware 从 Host 中解析 `tenantId`。
|
|
||||||
|
|
||||||
#### 5.6.2 自定义顶级域名
|
|
||||||
|
|
||||||
- 用户在 SaaS 后台绑定域名 `www.example.com`。
|
|
||||||
- SaaS 后端记录“域名 → 租户 + 应用”映射。
|
|
||||||
- Nginx 监听 `www.example.com`(或默认 `server_name` 兜底),转发到 Nuxt 服务。
|
|
||||||
- Nuxt Server Middleware 通过域名查询后端映射表获取租户信息。
|
|
||||||
- SSL 证书:建议使用 Nginx 统一管理,可采用 Let's Encrypt 自动化证书,或 SaaS 后台提供证书上传。
|
|
||||||
|
|
||||||
### 5.7 订阅过期校验与续费引导
|
|
||||||
|
|
||||||
1. **订阅校验位置**:
|
|
||||||
- Nuxt Server Middleware:每次请求先校验当前应用的订阅状态。
|
|
||||||
- 前端挂载时:再次校验(防止 SSR 后状态变化)。
|
|
||||||
2. **过期状态处理**:
|
|
||||||
- 未过期:正常渲染。
|
|
||||||
- 已过期/未购买:渲染“续费引导页”(`pages/renewal.vue`),可保留展示站点基础信息,但隐藏关键功能和联系方式,或整站显示续费提示。
|
|
||||||
3. **续费接口**:调用 SaaS 后端订阅/续费接口,支付完成后刷新页面。
|
|
||||||
4. **缓存策略**:订阅状态可缓存短时间(如 60 秒),避免每次请求都调用后端,但要保证过期时及时切换。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、开发阶段规划
|
|
||||||
|
|
||||||
建议按以下阶段推进,每个阶段产出可验证的里程碑。
|
|
||||||
|
|
||||||
### 第一阶段:基础框架搭建(约 1 周)
|
|
||||||
|
|
||||||
- [ ] 新建独立 Nuxt 4 项目(参考 `websopy-pc` 技术栈,但不含管理端代码)。
|
|
||||||
- [ ] 配置 TypeScript、Tailwind CSS、ESLint。
|
|
||||||
- [ ] 搭建基础目录结构(layouts、pages、composables、server)。
|
|
||||||
- [ ] 配置环境变量(`NUXT_PUBLIC_TENANT_ID`、`NUXT_PUBLIC_API_BASE`、`NUXT_PUBLIC_TEMPLATE_ID` 等)。
|
|
||||||
- [ ] 实现基础 Server Middleware:租户识别、域名解析、请求代理。
|
|
||||||
- [ ] 实现 `useSite()`、`useTenant()`、`usePageSeo()` 等核心 composables。
|
|
||||||
- [ ] 接入 SaaS 后端“站点信息”和“页面列表”接口。
|
|
||||||
|
|
||||||
**里程碑**:访问 `site-[tenantId].shoplnk.cn` 能显示基础首页,并从后端读取到站点标题和页面数据。
|
|
||||||
|
|
||||||
### 第二阶段:模板系统(约 1.5 周)
|
|
||||||
|
|
||||||
- [ ] 将现有 4 套模板设计稿转化为可运行的 Vue 组件(按 `templates/template-01/` 结构)。
|
|
||||||
- [ ] 设计模板配置规范(`config.ts`)和模板加载器(`useTemplate()`)。
|
|
||||||
- [ ] 实现模板样式隔离方案。
|
|
||||||
- [ ] 实现模板切换逻辑:根据后端 `templateId` 动态渲染对应模板。
|
|
||||||
- [ ] 提取公共组件:SiteHeader、SiteFooter、HeroSection、SectionTitle、ContactForm 等。
|
|
||||||
- [ ] 预留 CMS 模块接口:首页、关于我们、产品中心、案例展示、新闻资讯、联系我们。
|
|
||||||
|
|
||||||
**里程碑**:切换后端 `templateId`,页面能实时渲染不同模板风格。
|
|
||||||
|
|
||||||
### 第三阶段:页面路由与 CMS 渲染(约 1 周)
|
|
||||||
|
|
||||||
- [ ] 实现动态路由:`[slug].vue` 根据 CMS 页面配置渲染。
|
|
||||||
- [ ] 实现新闻、产品、案例列表页和详情页。
|
|
||||||
- [ ] 实现面包屑导航。
|
|
||||||
- [ ] 实现联系表单(提交到 SaaS 后端)。
|
|
||||||
- [ ] 对接 CMS 富文本内容渲染。
|
|
||||||
- [ ] 实现 404 页面和错误页。
|
|
||||||
|
|
||||||
**里程碑**:通过 CMS 配置的新页面能自动出现路由并正确渲染;新闻/产品/案例模块正常展示。
|
|
||||||
|
|
||||||
### 第四阶段:SEO / GEO 与性能优化(约 1 周)
|
|
||||||
|
|
||||||
- [ ] 全站动态 TDK、Open Graph、Canonical URL。
|
|
||||||
- [ ] 实现 JSON-LD 结构化数据注入。
|
|
||||||
- [ ] 实现动态 `sitemap.xml` 和 `robots.txt`。
|
|
||||||
- [ ] 配置图片懒加载、资源预加载、字体优化。
|
|
||||||
- [ ] 接入 Nuxt 性能分析,优化首屏加载时间。
|
|
||||||
- [ ] 验证 SSR 输出 HTML 是否完整可被爬虫抓取。
|
|
||||||
|
|
||||||
**里程碑**:搜索引擎能抓取完整页面;通过 Lighthouse SEO 评分基本项。
|
|
||||||
|
|
||||||
### 第五阶段:域名绑定与部署(约 1 周)
|
|
||||||
|
|
||||||
- [ ] Nginx 配置泛域名解析 `*.shoplnk.cn`。
|
|
||||||
- [ ] 实现自定义域名反向代理。
|
|
||||||
- [ ] 实现域名-租户映射查询(后端接口 + Nuxt Server Middleware)。
|
|
||||||
- [ ] SSL 证书方案确定并配置(通配符证书或单域名证书)。
|
|
||||||
- [ ] 编写部署脚本和 Dockerfile(可参考 `websopy-pc/Dockerfile`)。
|
|
||||||
- [ ] 配置 CI/CD 流程。
|
|
||||||
|
|
||||||
**里程碑**:`site-[tenantId].shoplnk.cn` 和自定义域名都能正常访问对应租户站点。
|
|
||||||
|
|
||||||
### 第六阶段:订阅过期与续费(约 0.5 周)
|
|
||||||
|
|
||||||
- [ ] 接入 SaaS 订阅查询接口。
|
|
||||||
- [ ] 在 Server Middleware 中校验订阅状态。
|
|
||||||
- [ ] 实现续费引导页(`renewal.vue`)。
|
|
||||||
- [ ] 实现过期后页面降级策略(展示基础信息 vs 完全屏蔽)。
|
|
||||||
- [ ] 对接续费支付流程。
|
|
||||||
|
|
||||||
**里程碑**:订阅过期后自动展示续费引导页;续费成功后恢复正常访问。
|
|
||||||
|
|
||||||
### 第七阶段:H5 适配与收尾(约 1 周)
|
|
||||||
|
|
||||||
- [ ] 所有模板完成 H5 响应式适配。
|
|
||||||
- [ ] 移动端导航、表单、图片、视频优化。
|
|
||||||
- [ ] 多端测试(iOS Safari、Android Chrome、微信内置浏览器)。
|
|
||||||
- [ ] 编写项目文档、模板开发规范、部署文档。
|
|
||||||
- [ ] 代码审查、性能测试、安全测试。
|
|
||||||
|
|
||||||
**里程碑**:PC 和 H5 都能正常访问;模板开发规范文档化。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 七、环境变量规划(示例)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# SaaS 后端接口地址
|
|
||||||
NUXT_PUBLIC_SERVER_API_BASE=https://cms-api.websoft.top
|
|
||||||
NUXT_PUBLIC_MODULES_API_BASE=https://cms-api.websoft.top/api
|
|
||||||
|
|
||||||
# 默认租户/应用(本地开发或演示)
|
|
||||||
NUXT_PUBLIC_TENANT_ID=1
|
|
||||||
NUXT_PUBLIC_APP_ID=website
|
|
||||||
NUXT_PUBLIC_TEMPLATE_ID=template-01
|
|
||||||
|
|
||||||
# 域名配置
|
|
||||||
NUXT_PUBLIC_BASE_DOMAIN=shoplnk.cn
|
|
||||||
|
|
||||||
# 缓存与订阅校验
|
|
||||||
NUXT_SUBSCRIPTION_CACHE_TTL=60
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 八、风险与注意事项
|
|
||||||
|
|
||||||
| 风险点 | 说明 | 建议 |
|
|
||||||
| ----------------- | ------------------------ | ----------------------------- |
|
|
||||||
| 模板样式冲突 | 多模板共享全局 CSS 可能互相污染 | 使用 CSS 命名空间或 CSS Modules 隔离 |
|
|
||||||
| 自定义域名 HTTPS | 每个域名都需要证书,管理复杂 | 优先使用通配符证书或 SaaS 后台统一证书管理 |
|
|
||||||
| 租户数据安全 | 域名解析错误可能导致串站 | Server Middleware 严格校验域名-租户映射 |
|
|
||||||
| SEO 数据缺失 | 后端 CMS 未配置 TDK 时页面为空 | 提供默认值和必填校验 |
|
|
||||||
| 订阅校验性能 | 每次请求都校验会增加延迟 | 使用服务端缓存,过期时间 60 秒内 |
|
|
||||||
| H5 适配工作量大 | 4 套模板都要做响应式 | 前期约定模板栅格规范,公共组件优先做响应式 |
|
|
||||||
| 与 `websopy-pc` 耦合 | 本项目为纯展示,不引入管理端依赖 | 独立项目,仅参考接口协议和代理思路 |
|
|
||||||
| 接口依赖 | 本项目所有功能依赖 SaaS 后端 API 可用 | 做好接口异常降级处理(默认数据、缓存兜底) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 九、下一步建议
|
|
||||||
|
|
||||||
1. **确认技术方案**:本项目作为纯前端展示项目,采用 Nuxt 4 + Vue 3 + TS 技术栈,不含管理端代码,所有功能通过 SaaS 后端 API 实现——是否确认?
|
|
||||||
2. **确认 SaaS 接口协议**:
|
|
||||||
- 站点信息接口字段定义
|
|
||||||
- 域名绑定查询接口
|
|
||||||
- 订阅状态查询接口
|
|
||||||
- 新闻/产品/案例 CMS 数据接口
|
|
||||||
- 表单/留言提交接口
|
|
||||||
3. **确认模板规范**:模板数据由 SaaS 后端管理后台配置,本项目根据 `templateId` 渲染——是否确认?
|
|
||||||
4. **确认域名与证书方案**:`shoplnk.cn` 泛域名证书是否已准备?自定义域名证书如何管理?
|
|
||||||
5. **确认首期范围**:是否 4 套模板一次全部实现?还是先实现 1 套 MVP,后续扩展?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*文档生成时间:2026-07-05*
|
|
||||||
*版本:v1.1(明确纯前端展示项目定位)*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 十、专项附录:标书购买功能(template-07 / 客户「汇吉采」)
|
|
||||||
|
|
||||||
> **性质**:这是叠加在基础官网框架之上的**客户专项需求**,不修改基础框架的定位与计划(一至九节保持不变)。
|
|
||||||
> **代码边界**:前端改动在本仓库(website-template);后端改动在 **`cms-java-code` 仓库(即线上 cms-api)**,本仓库的 `server/api/*` 只做代理转发,不含交易逻辑。
|
|
||||||
|
|
||||||
### 10.1 后端选型结论(已评估确定)
|
|
||||||
|
|
||||||
**在 `cms-api`(= `cms-java-code`,即自研 SaaS 后端)内新增「标书」业务包,复用其已有的 `shop` 订单 + `payment` 微信扫码支付底座;不单独启用 `guilixu-java`。**
|
|
||||||
|
|
||||||
依据(已查代码确认):
|
|
||||||
|
|
||||||
- 模板当前只连 cms-api(`modulesApiBase = https://cms-api.websoft.top/api`),前端零新增对接。
|
|
||||||
- cms-api 已内置:会员/用户体系(`ShopUser`、JWT)、`ShopOrder`、`PaymentController.createPaymentWithOrder`(建单+发起支付)、`WechatNativeStrategy`(返回 `codeUrl`)、`PaymentNotifyController`(异步回调)、多租户(`TenantId` 头)。
|
|
||||||
- `guilixu-java` 只是 cms-api 的裁剪分支(仅 payment+shop+common,无 cms),且独立部署、独立数据库,单独启用只增加跨库/跨服务成本、无收益。
|
|
||||||
|
|
||||||
> 详见 `outputs/汇吉采-标书购买-后端选型评估.md` 与 `outputs/汇吉采-标书购买功能方案评估.md`。
|
|
||||||
|
|
||||||
### 10.1.1 前端隔离策略(已决策:fork 独立网站端)
|
|
||||||
|
|
||||||
**决策依据(2026-08-26 与主人确认)**:
|
|
||||||
|
|
||||||
1. 标书购买是汇吉采**独家特色**,其他客户基本不会用 → 不应塞进通用模板污染内核。
|
|
||||||
2. 不排除汇吉采未来出现**更多深度定制**(不止标书) → 深度定制会持续侵蚀通用模板边界,fork 比在通用模板里写 `if (tenantId===10626)` 更干净。
|
|
||||||
3. 独立部署/运维:暂未知 → fork 不强制立即独立部署,可先 fork 代码、共用部署,待需求明确再切。
|
|
||||||
|
|
||||||
**结论**:前端**从本仓库 fork 出独立端(如 `website-huijicai`,基于 git branch 分出)**,汇吉采专属功能(标书购买 + 未来定制)只在独立端内开发,通用 website-template 保持纯净。
|
|
||||||
|
|
||||||
**fork 的正确姿势(规避维护噩梦)**:
|
|
||||||
|
|
||||||
- 用 **git branch / 独立仓库 + 共享子模块**,不要把通用内核复制粘贴成两份。
|
|
||||||
- 分层:通用内核(SEO、多租户中间件、部署脚本、基础组件)与汇吉采定制区(template-07 + 标书模块 + 未来定制)分离,定制区单独维护。
|
|
||||||
- 约定:通用安全/框架更新通过 **rebase / cherry-pick** 同步到独立端,防止长期漂移到"老版本分支"。
|
|
||||||
|
|
||||||
**后端不 fork(重要)**:cms-api 是多租户 monolith,fork 整个后端代价极高且不必要。`tender` 业务包直接在 cms-api 内新增,数据带 `tenantId=10626` 天然隔离,复用现有 shop/payment 底座。**即:前端物理隔离 + 后端租户逻辑隔离。**
|
|
||||||
|
|
||||||
### 10.2 前端任务清单(独立端 `website-huijicai`)
|
|
||||||
|
|
||||||
| 任务 | 做法 | 复用/新增 |
|
|
||||||
| ---------------------- | ---------------------------------------------------------------------------------------- | ---------------------- |
|
|
||||||
| ① 激活 `buy` 入口 | 新增 `app/pages/buy.vue`,照搬 `renewal.vue` 加载 `components.BuyDocument`;无需改 `useTemplate` 核心 | 复用现有 routeMap `buy` 钩子 |
|
|
||||||
| ② `BuyDocument.vue` 改造 | 占位 stub → 购买流程壳(列表 + 详情 + 表单 + 支付二维码 + 结果页) | 改写现有文件 |
|
|
||||||
| ③ 标书列表/详情 | 仿 `ProductList/ProductDetail` 结构接 `tender` 接口(status=onsale 筛选、关键词、分类) | 复制改造 |
|
|
||||||
| ④ 联系表单 | 姓名/电话/邮箱/公司,**复用 `ContactForm` 手机号校验 + 滑块验证码** | 复用 |
|
|
||||||
| ⑤ 注册/登录页 | `register.vue` / `login.vue` + `useSupplier` 登录态 composable(Token 存 cookie)+ 购买入口权限门控 | 新增 |
|
|
||||||
| ⑥ 支付页 | 展示后端返回的 `code_url` 二维码 + **轮询订单状态** + 支付成功展示「下载标书」/「已发邮件」 | 新增 |
|
|
||||||
|
|
||||||
### 10.3 后端任务清单(cms-java-code 仓库,新增 `tender` 业务包)
|
|
||||||
|
|
||||||
- 实体:`Tender`(招标编号、发售起止、状态 onsale/ended、售价、关联文件)、`TenderOrder`(或复用 `ShopOrder` + 类型标识)、可选 `SupplierProfile`。
|
|
||||||
- 接口:
|
|
||||||
- `GET /api/tender/list`(按 status=onsale + 分类/关键词筛选)
|
|
||||||
- `GET /api/tender/detail`
|
|
||||||
- `POST /api/tender/order`(建单 + 微信扫码支付,复用 `createPaymentWithOrder`)
|
|
||||||
- `POST /api/tender/notify`(复用 `WxPayNotifyService` 异步回调置已支付)
|
|
||||||
- `GET /api/tender/order/status`(前端轮询)
|
|
||||||
- `GET /api/tender/order/download`(登录态 + 订单校验后下载标书 / 发邮件)
|
|
||||||
- 供应商账号:复用现有 `shop` 用户体系(加 `supplier` 角色/字段),下单接口要求 `loginUser != null`(已有约束天然实现"注册登录后才能买")。
|
|
||||||
|
|
||||||
### 10.4 里程碑(建议)
|
|
||||||
|
|
||||||
1. **M1 后端底座就绪**:`tender` 实体 + 列表/详情/筛选接口 + 下单/支付/回调/交付全链路(复用 shop/payment)。
|
|
||||||
2. **M2 前端流程闭环**:`buy` 入口激活 + 列表/详情/表单/支付/结果页,能完整走通"注册→筛选项目→填信息→扫码付→下载"。
|
|
||||||
3. **M3 联调与权限校验**:下单时后端强制校验项目状态=onsale(防绕过)、支付回调稳定、邮件/下载双通道验证。
|
|
||||||
4. **M4 上线增强(P1)**:我的购买记录、后台标书上架审核、邮件模板润色。
|
|
||||||
|
|
||||||
### 10.5 仍需拍板(阻塞排期)
|
|
||||||
|
|
||||||
1. **生产拓扑**:汇吉采生产是否真的只连 cms-api?(从模板对接关系看是,需确认是否双后端)
|
|
||||||
2. **供应商建模**:复用 shop 会员表(加角色/字段)还是新建 `supplier` 表?(建议前者)
|
|
||||||
3. **标书建模**:新建 `Tender` 实体 vs 当特殊商品复用 `ShopGoods`?(建议新建 `Tender`,语义清晰)
|
|
||||||
4. **微信商户号**:cms-api 的微信支付配置(租户 10626)是否已配好商户号与公网 `notify_url`?
|
|
||||||
|
|
||||||
### 10.6 拍板结果(2026-08-26 已确认,含修正)
|
|
||||||
|
|
||||||
主人就 10.5 四项给出结论,并已 fork 出独立端 `/Users/gxwebsoft/VUE/website-huijicai`(master 分支、干净无改动、remote 指向 `website-huijicai.git`)。
|
|
||||||
|
|
||||||
1. **生产拓扑**:不排除双后端。→ **前端代理层预留可切换的 `tenderApiBase`:默认指向 cms-api(复用其 shop+payment 单库闭环),若确认交易走 guilixu-java 仅改 env 即可,前端代码不变**。最终生产拓扑需在开发启动前敲定(决定 baseURL 默认值)。
|
|
||||||
2. **供应商建模**:主人原提"复用 sys_user",已**纠正为复用 `ShopUser`(前端会员表)**。`sys_user` 是后台管理员表(`common/system` 包),供应商复用它会混进后台权限,不安全。
|
|
||||||
- `ShopUser` 字段已极全:`type`(0个人/1企业/2其他)、`phone`、`email`、`realName`、`companyId`、`certification`(实名认证)、`tenantId` 等;加一个 `supplierStatus`(0非供应商/1待认证/2已认证) 标记字段即可,**无需新建表**。
|
|
||||||
- **⚠️ 缺口(影响 P0)**:cms-api 当前前端会员登录以**微信登录为主**(`WxLoginController.loginByMpWxPhone`),**未找到账号密码/短信验证码的会员注册登录接口**。若客户要求账号密码/短信注册(招投标场景常见),需**新建会员注册登录接口**(P0 增量),待确认客户登录方式。
|
|
||||||
3. **标书建模**:确认新建 **`Tender` 实体**(标书项目表)。订单直接复用 **`ShopOrder`**(其已有 `type` 订单类型字段,标书订单用 `type=3`;需把 `PaymentWithOrderRequest.OrderInfo.type` 的 `@Max(2)` 放开到 3)。
|
|
||||||
4. **微信商户号**:已就绪(租户 10626 商户号 + 公网 `notify_url`)。✅ 支付可推进。
|
|
||||||
|
|
||||||
> 前后端详细清单见 `outputs/汇吉采-标书购买-前端清单与后端接口清单.md`。
|
|
||||||
Reference in New Issue
Block a user