Files
form-center-front/src/api/module/anon.ts
T
chenxing 60083517c7 t15:前端双探针引导——/f/ 页通道选择走匿名面,顺修 F1 未登录引导与 F2 desc 插槽
- 新 api/module/anon.ts:两把探针走原生 fetch(alova 的 302 拦截对全部 /api/* 生效且没有
  per-request 豁免位,用它问"有没有会话"会当场整页跳 CAS,C15 的代码事实);带 yschapirequest
  头让墙内未登录回 JSON 的 code=302 信封而不是 HTML 跳转。提交回落 alova(/anon 面无 302 语义)。
- Fill.vue 引导改双探针:probeSession → 有会话走登录通道并取"我的提交"段;无会话再 probeAnonForm
  → 命中即匿名渲染(匿名人无任何查询能力,编辑/删除段整条不出现);两跳都落空才 guideToLogin。
- F1 顺修(取"改判通道"那一支,不动全局 errorHandle):旧判据 e?.code===4 是死分支——alova 抛的是
  不带 code 的 Error,embed 的 needLogin 从来没发出去过;现在未登录引导只由探针结论触发。
- F2 顺修:slot desc → #desc 两处(Vue 3 下旧写法静默不进 Alert 描述插槽,报名号那行从未渲染)。
- App.vue:/f/ 前缀路由先以原生探针判会话,无会话则裸渲染且一个鉴权请求都不发(P124——否则无会话者
  还没走到填报页的探针就被带去 CAS,"匿名表单 page=直接可填"根本兑现不了);探到会话时结果直接复用,
  登录面不多发一次请求。其余路由引导一字未动。
- upload/image 两个附件控件的上传端点改 inject("fcUploadUrl", 默认 /api/form/upload),由 Fill.vue
  按通道 provide;设计器预览等调用点行为不变。vite.config.ts 代理加 /anon → 8086。

门禁:npx vue-tsc --noEmit exit 0;npm run build ✓ built in 11.61s。
2026-10-20 14:05:05 +08:00

81 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 匿名通道(契约 v2 §3.2/C6/C15,T15)。
*
* <p><b>两把探针必须走原生 fetch,不走 alova</b>:alova 实例的 302 拦截对全部 `/api/*` 生效且
* 没有 per-request 豁免位(C15 的代码事实,alova-core.ts:58-65),而引导期要问的问题恰恰是
* "这个人有没有会话"——用 alova 去问,无会话者当场被整页带去 CAS,双探针就没了意义。
* 探针只回数据,不跳转。</p>
*
* <p>提交回落 alova:`/anon` 面不存在 302 语义(无会话墙),业务错误提示链与登录面同一条;
* 上传端点由本模块给出 URL 串(附件控件按注入的 URL 走,见 Fill.vue 的 provide)。</p>
*/
import conf from "@/assets/config/host.config";
import Utils from "@/utils";
import { alovaInstance } from "@/utils/alova/alova-iview";
import type { ResolvedForm, SubmitRequest, SubmitView } from "@/types/answer";
/** 平台统一响应信封(探针只看 code,不套 alova 的错误处理) */
interface Envelope<T = any> {
code: number;
msg?: string;
data?: T;
}
/** 会话探针的结果:有没有会话 + 有会话时顺手把用户带回来(省一次重复请求) */
export interface SessionProbe {
logged: boolean;
user: any | null;
}
/**
* 探针请求。带 `yschapirequest: 1` 是给 CAS 过滤器认"这是 AJAX"的记号——不带它,墙内未登录会回
* HTML 跳转而不是 JSON 的 code=302 信封,fetch 会跟着跳到 CAS 域名然后撞 CORS,探针就变成了一次
* 无声的失败。`yschreferer` 同 alova 实例的头,保持两条通道对后端看起来是同一个前端。
*/
async function probe<T>(path: string): Promise<Envelope<T>> {
try {
const res = await fetch(`${conf.serviceHost}${path}`, {
credentials: "include",
headers: {
yschapirequest: "1",
yschreferer: Utils.strToHexCharCode(window.location.href)
}
});
if (!res.ok) return { code: -1, msg: `HTTP ${res.status}` };
return (await res.json()) as Envelope<T>;
} catch (e: any) {
return { code: -1, msg: e?.message || "探针请求失败" };
}
}
/** 探针 1(C15 第一跳):当前浏览器有没有 CAS 会话。非 0(含 302/网络错)一律按"无会话"回答 */
export async function probeSession(): Promise<SessionProbe> {
const envelope = await probe("/api/base/GetUserInfo");
return { logged: envelope.code === 0, user: envelope.code === 0 ? envelope.data ?? null : null };
}
/**
* 探针 2(C15 第二跳):这张表是不是匿名表单。
* 后端对"非匿名表单"和"压根不存在"给同一条 PARAM 表单不存在(C6/V54 防探测),
* 所以这里只需要"是不是 0"一个判据,msg 不进 UI。
*/
export async function probeAnonForm(code: string, appCode?: string): Promise<ResolvedForm | null> {
const query = appCode ? `&appCode=${encodeURIComponent(appCode)}` : "";
const envelope = await probe<ResolvedForm>(`/anon/form/resolve?code=${encodeURIComponent(code)}${query}`);
return envelope.code === 0 && envelope.data ? envelope.data : null;
}
/** 匿名提交(响应形状与登录面同一条:{answerId, answerNo, replayed},C7 幂等命中 200+原 answerId) */
export function submitAnonAnswer(body: SubmitRequest) {
return alovaInstance.Post<SubmitView>("/anon/form/submit", body).send();
}
/**
* 匿名上传的端点寻址(`code` 必带:后端只有表单能定 sch_id,落错校提交时的附件归属就查不到这行)。
* 返回的是给附件控件用的完整请求路径,appCode 以 query 参数交出去。
*/
export function anonUploadPath(code: string, appCode?: string): string {
const query = appCode ? `&appCode=${encodeURIComponent(appCode)}` : "";
return `/anon/form/upload?code=${encodeURIComponent(code)}${query}`;
}