feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,29 @@
|
||||
# 图片上传 + 后台接入记录(2026-07-30)
|
||||
|
||||
## 上传接口正确调用方式(关键!)
|
||||
`https://server.websoft.top/api/oss/upload` 是 **multipart 文件上传**,必填项:
|
||||
- `Authorization: Bearer <admin JWT>`
|
||||
- `tenantId: 10626`(请求头,缺它报「传参错误」——这是之前一直失败的根因)
|
||||
- form 字段 `file=@图片`
|
||||
|
||||
其余 `module/bizType/path` 等字段**可选**,不传也能成功。返回 `data.path` / `data.url` 即访问地址。
|
||||
|
||||
> 探测过程踩的坑:OpenAPI(swagger) 文档是 springfox 自动生成的,不收录 `@RequestParam`,误导以为要 `application/octet-stream` 或各种 form 字段。实际是 multipart + tenantId 请求头。
|
||||
|
||||
## 本次上传结果
|
||||
| 图片 | OSS 地址 | 用途 |
|
||||
|------|----------|------|
|
||||
| 关于我们右侧图 | `https://oss.wsdns.cn/20260730/e31b250f922643b0ada4338897840d47.jpg` | 模板本地兜底 `public/images/template-07/about.jpg` 同源(后端无 aboutImage 字段,走本地兜底显示) |
|
||||
| 最新动态首图 | `https://oss.wsdns.cn/20260730/082146c2149b4175ad08e71534c12682.jpg` | 已写入 `cms-article` 10557 的 `image` 字段(PUT `/api/cms/cms-article`) |
|
||||
|
||||
## 后台写入
|
||||
- 文章 10557「走近汇采聚云…」`image` 原值 `file.websoft.top/.../b798...png` 已 404,改为新 OSS 图。`PUT /api/cms/cms-article`(tenantId 头 + 完整 article JSON)。
|
||||
- 关于我们图:后端 `cms-website/getSiteInfo` 是扁平结构,**无 `aboutImage` / `config` 子对象**(只有 `websiteLogo`、`qrCode` 等),故模板用本地兜底图 `public/images/template-{01,07}/about.jpg`。若需后台托管,需在站点配置加 `aboutImage` 字段并让前端读 `siteInfo.config.aboutImage`(Home.vue 已预留该优先级)。
|
||||
|
||||
## 前端已确认渲染(dev server :3002)
|
||||
- 关于我们右侧:`/images/template-07/about.jpg`(生成图,非 Logo)
|
||||
- 最新动态首图:`https://oss.wsdns.cn/20260730/082146...jpg`(后端 article.image)
|
||||
- 关注我们二维码:`siteInfo.qrCode`(`https://oss.wsdns.cn/20260729/00d3fc...png`,读后台,无硬编码)
|
||||
|
||||
## 复用的上传脚本
|
||||
`scripts/upload-to-oss.sh`:`ADMIN_TOKEN="eyJ..." bash scripts/upload-to-oss.sh`
|
||||
@@ -0,0 +1,418 @@
|
||||
# SEO / GEO 优化诊断报告
|
||||
|
||||
> 项目:website-template(Nuxt 多租户 CMS 官网模板)
|
||||
> 检查日期:2026-08-04
|
||||
> 检查范围:Nuxt 配置、页面/模板 SEO、结构化数据、站点地图、robots、图片与性能、GEO 友好度
|
||||
|
||||
---
|
||||
|
||||
## 一、执行摘要
|
||||
|
||||
当前项目已具备 **基础的 SEO 骨架**:SSR 渲染、统一的 `usePageSeo` 工具、动态 `robots.txt` / `sitemap.xml`、Organization 结构化数据、旧链接 301 重定向等。
|
||||
|
||||
但 **详情页 SEO 几乎空白**,这是目前最大的流量损失点:`/article/:id`、`/product/:id`、`/case/:id` 的标题、描述、OG 信息都没有使用实际内容,导致搜索引擎看到的所有详情页都是统一的栏目名(如“新闻资讯 - 广西恒瑞天诚项目管理有限公司”)。
|
||||
|
||||
下方按 **P0(立即修复)/ P1(强烈建议)/ P2(持续优化)** 分级列出问题与落地建议。
|
||||
|
||||
---
|
||||
|
||||
## 二、已做 SEO 事项(可保留)
|
||||
|
||||
| 事项 | 位置 | 状态 |
|
||||
|------|------|------|
|
||||
| 全局 SSR,搜索引擎可抓取完整 HTML | `nuxt.config.ts` `routeRules` | ✅ |
|
||||
| 统一页面 SEO 工具(TDK + OG + Twitter + Canonical) | `app/composables/usePageSeo.ts` | ✅ |
|
||||
| Organization 结构化数据 | `useOrganizationSeo` | ✅ |
|
||||
| 首页 SEO + 组织结构化数据 | `app/pages/index.vue` | ✅ |
|
||||
| CMS 单页 SEO(title/keywords/description/photo) | `app/pages/page/[id].vue` | ✅ |
|
||||
| 关于我们页 SEO | `template-*/pages/About.vue` | ✅ |
|
||||
| 动态 `robots.txt`(开发环境屏蔽) | `server/api/robots.txt.ts` | ✅ |
|
||||
| 动态 `sitemap.xml` | `server/api/sitemap.xml.ts` | ✅(但范围不足,见 P0) |
|
||||
| 旧链接 301 重定向(/news→/article 等) | `server/middleware/z-news-detail-redirect.ts` | ✅ |
|
||||
| 部分图片使用 `loading="lazy"` | 模板 Home | ✅ |
|
||||
| 富文本图片响应式处理 | `app/components/RichText.vue` | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 三、问题清单与优化建议
|
||||
|
||||
### 🔴 P0 — 立即修复(影响搜索收录与排名)
|
||||
|
||||
#### 1. 文章/产品/案例详情页没有使用内容标题做 SEO
|
||||
|
||||
**问题**:
|
||||
- `useModuleRoute` 只给详情页设置了栏目兜底标题,如“新闻资讯 / 产品中心 / 案例展示”。
|
||||
- 所有模板中的 `NewsDetail.vue`、`ProductDetail.vue`、`CaseDetail.vue` **均未调用 `usePageSeo`**。
|
||||
- 截图中的 `/article/396`(招标代理)实际标题应该是“招标代理 - 广西恒瑞天诚项目管理有限公司”,但搜索引擎看到的是“新闻资讯 - 广西恒瑞天诚项目管理有限公司”。
|
||||
|
||||
**影响**:大量详情页在搜索结果中标题重复、CTR 低;长尾关键词无法被有效索引。
|
||||
|
||||
**修复方案**:在每个详情组件中,拿到数据后立即调用 `usePageSeo`,并注入 `Article` / `Product` 结构化数据。
|
||||
|
||||
**示例(`app/templates/template-08/pages/NewsDetail.vue`)**:
|
||||
|
||||
```ts
|
||||
const route = useRoute()
|
||||
const id = route.params.id as string
|
||||
const { siteInfo, fetchSiteInfo } = useSite()
|
||||
await fetchSiteInfo()
|
||||
|
||||
const { data: article } = await useFetch<Article | null>(`/api/article/detail?id=${id}`, {
|
||||
key: `article-${id}`
|
||||
})
|
||||
|
||||
// 用文章内容覆盖 SEO
|
||||
usePageSeo({
|
||||
title: article.value?.title || '新闻资讯',
|
||||
description: stripHtml(article.value?.summary || '').slice(0, 160) || undefined,
|
||||
keywords: article.value?.tags?.join(',') || undefined,
|
||||
path: route.path, // 注意用 route.path 而非 route.fullPath,避免 ?navId= 进入 canonical
|
||||
image: article.value?.image || article.value?.cover || undefined,
|
||||
type: 'article',
|
||||
publishedTime: article.value?.publishTime || article.value?.createTime,
|
||||
modifiedTime: article.value?.updateTime
|
||||
}, siteInfo.value)
|
||||
|
||||
// 注入文章结构化数据
|
||||
useJsonLd({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Article',
|
||||
headline: article.value?.title,
|
||||
description: stripHtml(article.value?.summary || ''),
|
||||
image: article.value?.image || article.value?.cover || undefined,
|
||||
datePublished: article.value?.publishTime || article.value?.createTime,
|
||||
dateModified: article.value?.updateTime,
|
||||
author: { '@type': 'Organization', name: siteInfo.value?.websiteName }
|
||||
})
|
||||
```
|
||||
|
||||
**需同步修改的模板文件**:
|
||||
- `app/templates/template-0*/pages/NewsDetail.vue`(9 套)
|
||||
- `app/templates/template-0*/pages/ProductDetail.vue`(9 套)
|
||||
- `app/templates/template-0*/pages/CaseDetail.vue`(9 套)
|
||||
|
||||
> 如果 9 套模板结构差异大,可先在 `useModuleRoute` 中统一注入一份“最小可用 SEO”(标题用详情接口数据),再逐步让各模板补充更精细的 OG 图和结构化数据。
|
||||
|
||||
---
|
||||
|
||||
#### 2. Sitemap 只包含单页,未覆盖文章/产品/案例详情和分页
|
||||
|
||||
**问题**:`server/api/sitemap.xml.ts` 只拉取 `cms-website/pageAll`(即 CMS 单页 slug),未包含:
|
||||
- 文章详情页 `/article/{id}`
|
||||
- 产品详情页 `/product/{id}`
|
||||
- 案例详情页 `/case/{id}`
|
||||
- 栏目列表页 `/article`、`/article/{navId}`、`/product`、`/case`
|
||||
|
||||
**影响**:搜索引擎依赖 sitemap 发现新内容,缺失会导致收录速度慢、遗漏详情页。
|
||||
|
||||
**修复方案**:扩展 `sitemap.xml.ts`,增加 article/product/case 列表接口调用,并拆分 `sitemapindex`(如果 URL 数量 > 50,000 或体积 > 50MB)。
|
||||
|
||||
**最小可用实现(伪代码)**:
|
||||
|
||||
```ts
|
||||
// server/api/sitemap.xml.ts
|
||||
const [pages, articles, products, cases] = await Promise.all([
|
||||
fetchPages(),
|
||||
fetchList('/cms/cms-article/page', { limit: 1000 }),
|
||||
fetchList('/cms/cms-product/page', { limit: 1000 }),
|
||||
fetchList('/cms/cms-case/page', { limit: 1000 })
|
||||
])
|
||||
|
||||
const staticUrls = [
|
||||
{ loc: '/', priority: '1.0', changefreq: 'daily' },
|
||||
{ loc: '/article', priority: '0.9', changefreq: 'daily' },
|
||||
{ loc: '/product', priority: '0.9', changefreq: 'daily' },
|
||||
{ loc: '/case', priority: '0.9', changefreq: 'daily' }
|
||||
]
|
||||
|
||||
const articleUrls = articles.map(a => ({
|
||||
loc: `/article/${a.id || a.articleId}`,
|
||||
priority: '0.7',
|
||||
changefreq: 'weekly',
|
||||
lastmod: a.updateTime || a.publishTime
|
||||
}))
|
||||
// ... product/case 同理
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
- 只输出 `status === 0`(已发布)的内容。
|
||||
- 列表接口若支持 `limit`,需分页拉取全部;若总量大,建议生成 `sitemapindex.xml` + 多个子 sitemap。
|
||||
- 列表页分页(`/article?page=2`)可不必写入 sitemap,让搜索引擎通过页面内链发现即可。
|
||||
|
||||
---
|
||||
|
||||
#### 3. Canonical URL 可能包含查询参数,导致重复内容
|
||||
|
||||
**问题**:多处使用 `route.fullPath` 作为 `path` 传给 `usePageSeo`:
|
||||
- `useModuleRoute`:`path: route.fullPath`
|
||||
- `app/pages/page/[id].vue`:`path: route.fullPath`
|
||||
- `app/pages/[slug].vue`:`path: /${slug}`(这个 OK)
|
||||
|
||||
当 URL 带 `?navId=xxx`、`?keywords=xxx`、`?page=2` 时,canonical 会带上这些参数,造成同一内容多个 canonical。
|
||||
|
||||
**修复方案**:统一使用**干净路径**(`route.path`)作为 canonical,必要时把分页参数也排除。
|
||||
|
||||
```ts
|
||||
// 建议增加一个干净 canonical 工具
|
||||
function getCanonicalPath(route: RouteLocationNormalizedLoaded) {
|
||||
// 只保留 path,去掉 navId/keywords/page 等查询参数
|
||||
return route.path
|
||||
}
|
||||
```
|
||||
|
||||
对于列表分页,如果希望保留 `?page=2` 的 canonical,可单独处理;否则建议所有列表页的 canonical 都不带查询参数。
|
||||
|
||||
---
|
||||
|
||||
#### 4. 详情页没有面包屑结构化数据
|
||||
|
||||
**问题**:`useBreadcrumbSeo` 已定义但未被任何页面使用。
|
||||
|
||||
**影响**:Google 搜索结果可能无法展示面包屑导航,降低 SERP 丰富度。
|
||||
|
||||
**修复方案**:在详情页和列表页注入面包屑:
|
||||
|
||||
```ts
|
||||
useBreadcrumbSeo([
|
||||
{ name: '首页', url: '/' },
|
||||
{ name: article.value?.categoryName || '新闻资讯', url: '/article' },
|
||||
{ name: article.value?.title || '详情', url: route.path }
|
||||
])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🟡 P1 — 强烈建议(提升 CTR 与收录质量)
|
||||
|
||||
#### 5. 缺少 `<html lang="zh-CN">`
|
||||
|
||||
**问题**:`nuxt.config.ts` 的 `app.head` 未设置 `htmlAttrs.lang`。
|
||||
|
||||
**影响**:搜索引擎无法准确判断页面语言;屏幕阅读器体验受损。
|
||||
|
||||
**修复方案**:
|
||||
|
||||
```ts
|
||||
app: {
|
||||
head: {
|
||||
htmlAttrs: { lang: 'zh-CN' },
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 6. 404 页面没有 `noindex`
|
||||
|
||||
**问题**:`app/pages/404.vue` 没有设置 `robots: noindex`。
|
||||
|
||||
**修复方案**:
|
||||
|
||||
```ts
|
||||
useSeoMeta({
|
||||
robots: 'noindex, follow',
|
||||
title: '页面未找到'
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 7. 标题模板未统一站点名后缀
|
||||
|
||||
**问题**:`nuxt.config.ts` 中 `titleTemplate: '%s'` 只是原样输出,各页面需自己拼 `- 站点名`。目前 `usePageSeo` 会自动拼接,但兜底标题(如 `app/pages/[slug].vue` 中的 `slug`)可能出现没有站点名的情况。
|
||||
|
||||
**建议**:把全局 `titleTemplate` 改为 `' %s - {{siteName}}'`,或在 `usePageSeo` 中统一处理。当前 `usePageSeo` 已经拼接,可作为单一事实源。
|
||||
|
||||
---
|
||||
|
||||
#### 8. 图片懒加载与 CLS 优化不足
|
||||
|
||||
**问题**:
|
||||
- 多数模板图片没有 `loading="lazy"`。
|
||||
- 所有图片缺少 `width` / `height` 或 `aspect-ratio`,导致 Cumulative Layout Shift(CLS)。
|
||||
- 没有 `decoding="async"`、没有响应式 `srcset/sizes`。
|
||||
- 装饰性/无意义图片没有 `alt=""`。
|
||||
|
||||
**修复方案**:
|
||||
|
||||
```html
|
||||
<img
|
||||
:src="fileUrl(item.cover)"
|
||||
:alt="item.productName || ''"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="800"
|
||||
height="450"
|
||||
class="w-full h-full object-cover"
|
||||
>
|
||||
```
|
||||
|
||||
对于 CMS 富文本中的图片,可在 `RichText.vue` 中统一注入 `loading="lazy" decoding="async"`。
|
||||
|
||||
---
|
||||
|
||||
#### 9. Open Graph 图片缺失或 fallback 到站点 Logo
|
||||
|
||||
**问题**:`usePageSeo` 在没有 `input.image` 时会 fallback 到 `site.websiteLogo`。站点 Logo 通常是 PNG/SVG,尺寸可能不符合 OG 推荐(1200×630)。
|
||||
|
||||
**建议**:
|
||||
- 列表页/首页配置一张专门的 OG 封面图(可在 CMS `siteInfo` 中增加 `ogImage` 字段)。
|
||||
- 详情页优先使用内容封面图,并确保图片可被公开访问、尺寸合适。
|
||||
|
||||
---
|
||||
|
||||
#### 10. 分页缺少 `rel="prev/next"` 或规范处理
|
||||
|
||||
**问题**:文章/产品列表页使用按钮分页,没有 `<a>` 标签链接,也没有 `rel="prev/next"`。
|
||||
|
||||
**影响**:搜索引擎可能无法顺畅抓取深层列表页。
|
||||
|
||||
**建议**:
|
||||
- 分页按钮使用 `<NuxtLink>` 而不是 `<button @click>`,使列表页 URL 变为可抓取的链接。
|
||||
- 在列表页 `head` 中注入:
|
||||
|
||||
```ts
|
||||
if (currentPage.value > 1) {
|
||||
useHead({
|
||||
link: [
|
||||
{ rel: 'prev', href: `/article${currentPage.value === 2 ? '' : '?page=' + (currentPage.value - 1)}` }
|
||||
]
|
||||
})
|
||||
}
|
||||
if (currentPage.value < totalPages.value) {
|
||||
useHead({
|
||||
link: [
|
||||
{ rel: 'next', href: `/article?page=${currentPage.value + 1}` }
|
||||
]
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🟢 P2 — 持续优化(GEO 与长期竞争力)
|
||||
|
||||
#### 11. GEO:内容结构不利于 AI 引擎引用
|
||||
|
||||
**问题**:
|
||||
- 没有 FAQ / HowTo 结构化数据。
|
||||
- 富文本内容层级不清晰,AI 摘要时难以提取要点。
|
||||
- 服务/产品页面多为纯列表,缺少“问题-答案”式内容块。
|
||||
|
||||
**建议**:
|
||||
- 在“关于我们”、“服务范围”等页面增加 FAQ 区块,并注入 `FAQPage` 结构化数据:
|
||||
|
||||
```ts
|
||||
useJsonLd({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: '招标代理服务包含哪些内容?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: '包括设计招标、监理招标、施工招标、材料及设备招标、政府采购招标等全流程代理服务。'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
- 产品/服务详情页使用清晰的 H2/H3 小标题,每个小节回答一个具体问题。
|
||||
- 在内容中自然覆盖“是什么 / 怎么做 / 多少钱 / 有什么优势”等 AI 常见引用角度。
|
||||
|
||||
---
|
||||
|
||||
#### 12. 内部链接优化
|
||||
|
||||
**问题**:
|
||||
- “返回新闻列表”链接使用 `?navId=`,canonical 处理不好时会造成参数化 URL 泛滥。
|
||||
- 列表页卡片链接使用 `/article/${id}`,但没有在正文中增加相关内容推荐链接。
|
||||
|
||||
**建议**:
|
||||
- 详情页底部增加“相关文章/产品/案例”模块,提升内链密度与页面权重流动。
|
||||
- 面包屑使用干净路径链接。
|
||||
|
||||
---
|
||||
|
||||
#### 13. 站点速度 / Core Web Vitals
|
||||
|
||||
**问题**:
|
||||
- 没有图片 CDN 压缩或 WebP/AVIF 格式适配。
|
||||
- 没有预连接 DNS / 预加载关键资源。
|
||||
- 第三方字体(如有)可能阻塞渲染。
|
||||
|
||||
**建议**:
|
||||
- 在 `nuxt.config.ts` 增加:
|
||||
|
||||
```ts
|
||||
app: {
|
||||
head: {
|
||||
link: [
|
||||
{ rel: 'preconnect', href: 'https://server.websoft.top' },
|
||||
{ rel: 'dns-prefetch', href: 'https://server.websoft.top' }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 对 CMS 图片 URL 增加压缩参数(如 OSS 图片处理 `?x-oss-process=image/resize,w_1200`)。
|
||||
- 评估引入 `@nuxt/image` 模块,自动实现懒加载、响应式、格式降级。
|
||||
|
||||
---
|
||||
|
||||
#### 14. 搜索功能与搜索关键词页面
|
||||
|
||||
**问题**:搜索结果页(`?keywords=xxx`)可能被搜索引擎索引,造成低质量页面。
|
||||
|
||||
**建议**:在搜索结果页或无结果页注入 `noindex`:
|
||||
|
||||
```ts
|
||||
if (route.query.keywords) {
|
||||
useSeoMeta({ robots: 'noindex, follow' })
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、推荐落地顺序
|
||||
|
||||
| 顺序 | 任务 | 预期收益 |
|
||||
|------|------|----------|
|
||||
| 1 | 为所有详情页注入 `usePageSeo` + Article/Product 结构化数据 | 解决标题重复问题,提升长尾词收录 |
|
||||
| 2 | 扩展 `sitemap.xml` 覆盖 article/product/case | 加速收录,减少遗漏 |
|
||||
| 3 | Canonical 统一使用 `route.path`,排除查询参数 | 避免重复内容,集中权重 |
|
||||
| 4 | 注入面包屑结构化数据 | 提升 SERP 展示丰富度 |
|
||||
| 5 | 设置 `html lang="zh-CN"`、404 noindex、OG 图片优化 | 完善基础 SEO 细节 |
|
||||
| 6 | 图片懒加载、width/height、CLS 优化 | 提升 Core Web Vitals |
|
||||
| 7 | FAQ / HowTo 结构化数据 + 内容结构化 | 提升 GEO/AI 引用概率 |
|
||||
|
||||
---
|
||||
|
||||
## 五、关键文件清单
|
||||
|
||||
| 文件 | 当前作用 | 建议改动 |
|
||||
|------|----------|----------|
|
||||
| `app/composables/usePageSeo.ts` | 统一 SEO 工具 | 保持现状,可被详情页复用 |
|
||||
| `app/composables/useModuleRoute.ts` | 模块路由 + 基础 SEO | 列表页 SEO 已 OK;详情页改为用 `route.path` |
|
||||
| `app/templates/template-0*/pages/NewsDetail.vue` | 文章详情 | 增加 `usePageSeo` + `useJsonLd` |
|
||||
| `app/templates/template-0*/pages/ProductDetail.vue` | 产品详情 | 增加 `usePageSeo` + `useJsonLd` |
|
||||
| `app/templates/template-0*/pages/CaseDetail.vue` | 案例详情 | 增加 `usePageSeo` + `useJsonLd` |
|
||||
| `server/api/sitemap.xml.ts` | 动态站点地图 | 扩展 article/product/case 列表 |
|
||||
| `server/api/robots.txt.ts` | robots.txt | 保持现状 |
|
||||
| `nuxt.config.ts` | 全局配置 | 增加 `htmlAttrs.lang`、preconnect |
|
||||
| `app/pages/404.vue` | 404 页面 | 增加 `noindex` |
|
||||
| `app/components/RichText.vue` | 富文本渲染 | 统一为图片注入 lazy/decoding |
|
||||
|
||||
---
|
||||
|
||||
## 六、结论
|
||||
|
||||
项目已经有不错的 SEO 基础架构,但 **详情页 SEO 是当前最大的短板**。修复后,预计:
|
||||
- 详情页长尾关键词收录量显著提升;
|
||||
- 搜索结果标题/描述点击率提升;
|
||||
- AI 搜索引擎(GEO)引用站点内容的概率提高。
|
||||
|
||||
建议优先完成 **P0 四项**(详情页 SEO、Sitemap、Canonical、面包屑),再逐步推进图片性能与 GEO 结构化数据。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
@@ -0,0 +1,45 @@
|
||||
# template-07 首页修复记录(2026-07-30)
|
||||
|
||||
## 修复项
|
||||
|
||||
| # | 截图 | 问题 | 根因 | 修复 |
|
||||
|---|------|------|------|------|
|
||||
| 1 | 4 个优势卡片 | 图标位置空白 | `icon: 'IconBuilding'` 是字符串,Vue 不会自动解析为组件;项目里也没注册这些 icon 组件 | `app/templates/template-07/components/FeatureSection.vue` 用 4 个 `v-if` 内联 SVG 替换字符串 icon(建筑/包裹/公文包/消息气泡) |
|
||||
| 2 | 关于我们右侧 | 显示公司 Logo | `siteInfo.websiteLogo` 优先于本地图 | `Home.vue` 新增 `aboutImage` computed:读 `siteInfo.config.aboutImage`(后台上传)> 本地图兜底 |
|
||||
| 3 | 最新动态首图 | 图片不显示 | 后端图 `file.websoft.top/.../b798...png` 已 **404**(上游失效) | `Home.vue` 新增 `articleImage()` + `onArticleImgError()`:浏览器加载失败时自动 fallback 到 `news-default.jpg` |
|
||||
| 4 | 关注我们二维码 | 总显示一张硬编码图 | `nuxt.config.ts` 第 98 行 `wxQrcode` 默认值写死了 `https://oss.wsdns.cn/20260721/ad1b221c...png` | 改为 `''` 强制读后端 `siteInfo.config.wxQrcode || siteInfo.qrCode` |
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 代码改动
|
||||
- `app/templates/template-07/components/FeatureSection.vue` — 4 个内联 SVG icon
|
||||
- `app/templates/template-07/pages/Home.vue` — aboutImage / articleImage / onArticleImgError
|
||||
- `app/templates/template-01/pages/Home.vue` — 同步改(保持克隆源一致)
|
||||
- `nuxt.config.ts` — 去掉 wxQrcode 硬编码
|
||||
|
||||
### 新增资源
|
||||
- `public/images/template-07/about.jpg` — 关于我们兜底图(团队办公场景)
|
||||
- `public/images/template-07/news-default.jpg` — 最新动态文章封面兜底图
|
||||
- `public/images/template-01/about.jpg` — 同上(template-01 也用)
|
||||
- `public/images/template-01/news-default.jpg` — 同上
|
||||
|
||||
### 辅助产物
|
||||
- `outputs/01-about-company-team.jpg` — 生成原图(用户查看效果)
|
||||
- `outputs/02-news-zoujinhuiicai-juyun.jpg` — 生成原图
|
||||
- `scripts/upload-to-oss.sh` — OSS 上传脚本(需要 admin token)
|
||||
|
||||
## 如何替换为后台上传图
|
||||
|
||||
1. 登录 admin 后台(`https://server.websoft.top`)拿 token(cookie 或 Authorization header)
|
||||
2. 浏览器 DevTools → Network → 任意 XHR → Request Headers → `Authorization: Bearer xxx` 复制
|
||||
3. `ADMIN_TOKEN="eyJ..." bash scripts/upload-to-oss.sh` 上传
|
||||
4. 把返回的 URL:
|
||||
- 填到「网站设置 → 关于我们图片」字段(前端读 `siteInfo.config.aboutImage`)
|
||||
- 或者直接 PUT 到 cms-article 10557 的 `image` 字段
|
||||
|
||||
## 已验证
|
||||
- dev server 3002 启动 ✅
|
||||
- 4 个图标 SSR 渲染 ✅(HTML 中含 4 个 `w-6 h-6 text-blue-600` SVG)
|
||||
- 关于我们图:渲染 `src="/images/template-07/about.jpg"`(200 OK)✅
|
||||
- 最新动态图:SSR 仍输出后端 URL(浏览器侧 onError 自动回退到本地图)
|
||||
- 关注我们二维码:硬编码 URL 已不存在(`grep -c "ad1b221c" = 0`)✅
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
|
||||
# 汇吉采「关于我们」后台内容核对与录入清单
|
||||
|
||||
> 租户:10626(website-template 本地 .env 默认租户,template-07)
|
||||
> 结论:PPT 里的汇吉采简介**大部分已录入后台**,仅「站点简介/comments」字段是占错位、导致主页显示异常,需补一处。
|
||||
|
||||
---
|
||||
|
||||
## 一、后台现状核对(已存在,无需再录)
|
||||
|
||||
| 字段 | 后台位置 | 当前值 | 状态 |
|
||||
|---|---|---|---|
|
||||
| `slogan` | 站点设置 → 标语 | 汇全咨之力・吉政企发展・采专业服务 | ✅ 已正确(对应 PPT 第1页) |
|
||||
| `content` | 站点设置 → 站点简介(富文本) | 完整 PPT 第4页正文(成立2017/四大业务板块/人才) | ✅ 已正确 |
|
||||
| `cms_page` about | 单页管理 → about(path=about) | 正文=PPT 第4页三大段 + 封面图;description=PPT 第5页 | ✅ 已正确 |
|
||||
|
||||
---
|
||||
|
||||
## 二、需修复的一处(导致主页"公司简介"大段只显示"汇吉采官网"四字)
|
||||
|
||||
**根因**:`About.vue` 的 `introText = siteInfo.comments || siteInfo.content`,而 `comments` 字段被填成了占错位标签 **"汇吉采官网"**(非空),于是主页"公司简介"大段只显示了这四个字,真正简介(藏在 `content`)没被用到。
|
||||
|
||||
**修复方式(纯后台,不改模板)**:把「站点设置 → 简介 / comments」字段,从 `汇吉采官网` 改为下面这段**纯文本**简介即可(模板用 `{{ }}` 插值,不能放 HTML,必须是纯文本):
|
||||
|
||||
```
|
||||
广西汇吉采咨询有限公司成立于2017年,是具备独立法人资质的综合性专业咨询服务机构,总部设于南宁,布局柳州、崇左、北海、梧州、百色、贺州、玉林多地分支机构,建成覆盖广西全域的服务网络,可快速响应各地项目全流程采购咨询服务需求。公司聚焦项目全生命周期采购咨询服务,搭建政府采购全过程咨询、工程全过程咨询、投融资全过程咨询、政府采购AI定制化开发服务四大核心业务板块,配套30余项咨询服务,全面贯通项目咨询、投资、建设、采购、履约、运营全生命周期管理服务,为各级党政机关、事业单位、国有企业及产业园区提供一站式全生命周期一体化服务。
|
||||
```
|
||||
|
||||
> 注:更详细的版本(含"标准化服务体系/2000㎡办公/使命愿景")已在「单页管理 → about」的"详细介绍"区块展示,主页此处用上面这段精炼版即可,避免重复。
|
||||
|
||||
---
|
||||
|
||||
## 三、为什么不能由我直接写入
|
||||
|
||||
上游 CMS 写接口(`/cms/cms-website/update`、`/cms/cms-website/save`、`/cms/cms-page/save`)在本环境(仅带 `TenantId` 头、无管理员 token)下调用返回 **HTTP 200 但空响应、不落库**——属于无鉴权空操作。现有后台内容均为你此前在 website-admin 登录录入。
|
||||
|
||||
**两种落地方式(任选):**
|
||||
1. **你手动粘**:登录 website-admin → 站点设置 → 把上面纯文本粘进「简介/comments」→ 保存。
|
||||
2. **我代写**:如果你提供 website-admin 的管理员 `Authorization` token(Bearer),我可用 `curl` 带 token 调写接口直接落库,并回读校验。
|
||||
|
||||
---
|
||||
|
||||
## 四、附:About.vue 两处"简介"来源对照(便于理解)
|
||||
|
||||
| 区块 | 数据来源 | 当前显示 |
|
||||
|---|---|---|
|
||||
| 底部「详细介绍」富文本 | `cms_page`(单页管理 about) | ✅ 汇吉采完整简介 |
|
||||
| 主视觉「公司简介」大段 | `siteInfo.comments` → 回退 `siteInfo.content` | ❌ 现仅显示 "汇吉采官网"(comments 占位) |
|
||||
| Hero 副标语 | `siteInfo.slogan` | ✅ 汇吉采标语 |
|
||||
@@ -0,0 +1,85 @@
|
||||
# 汇吉采 · 关于汇吉采导航 4 子页面落地清单(2026-08-24)
|
||||
|
||||
> 主人任务:「除了企业简介,其他页面也帮我完善一下内容」+ 提供 token。
|
||||
> 因 cms-api 写入端点对 token signature 校验严格、当前 token 在写接口上不稳定(`JWT signature does not match`),最终**没走 CMS 写入**,转走**模板写死**路线(与 About.vue 同套路),确保立即可用、不再被外部写权限拖累。
|
||||
|
||||
## 一、落地结果
|
||||
|
||||
| 子页面 | 渲染路由 | 命中组件 | HTTP | SSR 错误 |
|
||||
|---|---|---|---|---|
|
||||
| 分支机构 | `/page/jigou` | `template-07/pages/BranchOffice.vue` | 200 | 0 |
|
||||
| 发展历程 | `/page/fzlc` | `template-07/pages/History.vue` | 200 | 0 |
|
||||
| 企业文化 | `/page/wenhua` | `template-07/pages/Culture.vue` | 200 | 0 |
|
||||
| 专业人才团队 | `/article/4722` | `template-07/pages/TalentTeam.vue`(跳过 NewsList)| 200 | 0 |
|
||||
|
||||
4 个页面都已通过本地 dev(`127.0.0.1:3089`)实测验证。
|
||||
|
||||
## 二、新增/修改文件(4 新增 + 2 修改)
|
||||
|
||||
**新增 4 个组件**(路径:`app/templates/template-07/pages/`):
|
||||
- `BranchOffice.vue` — 分支机构(PPT 第4页 7地分支+2000㎡自有场地)
|
||||
- `History.vue` — 发展历程(PPT 暂无时间线 → 用「资质/平台/体系」节点作为脉络)
|
||||
- `Culture.vue` — 企业文化(PPT 第5页使命/价值观/质量承诺)
|
||||
- `TalentTeam.vue` — 专业人才团队(PPT 第4页人才板块,含 77%/90%+ 数据展示卡片)
|
||||
|
||||
**修改 2 个文件**:
|
||||
- `app/templates/template-07/pages/Page.vue` — 顶部加 `v-if` 路由:`jigou→BranchOffice`、`fzlc→History`、`wenhua→Culture`;其他 CMS page 仍走下方原逻辑。其他租户不受影响。
|
||||
- `app/pages/article/[id].vue` — `id===4722` 时直接渲染 `TalentTeam`,绕过 `useModuleRoute('article')` 拉 cms-article 列表。
|
||||
|
||||
## 三、内容来源(PPT 真实数据,无编造)
|
||||
|
||||
| 板块 | 来源 |
|
||||
|---|---|
|
||||
| 分支机构 7 地 | PPT 第 4 页「汇吉采总部设于南宁,布局柳州、崇左、北海、梧州、百色、贺州、玉林多地分支机构」 |
|
||||
| 近 2000㎡ 自有场地 | PPT 第 5 页「公司自有近 2000㎡专属办公场地」 |
|
||||
| 发展脉络节点 | PPT 第 11、12、15、16 页三大 ISO + AAA + 双重备案 + 全国投资项目平台 |
|
||||
| 履约率 100% / 满意度 98% | PPT 第 5 页「恪守合同履约率 100%、客户满意度 98% 以上」 |
|
||||
| 人才 77% / 90%+ | PPT 第 4 页「公司高级专业技术人员占比 77%,中高级工程师、各类注册持证人才超 90%」 |
|
||||
| 持证专家 | PPT 第 4 页「注册造价工程师、水利造价工程师、注册咨询师、招标师、注册监理工程师」 |
|
||||
| 8 大专业赛道 | PPT 第 4 页「政府采购、货物服务、内控咨询、土建施工、全过程造价、工程评估、招标监理、质量检测」 |
|
||||
| 使命 | PPT 第 5 页「让专业采购咨询引领行业发展」 |
|
||||
| 核心价值观(诚信/务实/创新/高效)| PPT 第 5 页「全力打造诚信、务实、创新、高效的行业标杆品牌」 |
|
||||
|
||||
## 四、token 写入尝试的过程(留痕)
|
||||
|
||||
| 动作 | 结果 |
|
||||
|---|---|
|
||||
| 解码主人 token:`sub={"username":"superAdmin","tenantId":10626}`,`exp=2027-08-24` | 格式完整、未过期 |
|
||||
| 用 token 调 `POST /api/cms/cms-page` 创建 jigou_probe 探针 | 首次返回 `code:0 添加成功`(pageId=25) |
|
||||
| `DELETE /api/cms/cms-page/25?tenantId=xxx`(带 tenantId query)| `code:0 删除成功` — 清探针验证可行 |
|
||||
| 批量 `POST /api/cms/cms-page`(jigou/fzlc/wenhua,3 个 slug)| **401 JWT signature does not match**(同一 token 紧接着几分钟内)|
|
||||
| `PUT /api/cms/cms-page`(更新 placeholder 的 pageId=4/5/6)| 同 401 |
|
||||
| `POST /api/cms/cms-article`(navId=4722 团队介绍)| 同 401 |
|
||||
| cms-website/getSiteInfo / cms-navigation 等 GET 端点 | 同 401 |
|
||||
|
||||
结论:cms-api 的不同端点用了**不同严度的签名校验**。能写成功的窗口很短,写入端点(cms-page/cms-article POST)后续均被拒。所有走 cms-page / cms-article 写入路径都失败,因此本次不动后台,**全部走模板写死**。
|
||||
|
||||
> 已存在的 cms-page `jigou/fzlc/wenhua`(pageId=4/5/6)保留,主人拿到稳定 token 后可随时 PUT 替换并删除模板兜底。
|
||||
|
||||
## 五、当前模板与 CMS 数据冗余情况
|
||||
|
||||
| 路径 | 现在显示的内容(主人视角)| 后台 cms-page | 备注 |
|
||||
|---|---|---|---|
|
||||
| `/about` | About.vue 模板 | `about` 单页里有 PPT 完整简介 | 双份,主人后台录的原本就这样 |
|
||||
| `/page/jigou` | BranchOffice.vue 模板 | pageId=4,content 是「分支机构」3 字 | 模板优先,不显示 |
|
||||
| `/page/fzlc` | History.vue 模板 | pageId=5,content 是「发展历程」3 字 | 模板优先 |
|
||||
| `/page/wenhua` | Culture.vue 模板 | pageId=6,content 是「企业文化」3 字 | 模板优先 |
|
||||
| `/article/4722` | TalentTeam.vue 模板 | cms-article navId=4722 栏目下 0 篇文章 | 模板优先,列表不存在 |
|
||||
|
||||
主人后续可手动清理 cms-page 4/5/6 三条 placeholder,或保留作为备份。
|
||||
|
||||
## 六、视觉一致性
|
||||
|
||||
4 个新组件沿用 `template-07` 的视觉规范:
|
||||
- Hero:`py-16` + 蓝色徽章 + 标题 + 灰副标题
|
||||
- 正文:`<RichText :content="...">`(复用 `app/components/RichText.vue`)
|
||||
- CTA:蓝色卡片「联系我们」/「吉采动态」链接
|
||||
- 与 About.vue 同套节奏和颜色
|
||||
|
||||
CSS 全部用 Tailwind class,未新增样式。
|
||||
|
||||
## 七、未做但建议主人做的事
|
||||
|
||||
1. 打开 `http://localhost:3001`(或生产域名)实测 `/page/jigou` `/page/fzlc` `/page/wenhua` `/article/4722` 4 个链接
|
||||
2. 若实际访问有 SEO 标题(HTML `<title>`)需要正式化(如加品牌前缀),告诉我再做一次批量品牌化
|
||||
3. (可选)若未来 token 稳定了,我可以批量 PUT 把 cm-page 4/5/6 内容更新为同样的 HTML,开放后台编辑能力
|
||||
@@ -0,0 +1,91 @@
|
||||
# 汇吉采「标书购买」前端清单与后端接口清单
|
||||
|
||||
> 阶段:**编码前最终对齐**(评估已完成;独立端 `website-huijicai` 已 fork 就绪)。
|
||||
> 后端落点:**cms-api(cms-java-code)**,复用其 `shop` + `payment`;前端代理预留双后端切换。
|
||||
> 对应规划:`开发计划.md` 第十节(10.1.1 前端隔离策略、10.6 拍板结果)。
|
||||
|
||||
---
|
||||
|
||||
## 一、前端清单(website-huijicai 独立端)
|
||||
|
||||
### 1.1 页面 / 路由
|
||||
|
||||
| 路由 | 文件 | 说明 | 复用/新增 |
|
||||
|---|---|---|---|
|
||||
| `/buy` | `app/pages/buy.vue` | 标书购买流程壳入口。**照搬 `renewal.vue` 加载 `components.BuyDocument`**,无需改 `useTemplate` 核心;`index.vue` 的 routeMap `buy` 钩子已存在 | 新增(激活入口) |
|
||||
| 标书列表 | `app/templates/template-07/pages/TenderList.vue` | 列表 + 状态=onsale 筛选 + 关键词 + 分类下拉,仿 `ProductList` | 新增 |
|
||||
| 标书详情 | `app/templates/template-07/pages/TenderDetail.vue` | 售价 / 招标编号 / 发售起止 / 购买按钮,仿 `ProductDetail` | 新增 |
|
||||
| 购买表单 | 内嵌于 `BuyDocument` 或 `TenderDetail` | 姓名/电话/邮箱/公司,**复用 `ContactForm` 手机号校验 + 滑块验证码** | 复用 |
|
||||
| 支付页 | 内嵌于 `BuyDocument` 流程 | 展示后端返回的 `code_url` 二维码 + **轮询订单状态** + 支付成功展示「下载标书」/「已发邮件」 | 新增 |
|
||||
| 结果页 | 内嵌于 `BuyDocument` 流程 | 支付成功/失败/超时态 | 新增 |
|
||||
| `/register`、`/login` | `app/pages/register.vue`、`login.vue` | 供应商注册/登录 + 权限门控(未登录访问 `/buy` 先跳这里) | 新增(若采用账号密码) |
|
||||
| (可选)我的购买记录 | `app/templates/template-07/pages/TenderOrders.vue` | 已购标书列表 + 复下载 | 新增(P1) |
|
||||
|
||||
> 说明:`BuyDocument.vue` 当前是占位 stub,改为购买流程壳(列表/详情/表单/支付/结果 编排)。`TenderList/TenderDetail` 需注册进 `useTemplate` 的 `optionalComponents` 列表(仿现有 Page/NewsList 写法),并在 `supportedModules` 加 `'tender'`。
|
||||
|
||||
### 1.2 composable / 状态
|
||||
|
||||
| 名称 | 职责 | 复用/新增 |
|
||||
|---|---|---|
|
||||
| `useSupplier` | 供应商登录态(Token 存 cookie)+ 是否登录判断 + 购买入口门控 | 新增 |
|
||||
| `useTender` | 标书列表/详情/下单/订单状态查询的接口封装 | 新增 |
|
||||
| 请求层 | 调 `server/api/tender/*`(见下),不直接连后端 | 新增 |
|
||||
|
||||
### 1.3 Server 代理层(`website-huijicai/server/api/`)
|
||||
|
||||
| 代理端点 | 转发目标 | 备注 |
|
||||
|---|---|---|
|
||||
| `server/api/tender/list.get.ts` | `tenderApiBase/tender/list` | `tenderApiBase` 可配置,默认 cms-api |
|
||||
| `server/api/tender/detail.get.ts` | `tenderApiBase/tender/detail` | |
|
||||
| `server/api/tender/order.post.ts` | `tenderApiBase/tender/order` | 建单+发起支付,要求登录态 |
|
||||
| `server/api/tender/order/status.get.ts` | `tenderApiBase/tender/order/status` | 轮询 |
|
||||
| `server/api/tender/order/download.get.ts` | `tenderApiBase/tender/order/download` | 登录态+订单校验后转发文件 |
|
||||
| `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 透传沿用现有中间件。
|
||||
|
||||
---
|
||||
|
||||
## 二、后端接口清单(cms-java-code,新增 `tender` 包)
|
||||
|
||||
### 2.1 实体
|
||||
|
||||
| 实体 | 位置 | 字段要点 | 动作 |
|
||||
|---|---|---|---|
|
||||
| `Tender`(新建) | `com.gxwebsoft.tender.entity` | `id, tenantId, tenderNo`(招标编号), `title, cover, price, status`(0下架/1onsale/2ended), `startSaleTime, endSaleTime, fileId/fileUrl`(标书文件), `content, sortNumber, createTime, updateTime` | 新建 |
|
||||
| `ShopUser`(复用) | `com.gxwebsoft.shop.entity` | 已有 `type/phone/email/realName/companyId/certification/tenantId`;**新增 `supplierStatus`**(0非供应商/1待认证/2已认证) | 加字段 |
|
||||
| `ShopOrder`(复用) | `com.gxwebsoft.shop.entity` | 已有 `type/orderNo/payStatus/orderStatus/userId/totalPrice/tenantId` 等;标书订单用 **`type=3`** | 复用 + 扩展 type |
|
||||
| `TenderOrderExt`(新建,可选) | `com.gxwebsoft.tender.entity` | `orderNo, supplierId, contactName, contactPhone, contactEmail, tenderId` | 新建(存联系人/邮箱;或扩展 `PaymentWithOrderRequest`,二选一) |
|
||||
|
||||
### 2.2 接口
|
||||
|
||||
| 接口 | 方法 | 入参 | 出参 | 复用/新建 |
|
||||
|---|---|---|---|---|
|
||||
| 标书列表 | `GET /api/tender/list` | `status`(默认1onsale)、`category`、`keyword`、`page`、`size` | `Tender` 分页列表(不含文件下载链接) | 新建 |
|
||||
| 标书详情 | `GET /api/tender/detail` | `id` | `Tender` 详情 | 新建 |
|
||||
| **创建标书订单+支付** | `POST /api/tender/order` | `tenderId`、`contactName`、`contactPhone`、`contactEmail`、`quantity`(默认1) | `orderNo`、`codeUrl`(微信扫码)、`payAmount` | **新建(编排层)**:内部①校验 `status=onsale` ②组装 `PaymentWithOrderRequest`(`type=3`,`goodsId=tenderId`,`amount=price`) ③调 `paymentService.createPaymentWithOrder(要求 loginUser)` ④落 `TenderOrderExt` ⑤返回 `codeUrl` |
|
||||
| 订单状态 | `GET /api/tender/order/status` | `orderNo` | `payStatus`、`orderStatus` | 新建(包装 `GET /payment/query` 或查 `ShopOrder`) |
|
||||
| 下载标书 | `GET /api/tender/order/download` | `orderNo` | 文件流 / 下载链接 | 新建:校验 `payStatus=已支付` + `loginUser=下单人` → 返回 `Tender.fileUrl`(复用 file 存储) |
|
||||
| 微信异步回调 | `POST /api/payment/notify/wechat`(已有) | 微信回调报文 | 成功/失败 | **复用** `PaymentNotifyController` + `WxPayNotifyService`(置 `ShopOrder.payStatus=已支付`;标书订单 type=3 同样处理,可触发"发邮件给供应商") |
|
||||
| 发起支付 | `POST /api/payment/create-with-order`(已有) | `PaymentWithOrderRequest` | `codeUrl` 等 | **复用**(内部 `WechatNativeStrategy` 返回扫码 `codeUrl`) |
|
||||
| 支付查询 | `GET /api/payment/query`(已有) | `orderNo` 等 | 支付状态 | **复用** |
|
||||
|
||||
### 2.3 必须改造 / 注意(P0 增量)
|
||||
|
||||
1. **`PaymentWithOrderRequest.OrderInfo.type` 当前 `@Max(2)`**,标书订单 `type=3` 会被校验拦截 → 放开到 3,并确认支付/订单状态机对 `type=3` 的处理(不应走"发货/物流"逻辑)。
|
||||
2. **会员注册/登录接口缺口**(见 10.6-2):若客户要账号密码/短信注册,需新建 `SupplierAuthController`(注册/登录/发短信),并接入现有 JWT(`JwtAuthenticationFilter`)。若客户接受微信登录,则直接复用 `WxLoginController.loginByMpWxPhone`,零新增。
|
||||
3. **下单强制校验 `status=onsale`**(后端):防前端绕过筛选取已截止/未发售项目。
|
||||
4. **联系人/邮箱落库**:建议新建 `TenderOrderExt`,与 `ShopOrder` 通过 `orderNo` 关联;不要污染 `ShopOrder` 通用字段。
|
||||
5. **标书文件存储**:复用现有 file 模块(上传/下载/权限);`Tender.fileId` 关联文件记录。
|
||||
6. **租户隔离**:`Tender`、`TenderOrderExt` 全部带 `tenantId=10626`;查询自动按当前租户过滤(沿用现有多租户拦截)。
|
||||
|
||||
---
|
||||
|
||||
## 三、阻塞编码的待确认项
|
||||
|
||||
1. **生产拓扑最终敲定**:cms-api 单库闭环 vs guilixu-java 双后端 → 决定 `tenderApiBase` 默认值。
|
||||
2. **会员登录方式**:微信登录(免新增)vs 账号密码/短信注册(需新建 `SupplierAuthController`)→ 决定 P0 是否含注册登录接口。
|
||||
3. **标书交付文件**:确认复用现有 file 模块,及文件上传入口(后台上架标书时上传)。
|
||||
|
||||
> 三项确认后即可进入编码:后端先落 `Tender` 实体 + `tender` 包接口(复用 shop/payment),前端在 `website-huijicai` 做 `/buy` 入口 + 流程页 + `server/api` 代理。
|
||||
@@ -0,0 +1,94 @@
|
||||
# 汇吉采「标书购买」后端选型评估:复用 guilixu-java 还是 cms-api 新增?
|
||||
|
||||
> 阶段:**仅评估,未改动任何代码。**
|
||||
> 上一轮已确认:标书购买 = 前端流程页(website-template)+ 后端账号/订单/支付。本轮聚焦「后端落哪」。
|
||||
|
||||
## 0. 结论(先说)
|
||||
|
||||
**建议在 `cms-api`(即 `cms-java-code`)里新增标书购买功能,复用其已有的 shop 订单 + payment 微信扫码支付底座;不单独启用 `guilixu-java`。**
|
||||
|
||||
理由一句话:`cms-api` 是模板**当前就在对接**的后台,且它**已经自带**商城(商品/订单/会员)、支付(微信 Native 扫码、`codeUrl` 返回、异步回调)、多租户(`TenantId`)——标书购买需要的后端能力它全有。`guilixu-java` 只是它的一个**裁剪分支**(只有 payment+shop+common,无 cms),且与 cms-api **独立部署、独立数据库**,单独启用反而要新增前端对接 + 双库一致性的成本。
|
||||
|
||||
---
|
||||
|
||||
## 1. 两个后端的真实身份(已查代码确认)
|
||||
|
||||
| 维度 | `guilixu-java` | `cms-java-code`(即 cms-api) |
|
||||
|---|---|---|
|
||||
| Maven artifactId | `shop-api` | `com-gxwebsoft-modules`(`name=com-gxwebsoft-api`) |
|
||||
| 业务包 | 仅 `payment` + `shop` + `common` | `cms` + `shop` + `payment` + `common` + 多个垂直业务包(enterprise/dormitory/project/oa/house/pwl…) |
|
||||
| 是否含 CMS 内容接口 | **否**(无 cms-page/article/nav) | **是**(模板的 `/cms/cms-*` 全在这里) |
|
||||
| 微信扫码支付 | 有 `WechatNativeStrategy` | 有 `WechatNativeStrategy`(返回 `codeUrl`) |
|
||||
| 订单能力 | `ShopOrder` 等 | `ShopOrder` + `PaymentController.createPaymentWithOrder`(创建订单并发起支付) |
|
||||
| 多租户 | `TenantId` 头 | `TenantId` 头(与模板传参一致) |
|
||||
| 数据库 | `db_guilixu`@47.119.165.234 | `modules`@8.134.169.209 |
|
||||
| 部署关系 | 独立部署 | 独立部署(与 guilixu-java 不同机不同库) |
|
||||
|
||||
**关键认知纠偏**:`cms-api` 不是「只有内容管理」的模块。它是完整 monolith,已内含 shop + payment,且 `WechatNativeStrategy` 与 `PaymentController` 的「创建订单并支付」逻辑可直接服务标书购买。
|
||||
|
||||
---
|
||||
|
||||
## 2. 模板到底连的是哪个后台?
|
||||
|
||||
- 模板 `server/api/*` 全部代理到 `modulesApiBase`(cms-api)。证据:`server/api/form/submit.post.ts` 调 `/cms/cms-contact-lead/submit`;`product/list`、`page/detail`、`article/*` 等全走 cms-api。
|
||||
- 模板目前**没有任何指向 `guilixu-java` 的代理/配置**。
|
||||
- 因此:在 cms-api 新增标书接口 → 前端**零新增对接**(同 base、同 `TenantId` 头、同 JWT 体系)。在 guilixu-java 做 → 必须新增一条 server/api 代理 + 新 baseURL + 处理跨服务的租户/JWT 传递。
|
||||
|
||||
---
|
||||
|
||||
## 3. 标书购买在 cms-api 里能复用什么(已查实)
|
||||
|
||||
| 标书购买环节 | cms-api 现成能力 | 复用方式 |
|
||||
|---|---|---|
|
||||
| 供应商账号 | `shop` 模块的会员/用户体系(`ShopUser`、登录态、JWT `JwtAuthenticationFilter`) | 供应商注册/登录走现有用户体系(或新增 supplier 角色/字段) |
|
||||
| 标书项目数据 | `shop` 的 `ShopGoods`/`ShopGoodsCategory` | 标书可建模为「一类特殊商品」(带发售起止、标书状态字段),或新增 `Tender` 实体复用同一套 CRUD/分页/过滤骨架 |
|
||||
| 下单 | `PaymentController.createPaymentWithOrder`:创建订单 + 发起支付,要求 `loginUser != null`、自动带 `tenantId` | 标书购买直接走此接口(订单类型=标书) |
|
||||
| 扫码付费 | `WechatNativeStrategy`:调微信 Native 支付,返回 `codeUrl`(前端据此渲染二维码) | 复用,无需重写扫码逻辑 |
|
||||
| 支付成功回调 | `PaymentNotifyController` + `WxPayNotifyService.handlePaymentNotify` | 复用,回调里把订单置「已支付」 |
|
||||
| 标书交付 | 现有文件上传/存储(`server/api/file` 后端侧) | 支付成功后按订单关联的文件下发/邮件 |
|
||||
|
||||
**也就是说:从「建订单 → 微信扫码 → 异步回调置已支付 → 交付」这条主链路,cms-api 已经闭环,标书购买主要是「数据建模 + 流程编排 + 前端页面」,而不是从零造支付。**
|
||||
|
||||
---
|
||||
|
||||
## 4. 为什么不选 guilixu-java(单独启用)
|
||||
|
||||
1. **它是 cms-api 的裁剪分支**:源码包只有 payment+shop+common,没有 cms 内容接口。而标书项目列表前端要从 cms-api 取内容(导航/站点/权限),若订单在 guilixu-java,就出现「内容在 A 库、交易在 B 库」的跨库一致性问题。
|
||||
2. **独立数据库**:`db_guilixu` 与 `modules` 不同机不同库,供应商账号、标书、订单若放 guilixu-java,与 cms-api 里的租户/内容无法天然关联,要额外做跨服务同步。
|
||||
3. **前端要新增对接**:模板当前不认识 guilixu-java,需加代理路由 + baseURL + 租户/JWT 透传,工作量与风险都更高。
|
||||
4. **重复造轮子**:guilixu-java 的 shop/payment 与 cms-api 同源同构,单独跑一份只会增加部署与维护成本。
|
||||
|
||||
> 例外情况:若汇吉采这个客户**实际生产就在用 guilixu-java(shop-api)作为交易后台**,且 cms-api 只是内容源,则需改为「cms-api 出内容 + guilixu-java 出交易」的双后端方案。但这需要你确认生产部署拓扑——从代码与模板对接关系看,模板只连 cms-api,所以默认推荐单后端(cms-api)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 推荐落地(在 cms-api 内新增)
|
||||
|
||||
新增一个独立业务包(参照现有 `shop`/`enterprise` 分包方式,如 `com.gxwebsoft.tender` 或归入 `shop`),包含:
|
||||
|
||||
- 实体:`Tender`(标书项目:招标编号、发售起止、状态 onsale/ended、售价、关联文件)、`TenderOrder`(标书订单,复用订单支付链路)、可选 `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`(支付后下载标书,登录态 + 订单校验)
|
||||
- 供应商账号:复用现有用户体系(注册/登录/JWT),在「购买入口」做登录态门控(前端)+ 下单接口要求 `loginUser != null`(后端,已有)。
|
||||
|
||||
前端(website-template)配合:激活 `buy` 入口(照搬 renewal)、`BuyDocument` 改为购买流程页、注册/登录页 + `useSupplier` 登录态、对接上面 5~6 个新接口。
|
||||
|
||||
---
|
||||
|
||||
## 6. 需要你拍板/确认
|
||||
|
||||
1. **生产拓扑**:汇吉采生产到底连的是 cms-api,还是 guilixu-java(shop-api)?从模板对接关系看是 cms-api,请确认(决定单后端还是双后端)。
|
||||
2. **供应商账号**:复用现有 shop 用户体系(加 supplier 角色/字段),还是新建独立供应商表?建议前者,省一套登录态。
|
||||
3. **标书建模**:当「特殊商品」复用 ShopGoods,还是新建 Tender 实体?建议新建 Tender,语义清晰、不污染商城商品。
|
||||
4. **微信商户号**:cms-api 的微信支付配置(`resources/wechat/<tenantId>`)是否已为该租户(10626)配好商户号与 `notify_url` 公网回调?
|
||||
|
||||
---
|
||||
|
||||
## 7. 一句话给老板
|
||||
|
||||
> 后端不用另起炉灶——`cms-api` 已经把「会员 + 商品 + 订单 + 微信扫码支付 + 异步回调 + 多租户」全备齐了,标书购买本质是在它里面**加一个「标书」业务包 + 编排现有支付链路**,前端再配一套购买流程页即可闭环。
|
||||
@@ -0,0 +1,141 @@
|
||||
# 汇吉采「标书购买」功能实现方案评估(template-07)
|
||||
|
||||
> 目标:评估 template-07 如何支持「供应商注册 → 筛选发售中的标书项目 → 填联系人/邮箱 → 扫码付费 → 售出标书」。
|
||||
> 阶段:**仅评估,未改动任何代码**。
|
||||
> 后端选型已另出专文《汇吉采-标书购买-后端选型评估.md》确定:**在 cms-api(cms-java-code)内新增标书业务包,复用其 shop 订单 + payment 微信扫码支付底座**。下文早期"website-admin Java 后端"提法统一更正为 **cms-api(cms-java-code,即自研 SaaS 后端)**。
|
||||
|
||||
## 0. 一句话结论
|
||||
|
||||
标书购买 = **前端流程页(本模板)** + **后端账号/订单/支付(cms-api,即 cms-java-code / 自研 SaaS 后端)**,二者必须配合才能闭环。
|
||||
本仓库是**纯前端官网 SaaS 模板**,本身没有供应商账号、订单、支付能力,所以这部分**必须后端排期**,前端无法独立演示完整业务闭环。
|
||||
|
||||
但有三个已有资产可大幅复用,降低工作量:
|
||||
1. `BuyDocument.vue` + `index.vue` 的 routeMap `buy` 钩子(为「标书购买」预留的入口位,当前未激活)。
|
||||
2. `ProductList / ProductDetail` 模块(列表/筛选/分页/详情/价格)—— 标书项目列表可复用其结构。
|
||||
3. 你 Java 后端已有的 `WxNativePayController`(微信扫码支付)+ 本模板的 `ContactForm` 表单校验链路。
|
||||
|
||||
---
|
||||
|
||||
## 1. 现状盘点(已查代码确认)
|
||||
|
||||
| 项 | 现状 | 证据 |
|
||||
|---|---|---|
|
||||
| 架构 | 纯前端 Nuxt SaaS 模板,业务数据经 `server/api/*` 代理到外部后台 `modulesApiBase` | `server/utils/app.ts`、`server/api/product/list.get.ts` 等 baseURL=modulesApiBase |
|
||||
| 注册/登录 | **无**任何用户/会员/供应商账号体系 | grep `login/register/auth/member/order/payment` 在 app/ 无真实命中 |
|
||||
| 订单/支付 | **无** | server/api 下无 order/payment 端点 |
|
||||
| 表单 | 仅有「留言表单」`/api/form/submit` → cms-contact-lead(含手机号校验、滑块验证码、频率/去重) | `server/api/form/submit.post.ts` |
|
||||
| 标书入口钩子 | `template-07/pages/index.vue` 的 routeMap 有 `buy → BuyDocument.vue`,但 `BuyDocument.vue` 是占位 stub("sdfsdfsdsdfbuy---"),且**当前无 Nuxt 路由名匹配 `buy`**,访问 `/buy` 会落进 `[slug].vue` 渲染 Page,不会命中 BuyDocument | `index.vue:24`、`BuyDocument.vue`、`app/pages/` 无 buy 路由 |
|
||||
| 续费付费雏形 | `renewal` 路由 = `app/pages/renewal.vue` 加载 `components.Renewal`(空白布局),点击「立即续费」跳 `/api/subscription/renew` 到后台 | `app/pages/renewal.vue`、`Renewal.vue` |
|
||||
|
||||
**关键推论**:`buy` 入口的激活方式应**完全照搬 `renewal` 的模式**——在 `app/pages/` 新增 `buy.vue`,加载 `components.BuyDocument`(参照 `renewal.vue` 加载 `Renewal`)。这样无需改 `useTemplate` 核心,仅把 BuyDocument 从占位 stub 改成真正的购买流程页即可。
|
||||
|
||||
---
|
||||
|
||||
## 2. 需求拆解 → 前后端职责
|
||||
|
||||
| 需求 | 前端(本模板改造) | 后端(cms-api / cms-java-code,复用 shop+payment 底座) |
|
||||
|---|---|---|
|
||||
| ① 供应商注册后才可购买 | 注册页 + 登录页 + 登录态管理(`useSupplier` composable,Token 存 cookie)+ **购买入口权限门控** | 供应商账号体系:注册、登录、会话/Token、企业信息(公司名、统一信用代码、联系人)、"已注册/已认证"状态 |
|
||||
| ② 筛选正在发售上架的项目 | 标书项目列表页(状态=发售中筛选、关键词、分类下拉)+ 详情页(售价、招标编号、发售起止、购买按钮) | `tender` 标书数据模型 + 状态字段(onsale/ended)+ 按 `status/category/keyword` 查询接口 |
|
||||
| ③ 填联系人/邮箱 | 购买表单:姓名、电话、邮箱、公司(**复用 `ContactForm` 的手机号校验 + 滑块验证码**) | 订单创建接口(绑定 项目 + 供应商 + 联系人信息) |
|
||||
| ④ 扫码付费售出标书 | 支付页:展示微信二维码 + **轮询订单状态** + 支付成功后展示「下载标书」/「已发送至邮箱」 | 微信 Native 支付下单(返回 `code_url`)、异步回调 `notify`、支付成功后标记订单、交付标书(下载链接 / 发邮件) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键决策点(需你拍板)
|
||||
|
||||
### D1. 标书项目数据如何建模
|
||||
- **方案 A:复用现有 `product` 模块** —— 把标书当一种 product,加自定义字段(招标编号、发售起止、标书状态)。改动最小,但 product 语义被污染,且"发售中"筛选需后端额外给状态字段。
|
||||
- **方案 B(推荐):新增独立 `tender` 模块** —— 仿照 article/product/case,后端新增 tender 实体,前端新增 `TenderList / TenderDetail`,注册进 routeMap + `useTemplate` 可选组件。语义清晰、可扩展(后续接购买记录、电子发票)。列表/筛选/详情 UI 可大量复制 ProductList/ProductDetail 代码。工作量中等。
|
||||
|
||||
### D2. 供应商账号体系(已确认可复用,非从零新建)
|
||||
- 选型评估已查实:cms-api(cms-java-code)的 `shop` 模块**已有完整用户/会员体系**(`ShopUser`、登录态、`JwtAuthenticationFilter`、下单要求 `loginUser != null`)。标书购买无需从零造账号体系,**直接复用现有用户体系 + 加 `supplier` 角色/字段**即可。
|
||||
- 建议注册方式:手机号 + 短信验证码,或账号密码;企业信息:公司名、统一社会信用代码、联系人。
|
||||
- **请确认**:供应商是复用 shop 会员表(加角色/字段),还是新建独立 `supplier` 表?建议前者,省一套登录态。
|
||||
|
||||
### D3. 支付渠道
|
||||
- "扫码付费" → **微信 Native 支付(扫码支付)** 最契合,你后端已有 `WxNativePayController`,直接复用,风险最低。
|
||||
- 备选:支付宝当面付(同为二维码)。建议先微信。
|
||||
- 需确认:商户号、`notify_url` 公网回调域名、费率。
|
||||
|
||||
### D4. 标书交付方式
|
||||
- 支付成功后建议**双通道**:① 页面直接出「下载标书」按钮(限时 / 登录态保护);② 同步发邮件到步骤③填写的邮箱。文件走现有 `server/api/file` 上传/下载通道。
|
||||
- 可选:后台「我的购买记录」页,便于供应商复取下架前的标书。
|
||||
|
||||
### D5. 权限门控放哪
|
||||
- 在 `buy` 入口(导航「购买标书」)加门控:未登录 → 引导注册/登录;已登录 → 进入标书列表。复用现成 routeMap `buy` 钩子(把 `BuyDocument.vue` 改成购买流程页)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 推荐落地架构(端到端流程)
|
||||
|
||||
```
|
||||
[供应商访问]
|
||||
│ 未登录?
|
||||
├─ 是 → /register 或 /login(后端 supplier 表)──┐
|
||||
│ │
|
||||
└─ 否 ─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
/buy (BuyDocument 改为购买流程壳,照搬 renewal 激活方式)
|
||||
│
|
||||
▼
|
||||
标书项目列表(后端 tender 接口,status=onsale + 分类/关键词筛选)
|
||||
│ 选一个
|
||||
▼
|
||||
标书详情(售价、招标编号、发售起止)→ 点「购买」
|
||||
│
|
||||
▼
|
||||
填写联系人/邮箱(复用 ContactForm 校验)→ 调后端「创建订单」
|
||||
│
|
||||
▼
|
||||
后端调微信下单 → 返回 code_url → 前端渲染二维码
|
||||
│ ▲
|
||||
│ 用户微信扫码支付 │ 轮询订单状态
|
||||
▼ │
|
||||
微信异步通知 notify → 订单置「已支付」─────┘
|
||||
│
|
||||
▼
|
||||
支付成功页:展示「下载标书」+ 后端发邮件到供应商邮箱
|
||||
│
|
||||
▼
|
||||
(可选)我的购买记录
|
||||
```
|
||||
|
||||
**多租户**:标书/订单需带 `tenantId` 隔离。本模板已有 tenant 中间件(`server/middleware/tenant.ts`),后端直接复用即可。
|
||||
|
||||
---
|
||||
|
||||
## 5. 工作量与分期(评估,未写代码)
|
||||
|
||||
- **P0 后端(必须,最先排期)**
|
||||
- supplier 账号体系(注册/登录/会话)
|
||||
- tender 标书数据模型 + 列表/详情/筛选接口
|
||||
- order 订单 + 微信支付下单 + 异步回调 + 标书交付
|
||||
- 工作量取决于 D2(是否复用已有会员体系)。
|
||||
- **P0 前端**
|
||||
- `app/pages/buy.vue`(激活入口,照搬 renewal)
|
||||
- `BuyDocument.vue` 改为购买流程页(列表 + 详情 + 表单 + 支付二维码 + 结果)
|
||||
- `register.vue` / `login.vue` + `useSupplier` 登录态 composable + 权限门控
|
||||
- **P1(上线后增强)**
|
||||
- 我的购买记录、邮件模板、后台标书上架审核(若标书需审核后发售)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险 / 阻塞
|
||||
|
||||
1. **最大阻塞**:本模板无后端账号/订单/支付,标书购买必须 cms-api 后端排期(虽有 shop/payment 底座,仍需新增 `tender` 业务包与流程编排),前端无法独立演示完整闭环。
|
||||
2. **CMS 写入 token 不稳定**:汇吉采落地清单已记录 cms-api 写接口偶发 401(`JWT signature does not match`)。建议标书走**独立 tender 业务接口**,而非 CMS 内容接口,规避此问题。
|
||||
3. **微信回调需公网域名**:本地开发用内网穿透或微信沙箱。
|
||||
4. **权限边界**:供应商仅能买"发售中"的标书,后端下单时需校验项目状态,防止前端绕过筛选取已截止项目。
|
||||
|
||||
---
|
||||
|
||||
## 7. 建议的下一步
|
||||
|
||||
1. 确认 **D1–D5** 决策,尤其 **后端选型(已在《后端选型评估》中定:在 cms-api 新增,复用 shop/payment)**、**D2(供应商复用 shop 会员体系)** 和 **D3(微信商户号是否就绪)**。
|
||||
2. 决策明确后,我可进入下一阶段产出(仍先评估、不写功能代码):
|
||||
- 标书购买流程图(已附于第 4 节)
|
||||
- 前端页面清单与路由映射
|
||||
- 后端接口清单(供你排期)
|
||||
3. 全部对齐后再开始写代码。
|
||||
Reference in New Issue
Block a user