build(deps): 添加 compressorjs 和 cropperjs 依赖并更新文档

添加 compressorjs 用于图片压缩功能,调整 cropperjs 依赖位置
新增 compressorjs 使用指南文档和项目开发说明文档
This commit is contained in:
dzq 2025-11-08 15:59:35 +08:00
parent 864ef9a1fa
commit 8819f17aa0
4 changed files with 603 additions and 4 deletions

252
CLAUDE.md Normal file
View File

@ -0,0 +1,252 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Environment Requirements
- **Node.js**: 16.0+ (recommended: Node.js 16 + pnpm 7.30.5)
- **pnpm**: 6.0+
### Common Commands
```bash
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm build
# Build for staging
pnpm build:staging
# Preview production build
pnpm preview
# Run type checking
pnpm typecheck
# Run all linters
pnpm lint
# Run individual linters
pnpm lint:eslint # ESLint with auto-fix
pnpm lint:prettier # Prettier formatting
pnpm lint:stylelint # Stylelint with auto-fix
# Clean install (removes node_modules and reinstalls)
pnpm clean:cache
# Generate SVG icons
pnpm svgo
```
### API Proxy Configuration
The dev server is configured with a proxy to backend API at `http://localhost:8080`:
- API calls starting with `/dev-api/` are proxied to the backend
- See `vite.config.ts:46-52` for proxy configuration
## Project Architecture
### Technology Stack
- **Frontend Framework**: Vue 3.3.4 with Composition API
- **Language**: TypeScript 5.0.4
- **UI Library**: Element Plus 2.3.6
- **Build Tool**: Vite 4.3.9
- **State Management**: Pinia 2.1.4
- **Router**: Vue Router 4.2.2
### High-Level Structure
```
src/
├── api/ # API layer - organized by business module
│ ├── ab98/ # Member management APIs
│ ├── cabinet/ # Smart cabinet management APIs
│ ├── common/ # Shared/common APIs
│ ├── qy/ # Enterprise user APIs
│ ├── shop/ # Shop management APIs
│ └── system/ # System administration APIs
├── components/ # Reusable Vue components
├── layout/ # App layout components
├── router/ # Vue Router configuration
│ └── modules/ # Route modules (global, home, error, etc.)
├── store/ # Pinia state management
│ └── modules/ # Store modules
├── utils/ # Utility functions
│ └── request.ts # Axios HTTP client with interceptors
├── views/ # Page components organized by feature
│ ├── cabinet/ # Smart cabinet management pages
│ ├── shop/ # Shop management pages
│ ├── qy/ # Enterprise management pages
│ ├── system/ # System administration pages
│ └── user/ # User management pages
├── directives/ # Custom Vue directives
├── plugins/ # Vue plugins configuration
└── style/ # Global styles
```
### Key Configuration Files
- `vite.config.ts`: Vite build configuration with path aliases (@ -> src, @build -> build)
- `tsconfig.json`: TypeScript configuration
- `tailwind.config.js`: Tailwind CSS configuration
- `.eslintrc.js`: ESLint rules
- `.stylelintrc.js`: Stylelint rules
## Core Business Modules
### 1. Smart Cabinet Management (cabinet)
- **柜体管理**: Smart cabinet device management
- **格口管理**: Cabinet cell/compartment management
- **设备操作**: Remote device control
- **MQTT 服务器**: MQTT server management for device communication
Key files:
- `src/api/cabinet/smart-cabinet.ts`
- `src/api/cabinet/cabinet-cell.ts`
- `src/api/cabinet/mqttServer.ts`
### 2. Shop Management (shop)
- **商品管理**: Product management and inventory
- **分类管理**: Product categories
- **订单管理**: Order processing
- **审批中心**: Business approval workflows
- **数据统计**: Analytics dashboard
Key files:
- `src/api/shop/shop.ts`
- `src/api/shop/goods.ts`
- `src/api/shop/order.ts`
- `src/api/shop/stats.ts`
### 3. Enterprise Management (qy)
- **企业用户管理**: Enterprise user management
- **余额管理**: User balance and transaction records
Key files:
- `src/api/qy/qyUser.ts`
### 4. System Administration (system)
- **用户管理**: System user management
- **权限管理**: Role and permission configuration
- **个人中心**: User profile management
Key files:
- `src/api/system/`
### 5. Member Management (ab98)
- **会员信息**: Member profile management
- **会员详情**: Detailed member information
Key files:
- `src/api/ab98/`
## Architecture Patterns
### Request Handling
- Centralized HTTP client: `src/utils/request.ts`
- Request interceptor adds authentication token
- Response interceptor handles errors globally
- API methods return typed responses
### State Management
- Pinia stores organized by feature in `src/store/modules/`
- Includes permission, button权限, system config, and WeChat integration
- Stores can be persisted to localStorage
### Routing
- Dynamic routing based on user permissions
- Static routes in `src/router/modules/`
- Route guards handle authentication and authorization
- Lazy-loaded route components for code splitting
### Component Development
- Composition API with `<script setup>` syntax
- TypeScript interfaces for props and emits
- Reusable components in `src/components/`
- Custom components follow naming conventions (PascalCase)
## Development Best Practices
### Code Style
- **Components**: PascalCase (e.g., `UserProfile.vue`)
- **Files**: kebab-case (e.g., `user-profile.ts`)
- **Variables**: camelCase (e.g., `userName`)
- **Constants**: UPPER_SNAKE_CASE (e.g., `MAX_COUNT`)
### TypeScript Guidelines
- Strict mode is disabled in tsconfig.json for flexibility
- Define interfaces for all API responses
- Use TypeScript generics for typed API calls
- Use `withDefaults` for prop definitions
### Git Workflow
- Husky pre-commit hooks for code quality
- Commit messages follow Conventional Commits format:
```
<type>(<scope>): <subject>
```
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
## Environment Configuration
### Environment Files
- `.env.development`: Development environment variables
- `.env.staging`: Staging environment variables
- `.env.production`: Production environment variables
### Key Environment Variables
- `VITE_ROUTER_HISTORY`: Router history mode
- `VITE_PORT`: Dev server port
- `VITE_PUBLIC_PATH`: Base path for production builds
- `VITE_API_BASE_URL`: Backend API base URL (in request.ts)
## Build & Deployment
### Build Process
- Vite for bundling and optimization
- Static assets organized by type (js/css/images)
- Source maps disabled in production
- Code splitting and chunk optimization
### Deployment
- Output directory: `dist/`
- Configure server for SPA routing
- Production builds include `serverConfig.json` for server addresses
- See `doc/部署和维护指南.md` for detailed deployment instructions
## Testing
Based on package.json, the project appears to have testing infrastructure in place but specific test commands are not defined. The codebase follows Vue 3 + Vite patterns compatible with:
- Vitest for unit testing
- Vue Test Utils for component testing
## Code Quality Tools
### Linting Stack
- **ESLint**: Code quality with @typescript-eslint
- **Prettier**: Code formatting
- **Stylelint**: CSS/SCSS linting with stylelint-config-standard
- All three are enforced via Husky pre-commit hooks
### Performance Optimization
- Lazy loading for routes and heavy components
- Asset optimization via Vite plugins
- CDN support for third-party libraries
- Tree-shaking for dead code elimination
## Documentation
See the `doc/` directory for detailed documentation:
- `项目说明文档.md`: Project overview and architecture
- `开发指南.md`: Development guide and best practices
- `API接口文档.md`: Complete API documentation
- `部署和维护指南.md`: Deployment and maintenance guide
## Key References
- This project is based on [Pure-Admin](https://github.com/pure-admin/vue-pure-admin)
- Vue 3 Documentation: https://v3.cn.vuejs.org
- Vite Documentation: https://cn.vitejs.dev
- Element Plus Documentation: https://element-plus.org/zh-CN
- Pinia Documentation: https://pinia.vuejs.org

318
doc/compressorjs-guide.md Normal file
View File

@ -0,0 +1,318 @@
# CompressorJS 实战使用文档
基于项目中的 `src/pages/approval/submit.vue` 实例编写。
## 1. 简介
CompressorJS 是一个轻量级的 JavaScript 图像压缩库,基于 Canvas API依赖少且易用。适用于文件上传前压缩可显著减少文件大小和上传时间。
## 2. 安装
```bash
pnpm add compressorjs
# 或
npm install compressorjs
```
## 3. 基础使用(基于项目实例)
### 3.1 基本语法
```javascript
import Compressor from 'compressorjs'
new Compressor(file, {
quality: 0.8,
maxWidth: 1280,
maxHeight: 1280,
success(result) {
// 压缩成功后的处理
},
error(err) {
// 压缩失败处理
}
})
```
### 3.2 实际项目代码解析
`submit.vue:54-115``handleFileUpload` 函数中:
```javascript
const handleFileUpload = async (items: UploaderFileListItem | UploaderFileListItem[]) => {
const files = Array.isArray(items) ? items : [items]
const uploadPromises = files.map(async (item) => {
const file = item.file as File
// 压缩文件
let compressedFile = file
try {
compressedFile = await new Promise<File>((resolve, reject) => {
new Compressor(file, {
quality: 0.8, // 压缩质量 0-1 之间
maxWidth: 1280, // 最大宽度
maxHeight: 1280, // 最大高度
success(result) {
// 转换为 File 对象,保持原始文件名
resolve(new File([result], file.name, {
type: 'image/jpeg', // 统一输出为 jpeg
lastModified: Date.now()
}))
},
error(err) {
reject(err)
}
})
})
} catch (error) {
console.error('压缩失败:', error)
// 压缩失败时使用原文件
}
// 继续上传...
})
}
```
**关键点:**
- 使用 Promise 包装 Compressor 的回调式 API
- 压缩失败时降级使用原文件
- 转换结果为标准 File 对象,便于后续处理
## 4. 配置参数详解
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `quality` | number | 0.8 | 压缩质量0-1 之间,值越小文件越小但质量越差 |
| `maxWidth` | number | Infinity | 最大宽度(像素) |
| `maxHeight` | number | Infinity | 最大高度(像素) |
| `minWidth` | number | 0 | 最小宽度 |
| `minHeight` | number | 0 | 最小高度 |
| `width` | number | - | 固定宽度(优先级高于 maxWidth |
| `height` | number | - | 固定高度(优先级高于 maxHeight |
| `resize` | string | 'cover' | 调整大小模式:'none'\|'contain'\|'cover'\|'crop' |
| `crop` | boolean | false | 是否裁剪(需配合 width/height |
| `rotate` | number | 0 | 旋转角度(度) |
| `blur` | number | 0 | 模糊半径 |
| `grayscale` | boolean | false | 转为灰度图 |
| `input` | string | 'file' | 输入类型 |
| `output` | string | 'blob' | 输出类型:'blob'\|'base64'\|'datauristring' |
## 5. 常见使用场景
### 5.1 批量图片压缩
```javascript
const compressFiles = async (files) => {
const promises = files.map(file => {
return new Promise((resolve, reject) => {
new Compressor(file, {
quality: 0.8,
maxWidth: 1280,
maxHeight: 1280,
success: resolve,
error: reject
})
})
})
return await Promise.all(promises)
}
```
### 5.2 限制文件大小
```javascript
const compressWithSizeLimit = (file, maxSizeMB = 2) => {
return new Promise((resolve, reject) => {
const maxSizeBytes = maxSizeMB * 1024 * 1024
new Compressor(file, {
quality: 0.6, // 降低质量确保文件更小
maxWidth: 1024,
maxHeight: 1024,
success: (result) => {
if (result.size > maxSizeBytes) {
// 仍超出限制,进一步压缩
new Compressor(file, {
quality: 0.4,
maxWidth: 800,
maxHeight: 800,
success: resolve,
error: reject
})
} else {
resolve(result)
}
},
error: reject
})
})
}
```
### 5.3 自定义输出格式
```javascript
// 输出为 base64
new Compressor(file, {
quality: 0.8,
maxWidth: 1280,
maxHeight: 1280,
output: 'datauristring',
success: (result) => {
const base64String = result // 已经是 base64 字符串
}
})
// 转换为 PNG
new Compressor(file, {
quality: 0.9,
maxWidth: 1280,
maxHeight: 1280,
success: (result) => {
new File([result], 'image.png', { type: 'image/png' })
}
})
```
## 6. 最佳实践
### 6.1 错误处理
```javascript
let compressedFile = originalFile
try {
compressedFile = await new Promise((resolve, reject) => {
new Compressor(file, options)
})
} catch (error) {
console.error('压缩失败:', error)
// 压缩失败时使用原文件,避免阻塞上传流程
}
```
### 6.2 文件类型检查
```javascript
const isImage = (file) => {
return /^image\//.test(file.type)
}
if (!isImage(file)) {
throw new Error('仅支持图片文件')
}
```
### 6.3 压缩级别建议
- **高质量**(适合产品图、头像):
```javascript
{
quality: 0.9,
maxWidth: 1920,
maxHeight: 1920
}
```
- **中等质量**(适合一般场景):
```javascript
{
quality: 0.8,
maxWidth: 1280,
maxHeight: 1280
}
```
- **高质量压缩**(适合缩略图、证明材料):
```javascript
{
quality: 0.6,
maxWidth: 800,
maxHeight: 800
}
```
### 6.4 进度提示(适用于 Vue/Vant
```javascript
item.status = 'uploading'
item.message = '压缩中...'
try {
// 压缩
item.message = '上传中...'
// 上传
item.status = 'done'
item.message = '上传成功'
} catch (error) {
item.status = 'failed'
item.message = '上传失败'
}
```
## 7. 性能优化
1. **避免重复压缩**:上传前检查文件是否已压缩
2. **合理设置尺寸**:根据业务需求设置最大宽高
3. **并发控制**:避免同时压缩过多文件
4. **使用 Web Workers**:大量文件时考虑使用 Web Worker
```javascript
// 并发控制示例
const processFilesWithLimit = async (files, limit = 3) => {
const results = []
const executing = []
for (const file of files) {
const promise = Promise.resolve().then(() => compressFile(file))
results.push(promise)
if (limit <= files.length) {
const executing_promise = promise.then(() =>
executing.splice(executing.indexOf(executing_promise), 1)
)
executing.push(executing_promise)
if (executing.length >= limit) {
await Promise.race(executing)
}
}
}
return Promise.all(results)
}
```
## 8. 注意事项
1. **只支持图片格式**CompressorJS 仅处理图片文件jpeg, png, gif 等)
2. **Canvas 依赖**:依赖浏览器 Canvas API部分旧浏览器可能不支持
3. **内存使用**:大文件压缩会消耗较多内存,建议控制文件大小
4. **EXIF 方向**:压缩可能会丢失图片 EXIF 方向信息
5. **文件类型转换**:默认输出 blob转换为 File 时需显式指定类型
## 9. 完整示例
参考项目中的完整实现:
- 文件:`src/pages/approval/submit.vue:54-115`
- 结合 Vant Uploader 组件的批量图片上传和压缩
- 集成错误处理和状态管理
- 支持压缩失败时的降级策略
## 10. 常见问题
**Q: 压缩后文件变大?**
A: 原文件已非常小,或开启了失真优化。尝试降低 quality 或设置更小的 maxWidth/maxHeight。
**Q: 支持 GIF 动态图?**
A: 不支持CompressorJS 仅处理静态图像。
**Q: 如何保持文件类型?**
A: 压缩时输出为 blob然后在 File 构造函数中指定目标类型:
```javascript
const type = file.type.includes('png') ? 'image/png' : 'image/jpeg'
new File([result], file.name, { type })
```

View File

@ -36,6 +36,8 @@
"@vueuse/motion": "^2.0.0",
"animate.css": "^4.1.1",
"axios": "^1.4.0",
"compressorjs": "^1.2.1",
"cropperjs": "^1.5.13",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.8",
"echarts": "^5.4.2",
@ -48,8 +50,6 @@
"path": "^0.12.7",
"pinia": "^2.1.4",
"pinyin-pro": "^3.15.2",
"cropperjs": "^1.5.13",
"vue-tippy": "^6.2.0",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"responsive-storage": "^2.2.0",
@ -57,6 +57,7 @@
"typeit": "^8.7.1",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
"vue-tippy": "^6.2.0",
"vue-types": "^5.1.0",
"xlsx": "^0.18.5"
},

View File

@ -29,6 +29,9 @@ importers:
axios:
specifier: ^1.4.0
version: 1.8.1
compressorjs:
specifier: ^1.2.1
version: 1.2.1
cropperjs:
specifier: ^1.5.13
version: 1.6.2
@ -771,36 +774,42 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
@ -1325,6 +1334,9 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
blueimp-canvas-to-blob@3.29.0:
resolution: {integrity: sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@ -1500,6 +1512,9 @@ packages:
compare-func@2.0.0:
resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
compressorjs@1.2.1:
resolution: {integrity: sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==}
computeds@0.0.1:
resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
@ -2295,6 +2310,10 @@ packages:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-blob@2.1.0:
resolution: {integrity: sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==}
engines: {node: '>=6'}
is-builtin-module@3.2.1:
resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
engines: {node: '>=6'}
@ -4540,7 +4559,7 @@ snapshots:
'@types/node': 20.5.1
chalk: 4.1.2
cosmiconfig: 8.3.6(typescript@5.8.2)
cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.8.2))(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.8.2))(typescript@5.8.2)
cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.0.4))(ts-node@10.9.2(@types/node@20.17.22)(typescript@5.0.4))(typescript@5.8.2)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@ -5494,6 +5513,8 @@ snapshots:
binary-extensions@2.3.0: {}
blueimp-canvas-to-blob@3.29.0: {}
boolbase@1.0.0: {}
brace-expansion@1.1.11:
@ -5680,6 +5701,11 @@ snapshots:
array-ify: 1.0.0
dot-prop: 5.3.0
compressorjs@1.2.1:
dependencies:
blueimp-canvas-to-blob: 3.29.0
is-blob: 2.1.0
computeds@0.0.1: {}
concat-map@0.0.1: {}
@ -5716,7 +5742,7 @@ snapshots:
convert-source-map@2.0.0: {}
cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.8.2))(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.8.2))(typescript@5.8.2):
cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.0.4))(ts-node@10.9.2(@types/node@20.17.22)(typescript@5.0.4))(typescript@5.8.2):
dependencies:
'@types/node': 20.5.1
cosmiconfig: 8.3.6(typescript@5.8.2)
@ -6590,6 +6616,8 @@ snapshots:
dependencies:
binary-extensions: 2.3.0
is-blob@2.1.0: {}
is-builtin-module@3.2.1:
dependencies:
builtin-modules: 3.3.0