From 956c2eca91e27d19ba80a47b62f4266e0c6c71ae Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 4 Jun 2026 09:30:13 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E4=BF=AE=E5=A4=8D=E5=A4=A7=E9=87=8F?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E8=B7=AF=E5=BE=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 迁移 files API 到 shared/api/ - 修复所有 features 中的 @/api/ 导入 - 修复所有 features 中的 @/composables/ 导入 - 更新 useOrderDetail 中的 uploadFile 调用 类型错误:272 → 190 → 135 --- docs/CLEANUP_PROGRESS.md | 152 ++++++++++++++++++ .../admin/views/AdminDashboardView.vue | 4 +- .../admin/views/AdminListingsView.vue | 4 +- .../admin/views/AdminMgrUsersView.vue | 2 +- .../features/admin/views/AdminOrdersView.vue | 2 +- .../features/admin/views/AdminRolesView.vue | 2 +- .../features/admin/views/AdminUsersView.vue | 2 +- .../features/chats/composables/useChatSSE.ts | 2 +- .../src/features/chats/views/ChatView.vue | 4 +- .../src/features/chats/views/MessagesView.vue | 4 +- .../features/chats/views/MobileChatView.vue | 4 +- .../chats/views/MobileMessagesView.vue | 2 +- .../listings/components/HomeFilters.vue | 4 +- .../listings/components/RangeFilter.vue | 2 +- .../__tests__/useFilterOptions.spec.ts | 2 +- .../__tests__/useHomeFilters.spec.ts | 2 +- .../src/features/listings/views/HomeView.vue | 6 +- .../orders/composables/useOrderDetail.ts | 8 +- .../features/orders/views/OrderDetailView.vue | 2 +- .../seller/composables/usePublishForm.ts | 2 +- frontend/src/shared/api/files.ts | 51 ++++++ frontend/src/shared/api/index.ts | 1 + 22 files changed, 234 insertions(+), 30 deletions(-) create mode 100644 docs/CLEANUP_PROGRESS.md create mode 100644 frontend/src/shared/api/files.ts diff --git a/docs/CLEANUP_PROGRESS.md b/docs/CLEANUP_PROGRESS.md new file mode 100644 index 0000000..4a40ef0 --- /dev/null +++ b/docs/CLEANUP_PROGRESS.md @@ -0,0 +1,152 @@ +# Features 架构迁移 - 清理工作总结 + +**完成时间:** 2026-06-04 +**分支:** refactor/features-architecture +**当前状态:** 清理进行中 + +--- + +## ✅ 已完成的清理工作 + +### 1. 路由配置更新 ✅ +**提交:** 3d5db9d + +更新了所有路由文件,将 `@/views/` 改为 `@/features/*/views/`: +- accountRoutes.ts - 10个路由 +- publicRoutes.ts - 2个路由 +- mobileRoutes.ts - 13个路由 +- sellerRoutes.ts - 4个路由 +- adminRoutes.ts - 15个路由 +- router/index.ts - 更新 authStorage 导入 + +**总计:** 44个路由已更新 + +--- + +### 2. 删除旧文件 ✅ +**提交:** 5e87858 + +删除了旧的目录结构: +- ❌ api/ - 21个文件 +- ❌ views/ - 57个文件 +- ❌ composables/ - 33个文件 + +**总计:** 111个旧文件已删除,33,317行代码移除 + +--- + +### 3. 批量更新导入路径 ✅ +**提交:** 待提交 + +更新了所有 features 中的导入路径: +- 8个 admin API 文件 +- 所有 admin 组件和视图 +- 所有 features 中的 Vue 文件 +- ChatAttachmentImage 组件 + +--- + +## 📊 清理进度 + +| 任务 | 状态 | 说明 | +|------|------|------| +| 更新路由配置 | ✅ | 44个路由全部更新 | +| 删除旧文件 | ✅ | 111个文件已删除 | +| 更新导入路径 | 🔄 | 大部分完成,剩余~190个错误 | +| 类型检查 | 🔄 | 从272个错误降到190个 | +| 功能测试 | ⏳ | 待完成 | + +--- + +## 🔧 剩余工作 + +### 类型错误修复(~190个) + +主要问题类型: + +#### 1. 缺失的模块 +- `@/shared/api/files` - files API 未迁移到 shared +- `@/api/auth` - 部分文件还在使用旧路径 +- `@/api/wallet` - 部分文件还在使用旧路径 +- `@/api/realname` - 部分文件还在使用旧路径 + +#### 2. Composables 导入 +- `@/composables/useAdminTable` → `@/features/admin/composables/` +- `@/composables/useMoney` → `@/shared/composables/` +- `@/composables/useSmsCountdown` → `@/shared/composables/` + +#### 3. 相对路径问题 +- admin 组件之间的导入需要使用相对路径 +- auth API 内部导入路径 + +#### 4. 类型导出冲突 +- `features/admin/index.ts` 中 AdminRole 重复导出 + +--- + +## 📁 当前目录结构 + +``` +frontend/src/ +├── features/ ✅ 新架构 +│ ├── wallet/ +│ ├── chats/ +│ ├── orders/ +│ ├── listings/ +│ ├── auth/ +│ ├── seller/ +│ ├── disputes/ +│ └── admin/ +├── shared/ ✅ 共享层 +├── components/ ✅ 保留(全局组件) +├── layouts/ ✅ 保留(布局模板) +├── router/ ✅ 已更新 +├── stores/ ✅ 保留(全局状态) +├── styles/ ✅ 保留(全局样式) +├── types/ ✅ 保留(全局类型) +└── utils/ ✅ 保留(工具函数) +``` + +--- + +## Git 提交历史 + +1. **3d5db9d** - chore: 更新路由配置,使用 features 架构路径 +2. **5e87858** - chore: 删除旧的 api/, views/, composables/ 目录 +3. **待提交** - chore: 批量更新导入路径到 features 架构 + +--- + +## ⏭️ 下一步计划 + +### 1. 完成类型错误修复 +- 迁移 files API 到 shared +- 修复所有剩余导入路径 +- 解决类型导出冲突 +- 目标:0个类型错误 + +### 2. 功能测试 +```bash +npm run dev +# 测试各个模块: +# - 首页浏览 +# - 登录/注册 +# - 订单创建 +# - 聊天功能 +# - 管理后台 +``` + +### 3. 更新文档 +- 更新 README.md +- 添加新架构说明 +- 更新开发指南 + +### 4. 创建 PR +- 推送到远程 +- 创建 Pull Request +- 等待团队审核 + +--- + +**当前状态:** 清理工作 70% 完成 +**预计剩余时间:** ~1小时 diff --git a/frontend/src/features/admin/views/AdminDashboardView.vue b/frontend/src/features/admin/views/AdminDashboardView.vue index e38e909..3081673 100644 --- a/frontend/src/features/admin/views/AdminDashboardView.vue +++ b/frontend/src/features/admin/views/AdminDashboardView.vue @@ -16,8 +16,8 @@ import { Warning } from '@element-plus/icons-vue' import { fetchAdminDashboard, type AdminDashboard } from '@/features/admin/api/adminDashboard' -import { useAdminTable } from '@/composables/useAdminTable' -import { useMoney } from '@/composables/useMoney' +import { useAdminTable } from '@/features/admin/composables/useAdminTable' +import { useMoney } from '@/shared/composables/useMoney' import { disputeStatusLabel, orderStatusLabel } from '@/utils/statusLabels' import { formatDateTime } from '@/utils/time' diff --git a/frontend/src/features/admin/views/AdminListingsView.vue b/frontend/src/features/admin/views/AdminListingsView.vue index d121738..0bccd47 100644 --- a/frontend/src/features/admin/views/AdminListingsView.vue +++ b/frontend/src/features/admin/views/AdminListingsView.vue @@ -4,8 +4,8 @@ import { ElMessage } from 'element-plus' import { computed, reactive, ref } from 'vue' import { fetchAdminListings, type AdminListingPage, type AdminListingQuery, type Listing } from '@/features/listings' -import { useAdminTable } from '@/composables/useAdminTable' -import { useMoney } from '@/composables/useMoney' +import { useAdminTable } from '@/features/admin/composables/useAdminTable' +import { useMoney } from '@/shared/composables/useMoney' import { assetRegions, formatEstimatedRentalDuration, diff --git a/frontend/src/features/admin/views/AdminMgrUsersView.vue b/frontend/src/features/admin/views/AdminMgrUsersView.vue index 5254fbc..f3b585f 100644 --- a/frontend/src/features/admin/views/AdminMgrUsersView.vue +++ b/frontend/src/features/admin/views/AdminMgrUsersView.vue @@ -3,7 +3,7 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { ref } from 'vue' import { fetchAdminMgrUsers, deleteAdminMgrUser, changeAdminPassword, type AdminMgrUser } from '@/features/admin/api/adminMgr' -import { useAdminPaginatedTable } from '@/composables/useAdminPaginatedTable' +import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable' import { formatDateTime } from '@/utils/time' import AdminUserDialog from './components/AdminUserDialog.vue' diff --git a/frontend/src/features/admin/views/AdminOrdersView.vue b/frontend/src/features/admin/views/AdminOrdersView.vue index 343bc1a..8c8e9d8 100644 --- a/frontend/src/features/admin/views/AdminOrdersView.vue +++ b/frontend/src/features/admin/views/AdminOrdersView.vue @@ -2,7 +2,7 @@ import { computed, ref } from 'vue' import { fetchAdminOrders, type Order } from '@/features/orders' -import { useAdminTable } from '@/composables/useAdminTable' +import { useAdminTable } from '@/features/admin/composables/useAdminTable' import { handoffStatusLabel, orderStatusLabel, settlementStatusLabel } from '@/utils/statusLabels' import { formatDateTime } from '@/utils/time' diff --git a/frontend/src/features/admin/views/AdminRolesView.vue b/frontend/src/features/admin/views/AdminRolesView.vue index 026525a..5b78eee 100644 --- a/frontend/src/features/admin/views/AdminRolesView.vue +++ b/frontend/src/features/admin/views/AdminRolesView.vue @@ -3,7 +3,7 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { ref } from 'vue' import { fetchRoles, deleteRole, type Role } from '@/features/admin/api/adminRoles' -import { useAdminTable } from '@/composables/useAdminTable' +import { useAdminTable } from '@/features/admin/composables/useAdminTable' import AssignPermissionsDialog from './components/AssignPermissionsDialog.vue' import RoleDialog from './components/RoleDialog.vue' diff --git a/frontend/src/features/admin/views/AdminUsersView.vue b/frontend/src/features/admin/views/AdminUsersView.vue index 9cca0a7..a48189f 100644 --- a/frontend/src/features/admin/views/AdminUsersView.vue +++ b/frontend/src/features/admin/views/AdminUsersView.vue @@ -3,7 +3,7 @@ import { ElMessage } from 'element-plus' import { ref } from 'vue' import { fetchAdminUsers, freezeAdminUser, unfreezeAdminUser, type AdminUserItem } from '@/features/admin/api/adminUsers' -import { useAdminPaginatedTable } from '@/composables/useAdminPaginatedTable' +import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable' import { userStatusLabel } from '@/utils/statusLabels' import { formatDateTime } from '@/utils/time' diff --git a/frontend/src/features/chats/composables/useChatSSE.ts b/frontend/src/features/chats/composables/useChatSSE.ts index d93f5af..ae472a5 100644 --- a/frontend/src/features/chats/composables/useChatSSE.ts +++ b/frontend/src/features/chats/composables/useChatSSE.ts @@ -1,5 +1,5 @@ import { onBeforeUnmount, ref, type Ref } from 'vue' -import { refreshAccessToken } from '@/api/client' +import { refreshAccessToken } from '@/shared/api/client' import { getAccessToken, type AuthScope } from '@/utils/authStorage' export interface SSEMessage { diff --git a/frontend/src/features/chats/views/ChatView.vue b/frontend/src/features/chats/views/ChatView.vue index def443d..63410a1 100644 --- a/frontend/src/features/chats/views/ChatView.vue +++ b/frontend/src/features/chats/views/ChatView.vue @@ -10,10 +10,10 @@ import { sendChatMessage, type ChatConversation, type ChatMessage, -} from '@/api/chats' +} from '@/features/chats/api/chats' import { uploadFile } from '@/shared/api/files' import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' -import { useChatSSE, type ChatEvent } from '@/composables/useChatSSE' +import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { formatDateMinute } from '@/utils/time' const currentUserId = Number(localStorage.getItem('user_id') || 0) diff --git a/frontend/src/features/chats/views/MessagesView.vue b/frontend/src/features/chats/views/MessagesView.vue index 3f8cb44..349bfe7 100644 --- a/frontend/src/features/chats/views/MessagesView.vue +++ b/frontend/src/features/chats/views/MessagesView.vue @@ -3,8 +3,8 @@ import { computed, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { ChatDotRound, Refresh, Tickets } from '@element-plus/icons-vue' -import { fetchChats, type ChatConversation } from '@/api/chats' -import { useChatSSE, type ChatEvent } from '@/composables/useChatSSE' +import { fetchChats, type ChatConversation } from '@/features/chats/api/chats' +import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { formatDateMinute } from '@/utils/time' const router = useRouter() diff --git a/frontend/src/features/chats/views/MobileChatView.vue b/frontend/src/features/chats/views/MobileChatView.vue index 1568070..6d36ba6 100644 --- a/frontend/src/features/chats/views/MobileChatView.vue +++ b/frontend/src/features/chats/views/MobileChatView.vue @@ -9,10 +9,10 @@ import { sendChatMessage, type ChatConversation, type ChatMessage, -} from '@/api/chats' +} from '@/features/chats/api/chats' import { uploadFile } from '@/shared/api/files' import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' -import { useChatSSE, type ChatEvent } from '@/composables/useChatSSE' +import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE' import { formatDateMinute } from '@/utils/time' const currentUserId = Number(localStorage.getItem('user_id') || 0) diff --git a/frontend/src/features/chats/views/MobileMessagesView.vue b/frontend/src/features/chats/views/MobileMessagesView.vue index 53f8e62..d537ac9 100644 --- a/frontend/src/features/chats/views/MobileMessagesView.vue +++ b/frontend/src/features/chats/views/MobileMessagesView.vue @@ -3,7 +3,7 @@ import { computed, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { showToast } from 'vant' import MobileBottomNav from '@/components/MobileBottomNav.vue' -import { fetchChats, type ChatConversation } from '@/api/chats' +import { fetchChats, type ChatConversation } from '@/features/chats/api/chats' import { formatDateMinute } from '@/utils/time' const router = useRouter() diff --git a/frontend/src/features/listings/components/HomeFilters.vue b/frontend/src/features/listings/components/HomeFilters.vue index 53f4708..2f86498 100644 --- a/frontend/src/features/listings/components/HomeFilters.vue +++ b/frontend/src/features/listings/components/HomeFilters.vue @@ -9,9 +9,9 @@ import { moneyRangeOptions, totalRangeOptions, fireLevelRangeOptions, -} from '@/composables/home/useFilterOptions' +} from '@/features/listings/composables/useFilterOptions' import type { ListingPublishOptions } from '@/features/listings/api/listingOptions' -import type { FilterPopoverKey, HomeFilters } from '@/composables/home/useHomeFilters' +import type { FilterPopoverKey, HomeFilters } from '@/features/listings/composables/useHomeFilters' interface Props { filters: HomeFilters diff --git a/frontend/src/features/listings/components/RangeFilter.vue b/frontend/src/features/listings/components/RangeFilter.vue index 1d8db13..635dbcd 100644 --- a/frontend/src/features/listings/components/RangeFilter.vue +++ b/frontend/src/features/listings/components/RangeFilter.vue @@ -2,7 +2,7 @@ import { ArrowDown } from '@element-plus/icons-vue' import { ElInputNumber, ElPopover } from 'element-plus' import { computed } from 'vue' -import { rangeLabel, isRangeSelected } from '@/composables/home/useFilterOptions' +import { rangeLabel, isRangeSelected } from '@/features/listings/composables/useFilterOptions' interface RangeOption { label: string diff --git a/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts b/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts index 0d77d62..35807e7 100644 --- a/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts +++ b/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { rangeLabel, isRangeSelected } from '@/composables/home/useFilterOptions' +import { rangeLabel, isRangeSelected } from '@/features/listings/composables/useFilterOptions' describe('useFilterOptions', () => { describe('rangeLabel', () => { diff --git a/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts b/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts index d784cc8..4762f8e 100644 --- a/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts +++ b/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { useHomeFilters } from '@/composables/home/useHomeFilters' +import { useHomeFilters } from '@/features/listings/composables/useHomeFilters' import { ref } from 'vue' import { emptyListingPublishOptions } from '@/api/listingOptions' diff --git a/frontend/src/features/listings/views/HomeView.vue b/frontend/src/features/listings/views/HomeView.vue index 29179ad..ab5301d 100644 --- a/frontend/src/features/listings/views/HomeView.vue +++ b/frontend/src/features/listings/views/HomeView.vue @@ -6,13 +6,13 @@ import { defaultHomeBanners, fetchMobileHomeConfig, type HomeBannerSlide, -} from '@/api/homeConfig' +} from '@/features/listings/api/homeConfig' import { emptyListingPublishOptions, type ListingPublishOptions, } from '@/features/listings/api/listingOptions' -import { useHomeFilters } from '@/composables/home/useHomeFilters' -import { useListingQuery } from '@/composables/home/useListingQuery' +import { useHomeFilters } from '@/features/listings/composables/useHomeFilters' +import { useListingQuery } from '@/features/listings/composables/useListingQuery' import HomeAnnouncement from './components/HomeAnnouncement.vue' import HomeBanner from './components/HomeBanner.vue' import HomeStats from './components/HomeStats.vue' diff --git a/frontend/src/features/orders/composables/useOrderDetail.ts b/frontend/src/features/orders/composables/useOrderDetail.ts index 44045ad..ff50437 100644 --- a/frontend/src/features/orders/composables/useOrderDetail.ts +++ b/frontend/src/features/orders/composables/useOrderDetail.ts @@ -9,9 +9,9 @@ import { submitHandoff, confirmReceive, } from '../api/orders' -import { createDispute } from '@/api/disputes' -import { uploadFile } from '@/api/files' -import { fetchOrderChat } from '@/api/chats' +import { createDispute } from '@/features/disputes/api/disputes' +import { uploadFile } from '@/shared/api/files' +import { fetchOrderChat } from '@/features/chats/api/chats' import { useSessionStore } from '@/stores/session' import { usePaymentPolling } from './usePaymentPolling' import { useSettlement } from './useSettlement' @@ -147,7 +147,7 @@ export function useOrderDetail() { async function handleUploadEvidence(file: File) { uploadingEvidence.value = true try { - const result = await uploadFile(file) + const result = await uploadFile(file, 'evidence') return result.url } finally { uploadingEvidence.value = false diff --git a/frontend/src/features/orders/views/OrderDetailView.vue b/frontend/src/features/orders/views/OrderDetailView.vue index 099c02e..d94ad48 100644 --- a/frontend/src/features/orders/views/OrderDetailView.vue +++ b/frontend/src/features/orders/views/OrderDetailView.vue @@ -5,7 +5,7 @@ import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import QRCode from 'qrcode' -import { fetchOrderChat } from '@/api/chats' +import { fetchOrderChat } from '@/features/chats/api/chats' import { createDispute } from '@/features/disputes' import { uploadFile } from '@/shared/api/files' import { diff --git a/frontend/src/features/seller/composables/usePublishForm.ts b/frontend/src/features/seller/composables/usePublishForm.ts index f18690f..da01765 100644 --- a/frontend/src/features/seller/composables/usePublishForm.ts +++ b/frontend/src/features/seller/composables/usePublishForm.ts @@ -1,7 +1,7 @@ import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue' import { useRouter } from 'vue-router' -import { fetchFileBlobByURL, uploadFile } from '@/api/files' +import { fetchFileBlobByURL, uploadFile } from '@/shared/api/files' import { emptyListingPublishOptions, emptyListingSalePriceConfig, diff --git a/frontend/src/shared/api/files.ts b/frontend/src/shared/api/files.ts new file mode 100644 index 0000000..45404dc --- /dev/null +++ b/frontend/src/shared/api/files.ts @@ -0,0 +1,51 @@ +import { apiClient } from './client' +import type { ApiResponse } from '@/shared/types/types' +import { optimizeImageForUpload } from '@/shared/utils/imageUpload' + +export interface UploadedFile { + object_key: string + url: string + thumbnail_url?: string + medium_url?: string + filename: string + content_type: string + size: number +} + +export async function uploadFile(file: File, scene: string) { + const uploadTarget = await optimizeImageForUpload(file, scene) + const form = new FormData() + form.append('file', uploadTarget) + form.append('scene', scene) + const { data } = await apiClient.post>('/files/upload', form, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + return data.data +} + +export async function uploadAdminFile(file: File, scene: string) { + const uploadTarget = await optimizeImageForUpload(file, scene) + const form = new FormData() + form.append('file', uploadTarget) + form.append('scene', scene) + const { data } = await apiClient.post>('/admin/files/upload', form, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + return data.data +} + +export async function fetchAdminFileBlob(key: string) { + const { data } = await apiClient.get('/admin/files/object', { + params: { key }, + responseType: 'blob', + }) + return data +} + +export async function fetchFileBlobByURL(fileURL: string) { + const apiPath = fileURL.startsWith('/api/') ? fileURL.slice(4) : fileURL + const { data } = await apiClient.get(apiPath, { + responseType: 'blob', + }) + return data +} diff --git a/frontend/src/shared/api/index.ts b/frontend/src/shared/api/index.ts index 42e7536..ac6a9be 100644 --- a/frontend/src/shared/api/index.ts +++ b/frontend/src/shared/api/index.ts @@ -1,2 +1,3 @@ // API 基础设施 export * from './client' +export * from './files'