finyx_data_frontend/docs/Vue迁移完整方案.md

1019 lines
24 KiB
Markdown
Raw Permalink 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.

# React 到 Vue 完整迁移方案
## 📋 文档说明
本文档提供从 React 技术栈完整迁移到 Vue 3 技术栈的详细方案,包括技术选型、迁移步骤、代码转换示例和测试策略。
---
## 🎯 迁移目标
- **完全移除 React 依赖**
- **使用 Vue 3 Composition API**
- **保持所有现有功能**
- **保持 Tailwind CSS 样式**
- **保持 TypeScript 类型系统**
---
## 🛠️ 技术选型
### 核心框架
- **Vue**: `^3.4.0` (使用 Composition API)
- **TypeScript**: `^5.2.2` (保持不变)
- **Vite**: `^5.0.8` (保持不变,更换插件)
### 状态管理
- **Pinia**: `^2.1.0` (推荐,替代 Context API)
### 路由(可选)
- **Vue Router**: `^4.2.0` (如果需要路由功能)
### UI 库
- **图标库**: `lucide-vue-next`: `^0.344.0`
- **图表库**: `echarts-for-vue`: `^1.2.0``vue-echarts`: `^6.6.0`
### 样式
- **Tailwind CSS**: `^3.3.6` (保持不变)
---
## 📦 依赖包替换清单
### 需要移除的依赖
```json
{
"react": "^18.2.0",
"react-dom": "^18.2.0",
"lucide-react": "^0.344.0",
"@vitejs/plugin-react": "^4.2.1",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5"
}
```
### 需要添加的依赖
```json
{
"vue": "^3.4.0",
"@vitejs/plugin-vue": "^5.0.0",
"vue-tsc": "^1.8.25",
"lucide-vue-next": "^0.344.0",
"pinia": "^2.1.0",
"vue-router": "^4.2.0",
"@vueuse/core": "^10.7.0"
}
```
### 开发依赖
```json
{
"@vitejs/plugin-vue": "^5.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"eslint-plugin-vue": "^9.20.0"
}
```
---
## 🗂️ 迁移阶段规划
### 阶段一项目基础配置1-2 天)
#### 1.1 更新 package.json
```json
{
"name": "finyx-frontend",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx",
"preview": "vite preview",
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"vue": "^3.4.0",
"pinia": "^2.1.0",
"vue-router": "^4.2.0",
"lucide-vue-next": "^0.344.0",
"@vueuse/core": "^10.7.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"eslint-plugin-vue": "^9.20.0",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"tailwindcss": "^3.3.6",
"vue-tsc": "^1.8.25"
}
}
```
#### 1.2 更新 vite.config.ts
```typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: '0.0.0.0',
port: 5173,
strictPort: false,
open: false,
},
preview: {
host: '0.0.0.0',
port: 4173,
},
})
```
#### 1.3 更新 tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
```
#### 1.4 创建 env.d.ts
```typescript
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
```
### 阶段二类型定义迁移1 天)
类型定义文件基本不需要修改,只需确保兼容 Vue。
**src/types/index.ts** - 保持不变
### 阶段三工具组件迁移2-3 天)
#### 3.1 ProgressBar 组件
**React 版本** (`src/components/ProgressBar.tsx`):
```typescript
import React from 'react';
interface ProgressBarProps {
percent: number;
status: string;
}
export const ProgressBar: React.FC<ProgressBarProps> = ({ percent, status }) => {
// ...
};
```
**Vue 版本** (`src/components/ProgressBar.vue`):
```vue
<template>
<div class="w-full bg-slate-200 rounded-full h-2">
<div
:class="colorClass"
class="h-2 rounded-full"
:style="{ width: `${percent}%` }"
></div>
</div>
</template>
<script setup lang="ts">
interface Props {
percent: number;
status: string;
}
const props = defineProps<Props>();
const colorClass = computed(() => {
const colorMap: Record<string, string> = {
risk: 'bg-red-500',
warning: 'bg-amber-500',
review: 'bg-purple-500',
new: 'bg-slate-300',
};
return colorMap[props.status] || 'bg-blue-500';
});
</script>
```
#### 3.2 Toast 组件迁移
**React 版本** (`src/components/Toast.tsx`):
```typescript
export const useToast = () => {
const [toasts, setToasts] = React.useState<Toast[]>([]);
// ...
};
```
**Vue 版本** (`src/components/Toast.vue`):
```vue
<template>
<div v-if="toasts.length > 0" class="fixed top-4 right-4 z-50 flex flex-col gap-2 pointer-events-none">
<div
v-for="toast in toasts"
:key="toast.id"
class="pointer-events-auto"
>
<ToastItem :toast="toast" @close="closeToast" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ToastItem from './ToastItem.vue';
import type { Toast, ToastType } from './types';
const toasts = ref<Toast[]>([]);
const showToast = (message: string, type: ToastType = 'info', duration?: number) => {
const id = Math.random().toString(36).substring(7);
const newToast: Toast = { id, message, type, duration };
toasts.value.push(newToast);
setTimeout(() => {
closeToast(id);
}, duration || 3000);
return id;
};
const closeToast = (id: string) => {
const index = toasts.value.findIndex(t => t.id === id);
if (index > -1) {
toasts.value.splice(index, 1);
}
};
defineExpose({
success: (message: string, duration?: number) => showToast(message, 'success', duration),
error: (message: string, duration?: number) => showToast(message, 'error', duration),
warning: (message: string, duration?: number) => showToast(message, 'warning', duration),
info: (message: string, duration?: number) => showToast(message, 'info', duration),
});
</script>
```
**Toast Context 迁移为 Pinia Store** (`src/stores/toast.ts`):
```typescript
import { defineStore } from 'pinia';
import { ref } from 'vue';
import type { Toast, ToastType } from '@/types';
export const useToastStore = defineStore('toast', () => {
const toasts = ref<Toast[]>([]);
const showToast = (message: string, type: ToastType = 'info', duration?: number) => {
const id = Math.random().toString(36).substring(7);
const newToast: Toast = { id, message, type, duration };
toasts.value.push(newToast);
setTimeout(() => {
closeToast(id);
}, duration || 3000);
return id;
};
const closeToast = (id: string) => {
const index = toasts.value.findIndex(t => t.id === id);
if (index > -1) {
toasts.value.splice(index, 1);
}
};
const success = (message: string, duration?: number) => showToast(message, 'success', duration);
const error = (message: string, duration?: number) => showToast(message, 'error', duration);
const warning = (message: string, duration?: number) => showToast(message, 'warning', duration);
const info = (message: string, duration?: number) => showToast(message, 'info', duration);
return {
toasts,
showToast,
closeToast,
success,
error,
warning,
info,
};
});
```
### 阶段四布局组件迁移2-3 天)
#### 4.1 MainLayout 组件
**React 版本**:
```typescript
export const MainLayout: React.FC<MainLayoutProps> = ({
currentView,
setCurrentView,
// ...
}) => (
<div className="flex h-screen w-full bg-slate-900">
{/* ... */}
</div>
);
```
**Vue 版本** (`src/layouts/MainLayout.vue`):
```vue
<template>
<div class="flex h-screen w-full bg-slate-900 font-sans text-slate-900 overflow-hidden">
<Sidebar
:current-view="currentView"
@update:current-view="setCurrentView"
:is-presentation-mode="isPresentationMode"
/>
<div class="flex-1 flex flex-col bg-slate-50 relative overflow-hidden">
<DashboardView
v-if="currentView === 'dashboard'"
@update:current-view="setCurrentView"
@update:current-step="setCurrentStep"
/>
<ProjectListView
v-if="currentView === 'projects'"
@update:current-view="setCurrentView"
@update:current-step="setCurrentStep"
/>
<EngagementView
v-if="currentView === 'engagement'"
:current-step="currentStep"
@update:current-step="setCurrentStep"
@update:current-view="setCurrentView"
:is-presentation-mode="isPresentationMode"
@update:is-presentation-mode="setIsPresentationMode"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Sidebar } from './Sidebar.vue';
import DashboardView from '@/pages/DashboardView.vue';
import ProjectListView from '@/pages/ProjectListView.vue';
import EngagementView from '@/pages/EngagementView.vue';
import type { ViewMode, Step } from '@/types';
const currentView = ref<ViewMode>('projects');
const currentStep = ref<Step>('setup');
const isPresentationMode = ref(false);
const setCurrentView = (view: ViewMode) => {
currentView.value = view;
};
const setCurrentStep = (step: Step) => {
currentStep.value = step;
};
const setIsPresentationMode = (mode: boolean) => {
isPresentationMode.value = mode;
};
</script>
```
### 阶段五页面组件迁移5-7 天)
#### 5.1 SetupStep 组件示例
**React 版本关键代码**:
```typescript
export const SetupStep: React.FC<SetupStepProps> = ({ setCurrentStep }) => {
const toast = useToast();
const [projectName, setProjectName] = useState('');
const [selectedIndustries, setSelectedIndustries] = useState<string[]>([]);
const [errors, setErrors] = useState({});
useEffect(() => {
// 验证逻辑
}, [projectName, selectedIndustries, touched]);
return (
<div className="p-8">
<input
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
/>
</div>
);
};
```
**Vue 版本** (`src/pages/engagement/SetupStep.vue`):
```vue
<template>
<div class="p-8 h-full flex flex-col overflow-y-auto animate-fade-in">
<div class="text-center mb-8">
<h2 class="text-3xl font-bold text-slate-900">项目初始化配置</h2>
<p class="text-slate-500 mt-2">请填写项目基本信息系统将为您配置数据资产盘点环境</p>
</div>
<div class="max-w-4xl mx-auto w-full space-y-6 mb-8">
<!-- Project Name -->
<div class="bg-white p-6 rounded-xl border border-slate-200 shadow-sm">
<label class="block text-sm font-bold text-slate-700 mb-3">
项目名称 <span class="text-red-500">*</span>
</label>
<input
type="text"
v-model="projectName"
@blur="touched.projectName = true"
:class="[
'w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
errors.projectName ? 'border-red-500' : 'border-slate-300'
]"
placeholder="请输入项目名称"
/>
<p v-if="errors.projectName" class="text-red-500 text-sm mt-1">
{{ errors.projectName }}
</p>
</div>
<!-- Industries Selection -->
<div class="bg-white p-6 rounded-xl border border-slate-200 shadow-sm">
<label class="block text-sm font-bold text-slate-700 mb-3">
所属行业 <span class="text-red-500">*</span>
</label>
<div class="flex flex-wrap gap-2">
<button
v-for="industry in industries"
:key="industry"
@click="toggleIndustry(industry)"
:class="[
'px-4 py-2 rounded-lg border transition-colors',
selectedIndustries.includes(industry)
? 'bg-blue-50 border-blue-500 text-blue-700'
: 'bg-white border-slate-300 text-slate-700 hover:border-blue-300'
]"
>
{{ industry }}
</button>
</div>
<p v-if="errors.industries" class="text-red-500 text-sm mt-2">
{{ errors.industries }}
</p>
</div>
</div>
<!-- Actions -->
<div class="max-w-4xl mx-auto w-full flex justify-end gap-4">
<button
@click="handleNext"
:disabled="!isValid"
class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
下一步
<ArrowRight :size="18" />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ArrowRight } from 'lucide-vue-next';
import { useToastStore } from '@/stores/toast';
import type { Step } from '@/types';
interface Props {
setCurrentStep?: (step: Step) => void;
}
const props = defineProps<Props>();
const toast = useToastStore();
const projectName = ref('');
const companyDescription = ref('');
const owner = ref('');
const selectedIndustries = ref<string[]>([]);
const errors = ref<{
projectName?: string;
companyDescription?: string;
industries?: string;
}>({});
const touched = ref<{
projectName?: boolean;
companyDescription?: boolean;
industries?: boolean;
}>({});
const industries = [
'零售 - 生鲜连锁',
'零售 - 快消品',
'金融 - 商业银行',
// ... 更多行业
];
// 实时验证
watch([projectName, companyDescription, selectedIndustries, touched], () => {
const newErrors: typeof errors.value = {};
if (touched.value.projectName && !projectName.value.trim()) {
newErrors.projectName = '请填写项目名称';
}
if (touched.value.companyDescription && !companyDescription.value.trim()) {
newErrors.companyDescription = '请填写企业及主营业务简介';
}
if (touched.value.industries && selectedIndustries.value.length === 0) {
newErrors.industries = '请至少选择一个所属行业';
}
errors.value = newErrors;
}, { deep: true });
const isValid = computed(() => {
return projectName.value.trim() !== '' &&
companyDescription.value.trim() !== '' &&
selectedIndustries.value.length > 0;
});
const toggleIndustry = (industry: string) => {
const index = selectedIndustries.value.indexOf(industry);
if (index > -1) {
selectedIndustries.value.splice(index, 1);
} else {
selectedIndustries.value.push(industry);
}
touched.value.industries = true;
};
const handleNext = () => {
if (!isValid.value) {
toast.error('请完成所有必填项');
return;
}
toast.success('项目配置已保存');
props.setCurrentStep?.('inventory');
};
</script>
```
#### 5.2 InventoryStep 组件迁移要点
**useMemo 转换**:
```typescript
// React
const sortedData = useMemo(() => {
// 排序逻辑
}, [sortField, sortDirection]);
// Vue
const sortedData = computed(() => {
if (!sortField.value || !sortDirection.value) return inventoryData;
// 排序逻辑
});
```
**useEffect 转换**:
```typescript
// React
useEffect(() => {
if (inventoryMode === 'processing') {
// 处理逻辑
}
}, [inventoryMode]);
// Vue
watch(() => props.inventoryMode, (newMode) => {
if (newMode === 'processing') {
// 处理逻辑
}
}, { immediate: true });
```
### 阶段六主应用入口迁移1 天)
#### 6.1 main.ts 文件
**React 版本** (`src/main.tsx`):
```typescript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
```
**Vue 版本** (`src/main.ts`):
```typescript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import './index.css'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#root')
```
#### 6.2 App.vue
**React 版本** (`src/App.tsx`):
```typescript
function App() {
const [currentView, setCurrentView] = useState<ViewMode>('projects');
// ...
return (
<ToastProvider>
<MainLayout ... />
</ToastProvider>
);
}
```
**Vue 版本** (`src/App.vue`):
```vue
<template>
<MainLayout
:current-view="currentView"
@update:current-view="setCurrentView"
:current-step="currentStep"
@update:current-step="setCurrentStep"
:is-presentation-mode="isPresentationMode"
@update:is-presentation-mode="setIsPresentationMode"
/>
<ToastContainer />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MainLayout from '@/layouts/MainLayout.vue';
import ToastContainer from '@/components/ToastContainer.vue';
import type { ViewMode, Step } from '@/types';
const currentView = ref<ViewMode>('projects');
const currentStep = ref<Step>('setup');
const isPresentationMode = ref(false);
const setCurrentView = (view: ViewMode) => {
currentView.value = view;
};
const setCurrentStep = (step: Step) => {
currentStep.value = step;
};
const setIsPresentationMode = (mode: boolean) => {
isPresentationMode.value = mode;
};
</script>
```
### 阶段七图标库迁移1 天)
#### 7.1 全局替换图标导入
**查找替换规则**:
```typescript
// 从
import { IconName } from 'lucide-react';
<IconName size={20} />
// 到
import { IconName } from 'lucide-vue-next';
<IconName :size="20" />
```
**批量替换脚本** (`scripts/replace-icons.sh`):
```bash
#!/bin/bash
# 替换所有文件中的 lucide-react 导入
find src -type f \( -name "*.vue" -o -name "*.ts" -o -name "*.tsx" \) -exec sed -i 's/lucide-react/lucide-vue-next/g' {} \;
# 替换图标属性 size={} 为 :size=""
find src -type f -name "*.vue" -exec sed -i 's/size={\([^}]*\)}/:size="\1"/g' {} \;
```
### 阶段八测试与验证3-5 天)
#### 8.1 功能测试清单
- [ ] Dashboard 页面渲染正常
- [ ] 项目列表显示正常
- [ ] 项目搜索和筛选功能
- [ ] 项目作业台所有步骤正常
- [ ] 步骤导航功能
- [ ] 表单验证功能
- [ ] Toast 通知功能
- [ ] 演示模式切换
- [ ] 响应式布局
- [ ] 所有交互功能
#### 8.2 性能测试
- [ ] 首屏加载时间
- [ ] 组件渲染性能
- [ ] 大数据量表格性能
- [ ] 内存使用情况
---
## 📝 代码转换对照表
### Hooks 转换对照
| React | Vue 3 |
|-------|-------|
| `useState(value)` | `ref(value)``reactive({})` |
| `useEffect(() => {}, [deps])` | `watch(() => deps, () => {})``watchEffect(() => {})` |
| `useMemo(() => value, [deps])` | `computed(() => value)` |
| `useCallback(() => {}, [deps])` | `computed(() => () => {})` 或直接定义函数 |
| `useContext(Context)` | `inject(key)` 或 Pinia store |
| `useRef(initial)` | `ref(initial)` |
| `useReducer(reducer, initial)` | `ref(initial)` + 自定义函数或 Pinia |
### 事件处理转换
| React | Vue 3 |
|-------|-------|
| `onClick={handler}` | `@click="handler"` |
| `onChange={(e) => {}}` | `@change="handler"``v-model="value"` |
| `onSubmit={handler}` | `@submit.prevent="handler"` |
| `onKeyDown={handler}` | `@keydown="handler"` |
### 条件渲染转换
| React | Vue 3 |
|-------|-------|
| `{condition && <Component />}` | `<Component v-if="condition" />` |
| `{condition ? <A /> : <B />}` | `<A v-if="condition" /><B v-else />` |
| `{items.map(item => <Item key={id} />)}` | `<Item v-for="item in items" :key="id" />` |
### Props 传递转换
| React | Vue 3 |
|-------|-------|
| `<Component prop={value} />` | `<Component :prop="value" />` |
| `<Component onClick={handler} />` | `<Component @click="handler" />` |
| `{...props}` | `v-bind="props"` |
---
## 🔧 配置文件更新清单
### 1. package.json
- [ ] 移除所有 React 相关依赖
- [ ] 添加 Vue 3 相关依赖
- [ ] 更新构建脚本
- [ ] 更新 lint 脚本
### 2. vite.config.ts
- [ ] 替换 `@vitejs/plugin-react``@vitejs/plugin-vue`
- [ ] 添加路径别名配置
- [ ] 保持服务器配置
### 3. tsconfig.json
- [ ] 更新 `jsx` 选项为 `"preserve"`
- [ ] 添加 Vue 文件类型支持
- [ ] 添加路径别名
### 4. 创建 env.d.ts
- [ ] 添加 Vue 模块声明
- [ ] 添加 Vite 客户端类型
### 5. ESLint 配置
创建 `.eslintrc.cjs`:
```javascript
module.exports = {
root: true,
env: {
node: true,
browser: true,
es2021: true,
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
],
parserOptions: {
ecmaVersion: 2021,
},
rules: {
// 自定义规则
},
};
```
---
## 🚀 迁移执行步骤
### 第一步:备份当前代码
```bash
git checkout -b vue-migration
git commit -am "Backup before Vue migration"
```
### 第二步:安装新依赖
```bash
npm uninstall react react-dom lucide-react @vitejs/plugin-react @types/react @types/react-dom
npm install vue@^3.4.0 pinia@^2.1.0 vue-router@^4.2.0 lucide-vue-next@^0.344.0 @vueuse/core@^10.7.0
npm install -D @vitejs/plugin-vue@^5.0.0 vue-tsc@^1.8.25 @vue/eslint-config-typescript@^12.0.0 eslint-plugin-vue@^9.20.0
```
### 第三步:更新配置文件
按照上述配置文件更新清单逐一更新。
### 第四步:迁移组件(按阶段执行)
1. 先迁移简单组件ProgressBar、SidebarItem
2. 迁移工具组件Toast
3. 迁移布局组件Sidebar、MainLayout
4. 迁移页面组件(按复杂度从低到高)
### 第五步:测试验证
- 运行 `npm run dev` 检查编译错误
- 运行 `npm run type-check` 检查类型错误
- 手动测试所有功能
### 第六步:清理
- 删除所有 `.tsx` 文件
- 删除 React 相关配置
- 更新文档
---
## ⚠️ 常见问题与解决方案
### 问题 1: 图标组件不显示
**解决方案**: 确保使用 `lucide-vue-next` 并正确导入:
```typescript
import { IconName } from 'lucide-vue-next';
// 使用 :size 而不是 size
<IconName :size="20" />
```
### 问题 2: TypeScript 类型错误
**解决方案**:
- 确保 `env.d.ts` 文件存在
- 检查 `tsconfig.json` 配置
- 使用 `vue-tsc` 进行类型检查
### 问题 3: 响应式数据不更新
**解决方案**:
- 使用 `ref()` 包装基本类型
- 使用 `reactive()` 包装对象
- 访问 ref 值时使用 `.value`
### 问题 4: 事件处理不工作
**解决方案**:
- 使用 `@click` 而不是 `onClick`
- 使用 `@submit.prevent` 阻止默认行为
- 事件参数通过 `$event` 访问
---
## 📊 迁移进度跟踪
### 组件迁移清单
#### 基础组件
- [ ] ProgressBar.vue
- [ ] SidebarItem.vue
- [ ] TableCheckItem.vue
- [ ] Toast.vue
- [ ] ToastContainer.vue
#### 布局组件
- [ ] Sidebar.vue
- [ ] MainLayout.vue
#### 页面组件
- [ ] DashboardView.vue
- [ ] ProjectListView.vue
- [ ] EngagementView.vue
#### 工作流步骤组件
- [ ] SetupStep.vue
- [ ] InventoryStep.vue
- [ ] ContextStep.vue
- [ ] ValueStep.vue
- [ ] DeliveryStep.vue
#### 状态管理
- [ ] stores/toast.ts (Pinia)
- [ ] stores/app.ts (可选,全局状态)
#### 配置文件
- [ ] package.json
- [ ] vite.config.ts
- [ ] tsconfig.json
- [ ] env.d.ts
- [ ] .eslintrc.cjs
- [ ] main.ts
- [ ] App.vue
---
## 🎯 验收标准
### 功能完整性
- ✅ 所有原有功能正常工作
- ✅ 所有交互行为保持一致
- ✅ 所有样式保持一致
### 代码质量
- ✅ 无 TypeScript 类型错误
- ✅ 无 ESLint 错误
- ✅ 代码符合 Vue 3 最佳实践
### 性能指标
- ✅ 首屏加载时间 ≤ 2s
- ✅ 页面切换流畅
- ✅ 无内存泄漏
---
## 📚 参考资源
- [Vue 3 官方文档](https://vuejs.org/)
- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
- [Pinia 文档](https://pinia.vuejs.org/)
- [Vue Router 文档](https://router.vuejs.org/)
- [Vite 文档](https://vitejs.dev/)
---
## 📅 时间估算
| 阶段 | 工作量 | 说明 |
|------|--------|------|
| 阶段一:项目配置 | 1-2 天 | 配置文件更新 |
| 阶段二:类型定义 | 1 天 | 类型文件检查 |
| 阶段三:工具组件 | 2-3 天 | 简单组件迁移 |
| 阶段四:布局组件 | 2-3 天 | 布局组件迁移 |
| 阶段五:页面组件 | 5-7 天 | 复杂页面迁移 |
| 阶段六:应用入口 | 1 天 | 主文件迁移 |
| 阶段七:图标库 | 1 天 | 图标替换 |
| 阶段八:测试验证 | 3-5 天 | 功能测试和修复 |
| **总计** | **16-25 天** | **约 3-5 周** |
---
## 📝 更新记录
- **2025-01-XX**: 初始迁移方案创建
---
## 👥 贡献者
- Finyx AI Team