chore: 修复大量导入路径错误

- 迁移 files API 到 shared/api/
- 修复所有 features 中的 @/api/ 导入
- 修复所有 features 中的 @/composables/ 导入
- 更新 useOrderDetail 中的 uploadFile 调用

类型错误:272 → 190 → 135
This commit is contained in:
yml2213
2026-06-04 09:30:13 +08:00
parent e010f2ce7e
commit 956c2eca91
22 changed files with 234 additions and 30 deletions
+152
View File
@@ -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小时
@@ -16,8 +16,8 @@ import {
Warning Warning
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { fetchAdminDashboard, type AdminDashboard } from '@/features/admin/api/adminDashboard' import { fetchAdminDashboard, type AdminDashboard } from '@/features/admin/api/adminDashboard'
import { useAdminTable } from '@/composables/useAdminTable' import { useAdminTable } from '@/features/admin/composables/useAdminTable'
import { useMoney } from '@/composables/useMoney' import { useMoney } from '@/shared/composables/useMoney'
import { disputeStatusLabel, orderStatusLabel } from '@/utils/statusLabels' import { disputeStatusLabel, orderStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time' import { formatDateTime } from '@/utils/time'
@@ -4,8 +4,8 @@ import { ElMessage } from 'element-plus'
import { computed, reactive, ref } from 'vue' import { computed, reactive, ref } from 'vue'
import { fetchAdminListings, type AdminListingPage, type AdminListingQuery, type Listing } from '@/features/listings' import { fetchAdminListings, type AdminListingPage, type AdminListingQuery, type Listing } from '@/features/listings'
import { useAdminTable } from '@/composables/useAdminTable' import { useAdminTable } from '@/features/admin/composables/useAdminTable'
import { useMoney } from '@/composables/useMoney' import { useMoney } from '@/shared/composables/useMoney'
import { import {
assetRegions, assetRegions,
formatEstimatedRentalDuration, formatEstimatedRentalDuration,
@@ -3,7 +3,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue' import { ref } from 'vue'
import { fetchAdminMgrUsers, deleteAdminMgrUser, changeAdminPassword, type AdminMgrUser } from '@/features/admin/api/adminMgr' 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 { formatDateTime } from '@/utils/time'
import AdminUserDialog from './components/AdminUserDialog.vue' import AdminUserDialog from './components/AdminUserDialog.vue'
@@ -2,7 +2,7 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { fetchAdminOrders, type Order } from '@/features/orders' 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 { handoffStatusLabel, orderStatusLabel, settlementStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time' import { formatDateTime } from '@/utils/time'
@@ -3,7 +3,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue' import { ref } from 'vue'
import { fetchRoles, deleteRole, type Role } from '@/features/admin/api/adminRoles' 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 AssignPermissionsDialog from './components/AssignPermissionsDialog.vue'
import RoleDialog from './components/RoleDialog.vue' import RoleDialog from './components/RoleDialog.vue'
@@ -3,7 +3,7 @@ import { ElMessage } from 'element-plus'
import { ref } from 'vue' import { ref } from 'vue'
import { fetchAdminUsers, freezeAdminUser, unfreezeAdminUser, type AdminUserItem } from '@/features/admin/api/adminUsers' 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 { userStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time' import { formatDateTime } from '@/utils/time'
@@ -1,5 +1,5 @@
import { onBeforeUnmount, ref, type Ref } from 'vue' 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' import { getAccessToken, type AuthScope } from '@/utils/authStorage'
export interface SSEMessage { export interface SSEMessage {
@@ -10,10 +10,10 @@ import {
sendChatMessage, sendChatMessage,
type ChatConversation, type ChatConversation,
type ChatMessage, type ChatMessage,
} from '@/api/chats' } from '@/features/chats/api/chats'
import { uploadFile } from '@/shared/api/files' import { uploadFile } from '@/shared/api/files'
import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' 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' import { formatDateMinute } from '@/utils/time'
const currentUserId = Number(localStorage.getItem('user_id') || 0) const currentUserId = Number(localStorage.getItem('user_id') || 0)
@@ -3,8 +3,8 @@ import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { ChatDotRound, Refresh, Tickets } from '@element-plus/icons-vue' import { ChatDotRound, Refresh, Tickets } from '@element-plus/icons-vue'
import { fetchChats, type ChatConversation } from '@/api/chats' import { fetchChats, type ChatConversation } from '@/features/chats/api/chats'
import { useChatSSE, type ChatEvent } from '@/composables/useChatSSE' import { useChatSSE, type ChatEvent } from '@/features/chats/composables/useChatSSE'
import { formatDateMinute } from '@/utils/time' import { formatDateMinute } from '@/utils/time'
const router = useRouter() const router = useRouter()
@@ -9,10 +9,10 @@ import {
sendChatMessage, sendChatMessage,
type ChatConversation, type ChatConversation,
type ChatMessage, type ChatMessage,
} from '@/api/chats' } from '@/features/chats/api/chats'
import { uploadFile } from '@/shared/api/files' import { uploadFile } from '@/shared/api/files'
import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue' 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' import { formatDateMinute } from '@/utils/time'
const currentUserId = Number(localStorage.getItem('user_id') || 0) const currentUserId = Number(localStorage.getItem('user_id') || 0)
@@ -3,7 +3,7 @@ import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { showToast } from 'vant' import { showToast } from 'vant'
import MobileBottomNav from '@/components/MobileBottomNav.vue' 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' import { formatDateMinute } from '@/utils/time'
const router = useRouter() const router = useRouter()
@@ -9,9 +9,9 @@ import {
moneyRangeOptions, moneyRangeOptions,
totalRangeOptions, totalRangeOptions,
fireLevelRangeOptions, fireLevelRangeOptions,
} from '@/composables/home/useFilterOptions' } from '@/features/listings/composables/useFilterOptions'
import type { ListingPublishOptions } from '@/features/listings/api/listingOptions' 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 { interface Props {
filters: HomeFilters filters: HomeFilters
@@ -2,7 +2,7 @@
import { ArrowDown } from '@element-plus/icons-vue' import { ArrowDown } from '@element-plus/icons-vue'
import { ElInputNumber, ElPopover } from 'element-plus' import { ElInputNumber, ElPopover } from 'element-plus'
import { computed } from 'vue' import { computed } from 'vue'
import { rangeLabel, isRangeSelected } from '@/composables/home/useFilterOptions' import { rangeLabel, isRangeSelected } from '@/features/listings/composables/useFilterOptions'
interface RangeOption { interface RangeOption {
label: string label: string
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest' import { describe, it, expect } from 'vitest'
import { rangeLabel, isRangeSelected } from '@/composables/home/useFilterOptions' import { rangeLabel, isRangeSelected } from '@/features/listings/composables/useFilterOptions'
describe('useFilterOptions', () => { describe('useFilterOptions', () => {
describe('rangeLabel', () => { describe('rangeLabel', () => {
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest' import { describe, it, expect } from 'vitest'
import { useHomeFilters } from '@/composables/home/useHomeFilters' import { useHomeFilters } from '@/features/listings/composables/useHomeFilters'
import { ref } from 'vue' import { ref } from 'vue'
import { emptyListingPublishOptions } from '@/api/listingOptions' import { emptyListingPublishOptions } from '@/api/listingOptions'
@@ -6,13 +6,13 @@ import {
defaultHomeBanners, defaultHomeBanners,
fetchMobileHomeConfig, fetchMobileHomeConfig,
type HomeBannerSlide, type HomeBannerSlide,
} from '@/api/homeConfig' } from '@/features/listings/api/homeConfig'
import { import {
emptyListingPublishOptions, emptyListingPublishOptions,
type ListingPublishOptions, type ListingPublishOptions,
} from '@/features/listings/api/listingOptions' } from '@/features/listings/api/listingOptions'
import { useHomeFilters } from '@/composables/home/useHomeFilters' import { useHomeFilters } from '@/features/listings/composables/useHomeFilters'
import { useListingQuery } from '@/composables/home/useListingQuery' import { useListingQuery } from '@/features/listings/composables/useListingQuery'
import HomeAnnouncement from './components/HomeAnnouncement.vue' import HomeAnnouncement from './components/HomeAnnouncement.vue'
import HomeBanner from './components/HomeBanner.vue' import HomeBanner from './components/HomeBanner.vue'
import HomeStats from './components/HomeStats.vue' import HomeStats from './components/HomeStats.vue'
@@ -9,9 +9,9 @@ import {
submitHandoff, submitHandoff,
confirmReceive, confirmReceive,
} from '../api/orders' } from '../api/orders'
import { createDispute } from '@/api/disputes' import { createDispute } from '@/features/disputes/api/disputes'
import { uploadFile } from '@/api/files' import { uploadFile } from '@/shared/api/files'
import { fetchOrderChat } from '@/api/chats' import { fetchOrderChat } from '@/features/chats/api/chats'
import { useSessionStore } from '@/stores/session' import { useSessionStore } from '@/stores/session'
import { usePaymentPolling } from './usePaymentPolling' import { usePaymentPolling } from './usePaymentPolling'
import { useSettlement } from './useSettlement' import { useSettlement } from './useSettlement'
@@ -147,7 +147,7 @@ export function useOrderDetail() {
async function handleUploadEvidence(file: File) { async function handleUploadEvidence(file: File) {
uploadingEvidence.value = true uploadingEvidence.value = true
try { try {
const result = await uploadFile(file) const result = await uploadFile(file, 'evidence')
return result.url return result.url
} finally { } finally {
uploadingEvidence.value = false uploadingEvidence.value = false
@@ -5,7 +5,7 @@ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import QRCode from 'qrcode' import QRCode from 'qrcode'
import { fetchOrderChat } from '@/api/chats' import { fetchOrderChat } from '@/features/chats/api/chats'
import { createDispute } from '@/features/disputes' import { createDispute } from '@/features/disputes'
import { uploadFile } from '@/shared/api/files' import { uploadFile } from '@/shared/api/files'
import { import {
@@ -1,7 +1,7 @@
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue' import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { fetchFileBlobByURL, uploadFile } from '@/api/files' import { fetchFileBlobByURL, uploadFile } from '@/shared/api/files'
import { import {
emptyListingPublishOptions, emptyListingPublishOptions,
emptyListingSalePriceConfig, emptyListingSalePriceConfig,
+51
View File
@@ -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<ApiResponse<UploadedFile>>('/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<ApiResponse<UploadedFile>>('/admin/files/upload', form, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return data.data
}
export async function fetchAdminFileBlob(key: string) {
const { data } = await apiClient.get<Blob>('/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<Blob>(apiPath, {
responseType: 'blob',
})
return data
}
+1
View File
@@ -1,2 +1,3 @@
// API 基础设施 // API 基础设施
export * from './client' export * from './client'
export * from './files'