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)。
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
import { createError, defineEventHandler, readBody } from 'h3'
|
||||
import { useRuntimeConfig } from '#imports'
|
||||
import { getTenantFromContext } from '../../../utils/tenant'
|
||||
|
||||
/**
|
||||
* 密码找回-提交申请(**匿名**,企业未登录时提交)
|
||||
* POST /api/tender/password/apply { enterpriseName, creditCode, newPassword, confirmPassword, handbookUrl }
|
||||
* 代理到 mp-api /api/hjc/auth/password/apply。
|
||||
*
|
||||
* 注意:该接口对「企业不存在 / 双要素不匹配 / 已有待审核申请」一律返回**同一句话且 data 为 null**,
|
||||
* 以免暴露某个企业是否在本平台注册过。前端因此不能靠 message 区分结果,只提示「已提交」并引导去查进度。
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const ctx = getTenantFromContext(event, config)
|
||||
const body = await readBody(event)
|
||||
|
||||
try {
|
||||
return await $fetch('/hjc/auth/password/apply', {
|
||||
baseURL: config.public.modulesApiBase,
|
||||
method: 'POST',
|
||||
headers: { TenantId: ctx.tenantId },
|
||||
query: { TenantId: ctx.tenantId },
|
||||
body
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw createError({
|
||||
statusCode: error?.statusCode || error?.response?.status || 502,
|
||||
statusMessage: error?.data?.message || error?.statusMessage || '提交失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
import { createError, defineEventHandler, readBody, getHeader, getCookie } from 'h3'
|
||||
import { useRuntimeConfig } from '#imports'
|
||||
import { getTenantFromContext } from '../../../utils/tenant'
|
||||
|
||||
/**
|
||||
* 修改密码(**需登录态**,双因子:旧密码 + 账号绑定手机号短信验证码)
|
||||
* PUT /api/tender/password/change { oldPassword, smsCode, newPassword, confirmPassword }
|
||||
* 代理到 mp-api /api/hjc/auth/password/change。
|
||||
*
|
||||
* 原样透传 ApiResult:后端对「旧密码不正确 / 短信验证码不正确 / 密码强度不够」都有明确文案,
|
||||
* 由前端直接展示。
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const ctx = getTenantFromContext(event, config)
|
||||
const body = await readBody(event)
|
||||
const cookieToken = getCookie(event, 'hjc_token')
|
||||
const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null)
|
||||
|
||||
try {
|
||||
return await $fetch('/hjc/auth/password/change', {
|
||||
baseURL: config.public.modulesApiBase,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
TenantId: ctx.tenantId,
|
||||
...(auth ? { Authorization: auth } : {})
|
||||
},
|
||||
query: { TenantId: ctx.tenantId },
|
||||
body
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw createError({
|
||||
statusCode: error?.statusCode || error?.response?.status || 502,
|
||||
statusMessage: error?.data?.message || error?.statusMessage || '修改密码失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
import { createError, defineEventHandler, getHeader, getCookie } from 'h3'
|
||||
import { useRuntimeConfig } from '#imports'
|
||||
import { getTenantFromContext } from '../../../utils/tenant'
|
||||
|
||||
/**
|
||||
* 修改密码-发送短信验证码(**需登录态**)
|
||||
* POST /api/tender/password/sms(无 body)
|
||||
* 代理到 mp-api /api/hjc/auth/password/sms。
|
||||
*
|
||||
* 收件号由后端从登录态里取核心实例上的手机号(账号绑定手机号),**前端不能指定号码**——
|
||||
* 否则任何人都能给任意手机号发短信。
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const ctx = getTenantFromContext(event, config)
|
||||
const cookieToken = getCookie(event, 'hjc_token')
|
||||
const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null)
|
||||
|
||||
try {
|
||||
return await $fetch('/hjc/auth/password/sms', {
|
||||
baseURL: config.public.modulesApiBase,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
TenantId: ctx.tenantId,
|
||||
...(auth ? { Authorization: auth } : {})
|
||||
},
|
||||
query: { TenantId: ctx.tenantId }
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw createError({
|
||||
statusCode: error?.statusCode || error?.response?.status || 502,
|
||||
statusMessage: error?.data?.message || error?.statusMessage || '验证码发送失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
import { createError, defineEventHandler, getQuery } from 'h3'
|
||||
import { useRuntimeConfig } from '#imports'
|
||||
import { getTenantFromContext } from '../../../utils/tenant'
|
||||
|
||||
/**
|
||||
* 密码找回-查询进度(**匿名**)
|
||||
* GET /api/tender/password/status?enterpriseName=&creditCode=
|
||||
* 代理到 mp-api /api/hjc/auth/password/apply/status。
|
||||
*
|
||||
* 返回的 data 为 null 表示「未查询到申请记录」(查不到企业与命中多条脏数据都归为这一种,
|
||||
* 同样不区分企业是否存在);有记录时只含状态与时间,**不含材料与新密码**。
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const ctx = getTenantFromContext(event, config)
|
||||
const query = getQuery(event)
|
||||
|
||||
try {
|
||||
return await $fetch('/hjc/auth/password/apply/status', {
|
||||
baseURL: config.public.modulesApiBase,
|
||||
method: 'GET',
|
||||
headers: { TenantId: ctx.tenantId },
|
||||
query: {
|
||||
TenantId: ctx.tenantId,
|
||||
enterpriseName: query.enterpriseName,
|
||||
creditCode: query.creditCode
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw createError({
|
||||
statusCode: error?.statusCode || error?.response?.status || 502,
|
||||
statusMessage: error?.data?.message || error?.statusMessage || '查询失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user