Core Highlights Summary
The latest article from Juejin’s “WMS Warehouse Management System AI Agent Integration” Series - Episode 7, details a Vue 3.5.13 + Vue Router 4.5 + Pinia 2.3 frontend engineering implementation. Additional tech stack includes Axios 1.7.9 + Element Plus 2.9.1 + markdown-it 14.1 + Vite 6.2.4, with pnpm as package manager.
The project follows a clear directory structure: api directory for network request encapsulation (request.js as core interceptor), stores directory managing separate user.js and chat.js Pinia stores, and components directory housing GlobalLayout.vue and PermissionBtn.vue.
Key implementation points include:
- Dual Pinia Store separation: user.js handles authentication state (token/role/expiresAt) with localStorage dual-write; chat.js specifically manages streaming conversation state
- Concurrent refresh lock: Token refresh lock implementation based on Axios interceptor to prevent duplicate refresh requests
- Markdown rendering safety: Uses markdown-it’s ‘zero’ preset with whitelist syntax mechanism
- Three-layer RBAC frontend: Route guards, menu filtering, and button-level directives配合实现
Dual Store Design & Streaming Dialogue Bug Fix
The user.js store maintains five reactive states (token, refreshToken, role, userName, expiresAt), with computed property isAdmin for role derivation. Its core design is using an independent axios instance for token refresh, bypassing the main interceptor to prevent infinite loops.
The localStorage dual-write strategy solves the issue of Pinia in-memory state loss during page refresh. The author specifically notes: frontend role can be modified via F12 but only affects the presentation layer, while backend RBAC verification remains the actual security gate.
The updateLastMessage → updateMessageAt fix addresses a critical bug:
| Issue | Vue 3.5.13 + Pinia 2.3 |
|---|---|
| Reproduction scenario | Streaming dialogue with tool calls (tool_call inserted, then text tokens continue arriving) |
| Actual symptom | AI bubble stops mid-sentence, subsequent tokens silently lost |
| Root cause | After tool card insertion as independent message, “last item” no longer points to AI message |
This fix proves streaming scenarios must use index-based message location, not rely on “last item” logic. This experience equally applies to appendToolCallToMessage and appendToolResultToMessage operations.
Axios Interceptor Three-Layer Refresh & Dual-Path 401 Handling
The project implements complete three-layer refresh logic in request.js:
- Infinite loop prevention: Authentication endpoints (login/register/refresh) are directly bypassed
- Refresh failure熔断: refresh endpoint returning 401 immediately triggers logout without retry
- Dual-path handling for business code and HTTP status: Both backend 401 types must be handled
Backend 401 occurs in two forms:
- HTTP 200 + body.code === 401 (business layer Result.error)
- HTTP 401 status code (Security filter chain direct interception)
The body formats differ (the latter lacks unified code field), requiring separate frontend handling. 403 handling strictly distinguishes from 401: only redirects to the 403 page (router.push(’/403’)), no logout triggered - as 403 indicates insufficient permissions rather than authentication failure.
Markdown Rendering Safety Boundary
Markdown rendering uses markdown-it 14.1.0 but abandons the default commonmark preset in favor of ‘zero’ preset with whitelist enable:
| |
| Issue | Streaming rendering shows randomly oversized headings or bold text |
|---|---|
| Layer 1 cause | Setext syntax: incomplete Markdown (e.g., “###” followed by newline)误识别为 heading |
| Layer 2 cause | Business data containing --- and **被解析为 hr and strong |
Whitelist approach is safer than blacklist: conversation scenarios actually only require basic syntax like paragraph, link, and code; advanced syntax like heading/strong/em/hr are all disabled, ensuring both streaming intermediate states and business data render correctly.
RBAC Frontend Implementation & Knowledge Base Engineering Details
Frontend implements three-layer RBAC control:
- Route meta + navigation guard: Role verification via to.meta.role field, unauthorized redirect to 403 page
- Dynamic menu filtering: GlobalLayout.vue filters sidebar menu by meta.role (USER role invisible to knowledge base management etc.)
- PermissionBtn button directive: Component-level control of material edit/delete button visibility
The author emphasizes all three layers are experience-layer only; backend interfaces must have @PreAuthorize or Security configuration fallback.
Two engineering details for knowledge base management:
- Batch upload sequential processing: Each file requires Tika parsing + chunking + bge-m3 vectorization (20MB PDF takes tens of seconds), concurrency would overwhelm Ollama embedding queue
- Blob manual download: Since download endpoint requires Authorization header, fetch + createObjectURL triggers download and URL.revokeObjectURL releases memory
RAG answer supports follow-up questions requiring historical dialogue context, though the article does not provide specific implementation parameters.
Implementation Recommendations
- For developers: Building mid-to-large backend projects with streaming AI dialogue and permission systems; scenarios requiring JWT token refresh and Markdown rendering
- Consider waiting: If project lacks tool-call streaming dialogue scenario, can delay implementing index-based message location approach; if using newer markdown-it (after 14.1+), verify zero preset compatibility
Final Notes
Frontend engineering challenges lie not in technology itself, but in identifying “silent bugs” that don’t throw exceptions yet genuinely impact user experience. This episode reveals two key learnings - streaming dialogue must use index-based location, and Markdown whitelist beats blacklist - reflecting deep reflection and accurate boundary definition of framework defaults. As AI model capabilities grow, frontend-large model interaction contracts will continuously evolve, yet reliable engineering practices remain the foundation of consistent experience.
