feat(frontend): implement P0 optimization baseline
This commit is contained in:
@@ -33,11 +33,11 @@ export function getAdminRole() {
|
|||||||
return role
|
return role
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'operator'
|
return 'support'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasAdminRole(role: 'admin' | 'operator' | 'support') {
|
export function hasAdminRole(role: 'admin' | 'operator' | 'support') {
|
||||||
if (!getAdminToken()) {
|
if (!hasAdminSession()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,5 +74,26 @@ export function clearAdminSession() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function hasAdminSession() {
|
export function hasAdminSession() {
|
||||||
return Boolean(getAdminToken())
|
const token = getAdminToken()
|
||||||
|
const expiresAt = getAdminTokenExpiresAt()
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExpired(expiresAt)) {
|
||||||
|
clearAdminSession()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExpired(expiresAt: string) {
|
||||||
|
if (!expiresAt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiresAtMs = Date.parse(expiresAt)
|
||||||
|
return Number.isFinite(expiresAtMs) && expiresAtMs <= Date.now()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const pagination = ref<AdminPagination>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const canReplay = hasAdminRole('admin')
|
const canReplay = hasAdminRole('admin')
|
||||||
|
let latestRequestId = 0
|
||||||
|
|
||||||
const summary = computed(() => ({
|
const summary = computed(() => ({
|
||||||
processedCount: items.value.filter((item) => item.processed).length,
|
processedCount: items.value.filter((item) => item.processed).length,
|
||||||
@@ -38,6 +39,7 @@ const summary = computed(() => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
async function loadEvents(page = pagination.value.page) {
|
async function loadEvents(page = pagination.value.page) {
|
||||||
|
const requestId = ++latestRequestId
|
||||||
loading.value = true
|
loading.value = true
|
||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
@@ -54,14 +56,25 @@ async function loadEvents(page = pagination.value.page) {
|
|||||||
dateFrom: dateFrom.value.trim(),
|
dateFrom: dateFrom.value.trim(),
|
||||||
dateTo: dateTo.value.trim(),
|
dateTo: dateTo.value.trim(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (requestId !== latestRequestId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
items.value = response.data.items
|
items.value = response.data.items
|
||||||
pagination.value = response.data.pagination
|
pagination.value = response.data.pagination
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (requestId !== latestRequestId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
errorMessage.value = error instanceof Error ? error.message : '读取 webhook 列表失败'
|
errorMessage.value = error instanceof Error ? error.message : '读取 webhook 列表失败'
|
||||||
} finally {
|
} finally {
|
||||||
|
if (requestId === latestRequestId) {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submitReplay(item: AdminWebhookEventListItem) {
|
async function submitReplay(item: AdminWebhookEventListItem) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
|||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), '')
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
|
const allowedHosts = parseAllowedHosts(env.VITE_ALLOWED_HOSTS)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -28,11 +29,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
// 👇 加入这行,允许通过 IP 和网络访问
|
host: env.VITE_DEV_HOST === 'true',
|
||||||
host: true,
|
allowedHosts: env.VITE_ALLOW_ALL_HOSTS === 'true' ? true : allowedHosts,
|
||||||
// 👇 加入这行,允许所有外部域名(包括 Cloudflare 分配的域名)访问
|
|
||||||
allowedHosts: true,
|
|
||||||
|
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: env.VITE_API_TARGET || 'http://localhost:3000',
|
target: env.VITE_API_TARGET || 'http://localhost:3000',
|
||||||
@@ -42,3 +40,12 @@ export default defineConfig(({ mode }) => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function parseAllowedHosts(value?: string) {
|
||||||
|
const hosts = value
|
||||||
|
?.split(',')
|
||||||
|
.map((host) => host.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
return hosts && hosts.length > 0 ? hosts : ['localhost', '127.0.0.1']
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,524 @@
|
|||||||
|
# apps/frontend 前端优化计划
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
本计划基于当前 `apps/frontend` 代码现状整理。当前前端使用 Vue 3、Vite、TypeScript、Vue Router、Element Plus,已开启 TypeScript strict 相关检查,路由页面基本采用动态 import,`npm run build` 当前可通过。
|
||||||
|
|
||||||
|
当前主要问题不是功能不可用,而是后台管理页面增多后,列表页重复逻辑、请求稳定性、登录态安全、构建产物治理和工程规范需要系统化优化。
|
||||||
|
|
||||||
|
## 优化目标
|
||||||
|
|
||||||
|
- 提升后台页面的可维护性和复用性。
|
||||||
|
- 减少列表页请求竞态和状态错乱风险。
|
||||||
|
- 强化管理端登录态和开发服务器安全边界。
|
||||||
|
- 规范构建产物和依赖拆包,提升缓存效果。
|
||||||
|
- 补齐 lint / format / CI 质量门禁。
|
||||||
|
- 为后续后台功能扩展降低开发成本。
|
||||||
|
|
||||||
|
## 当前观察
|
||||||
|
|
||||||
|
### 已确认现状
|
||||||
|
|
||||||
|
- `apps/frontend/package.json` 当前脚本包含 `dev`、`build`、`preview`、`typecheck`。
|
||||||
|
- `apps/frontend/tsconfig.app.json` 已开启 `strict`、`noUnusedLocals`、`noUnusedParameters` 等检查。
|
||||||
|
- `apps/frontend/src/router/index.ts` 已使用动态 import 拆分页面。
|
||||||
|
- `apps/frontend/src/lib/http.ts` 统一封装 axios 请求和后台 401 处理。
|
||||||
|
- 后台列表页普遍存在相同的 loading、error、pagination、load、reset 逻辑。
|
||||||
|
- `apps/frontend/vite.config.ts` 当前 `server.host` 与 `server.allowedHosts` 配置偏开放。
|
||||||
|
- 管理端 token 当前保存在 `localStorage`,`hasAdminSession()` 主要检查 token 是否存在。
|
||||||
|
|
||||||
|
### 构建验证
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/frontend
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:构建成功。
|
||||||
|
|
||||||
|
## 优先级 P0:安全与稳定性
|
||||||
|
|
||||||
|
### 1. 收紧 Vite dev server 访问控制
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `apps/frontend/vite.config.ts`
|
||||||
|
|
||||||
|
当前配置:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
allowedHosts: true,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
风险:
|
||||||
|
|
||||||
|
- `allowedHosts: true` 会允许任意 Host 访问开发服务。
|
||||||
|
- 如果配合公网隧道或局域网暴露,开发环境边界过宽。
|
||||||
|
|
||||||
|
建议方案:
|
||||||
|
|
||||||
|
- 默认只允许本地访问。
|
||||||
|
- 需要公网调试时通过环境变量显式开启。
|
||||||
|
- 支持 Host 白名单,例如 `VITE_ALLOWED_HOSTS`。
|
||||||
|
|
||||||
|
建议配置方向:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const allowedHosts = env.VITE_ALLOWED_HOSTS
|
||||||
|
? env.VITE_ALLOWED_HOSTS.split(',').map((host) => host.trim()).filter(Boolean)
|
||||||
|
: ['localhost', '127.0.0.1']
|
||||||
|
|
||||||
|
server: {
|
||||||
|
host: env.VITE_DEV_HOST === 'true',
|
||||||
|
allowedHosts: env.VITE_ALLOW_ALL_HOSTS === 'true' ? true : allowedHosts,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 本地 `npm run dev` 正常。
|
||||||
|
- 未设置环境变量时不默认允许任意 Host。
|
||||||
|
- 需要公网调试时可以通过环境变量开启。
|
||||||
|
|
||||||
|
### 2. 完善管理端登录态过期判断
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `apps/frontend/src/utils/admin-auth.ts`
|
||||||
|
- `apps/frontend/src/router/index.ts`
|
||||||
|
- `apps/frontend/src/lib/http.ts`
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
- `hasAdminSession()` 只检查 token 是否存在。
|
||||||
|
- `expiresAt` 已存储但前端路由守卫未主动判断是否过期。
|
||||||
|
- `getAdminRole()` 在本地 role 缺失时默认返回 `operator`,权限默认值偏宽。
|
||||||
|
|
||||||
|
建议方案:
|
||||||
|
|
||||||
|
- `hasAdminSession()` 校验 token 和 expiresAt。
|
||||||
|
- token 过期时主动 `clearAdminSession()`。
|
||||||
|
- `getAdminRole()` 默认值调整为最低权限,或返回空角色并让调用方处理。
|
||||||
|
- 保持后端接口鉴权作为最终安全边界,前端仅用于体验优化。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 过期 token 刷新页面会跳转登录页。
|
||||||
|
- role 缺失时不会默认获得 operator 权限。
|
||||||
|
- 401 且错误码为认证失效时仍会清理会话。
|
||||||
|
|
||||||
|
### 3. 列表请求增加竞态保护
|
||||||
|
|
||||||
|
涉及页面:
|
||||||
|
|
||||||
|
- `apps/frontend/src/views/admin/webhook-events/AdminWebhookEventsView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/orders/AdminOrdersView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/tasks/AdminTasksView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/message-deliveries/AdminMessageDeliveriesView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/users/AdminUsersView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/audit-logs/AdminAuditLogsView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/inventory/AdminInventoryView.vue`
|
||||||
|
|
||||||
|
当前风险:
|
||||||
|
|
||||||
|
- 用户连续查询、翻页或回车搜索时,旧请求可能晚于新请求返回并覆盖新结果。
|
||||||
|
|
||||||
|
建议方案:
|
||||||
|
|
||||||
|
- 短期使用 `requestId` 忽略过期响应。
|
||||||
|
- 中期在 HTTP 层或列表 composable 中支持 `AbortController`。
|
||||||
|
|
||||||
|
示例方向:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
let latestRequestId = 0
|
||||||
|
|
||||||
|
async function loadPage(page = pagination.value.page) {
|
||||||
|
const requestId = ++latestRequestId
|
||||||
|
loading.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetchList(page)
|
||||||
|
if (requestId !== latestRequestId) return
|
||||||
|
|
||||||
|
items.value = response.data.items
|
||||||
|
pagination.value = response.data.pagination
|
||||||
|
} catch (error) {
|
||||||
|
if (requestId !== latestRequestId) return
|
||||||
|
errorMessage.value = error instanceof Error ? error.message : '读取列表失败'
|
||||||
|
} finally {
|
||||||
|
if (requestId === latestRequestId) {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 快速连续点击查询或分页时,页面最终展示最后一次请求结果。
|
||||||
|
- loading 状态不会被旧请求提前关闭。
|
||||||
|
|
||||||
|
## 优先级 P1:后台列表页复用与体验
|
||||||
|
|
||||||
|
### 4. 抽取通用 `useAdminListPage`
|
||||||
|
|
||||||
|
建议新增:
|
||||||
|
|
||||||
|
- `apps/frontend/src/composables/useAdminListPage.ts`
|
||||||
|
|
||||||
|
目标统一处理:
|
||||||
|
|
||||||
|
- `loading`
|
||||||
|
- `errorMessage`
|
||||||
|
- `items`
|
||||||
|
- `pagination`
|
||||||
|
- `loadPage`
|
||||||
|
- `resetPageAndLoad`
|
||||||
|
- 请求竞态保护
|
||||||
|
- 默认错误提示
|
||||||
|
- 默认 `pageSize`
|
||||||
|
|
||||||
|
适合改造页面:
|
||||||
|
|
||||||
|
- Webhook 日志
|
||||||
|
- 订单列表
|
||||||
|
- 任务列表
|
||||||
|
- 消息发送记录
|
||||||
|
- 审计日志
|
||||||
|
- 用户列表
|
||||||
|
- 库存列表
|
||||||
|
|
||||||
|
预期收益:
|
||||||
|
|
||||||
|
- 列表页脚本代码减少约 30% 到 50%。
|
||||||
|
- 分页、查询、错误处理行为统一。
|
||||||
|
- 后续增加 URL query 同步或请求取消时只需改一处。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 至少完成 2 个代表页面改造,例如 Webhook 日志和订单列表。
|
||||||
|
- 改造后 `npm run typecheck` 与 `npm run build` 通过。
|
||||||
|
|
||||||
|
### 5. 筛选条件同步到 URL Query
|
||||||
|
|
||||||
|
适合页面:
|
||||||
|
|
||||||
|
- `AdminWebhookEventsView.vue`
|
||||||
|
- `AdminOrdersView.vue`
|
||||||
|
- `AdminTasksView.vue`
|
||||||
|
- `AdminMessageDeliveriesView.vue`
|
||||||
|
- `AdminAuditLogsView.vue`
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
- 刷新页面后筛选条件丢失。
|
||||||
|
- 无法复制当前筛选链接给其他管理员。
|
||||||
|
|
||||||
|
建议方案:
|
||||||
|
|
||||||
|
- 页面初始化时从 `route.query` 读取筛选条件和页码。
|
||||||
|
- 查询、重置、分页时使用 `router.replace()` 更新 query。
|
||||||
|
- 空筛选项不写入 URL。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 刷新页面保留筛选条件。
|
||||||
|
- 分享 URL 后能恢复相同列表视图。
|
||||||
|
- 重置筛选会清理相关 query。
|
||||||
|
|
||||||
|
### 6. 统一日期范围组件
|
||||||
|
|
||||||
|
当前观察:
|
||||||
|
|
||||||
|
- 订单页已经使用 `type="daterange"`。
|
||||||
|
- Webhook、审计日志等页面仍使用两个独立日期选择器。
|
||||||
|
|
||||||
|
建议方案:
|
||||||
|
|
||||||
|
- 日志类、列表类页面统一使用 `el-date-picker type="daterange"`。
|
||||||
|
- 内部统一映射为 `dateFrom` 和 `dateTo` API 参数。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- Webhook 日志、审计日志、消息发送记录日期筛选交互一致。
|
||||||
|
- API 参数保持兼容。
|
||||||
|
|
||||||
|
### 7. 统一筛选触发策略
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
- 输入框通常支持 Enter 查询。
|
||||||
|
- select/date 改变后多数页面不自动查询。
|
||||||
|
- 不同列表页交互不完全一致。
|
||||||
|
|
||||||
|
建议二选一:
|
||||||
|
|
||||||
|
- 方案 A:所有筛选项变更后不自动查询,统一点击“查询”。
|
||||||
|
- 方案 B:select/date 变更自动查询,文本输入 Enter 或防抖查询。
|
||||||
|
|
||||||
|
建议优先方案 A,原因是后台列表数据量可能较大,可减少无意请求。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 后台列表页筛选交互一致。
|
||||||
|
- 查询按钮与重置按钮行为一致。
|
||||||
|
|
||||||
|
## 优先级 P2:工程规范与类型质量
|
||||||
|
|
||||||
|
### 8. 增加 ESLint / Prettier
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `apps/frontend/package.json`
|
||||||
|
- `apps/frontend/eslint.config.*`
|
||||||
|
- `apps/frontend/.prettierrc` 或等价配置
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
- package scripts 未提供 lint / format。
|
||||||
|
- 仅依赖 TypeScript 编译检查,无法覆盖 Vue 模板规范、Promise 处理、风格一致性等问题。
|
||||||
|
|
||||||
|
建议增加脚本:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint . --ext .ts,.vue",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check ."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
建议依赖:
|
||||||
|
|
||||||
|
- `eslint`
|
||||||
|
- `@eslint/js`
|
||||||
|
- `typescript-eslint`
|
||||||
|
- `eslint-plugin-vue`
|
||||||
|
- `prettier`
|
||||||
|
- `eslint-config-prettier`
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- `npm run lint` 可执行。
|
||||||
|
- `npm run format:check` 可执行。
|
||||||
|
- CI 至少执行 `typecheck`、`lint`、`build`。
|
||||||
|
|
||||||
|
### 9. 优化 HTTP 封装类型
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `apps/frontend/src/lib/http.ts`
|
||||||
|
- `apps/frontend/src/services/**/*`
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
return http.get<ApiEnvelope<T>>(url, { params }) as unknown as Promise<ApiEnvelope<T>>
|
||||||
|
```
|
||||||
|
|
||||||
|
由于 axios interceptor 返回 `response.data`,代码使用 `as unknown as` 消除类型差异。
|
||||||
|
|
||||||
|
建议方案:
|
||||||
|
|
||||||
|
- 封装明确的 `request<T>()`。
|
||||||
|
- 统一返回 `Promise<ApiEnvelope<T>>`。
|
||||||
|
- blob 请求单独封装。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 减少双重类型断言。
|
||||||
|
- 服务层返回值更可信。
|
||||||
|
- 错误类型可以逐步标准化。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- `apiGet`、`apiPost`、`apiDelete` 不再依赖 `as unknown as`。
|
||||||
|
- 现有 services 类型不退化。
|
||||||
|
- `npm run typecheck` 通过。
|
||||||
|
|
||||||
|
## 优先级 P3:构建产物与性能
|
||||||
|
|
||||||
|
### 10. 分析并治理 bundle
|
||||||
|
|
||||||
|
当前构建观察:
|
||||||
|
|
||||||
|
- 构建成功。
|
||||||
|
- 输出中出现较多 `css-*.js` 小 chunk。
|
||||||
|
- Element Plus 相关依赖和样式可能存在进一步优化空间。
|
||||||
|
|
||||||
|
建议步骤:
|
||||||
|
|
||||||
|
1. 引入 bundle 分析工具。
|
||||||
|
2. 查看 Vue、Element Plus、lodash、qrcode、dayjs 等依赖占比。
|
||||||
|
3. 评估是否需要配置 `manualChunks`。
|
||||||
|
4. 检查 Element Plus 样式自动导入是否导致过多碎片 chunk。
|
||||||
|
|
||||||
|
可选工具:
|
||||||
|
|
||||||
|
- `rollup-plugin-visualizer`
|
||||||
|
- Vite 官方构建输出分析
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 输出一份主要 chunk 占比结论。
|
||||||
|
- 明确是否需要手动拆分 vendor。
|
||||||
|
- 优化后首屏关键 chunk 不变大。
|
||||||
|
|
||||||
|
### 11. 稳定 vendor chunk 拆分
|
||||||
|
|
||||||
|
建议方向:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'vue-vendor': ['vue', 'vue-router'],
|
||||||
|
'element-plus': ['element-plus'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意事项:
|
||||||
|
|
||||||
|
- 不建议盲目拆太细。
|
||||||
|
- 需要结合 bundle 分析结果决定。
|
||||||
|
- 后台页面较多时,可以考虑 admin 公共模块分组。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 构建产物命名更稳定。
|
||||||
|
- 主要 vendor 包可长期缓存。
|
||||||
|
- 页面懒加载不被破坏。
|
||||||
|
|
||||||
|
## 优先级 P4:大型页面拆分
|
||||||
|
|
||||||
|
### 12. 拆分超大 Vue 文件
|
||||||
|
|
||||||
|
当前行数较大的文件包括:
|
||||||
|
|
||||||
|
- `apps/frontend/src/views/admin/fulfillment/AdminKuaishouCloudFulfillmentView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/tasks/AdminTaskDetailView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/fulfillment/AdminFulfillmentBindingsView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/inventory/AdminInventoryView.vue`
|
||||||
|
- `apps/frontend/src/views/admin/platform-shops/AdminPlatformShopsView.vue`
|
||||||
|
|
||||||
|
建议拆分维度:
|
||||||
|
|
||||||
|
- 筛选区组件
|
||||||
|
- 表格区组件
|
||||||
|
- 表单区组件
|
||||||
|
- 弹窗组件
|
||||||
|
- 业务 composable
|
||||||
|
- 数据转换函数
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 单个 Vue 文件尽量控制在 300 到 500 行以内。
|
||||||
|
- 业务状态集中在 composable 或页面容器中。
|
||||||
|
- 展示组件保持 props / emits 清晰。
|
||||||
|
|
||||||
|
## 分阶段执行计划
|
||||||
|
|
||||||
|
### 阶段一:安全与稳定性基线
|
||||||
|
|
||||||
|
范围:
|
||||||
|
|
||||||
|
- 收紧 `vite.config.ts` dev server 配置。
|
||||||
|
- `hasAdminSession()` 增加过期判断。
|
||||||
|
- 给 Webhook 列表增加请求竞态保护。
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/frontend
|
||||||
|
npm run typecheck
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 阶段二:列表页抽象
|
||||||
|
|
||||||
|
范围:
|
||||||
|
|
||||||
|
- 新增 `useAdminListPage`。
|
||||||
|
- 先改造 Webhook 日志和订单列表。
|
||||||
|
- 确认抽象合理后推广到任务、消息、用户、审计日志。
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
- 手动检查查询、重置、分页、错误提示。
|
||||||
|
- `npm run typecheck`
|
||||||
|
- `npm run build`
|
||||||
|
|
||||||
|
### 阶段三:筛选体验统一
|
||||||
|
|
||||||
|
范围:
|
||||||
|
|
||||||
|
- 日期范围统一为 `daterange`。
|
||||||
|
- 列表页 URL query 同步。
|
||||||
|
- 统一查询触发策略。
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
- 刷新页面保留筛选。
|
||||||
|
- 分享链接可恢复筛选。
|
||||||
|
- 重置筛选清理 URL。
|
||||||
|
|
||||||
|
### 阶段四:工程质量门禁
|
||||||
|
|
||||||
|
范围:
|
||||||
|
|
||||||
|
- 增加 ESLint / Prettier。
|
||||||
|
- 增加 lint / format:check 脚本。
|
||||||
|
- CI 或本地验证流程加入 typecheck、lint、build。
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/frontend
|
||||||
|
npm run typecheck
|
||||||
|
npm run lint
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 阶段五:构建与大型页面治理
|
||||||
|
|
||||||
|
范围:
|
||||||
|
|
||||||
|
- Bundle 分析。
|
||||||
|
- 评估并配置 manualChunks。
|
||||||
|
- 拆分 900 行以上的大型 Vue 页面。
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
- 构建成功。
|
||||||
|
- 产物 chunk 数量和体积有明确对比。
|
||||||
|
- 页面功能无回归。
|
||||||
|
|
||||||
|
## 风险与注意事项
|
||||||
|
|
||||||
|
- 列表页抽象不要一次性改造所有页面,建议先选 2 个页面试点。
|
||||||
|
- URL query 同步要避免 watcher 循环触发请求。
|
||||||
|
- token 存储机制如果改为 Cookie,需要后端配合,不建议前端单独推进。
|
||||||
|
- manualChunks 需要基于分析结果调整,避免拆包过度导致请求数过多。
|
||||||
|
- ESLint 首次接入可能暴露大量历史问题,建议先设置合理规则,再逐步收紧。
|
||||||
|
|
||||||
|
## 推荐近期落地清单
|
||||||
|
|
||||||
|
1. 修改 `vite.config.ts`,收紧 `allowedHosts`。
|
||||||
|
2. 修改 `admin-auth.ts`,增加 token 过期判断。
|
||||||
|
3. 在 `AdminWebhookEventsView.vue` 试点请求竞态保护和日期范围统一。
|
||||||
|
4. 抽取 `useAdminListPage`,先改造 Webhook 和订单列表。
|
||||||
|
5. 增加 ESLint / Prettier 基础配置。
|
||||||
|
6. 引入 bundle 分析,确认 Element Plus 和 CSS chunk 优化方向。
|
||||||
Reference in New Issue
Block a user