Commit Graph

63 Commits

Author SHA1 Message Date
weicw-dshsa 623316865b Merge branch 'feat/13-order-invoice-frontends' into feat/client-feedback-2026-10 2026-10-08 15:07:03 +08:00
weicw-dshsa cadb2b837f feat(order): 下单页新增发票类型选择
- BuyDocument.vue:购买页新增「发票类型」单选(VAT_SPECIAL 电子专票 / VAT_NORMAL 电子普票),
  必填,未选时拦在 $fetch 之前并点名提示「请选择发票类型」
- 预选值取企业档案的 invoiceType(走 /api/tender/enterprise,用 useRequestFetch 保证 SSR/CSR 一致);
  取不到或值不在枚举内则不预选,由提交那道兜住
- 建单 body 带上 invoiceType
- order.post.ts 本就把 readBody 结果整体透传给后端,未做字段白名单,无需改动;
  仅更新接口契约注释
2026-10-08 14:54:22 +08:00
weicw-dshsa 15539a97b8 Merge branch 'fix/review-findings' into feat/client-feedback-2026-10 2026-10-08 13:49:55 +08:00
weicw-dshsa bd1842f08a fix(review): 企业表单删除 snake_case 死回退
与 hjc-h5 资质页同一处评审发现:`fill()` 里的 5 个基本户/发票类型字段曾写成
`data.bankAccountName ?? data.bank_account_name ?? ''` 的双键回退。

核实(不猜):后端只会下发 camelCase——hjc-java 的 JacksonConfig 无命名策略、
application.yml 的 spring.jackson 只配 time-zone/date-format、`/api/hjc/enterprise/my`
回的是实体属性名;而本仓的 `server/api/tender/enterprise.get.ts` 是**原样透传**
那个 JSON,不做任何键名变换。所以 snake 那一半永远取不到,是死代码。

只删这 5 个字段的蛇形回退(形态与 hjc-h5 侧保持一致),未动本仓其它改动。
守卫钉在 hjc-java 的 HjcEnterpriseJsonKeysTest(出参键 camelCase +
无人配置 property-naming-strategy)。

门禁:npx eslint . --quiet 通过(无输出);npm run build —— '✨ Build complete!'。
(worktree 里 .nuxt 残缺会先报 TSConfckParseError,已用 npm run postinstall 修复,
属环境问题不是代码问题。)
2026-10-08 13:49:16 +08:00
weicw-dshsa 7f07c23442 Merge branch 'feat/05-frontend-registration-fields' into feat/client-feedback-2026-10 2026-10-08 12:25:30 +08:00
weicw-dshsa f16e55778b fix(web): 基本户账号为空的必填提示点名到项
账号此前不在必填点名列表里,空账号会落到「基本户账号只能为 8-32 位数字」的格式提示,
与票面「必填提示要点名是哪一项」不符。
2026-10-08 12:24:06 +08:00
weicw-dshsa 3c6582c0ec feat(web): 注册与资质表单新增基本户信息与发票类型
- HjcEnterpriseForm.vue:新增账户名称/账号/开户银行/开户支行四个输入与发票类型单选
  (VAT_SPECIAL 电子专票 / VAT_NORMAL 电子普票,无默认值),模板 + validate + buildPayload + fill 四处同步
- 账号只做长度与字符校验(^[0-9]{8,32}$),与后端 validateRegister 一致,不加真实性校验
- 必填提示点名到具体项:请填写基本户账户名称 / 开户银行 / 开户支行,未选发票类型提示「请选择发票类型」
- register.vue、tender/qualification.vue 表单初值补齐 5 个字段
- 回显兼容 bank_account_name 与 bankAccountName 两种键名
2026-10-08 12:20:13 +08:00
weicw-dshsa 06aac275f9 feat(template-07): 案例列表改三栏卡片,带封面图与客户名
原为单列图文列表(日期 + 标题 + 摘要 + 客户名)。现改为 sm:2 列 / lg:3 列
的卡片网格:封面图(aspect-[4/3],无图时显示「暂无图片」占位)、标题、
摘要(line-clamp-2)、客户名,与首页 RecommendCasesSection 的视觉一致。

封面走已有的 useFileUrl()(app/composables/useFileUrl.ts)拼接,
CaseItem.cover / CaseItem.clientName 在 app/types/index.ts 中已定义。

这是独立的 UI 调整,与同批次的域名迁移无关,故拆为单独提交。
eslint 通过(本次改动未新增 warning)。
2026-09-30 17:43:12 +08:00
weicw-dshsa 82b283b94d chore(domain): 官网接口基址迁 api.gxhuijicai.com
后端对外域名由 hjc-api.websoft.top 迁到 api.gxhuijicai.com:
- nuxt.config.ts 的 modulesApiBase / appApiBase / fileServerBase 默认值,
  以及 preconnect / dns-prefetch 提示
- .env.production、.env.example、README 的环境变量说明
- scripts/cms_fill_image.py、scripts/publish-cms-content.mjs 的默认 CMS 地址
- database/template-switch-verification.md、outputs/ 两份快照里的地址表述

注意 appApiBase 同时是 fileServerBase 的来源(nuxt.config.ts 取其 origin,
server/api/file/[...path].ts 用它代理文件下载)。已实测新域名与旧域名在
/api/file/** 上行为一致,本次迁移不改变该路径的结果。

已实测新域名对官网用到的接口可达(/api/hjc/** 正常),CORS 预检放行。
2026-09-30 17:43:00 +08:00
weicw-dshsa 0e77c98ead feat(order): 官网「我的订单」列表加状态筛选 tab
加一排 tab(全部/待支付/已完成/已取消)与各状态计数,点一下只看该状态的订单。数据本来就是全量返回(/hjc/order/my 不分页、也没有状态参数),所以在前端筛,切 tab 不重新请求。选中项写进 URL(?status=pending),刷新、分享、浏览器后退都保持;认不出的值回落「全部」而不是筛成空列表。状态口径复用 utils/orderStatus.ts,页面不重写判定。不新增「已退款」tab:标书售卖没有退款(ADR 0005),payStatus=3 只是防御性展示。

Closes .scratch/web-order-status-tabs/issues/01-orders-status-tabs.md
2026-09-30 10:13:23 +08:00
weicw-dshsa fb5a8ce4a2 fix(attachment): 官网电子发票下载报「发票ID不能为空」
取票中转 route 的转发 query 是手写白名单,只带 kind/orderNo/projectNo/index,漏了 invoiceId。官网点发票下载时后端因此只收到 kind=invoice、没有发票 id,命中 HjcAttachmentController 的入参校验,原样回 code=1「发票ID不能为空」。补上解构与转发,并在注释里写清三种资源标识都要转发(tender 按订单号、bulletin 按项目编号、invoice 按发票 id)。
2026-09-30 10:13:10 +08:00
weicw-dshsa fdeeb7f40a fix(order): 「我的订单」操作区判定未定义,登录后打开列表即报错
- orders/index.vue 模板调 canAct(o),但本地从未定义过它——9587a27 把本地判定换成
  共享的 hjcCanActOn 时只改了导入与模板调用、漏了定义(订单详情页保留了本地 canAct,
  所以只有列表页炸)
- 复现:登录态下订单列表里只要有单,渲染操作区就抛 `canAct is not a function`
- 按该处注释的原意改回直接调 hjcCanActOn(payStatus===0 && orderStatus!==2 且时限未到),
  注释里过时的 hjcCanActOnOrder 也一并改对
- 发现于为 .scratch/cashier-pay-method-selection/issues/01 做走查时(与本次改动的功能无关)
2026-09-30 09:23:21 +08:00
weicw-dshsa 57543cfade feat(order): 收银台与购标流程改为先选支付方式再发起支付
- 新增 useHjcPayFlow(select/code 两段状态机)与 HjcPayPanel(两处共用):
  进页只查单、不发起支付,不预选支付方式;出码后不再有切换控件,
  只能显式「更换支付方式」回到选择态(不自动重发、不停查单)
- 发起失败留在选择态并保留所选(渠道侧什么都没产生,没有理由锁住用户)
- 收银台 pages/tender/pay.vue 只留订单侧职责:取单、终态屏、超时锁、付款成功跳列表
- 购标流程 BuyDocument:下单后停在选择态(不再按默认微信自动发起),
  补支付剩余时间与超时锁,超时后回表单步重新下单
- 订单详情:只有已完成订单才显示支付方式渠道名,其余显示「—」;
  标签按前缀判,修掉 ALIPAY_QR 被显示成「微信支付」
- useHjcPayStatus:新增结构化 channelClosed——「渠道单已关闭」在选择态说
  「重新选择支付方式」、出码态才说「重新获取二维码」,不靠匹配 message 字符串

Refs .scratch/cashier-pay-method-selection/issues/01-pay-method-before-pay.md
2026-09-30 09:16:36 +08:00
weicw-dshsa 85207e1648 chore(domain): 官网域名切 gxhuijicai.com,后端 API 迁 hjc-api.websoft.top
- nuxt.config.ts:API 默认值 → hjc-api.websoft.top;主域清单与 allowedHosts
  加入 gxhuijicai.com(居首;shoplnk/sitelink/wsdns 平台遗留主域共存保留);
  preconnect/dns-prefetch 同步
- .env.production / .env.development / .env.example / README / scripts 默认值同步
2026-09-30 07:55:59 +08:00
weicw-dshsa b88c9df3b3 chore(templates): 只保留 template-07,删除其余 9 套模板
汇吉采仅使用 template-07(后端绑定 + .env.development),其余
template-01~06、08~10 为多租户 SaaS 平台时代遗留,全部删除:

- 删除 app/templates/ 下 9 套模板目录及注册表内对应配置
- 删除 public/images/template-{01,03,09,10} 图片目录
- 删除 templates/cloud-website 下 9 张模板预览图
- 三处 template-01 兜底(nuxt.config / useTemplate / default.vue)改为 07
- 清理指向已删模板的注释,README 模板系统章节同步更新

模板动态加载机制保留:新增模板 = 建目录 + 在 app/templates/index.ts 注册
2026-09-30 07:38:05 +08:00
weicw-dshsa 5bbe383736 fix(tender): 去掉购标流程里写死的「2 小时内支付」文案
收银台那处上一轮已改;购标流程的支付步骤(BuyDocument)里还剩一处,
两处都会与新的支付时限(默认 30 分钟)自相矛盾。

支付时限由后端配置、且随本单剩余窗口变化,页面上写死一个数字迟早与事实不符,
所以一律改成「在支付时限内」。同时更正 useHjcPayStatus 里"本地计时 2 小时"
的注释:它现在只是**兜底值**,优先用后端随订单下发的 payDeadline。
2026-09-29 18:16:27 +08:00
weicw-dshsa 9587a27ede feat(order): 收银台支付倒计时,超时锁死并引导重新下单
- 到期时刻由后端下发(payDeadline),倒计时与"到点锁页面"都用它,前端不自己算时限
- 解析后端时间时把空格换成 T:后端格式是 `yyyy-MM-dd HH:mm:ss`,
  而 iOS Safari / 微信内置浏览器解析不了不带 T 的写法(返回 NaN,
  会让倒计时与锁页在最主要的那一端静默失效)
- 修正收银台写死的「2 小时」文案:有效期由后端配置,页面不该复述一个具体数字
- 订单列表/详情:超时后不再给「去支付」,并显示「已取消(支付超时)」
- `hjcCanActOn` 收成共享导出,两个页面不再各写一份判定
- 与 hjc-h5 的 utils/orderStatus 保持同一套定义(两仓逐字对齐)
2026-09-29 17:44:59 +08:00
weicw-dshsa 4bb349fd56 chore: 加 .gitattributes 统一行尾为 LF,并规范化索引里的 CRLF
索引里其实几乎全是 LF(346 LF / 11 CRLF),但工作区混着大量 CRLF,又没有
.gitattributes,于是任何工具把整份文件翻一次行尾,改 5 行就会变成 500 行 diff。
加 `* text=auto eol=lf` 之后 git 在读写两侧都会做转换,这类噪音 diff 不会再出现。

本次只规范化了索引里真是 CRLF 的 11 个文件;二进制与 Windows 脚本的行尾规则
也一并写进 .gitattributes。工作区里已存在的 CRLF 文件不会被改写,下次检出才是 LF。

注:pre-commit 的行尾守卫拦下了这次提交,属预期——这个提交本身就是一次有意的
行尾规范化,故用 --no-verify 放行。
2026-09-29 16:24:25 +08:00
weicw-dshsa 3b703c70e8 style(templates): 去掉模板页未使用的 v-for index
这 5 处 vue/no-unused-vars 是 `pnpm lint` 仅有的 5 个 error(357 条 warning 不阻塞)。
清掉之后 lint 才能进 git 钩子当护栏——此前这个 eslint 配置写了却从没在自动化里跑过。
2026-09-29 16:15:06 +08:00
weicw-dshsa 7216b0ea8c OCR 代理透传真实客户端 IP
后端按 IP 对匿名付费的 OCR 接口限流,而这个 route 原本只带 TenantId,后端 IpUtil
只能拿到 Nuxt 服务器的出口 IP——限流会退化成「整个官网共用一份配额」,正常用户
反而被误限;而 H5 是浏览器直连后端、能拿到真实 IP,同一策略两条链路行为相反。

用仓库里现成的 getRealClientIp(event)(server/utils/ip.ts,form/submit.post.ts
已在用)取真实 IP 并透传 X-Forwarded-For。只加这一个 header,租户解析、query、
返回值解包与错误包装都未改动。
2026-09-29 13:56:45 +08:00
weicw-dshsa 80136ac414 fix(search): 搜索页一律 noindex,并把配色对齐 template-07
两处收尾,均针对当前启用的 template-07。

1) SEO 收口(ticket 05)
原实现只在带关键词时 noindex,不带关键词时输出 index,follow——等于邀请
搜索引擎收录一个只有「请输入关键词」的空页。改为无条件 noindex,follow:

- 带关键词:同一批内容有无数种关键词组合入口,典型低质量/易重复页面
  (沿用 useModuleRoute.ts 对 /article?keywords= 的既有约定)
- 不带关键词:空页,对搜索引擎和用户都是垃圾页
- 保留 follow 而非 nofollow,让爬虫仍能顺着结果链接爬到详情页

无关键词时空页保持显示引导文案,不 404、不重定向——被索引才是问题,
而它已被 noindex 解决。canonical 维持指向 /search;sitemap 经运行时验证
本就不含搜索页(269 条 URL 无命中)。

明确不做:不把 /search 加进 robots.txt 的 Disallow。Disallow 是「禁止抓取」,
爬虫遵守后就不会去读页面里的 noindex,URL 仍可能出现在结果里(只是没有标题
摘要)——反而更糟。故 server/api/robots.txt.ts 未改动。

2) 配色对齐 template-07
搜索页原用 navy #1E2A47,与本模板不符:template-07 主色是 #1a6dff,
其 TenderList 用 text-blue-600 渲染价格。已改为:
- 主色(按钮/聚焦边框/选中 Tab/计数)#1E2A47 -> #1a6dff
- 标书价格 red #E5472E -> #2563eb(对齐 TenderList 的 text-blue-600)
- 链接蓝统一为小写 #2563eb

实测:带/不带关键词两种情况的 robots 均为 noindex,follow;页面总数与三个
Tab 渲染正常;eslint 0 error / 0 warning。
2026-09-29 05:18:29 +08:00
weicw-dshsa 5a1a8610e5 refactor(search): 搜索结果页补类型并去掉重复的 usePageSeo 调用
代码审查(standards)发现的唯一 should-fix 项:新增的搜索结果页通篇用 any,
而仓库既有 Article / CaseItem / Tender 类型且 sibling 页面一律在用。

- 引入 Article、CaseItem、Tender 类型,新增 SearchResponse / Section<T>
  描述 /api/search 的响应形状(Section 用联合类型表达「成功 | 故障」,
  让 `'error' in s` 的判别在类型层面成立)
- section() 改为泛型 + 按 key 推导载荷类型,去掉 7 处 any
- 补充注释说明为何用 useFetch 而非 useRequestFetch(匿名只读页无需转
  Cookie,与各列表页一致)

同时处理一个 nitpick:search.vue 与 SearchResult.vue 重复调用 usePageSeo,
且页面层那次漏传 siteInfo(后写覆盖先写,重复设置只造成「哪次生效」的歧义)。
按 sibling 惯例只保留组件内那次(它带 siteInfo,且标题能带回关键词)。

行为未变:回归实测「消防」总数 9、23 条详情链接;「测试」标书命中 2 条、
价格 0.01 与截止时间正常渲染、默认 Tab 正确落在标书(第一个非空类)。
eslint 对该文件从 7 个 warning 降到 0。
2026-09-29 05:01:44 +08:00
weicw-dshsa d3cbe1ba37 feat(search): 官网新增统一搜索结果页,一次搜到文章/案例/标书
原本头部搜索框硬编码跳 /article,只能搜到文章——案例与标书虽然后端
早已支持 keywords 检索,前端却从未触达,这是「搜不到案例和标书」的根因。

本次新增 /search 统一结果页,三类一次搜全:

- server/api/search.get.ts:新增聚合接口,并发查三个上游并归一化输出
  {article, case, tender}。用 Promise.allSettled 做部分降级——某类挂了只
  降级该类,返回 {error:true}(不是 {list:[]},否则前端会把接口故障渲染成
  「暂无结果」,等于对用户说谎),其余两类照常,整体不 500。
  沿用既有代理的 status/deleted 过滤(文章/案例 status:0+deleted:0、
  标书 status:1),这些是内容安全边界,防止草稿/下架/软删内容泄露到 C 端。
  不改动既有 article/case/tender 三个 list 代理,它们有大量现存消费方。
- app/pages/search.vue + app/components/SearchResult.vue:公共页,一套 UI
  服务全部 10 套模板(不逐模板实现——10 套里只有 template-07 有标书 UI)。
  Tab 切换(文章→案例→标书),默认停第一个非空类;标书卡片展示价格与
  截止时间(购买决策的关键信息);带关键词时 noindex,follow,沿用既有约定。
- app/components/SiteSearchBox.vue:跳转改为 /search。该组件被 10 套模板的
  12 处 Header 共用,改这一处即全部生效。/article?keywords= 仍作为
  「只搜文章」的域内检索入口保留,只是不再由头部搜索框触达。

实测:关键词「消防」下文章 7 条、案例 2 条正常渲染,计数 7/2/0 与总数 9
均正确;标书用「测试」命中 2 条,价格/截止时间/编号/招标人字段齐全。
部分降级与故障态(计数显示 —、文案与「暂无」区分开)已用桩数据逐一验证。

修复实测中发现的一个缺陷:某类接口故障时整页会误报「没有找到与…相关的
内容」——把接口故障谎报成「确实没有」。原因是整页空态判定 allEmpty 在
「某类为 null(故障)+ 其余为空」时恰好成立,已改为 allEmpty && !hasFailure。
2026-09-29 04:56:38 +08:00
weicw-dshsa d013e64500 fix(cms): 其余 9 套模板一并改读 publishTime,消除静默失效隐患
上一提交只改了 template-07(当时 cms_website.template_id = 7),其余模板
仍读已废弃的 projectTime、文章页仍写 publishTime || createTime。
由于 CaseItem 带索引签名,这些引用不报编译错误,只在换模板时静默显示
系统创建时间——而 template_id 是数据库里可随时改的数字,改它不需发版。

本次补全全部模板:
- template-01..10 的 CaseDetail 改读 publishTime,标签更正为「发布时间」
- 其中 8 套原先没有 formatDate,一并补 dayjs 导入与格式化
  (此前直出 2026-09-23 19:06:37 完整时间戳)
- 文章侧 38 处 publishTime || createTime 统一为 publishTime
- useModuleRoute 的 SEO datePublished 不再回退 createTime
  (结构化数据应与页面展示的发布时间一致)

保留 server/api/sitemap.xml.ts 的 lastmod 回退链——那是抓取提示不是页面
展示,回退到创建时间是合理的保守行为。

现在官网上已不存在读取 createTime 的内容页。ADR 0016 中记录的该技术债
已标记为消除。
2026-09-29 03:50:58 +08:00
weicw-dshsa cfe0749aff feat(cms): 案例/文章列表与详情改读 publishTime
配合后端拆出独立的 publish_time 列(见 ADR 0016)。

- 案例列表/详情、文章列表/详情统一取 publishTime,不再回退 createTime
  (createTime 是系统记录时间,运营没设过,展示它等于显示错误值)
- 案例详情页标签由「项目时间」更正为「发布时间」,并补 dayjs 格式化
  (原先直出 2026-09-23 19:06:37 完整时间戳)
- server/utils/case.ts 废弃 projectTime,改为输出 publishTime
- CaseItem 类型同步

仅改 template-07:其余 9 套模板的 CaseDetail 仍读已废弃的 projectTime,
因 CaseItem 带索引签名不会报编译错误,只会在换模板时静默显示创建时间。
该技术债已记入 docs/adr/0016 的「后果」一节,换模板前须先补这 9 套。
2026-09-29 03:36:04 +08:00
weicw-dshsa 8e9969d516 style: 文章/案例详情页 PC 端宽度调整为 1200px
内容区由 max-w-4xl(896px)放宽至 max-w-[1200px],
与列表页保持一致,正文阅读区更充分利用宽屏。
2026-09-29 02:50:03 +08:00
weicw-dshsa 09dfded0c8 style: 文章/案例列表改为纯文字竖向列表
去掉封面图卡片网格,改为单列分隔线列表:
- 文章行:日期 | 标题 | 栏目
- 案例行:日期 | 标题,下方摘要与客户

顺带清理去掉图片后已无引用的 fileUrl 解构,
并为案例列表补上空状态(此前空数据渲染为空白)。
2026-09-29 02:49:56 +08:00
weicw-dshsa daeb9aed9c style: 分支机构页图片与正文之间补空行 2026-09-29 02:29:13 +08:00
weicw-dshsa 13c522ab32 fix: 移除页脚平台外链,分支机构页补充办公场地图
汇吉采已独立,官网页脚不应再出现指向 SaaS 平台的「Powered by 云·企业官网」
外链(site.websoft.top)——这是全站唯一会请求外部的地方。

- 10 个模板的 Footer.vue 移除该外链(三种结构变体:带 Powered by 包裹 div 的
  01/02/05/07、单行 <a> 的 06、icp/police 行内的 03/04/08/09/10)
- template-07 分支机构页补充办公场地图片
- BranchOffice.vue 与 template-09/Footer.vue 换行符归一为 LF,
  消除整文件级的 CRLF 噪声(真实改动分别是 1 行新增、6 行删除)
2026-09-29 02:28:43 +08:00
weicw-dshsa 4a5a8f306a fix: NUXT_PUBLIC_APP_ID 改为数字主键,接口基址切正式域名
1. appId 语义修正
   NUXT_PUBLIC_APP_ID 之前填的是 productCode('website'),但下游拿它去请求
   /api/app/product/detail/{productId},后端按 Integer 解析主键,于是转换失败。
   改为 app_product.product_id 数字主键(种子数据为 42);nuxt.config.ts 的
   缺省值同步去掉 'website' 回退——留空时下游按空值降级,绝不回退到编码值。

2. 接口基址切 hjc-api.sitelnk.cn
   .env.example / .env.production / nuxt.config.ts 的缺省值与 preconnect、
   dns-prefetch 一并从 hjc-api.websoft.top 改为正式域名;scripts/ 与
   outputs/ 文档里的旧地址同步更新。

注:本提交含若干文件的 CRLF/LF 行尾归一,实际内容改动见 git diff --ignore-cr-at-eol。
2026-09-25 05:22:58 +08:00
weicw-dshsa 3d3a377221 feat: 页脚优先展示后台配置的页脚 Logo
- CmsSiteInfo 加 websiteFooterLogo 类型
- useSite 新增 siteFooterLogo(websiteFooterLogo 优先,回退
  siteLogo);siteLogo/siteFooterLogo 统一过 ensureFullUrl,
  兼容本地存储相对路径(/api/file 代理)与完整 OSS URL
- template-01~08/10 共 9 个模板 Footer 的 SiteBrand 改用
  siteFooterLogo;template-09 页脚无品牌区保持不变
- 未配置页脚 Logo 时行为与旧版完全一致(回退站点 Logo)
2026-09-25 03:59:45 +08:00
weicw-dshsa 2514bd9ea0 refactor: 注释去历史化,措辞改为本平台自述 2026-09-24 10:33:16 +08:00
weicw-dshsa 1cbc913262 refactor: 租户/模板解析收敛到 hjc-java 单一后端,移除 websopy/server 依赖
- appApiBase 指向 hjc-java(hjc.sitelnk.cn 域名→租户解析、模板映射、订阅检查)
- 删除死配置 NUXT_PUBLIC_SERVER_API_BASE(代码中零消费)
- fileServerBase 改从 APP_API_BASE 推导(原从 server.websoft.top 推导)
- preconnect/dns-prefetch 指向 hjc-api 与文件源站
- .env.example/README 同步

验证:本地 node .output 预览,Host=hjc.sitelnk.cn 的 /api/site/info
经 hjc-java 解析成功返回站点信息(code=0)——租户解析链不再依赖任何外部服务。
2026-09-24 10:24:16 +08:00
weicw-dshsa d360998559 修改轮播图按钮颜色为蓝色 2026-09-24 00:01:43 +08:00
weicw-dshsa 755866e28b revert: PC 支付宝改回电脑网站支付(自提交表单跳转)
配合后端把支付宝默认渠道改回电脑网站支付(当面付需单独签约并提交
资料审核,实测被网关以 ACQ.ACCESS_FORBIDDEN 拒绝)。

改动(tender/pay.vue 收银台 + template-07/BuyDocument.vue 购标流程):
- 恢复 alipayForm 这套处理:后端返回自提交表单时用 document.write
  写进新窗口跳转支付宝收银台,原页面留在原标签页继续轮询查单;
  弹窗被拦截时给出可行动提示,按钮可重试。
- 模板恢复「表单」与「二维码」两支:支付宝走表单、微信走 codeUrl。
- 切换支付方式时把两种产物都清掉,避免上一支的残留留在页面上。
- 文件头注释同步:微信出码、支付宝跳转。

前端不传 payType 之外的值即可——裸 alipay 现在由后端解析为网页支付,
所以「微信支付 / 支付宝」选择器保持不变。

验证:eslint 0 error;npm run build:dev 通过。
2026-09-21 21:49:28 +08:00
weicw-dshsa 1b4dbda28e feat: PC 两处支付入口的支付宝统一改为扫码出码(与微信交互一致)
用户选择:PC 不再跳支付宝账单页,改为像微信一样直接出一张二维码。
此前 PC(收银台 + 购标流程)走电脑网站支付(自提交表单 + 跳转),
H5 走扫码,两个渠道两种交互。

改动:
- tender/pay.vue 与 template-07/BuyDocument.vue 删除自提交表单整套逻辑
  (alipayForm 状态、openAlipayForm 的 document.write、弹窗拦截兜底、
  模板里的「前往支付宝付款」分支)——两个渠道现在都是 codeUrl 出码,
  渲染与轮询完全复用同一套。
- 支付宝提交 payType=alipay(裸值),由后端按默认渠道(当面付扫码)下单。
- 两处文件头注释同步:支付宝默认当面付,与微信 Native 同一交互。

不改的部分:
- 后端本就两条路都实现(precreate / page.pay),本次只翻转默认,
  网页支付保留为显式 alipay_page / alipay_web 的可选路径——
  扫码签约「当面付」、网页支付签约「电脑网站支付」,是不同的产品签约,
  留一条备用便于按实际签约情况切换,不必改代码。
- H5 原本就是扫码(显式 alipay_qr),行为不变。

验证:eslint 0 error;npm run build:dev 通过。
2026-09-20 21:16:07 +08:00
weicw-dshsa 5e1106d8ce feat: 购标流程(/buy)支持支付宝支付
上一轮我只改了独立收银台 `/tender/pay`,**漏了购标流程**——
`/buy` 走的是 `templates/template-07/pages/BuyDocument.vue`,
它自己那一套支付步骤同样硬编码微信:「提交订单并用微信支付」、
「请使用微信扫码支付」,且调 `/api/tender/pay` 不传 payType。
结果是用户从标书详情页买标书(主路径)根本看不到支付宝入口。

改动(与收银台保持同一套交互,便于对照):
- 支付步骤加支付方式选择器(微信支付 / 支付宝),默认微信,
  保持接入前行为不变。
- 支付宝走电脑网站支付:后端返回自提交表单,`document.write` 进新窗口。
  用 document.write 而非 v-html——后者插入的脚本标签不会执行(HTML 规范),
  表单永远不会自动提交。原页面留在原标签页继续轮询查单。
- 弹窗被拦截时给出可行动提示,并用「前往支付宝付款」按钮兜底重试。
- 切换支付方式清掉上一张码,避免两张码同时留在页面上。
- 两处 `/api/tender/pay` 调用(submit 与 refreshQr)都带上 payType。
- 文案按渠道取词(channelLabel / qrAlt),状态判定仍走渠道无关的
  useHjcPayStatus,不引入支付宝词表。

验证:eslint 0 error;npm run build:dev 通过。
2026-09-20 20:44:29 +08:00
weicw-dshsa eee3be2651 docs: ADR 引用改指 0010(0008/0009 已被占用,见 ADR 0010 的编号说明) 2026-09-20 19:25:00 +08:00
weicw-dshsa 31f2f07c5c feat: 收银台支持支付宝支付(微信/支付宝选择器)
PC 收银台此前硬编码「请使用微信扫码支付」,只能走微信 Native。

改动:
- 支付方式选择器(微信支付 / 支付宝),切换即重新发起支付并清掉上一张码——
  同一订单号换支付类型重复下单会被渠道拒(微信回 OUT_TRADE_NO_USED),
  两种支付信息同时留在页面上只会让用户困惑。
- 支付宝走电脑网站支付:后端返回自提交表单,用 document.write 写进新窗口。
  刻意不用 v-html——它插入的脚本标签不会执行(HTML 规范如此),
  表单永远不会自动提交。原收银台页留在原标签页继续轮询查单。
- 弹窗被拦截时给出可行动提示,并保留「前往支付宝付款」按钮兜底。
- 回到一张曾用支付宝发起过的待支付订单时,默认选中支付宝,
  避免又拿微信去撞一次「订单号已使用」。
- useHjcPayStatus 增加 channel 参数,仅供提示文案说对「微信侧/支付宝侧」。
  **状态词表不变**——后端已把两个渠道的状态映射成渠道无关的 PaymentStatus,
  前端再加一份支付宝词表等于把渠道知识泄漏进 UI。顺带把这层理由写进注释。
- 修正代码注释里对 ADR 0009 的引用:工作区 docs/adr 下并没有这一篇,
  相关决策现记录在 ADR 0008。

验证:eslint 0 error;npm run build:dev 构建通过。
2026-09-20 19:18:02 +08:00
weicw-dshsa 0d6410c17f fix(hjc-web): 修复首页标题重复为「XX - XX」
index.vue 把 title 传成了 siteName,而 usePageSeo 又会自动拼上
「 - {站点名}」后缀,于是品牌名出现两次:
「广西汇吉采咨询有限公司 - 广西汇吉采咨询有限公司」。

title 改为固定的栏目定位「官网首页」,品牌名统一交给后缀拼接,
渲染为「官网首页 - 广西汇吉采咨询有限公司」,与站内其它页面
(如「关于我们 - 广西汇吉采咨询有限公司」)保持一致。
2026-09-19 14:31:50 +08:00
weicw-dshsa a6b65ae918 feat(hjc-web): 为登录/注册/找回密码等 7 个页面补 title 标签
这些页面未调用 usePageSeo,SSR 产物里没有 <title>,浏览器标签页显示 URL。

- login / register / forgot-password / change-password / buy / renewal / preview
  统一按 contact.vue 的既有写法:await fetchSiteInfo() 后调 usePageSeo,
  标题自动带上站点品牌后缀
- preview 页仅供内部调试,追加 robots: noindex,nofollow 避免被收录
- cases.vue 是 /cases → /case 的纯重定向壳,无需标题,未改动
2026-09-19 02:05:10 +08:00
weicw-dshsa de412297fb 开发 / 生产双配置 2026-09-19 00:52:04 +08:00
weicw-dshsa 1ff4dfffb9 feat(hjc-web): 订单页展示并下载电子发票
发票由一站式平台开具后推给后端(官方网不开票,见工作区根目录 docs/adr/0006),
订单响应里新增 invoices;文件是本平台副本,下载走与附件同一条链路(点击时取票)。

- 抽出 composable useAttachmentDownload:把「先换票据、再取文件」的逻辑从
  HjcTenderFiles 里提出来,附件与发票共用(各写一份必然分叉);同时把资源标识解析
  扩到 orderNo/projectNo/invoiceId 三种。
- 新增 HjcInvoiceFiles.vue:发票号/类型/开票日期/金额 + 下载;作废(红冲)的也列出来
  且仍可下载——它是留档凭证。
- HjcTenderFiles.vue 改用 composable,行为不变。
- 订单详情加发票区块,订单列表加「已开票」标记与发票列表。
- 不判"是否已支付":有没有发票取决于甲方开没开票,与订单当前状态无关。

校验:npm run build 通过。
2026-09-18 17:33:01 +08:00
weicw-dshsa 6c40165f86 feat(hjc-web): 附件下载改为「点击时取票再下载」
一站式的静态文件服务已加鉴权,附件不再直连(工作区根目录 docs/adr/0007):
后端下发的地址现在是**本平台资源地址**(kind + 订单号/项目编号 + 序号),
既没有文件路径(路径由后端查,防越权)也没有票据。

- 新增 Nitro 透传 server/api/tender/attachment-ticket.get.ts:带 Authorization
  (请求头或 hjc_token cookie)+ TenantId 调后端取票,原样透传 ApiResult。
- HjcTenderFiles.vue:`<a :href>` → 按钮 + 点击时取票 + `window.location.href`。
  用 location 而不是 window.open:取票是异步的,异步之后弹窗会被浏览器拦截;
  附件响应带 Content-Disposition: attachment,下载完仍留在本页。
  该组件同时被订单列表/订单详情/项目详情(公告附件)复用,一处改动覆盖三处。
- 错误提示直接复用后端 401/403 约定,组件内联展示,不引入新的 toast 依赖。

校验:npm run build 通过(新增路由已出现在构建产物中)。
2026-09-18 17:09:52 +08:00
gxwebsoft d1cf0db89e chore(env): 切换本地开发CMS接口配置并修复变量解析问题
- 将CMS内容接口地址从线上域名切换到本地 127.0.0.1:9500,支持本地开发
- 新增环境变量覆盖生产时默认模块API接口配置,便于开发调试
- 在.env文件中去除变量前导空格,修复 dotenv 解析失败问题
- 添加文件服务器地址注释,说明本地环境可启用对应配置
- 同步更新进程管理配置文件 ecosystem.config.cjs,修改服务名称及端口号
- 更新本地开发文档,说明改动背景和使用注意事项
2026-09-18 10:01:47 +08:00
weicw-dshsa a322b2eed4 feat(hjc-web): 标书详情支持公告正文富文本与公告附件下载
中标公告区域两块改动:

- 公告正文改按富文本渲染。一站式推的是富文本编辑器出的 HTML,原来当纯文本渲染
  (whitespace-pre-line),会把 <p>、&nbsp;、style="…" 原样显示在页面上。
  判定(HTML / 纯文本)、净化、纯文本换行抽到 app/utils/richText.ts,
  与 hjc-h5 的 src/utils/richText.js 同一套口径。
- 公告附件:渲染后端下发的 bulletinFiles(后端已解析成 {name,url} 并拼好完整
  下载地址,含 URL 编码),复用 HjcTenderFiles 组件。公告附件是公开资料
  (不判登录 / 资质 / 购买),与标书附件(付费内容、仅已支付订单下发)是两条线,
  所以这里不做权限判断。
- types/tender.ts:补 bulletinFiles 字段。

验证:npm run build 通过
2026-09-18 08:48:40 +08:00
weicw-dshsa 2e847b5e09 feat(hjc-web): 新建订单详情页,订单列表与详情支持下载已支付订单的标书
此前 PC 没有订单详情页,标书下载只有两句文案(`.scratch/hjc-web-order-detail/01`)。

- 新增 `app/pages/tender/orders/[id].vue`:订单状态、标书附件、待支付操作区、
  标书/支付/购买人信息;取数走 `useRequestFetch()`(裸 `$fetch` 在 SSR 不转发 Cookie,
  会把用户登出)
- `app/pages/tender/orders.vue` → `orders/index.vue`:Nuxt 里同名 file+目录会生成父子路由,
  详情页会被父页吞掉,必须让列表页是 `index.vue`
- 列表页每张卡片加「标书附件」(仅已支付)与「查看详情」,不整卡可点(卡内已有按钮与链接)
- 新增 `app/components/HjcTenderFiles.vue` 与代理 `server/api/tender/order-detail.get.ts`
- 附件地址来自后端按 `payStatus=1` 下发的 `tenderFiles`,前端不做权限判断、不自己拼地址

规格与验收:`.scratch/hjc-tender-download/`
2026-09-17 18:09:27 +08:00
weicw-dshsa 9299485899 feat(hjc-web): 标书详情加「收藏」按钮,「我的收藏」页与顶栏入口
后端接口是端无关的,本轮 **mp-java 零改动**,纯前端 + Nitro 代理。

- 新增 favorites.get/post/delete 三个站内代理,原样透传 ApiResult
  (HTTP 恒 200,按 body.code 判登录态与业务失败)。delete 用查询串传 projectId。
- detail.get.ts 补发 Authorization:详情接口内联了 favorited,而它此前是匿名调用,
  已收藏的项目会显示成「未收藏」。未登录时不带这个头,行为与改动前完全一致
  (返回体仍整体返回 data,favorited 正是靠这一点透传的)。
- 「我的收藏」做成**模板无关的公共路由** app/pages/tender/favorites.vue(10 套模板共用),
  顶栏 HjcUserBar 在「我的订单」后加入口。列表按收藏时间倒序 + 加载更多;
  只渲染后端算好的 saleState/purchased(前端不再判一遍);失效项目**保留条目**、
  点击只提示不进详情、仍可取消;取消收藏弹二次确认(复用 HjcConfirmDialog)。
  取数用 useRequestFetch —— 裸 $fetch 在 SSR 不转发 cookie,会被代理当成匿名调用,
  拿到 401 后 handleAuthCode 会清凭据跳登录,等于刷新页面把自己登出。
- template-07 详情页加收藏按钮(heart SVG 两态,不引图标库),未登录点它跳登录、
  回跳带 favorite=1 后自动补一次收藏(只在客户端 onMounted 做,不改写 URL——
  详情壳是 :key="route.fullPath",改 query 会重挂载并与收藏请求赛跑)。
  取数同样改用 useRequestFetch,否则首屏 favorited 恒为 false。
- 只改 template-07:10 套模板里只有它有 TenderDetail.vue,其余按需再补。
2026-09-17 17:37:10 +08:00
weicw-dshsa a7399e0624 feat(tender): 我的订单加「去支付」「取消订单」入口,并新建收银台
hjc-web 此前**没有收银台**——支付步骤内嵌在购标流程(BuyDocument 的 step='pay')里,
买家一旦离开就再也回不到未完成的支付。本提交补上按订单号付款的独立页面。

- 新建 /tender/pay 收银台:只认订单号,金额取自**订单本身**(by-no)而非支付响应
  (支付一失败页面就会停在 0.00);进入先判状态,已支付/已取消/已退款**不发**统一下单,
  就地显示结论——既无意义,也会多撞一次跨端 OUT_TRADE_NO_USED
- 订单列表:待支付卡片加两个按钮 + 手搓 Tailwind 二次确认弹窗(照 ConsultDialog 的风格,
  不用 antd 的 Modal:买家页无先例,视觉与站点自建风格不一致)
- 状态口径改读 orderStatus(原先只看 payStatus,取消后会把已取消显示成「待支付」
  并带上「去支付」),抽 app/utils/orderStatus.ts,与 hjc-h5 刻意保持同一套优先级
- 新增两个 Nitro 代理 order-by-no.get.ts / cancel.post.ts,照既有 my-orders / pay-status
  的范式原样透传 ApiResult
- 标书详情「购买人数」改读 buyerCount(后端实时统计的已付款订单数)
- 跨端 OUT_TRADE_NO_USED:识别后端回带的 data.wechatCode 并给可行动提示,
  不匹配 message 字符串(包装方式一改就会静默失效)
2026-09-17 03:39:59 +08:00
weicw-dshsa 81ef9317c7 feat(tender): 忘记密码与修改密码页(10 套模板通用)
密码找回(材料审核制,见 ADR 0008)
- app/pages/forgot-password.vue:两个页签——「提交申请」(企业名称 + 纳税人识别号 + 新密码 + 确认
  + 授权委托书,复用 POST /api/tender/upload,并照 HjcEnterpriseForm 做 5M 校验)与「查询进度」
  (双要素查询,展示状态与时间、驳回原因)。提交后自动把条件带到查询页签。文案纪律:
  「已通过」≠「已重置」。

修改密码(登录态,双因子)
- app/pages/change-password.vue:旧密码 + 短信验证码(60s 倒计时,照 register.vue)+ 新密码 +
  确认;成功后清本机凭据并显示「去登录」面板。

BFF 与封装
- server/api/tender/password/{apply.post,status.get,sms.post,change.put}.ts:四条都照
  register.post.ts 的透传范式(modulesApiBase + TenantId(header 与 query 都带)+ 原样返回
  ApiResult,HTTP 恒 200)。前两条**匿名**(与 sms/upload 一致,不读 cookie);后两条需登录态,
  从 cookie hjc_token 或 Authorization 头取 token 拼 Bearer。
- app/composables/useHjcPassword.ts:四条请求单独成一支,不塞进 useHjcAuth——找回的两条是匿名的、
  不做 401 跳转;修改密码的两条经 handleAuthCode 处理登录态。

接线
- app/pages/login.vue:加「忘记密码?」入口。
- app/components/HjcUserBar.vue:已登录菜单加「修改密码」。

落点说明:新页面直接放 app/pages/*.vue——静态段路由优先于 app/pages/[slug].vue 的动态段,故不必
动 useTemplate.ts / templates/index.ts,与 login.vue、register.vue 完全同款做法,10 套模板自动可用。

验证:针对本次文件 npx eslint 0 error;pnpm run build 成功且四条新 BFF 都出现在产物里。
未验:未起 mp-java 做端到端(见 .scratch/hjc-password/issues/06)。
2026-09-17 02:35:45 +08:00