Files
zcglxt/COMPONENT_USAGE_GUIDE.md
Claude e48975f9d5 fix: 修复前端登录体验和API调用问题
- 修复路由守卫:未登录时直接跳转,不显示提示信息
- 修复API拦截器:401错误直接跳转,无需确认
- 移除不必要的ElMessageBox确认框
- 优化Token过期处理逻辑
- 修复文件管理API引入路径和URL前缀
- 修复调拨/回收管理API端点不匹配问题
- 修复通知管理API方法不匹配问题
- 统一系统配置API路径为单数形式

影响文件:
- src/router/index.ts
- src/api/request.ts
- src/api/file.ts
- src/api/index.ts

测试状态:
- 前端构建通过
- 所有API路径已验证
- 登录流程测试通过

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-25 00:26:33 +08:00

785 lines
16 KiB
Markdown
Raw 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.
# 资产管理系统 - 组件使用文档
## 目录
1. [批量导入组件](#批量导入组件)
2. [批量导出组件](#批量导出组件)
3. [扫码查询组件](#扫码查询组件)
4. [资产分配组件](#资产分配组件)
5. [维修管理组件](#维修管理组件)
6. [统计报表组件](#统计报表组件)
---
## 1. 批量导入组件
### 组件信息
- **路径**: `src/views/assets/components/BatchImportDialog.vue`
- **名称**: `BatchImportDialog`
- **功能**: 批量导入资产数据
### Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | boolean | - | 对话框显示状态 |
### Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | (value: boolean) | 显示状态变化 |
| success | - | 导入成功触发 |
### 使用示例
```vue
<template>
<el-button @click="handleImport">批量导入</el-button>
<BatchImportDialog
v-model="importVisible"
@success="handleImportSuccess"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BatchImportDialog from '@/views/assets/components/BatchImportDialog.vue'
import { ElMessage } from 'element-plus'
const importVisible = ref(false)
const handleImport = () => {
importVisible.value = true
}
const handleImportSuccess = () => {
ElMessage.success('导入成功')
// 刷新列表
}
</script>
```
### 功能说明
#### 三步导入流程
**步骤1: 上传文件**
- 支持拖拽上传
- 支持 .xlsx 和 .xls 格式
- 提供模板下载
**步骤2: 数据预览**
- 显示解析后的数据
- 标记错误行(红色背景)
- 显示错误信息
- 统计错误数量
**步骤3: 导入结果**
- 显示导入统计(总数、成功、失败)
- 失败明细列表
- 导出错误日志
- 导入进度条
### 注意事项
- 文件大小限制建议不超过10MB
- 单次导入数量最多1000条
- 必须先下载模板,按模板格式填写
- 错误数据不会导入,需修改后重新导入
---
## 2. 批量导出组件
### 组件信息
- **路径**: `src/views/assets/components/BatchExportDialog.vue`
- **名称**: `BatchExportDialog`
- **功能**: 批量导出资产数据
### Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | boolean | - | 对话框显示状态 |
### Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | (value: boolean) | 显示状态变化 |
### 使用示例
```vue
<template>
<el-button @click="handleExport">批量导出</el-button>
<BatchExportDialog v-model="exportVisible" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BatchExportDialog from '@/views/assets/components/BatchExportDialog.vue'
const exportVisible = ref(false)
const handleExport = () => {
exportVisible.value = true
}
</script>
```
### 功能说明
#### 导出字段选择
可选择的字段:
- 资产编码assetCode
- 资产名称assetName
- 设备类型deviceTypeName
- 品牌brandName
- 型号modelName
- 序列号serialNumber
- 所属网点orgName
- 位置location
- 状态status
- 采购日期purchaseDate
- 采购价格purchasePrice
- 保修截止warrantyExpireDate
#### 筛选条件
- 设备类型
- 所属网点
- 资产状态
- 关键词搜索
#### 导出格式
- Excel (.xlsx)
- CSV (.csv)
---
## 3. 扫码查询组件
### 组件信息
- **路径**: `src/views/assets/AssetScan.vue`
- **名称**: `AssetScan`
- **功能**: 扫码查询资产
### 主要功能
#### 1. 相机扫码
```typescript
// 启动相机
const startCamera = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
})
videoRef.value.srcObject = stream
}
// 停止相机
const stopCamera = () => {
const stream = videoRef.value.srcObject
stream.getTracks().forEach(track => track.stop())
}
```
#### 2. 手动输入
```vue
<el-input
v-model="inputCode"
placeholder="请输入资产编码"
@keyup.enter="handleManualSearch"
>
<template #append>
<el-button @click="handleManualSearch">查询</el-button>
</template>
</el-input>
```
#### 3. 扫码历史
- 保存在 localStorage
- 最多保存20条
- 点击历史记录可快速查询
#### 4. 扫码音效
```typescript
// 使用Web Audio API
const playBeep = () => {
const audioContext = new AudioContext()
const oscillator = audioContext.createOscillator()
oscillator.frequency.value = 800
oscillator.start()
setTimeout(() => oscillator.stop(), 100)
}
```
### 使用示例
```vue
<template>
<router-link to="/assets/scan">
<el-button>扫码查询</el-button>
</router-link>
</template>
```
### 注意事项
- 摄像头访问需要HTTPS或localhost
- 需要授予摄像头权限
- 二维码识别需集成 @zxing/library
---
## 4. 资产分配组件
### 4.1 分配单列表
**路径**: `src/views/allocation/AllocationList.vue`
#### 筛选条件
- 单据类型allocation/transfer/recovery/maintenance/scrap
- 审批状态pending/approved/rejected/cancelled
- 执行状态pending/executing/completed
- 关键词(单号/申请人)
#### 操作按钮
- 新建分配单
- 查看详情
- 编辑(草稿状态)
- 删除(草稿状态)
- 提交审批
- 审批(待审批状态)
- 执行(已通过状态)
### 4.2 创建分配单对话框
**路径**: `src/views/allocation/components/CreateAllocationDialog.vue`
#### Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | boolean | - | 对话框显示状态 |
| orderId | number \| null | null | 分配单ID编辑时传入 |
#### Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | (value: boolean) | 显示状态变化 |
| success | - | 操作成功触发 |
#### 表单字段
```typescript
{
orderType: 'allocation', // 单据类型
targetOrganizationId: 1, // 目标机构ID
title: '分配单标题', // 标题
assetIds: [1, 2, 3], // 资产ID列表
remark: '备注信息' // 备注
}
```
### 4.3 资产选择器对话框
**路径**: `src/views/allocation/components/AssetSelectorDialog.vue`
#### Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | boolean | - | 对话框显示状态 |
| excludeIds | number[] | [] | 排除的资产ID |
#### Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | (value: boolean) | 显示状态变化 |
| confirm | (assets: any[]) | 确认选择 |
#### 使用示例
```vue
<template>
<el-button @click="showSelector">选择资产</el-button>
<AssetSelectorDialog
v-model="selectorVisible"
:exclude-ids="selectedIds"
@confirm="handleConfirm"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import AssetSelectorDialog from '@/views/allocation/components/AssetSelectorDialog.vue'
const selectorVisible = ref(false)
const selectedIds = ref<number[]>([])
const showSelector = () => {
selectorVisible.value = true
}
const handleConfirm = (assets: any[]) => {
console.log('已选择:', assets)
selectedIds.value = assets.map(a => a.id)
}
</script>
```
### 4.4 分配单详情对话框
**路径**: `src/views/allocation/components/AllocationDetailDialog.vue`
#### Tabs
1. **基本信息** - 分配单基本信息
2. **资产明细** - 分配的资产列表
3. **审批流程** - 审批历史时间轴
#### 操作功能
- 审批(通过/拒绝)
- 执行(开始/完成)
- 查看审批历史
---
## 5. 维修管理组件
### 5.1 维修管理页面
**路径**: `src/views/assets/MaintenanceManagement.vue`
#### 筛选条件
- 状态(待维修/维修中/已完成/已取消)
- 优先级(低/中/高)
- 关键词(资产名称/编码)
#### 操作按钮
- 新建维修记录
- 查看
- 编辑(待维修状态)
- 开始维修
- 完成维修
- 取消维修
### 5.2 维修记录对话框
**路径**: `src/views/assets/components/MaintenanceDialog.vue`
#### Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | boolean | - | 对话框显示状态 |
| recordId | number \| null | null | 记录ID编辑时传入 |
| assetId | number \| null | null | 资产ID预选 |
#### Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | (value: boolean) | 显示状态变化 |
| success | - | 操作成功触发 |
#### 表单字段
```typescript
{
assetId: 1, // 资产ID
faultType: 'hardware', // 故障类型
priority: 'medium', // 优先级
maintenanceType: 'self_repair', // 维修类型
faultDescription: '...', // 故障描述
maintenancePersonnel: '张三', // 维修人员
maintenanceCost: 500.00, // 维修费用
startDate: '2025-01-24', // 开始日期
endDate: '2025-01-25', // 结束日期
remark: '备注', // 备注
photos: [] // 维修照片
}
```
#### 使用示例
```vue
<template>
<el-button @click="showMaintenance">新建维修记录</el-button>
<MaintenanceDialog
v-model="maintenanceVisible"
:asset-id="currentAssetId"
@success="handleSuccess"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import MaintenanceDialog from '@/views/assets/components/MaintenanceDialog.vue'
const maintenanceVisible = ref(false)
const currentAssetId = ref<number>(1)
const showMaintenance = () => {
maintenanceVisible.value = true
}
const handleSuccess = () => {
console.log('维修记录已保存')
}
</script>
```
---
## 6. 统计报表组件
### 组件信息
- **路径**: `src/views/assets/StatisticsDashboard.vue`
- **名称**: `StatisticsDashboard`
- **功能**: 资产统计和可视化
### 主要功能
#### 1. 统计卡片
```vue
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-icon total">
<el-icon><Box /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ totalAssets }}</div>
<div class="stat-label">资产总数</div>
</div>
</div>
</el-card>
```
卡片类型:
- 资产总数(紫色)
- 在用资产(绿色)
- 维修中(橙色)
- 待报废(红色)
#### 2. ECharts图表
**图表1: 资产状态分布(饼图)**
```typescript
const statusPieOption = {
series: [{
type: 'pie',
radius: ['40%', '70%'], // 环形
data: [
{ value: 735, name: '在用' },
{ value: 580, name: '在库' },
{ value: 484, name: '维修中' },
{ value: 300, name: '待报废' }
]
}]
}
```
**图表2: 资产类型分布(柱状图)**
```typescript
const typeBarOption = {
xAxis: { data: ['计算机', '打印机', '复印机', ...] },
series: [{
type: 'bar',
data: [326, 208, 156, ...]
}]
}
```
**图表3: 资产价值趋势(折线图)**
```typescript
const valueTrendOption = {
xAxis: { data: ['1月', '2月', '3月', ...] },
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '价值(万元)' }
],
series: [
{ name: '资产数量', type: 'line' },
{ name: '资产价值', type: 'line', yAxisIndex: 1 }
]
}
```
**图表4: 机构资产分布(树图)**
```typescript
const orgDistributionOption = {
series: [{
type: 'tree',
data: [
{
name: '广东省',
children: [
{ name: '广州市', children: [...] },
{ name: '深圳市', children: [...] }
]
}
]
}]
}
```
**图表5: 维修统计(堆叠柱状图)**
```typescript
const maintenanceOption = {
series: [
{ name: '硬件故障', type: 'bar', stack: 'total' },
{ name: '软件故障', type: 'bar', stack: 'total' },
{ name: '其他', type: 'bar', stack: 'total' }
]
}
```
### 使用示例
```vue
<template>
<router-link to="/assets/statistics">
<el-button>统计报表</el-button>
</router-link>
</template>
```
### ECharts按需引入
```typescript
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { PieChart, BarChart, LineChart, TreeChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent
} from 'echarts/components'
use([
CanvasRenderer,
PieChart,
BarChart,
LineChart,
TreeChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent
])
```
---
## 通用组件模式
### 对话框组件模式
所有对话框组件遵循统一的模式:
```vue
<template>
<el-dialog
v-model="visible"
:title="title"
width="800px"
:close-on-click-modal="false"
@close="handleClose"
>
<!-- 内容 -->
<el-form ref="formRef" :model="formData" :rules="formRules">
<!-- 表单字段 -->
</el-form>
<!-- 底部按钮 -->
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
interface Props {
modelValue: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const handleClose = () => {
visible.value = false
}
</script>
```
### 表单验证模式
```typescript
const formRules = {
fieldName: [
{ required: true, message: '请输入', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在2-50个字符', trigger: 'blur' }
]
}
const handleSubmit = async () => {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
// 提交逻辑
}
```
### API调用模式
```typescript
const fetchData = async () => {
loading.value = true
try {
const data = await apiFunction(params)
// 处理数据
} catch (error) {
ElMessage.error('操作失败')
} finally {
loading.value = false
}
}
```
---
## 样式规范
### SCSS变量
```scss
// 主题色
$primary-color: #409EFF;
$success-color: #67C23A;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
$info-color: #909399;
// 文本色
$text-primary: #303133;
$text-regular: #606266;
$text-secondary: #909399;
// 边框色
$border-base: #DCDFE6;
$border-light: #E4E7ED;
$border-lighter: #EBEEF5;
$border-extra-light: #F2F6FC;
// 背景色
$bg-color: #F5F7FA;
```
### 响应式断点
```scss
// 屏幕断点
$sm: 768px;
$md: 992px;
$lg: 1200px;
$xl: 1920px;
@media (max-width: $sm) {
// 小屏幕样式
}
```
---
## 常见问题
### Q: 如何自定义表单验证?
```typescript
const customValidator = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error('不能为空'))
} else if (value.length < 6) {
callback(new Error('长度不能少于6位'))
} else {
callback()
}
}
const formRules = {
password: [
{ validator: customValidator, trigger: 'blur' }
]
}
```
### Q: 如何处理文件上传?
```vue
<el-upload
action="/api/upload"
:on-success="handleSuccess"
:on-error="handleError"
:before-upload="beforeUpload"
>
<el-button>上传文件</el-button>
</el-upload>
```
### Q: 如何实现分页?
```typescript
import { usePagination } from '@/composables/usePagination'
const { pagination, resetPage, setTotal } = usePagination()
const fetchData = async () => {
const data = await apiFunction({
page: pagination.page,
page_size: pagination.pageSize
})
setTotal(data.total)
}
```
---
## 最佳实践
### 1. 组件命名
- 使用大驼峰命名
- 文件名与组件名一致
- 对话框以Dialog结尾
### 2. Props定义
- 使用TypeScript接口
- 提供默认值
- 添加注释说明
### 3. 事件命名
- 使用kebab-case
- 事件名语义明确
- 参数类型明确
### 4. 样式编写
- 使用scoped避免污染
- 使用SCSS变量
- 遵循BEM命名
### 5. 性能优化
- 合理使用computed
- 避免不必要的watch
- 按需引入组件
---
**更新时间**: 2025-01-24
**版本**: v1.0.0