This commit is contained in:
oneao committed 2026-04-28 17:29:53 +08:00
1 parent ae6ccad238
commit b2d113db38
17 files changed
+3087 -1630

No files matched your search

@@ -0,0 +1,141 @@
# G3File 左右布局改造计划
## 需求分析
用户需要将 g3-file-container 的布局从原来的垂直布局改为左右布局:
**原布局:**
```
┌─────────────────────────────────────┐
│ header │
├─────────────────────────────────────┤
│ main │
├─────────────────────────────────────┤
│ footer │
└─────────────────────────────────────┘
```
**新布局:**
```
┌─────────────────────────────────────────────────────────────┐
│ 左边区域 │ 右边区域 │
│ ┌─────────────────────────────┐ │ ┌─────────────────┐ │
│ │ header │ │ │ toolbar │ │ (10%)
│ ├─────────────────────────────┤ │ └─────────────────┘ │
│ │ main │ │ ┌─────────────────┐ │
│ ├─────────────────────────────┤ │ │ preview │ │ (90%)
│ │ footer │ │ │ area │ │
│ └─────────────────────────────┘ │ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
**Toolbar 功能需求:**
* 上一个/下一个按钮(用于切换预览)
- 当前预览的文件名
- 顺序显示(当前索引/总数)
**预览区域:**
* 预览功能暂不实现,显示测试内容即可
## 修改方案
### 1. 修改 index.js
**需要修改的内容:**
* 在 `render()` 方法中添加右侧预览区域的 HTML 结构
* 添加预览相关的状态管理(当前预览索引、已上传文件列表)
* 添加预览区域的 DOM 引用
* 添加上下按钮的事件处理
**新增状态:**
```javascript
this.previewIndex = 0; // 当前预览索引
```
**新增 DOM 引用:**
```javascript
this.$previewPanel = this.$container.find('.g3-file-preview-panel');
this.$previewToolbar = this.$container.find('.g3-preview-toolbar');
this.$previewContent = this.$container.find('.g3-preview-content');
this.$btnPrev = this.$container.find('.preview-prev');
this.$btnNext = this.$container.find('.preview-next');
this.$previewInfo = this.$container.find('.preview-info');
```
### 2. 修改 index.css
**需要修改的内容:**
* 修改 `.g3-file-container` 为横向 flex 布局
* 添加左侧区域样式 `.g3-file-left-panel`
* 添加右侧预览区域样式 `.g3-file-preview-panel`
* 添加 toolbar 样式 `.g3-preview-toolbar`
* 添加预览内容区域样式 `.g3-preview-content`
* 添加导航按钮样式
## 文件修改清单
| 文件 | 修改内容 |
| ------------------- | ---------------------------------- |
| `g3-file/index.js` | 修改 render 方法添加预览区域HTML,添加预览相关状态和事件 |
| `g3-file/index.css` | 修改容器布局,添加左右区域样式、toolbar 样式、预览区域样式 |
## 实现步骤
1. **修改 CSS 布局**
* 修改 `.g3-file-container` 为 `flex-direction: row`
* 添加左侧面板样式(占50%宽度)
* 添加右侧预览面板样式(占50%宽度)
* 添加 toolbar 和预览内容区域样式
2. **修改 JS 渲染**
* 在 render 方法中添加右侧预览区域 HTML
* 添加预览相关状态
* 添加上下按钮事件绑定
3. **添加辅助方法**
* `getUploadedFiles()` - 获取已上传的文件列表
* `updatePreview()` - 更新预览区域显示
## 风险评估
* 现有功能(文件上传、下载、删除)不受影响
* 预览功能暂不实现,仅显示测试内容
* 布局变化可能影响现有样式,需要仔细测试
## 测试要点
1. 布局是否正确显示为左右结构
2. 左侧原有功能是否正常工作
3. Toolbar 是否正确显示文件信息
4. 上下按钮是否可点击(即使预览功能未实现)
5. 预览区域是否正常显示测试内容
@@ -0,0 +1,386 @@
# G3File 预览区域布局修改计划
## 一、需求分析
根据用户需求,需要对 g3-file-container 进行以下修改:
1. **布局调整**:改为左右两栏布局
- **左侧区域**:保持现有完整结构(header + main + footer)
- **右侧区域**:新增预览区域(toolbar 10% + 预览内容区 90%)
2. **预览区域 Toolbar**:
- 文件总数(仅统计已上传文件)
- 当前预览文件名
- 顺序导航按钮(上一个/下一个)
3. **预览规则**:
- 只有 `status === 'uploaded'` 的文件才可预览
- 预览总数只计算已上传文件
4. **预览功能**:暂时不实现实际预览功能,只显示测试内容
## 二、布局结构
### 2.1 原布局
```
┌─────────────────────────────────────┐
│ Header │
├─────────────────────────────────────┤
│ Main │
│ (文件列表) │
├─────────────────────────────────────┤
│ Footer │
└─────────────────────────────────────┘
```
### 2.2 新布局
```
┌─────────────────────────────────────────────────────────────────┐
│ 左侧区域 │ 右侧预览区域 │
│ ┌─────────────────────────────────────┐ │ ┌───────────┐ │
│ │ Header │ │ │ Toolbar │ │
│ ├─────────────────────────────────────┤ │ │(10%高度) │ │
│ │ Main │ ├───────────────┤ │
│ │ (文件列表) │ │ │ │
│ ├─────────────────────────────────────┤ │ 预览内容区 │ │
│ │ Footer │ │ (90%高度) │ │
│ └─────────────────────────────────────┘ │ │ │
│ └─────────────┘ │
└───────────────────────────────────────────────────────────────┘
```
### 2.3 Toolbar 内容
```
┌─────────────────────────────────────────────────────────┐
│ [上一个] 第 1/5 个文件 当前: filename.pdf [下一个] │
└─────────────────────────────────────────────────────────┘
```
## 三、修改方案
### 3.1 修改文件
| 文件 | 修改内容 |
|------|----------|
| `g3-file/index.css` | 添加左右布局和预览区域样式 |
| `g3-file/index.js` | 修改布局结构、添加预览区域逻辑 |
### 3.2 JS 修改要点
1. **渲染方法 (`render`)**:修改 HTML 结构为左右布局
2. **状态管理**:
- `currentPreviewIndex`: 当前预览文件索引
3. **预览操作方法**:
- `getPreviewableFiles()`: 获取已上传文件列表
- `previewFile(file)`: 预览指定文件
- `previewNext()`: 预览下一个文件
- `previewPrev()`: 预览上一个文件
- `updatePreviewArea()`: 更新预览区域 UI
4. **事件绑定**:绑定预览区域按钮事件
### 3.3 CSS 修改要点
1. **整体容器**:改为横向 flex 布局
2. **左侧区域**:保持原有结构,设置宽度比例(如 50%)
3. **右侧区域**:
- 设置宽度比例(如 50%)
- 纵向 flex 布局
- toolbar 占 10% 高度
- 预览内容区占 90% 高度
4. **Toolbar 样式**:按钮、进度显示等
## 四、实施步骤
### 步骤 1:修改 CSS 样式
```css
/* 整体布局 - 横向排列 */
.g3-file-container {
height: 100%;
display: flex;
background: #ffffff;
overflow: hidden;
}
/* 左侧区域 */
.g3-file-left-panel {
flex: 1;
display: flex;
flex-direction: column;
border-right: 1px solid #eef0f2;
}
/* 右侧预览区域 */
.g3-file-right-panel {
flex: 1;
display: flex;
flex-direction: column;
background: #fafbfc;
}
/* 预览 Toolbar */
.g3-preview-toolbar {
height: 10%;
min-height: 48px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
background: #fff;
border-bottom: 1px solid #eef0f2;
}
/* 预览内容区域 */
.g3-preview-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
overflow: auto;
}
/* 预览空状态 */
.g3-preview-empty {
text-align: center;
color: #999;
font-size: 14px;
}
/* 预览文件信息 */
.g3-preview-file-info {
text-align: center;
}
/* 预览按钮 */
.g3-preview-btn {
padding: 6px 12px;
border: 1px solid #d9d9d9;
background: #fff;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.g3-preview-btn:hover:not(:disabled) {
border-color: #165dff;
color: #165dff;
}
.g3-preview-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 预览状态文字 */
.g3-preview-status {
font-size: 13px;
color: #666;
}
```
### 步骤 2:修改 JS 渲染方法
修改 `render()` 方法的 HTML 结构:
```javascript
render: function () {
const html = `
<div class="g3-file-container">
<!-- 左侧区域 -->
<div class="g3-file-left-panel">
<div class="header">...</div>
<div class="main" style="display: none;">main</div>
<div class="empty-tip">...</div>
<div class="footer">...</div>
</div>
<!-- 右侧预览区域 -->
<div class="g3-file-right-panel">
<div class="g3-preview-toolbar">
<button class="g3-preview-btn preview-prev" disabled>上一个</button>
<div class="g3-preview-status">
<span class="preview-counter">第 0/0 个文件</span>
<span class="preview-filename" style="margin-left: 12px;"></span>
</div>
<button class="g3-preview-btn preview-next" disabled>下一个</button>
</div>
<div class="g3-preview-content">
<div class="g3-preview-empty">请选择一个已上传的文件进行预览</div>
</div>
</div>
<input type="file" class="file-input" style="display: none;" multiple />
<div class="g3-global-menu"></div>
</div>
`;
this.$container.html(html);
// 缓存 DOM 元素
this.$leftPanel = this.$container.find(".g3-file-left-panel");
this.$rightPanel = this.$container.find(".g3-file-right-panel");
this.$fileList = this.$leftPanel.find(".main");
this.$emptyTip = this.$leftPanel.find(".empty-tip");
this.$checkAll = this.$leftPanel.find(".check-all-checkbox");
this.$btnDownload = this.$leftPanel.find(".download-all");
this.$btnDelete = this.$leftPanel.find(".delete-all");
this.$btnUpload = this.$leftPanel.find(".upload");
this.$btnSelect = this.$leftPanel.find(".select");
this.$fileInput = this.$container.find(".file-input");
this.$pendingCount = this.$leftPanel.find(".pending-number");
// 预览区域元素
this.$previewToolbar = this.$container.find(".g3-preview-toolbar");
this.$previewContent = this.$container.find(".g3-preview-content");
this.$btnPreviewPrev = this.$container.find(".preview-prev");
this.$btnPreviewNext = this.$container.find(".preview-next");
this.$previewCounter = this.$container.find(".preview-counter");
this.$previewFilename = this.$container.find(".preview-filename");
return this;
}
```
### 步骤 3:添加预览相关状态和方法
```javascript
// 构造函数中添加
this.currentPreviewIndex = -1;
// 获取可预览文件(已上传文件)
getPreviewableFiles: function () {
return this.files.filter(file => file.status === 'uploaded');
},
// 预览指定文件
previewFile: function (file) {
const previewableFiles = this.getPreviewableFiles();
const index = previewableFiles.findIndex(f => f._id === file._id);
if (index !== -1) {
this.currentPreviewIndex = index;
this.updatePreviewArea();
}
},
// 预览下一个
previewNext: function () {
const previewableFiles = this.getPreviewableFiles();
if (previewableFiles.length === 0) return;
if (this.currentPreviewIndex < previewableFiles.length - 1) {
this.currentPreviewIndex++;
this.updatePreviewArea();
}
},
// 预览上一个
previewPrev: function () {
if (this.currentPreviewIndex > 0) {
this.currentPreviewIndex--;
this.updatePreviewArea();
}
},
// 更新预览区域
updatePreviewArea: function () {
const previewableFiles = this.getPreviewableFiles();
const total = previewableFiles.length;
// 更新计数器
if (this.currentPreviewIndex >= 0 && this.currentPreviewIndex < total) {
this.$previewCounter.text(`第 ${this.currentPreviewIndex + 1}/${total} 个文件`);
this.$previewFilename.text(`当前: ${previewableFiles[this.currentPreviewIndex].name}`);
} else {
this.$previewCounter.text(`第 0/${total} 个文件`);
this.$previewFilename.text('');
}
// 更新按钮状态
this.$btnPreviewPrev.prop('disabled', this.currentPreviewIndex <= 0);
this.$btnPreviewNext.prop('disabled',
this.currentPreviewIndex >= total - 1 || total === 0);
// 更新预览内容
if (this.currentPreviewIndex >= 0 && this.currentPreviewIndex < total) {
const file = previewableFiles[this.currentPreviewIndex];
this.$previewContent.html(`
<div class="g3-preview-file-info">
<h3>${file.name}</h3>
<p>大小: ${this.formatFileSize(file.size)}</p>
<p>上传时间: ${this.formatDate(file.uploadDate)}</p>
<p>状态: 已上传</p>
<p>URL: ${file.url || '-'}</p>
</div>
`);
} else {
this.$previewContent.html('<div class="g3-preview-empty">请选择一个已上传的文件进行预览</div>');
}
}
```
### 步骤 4:绑定预览事件
```javascript
bindEvents: function () {
var self = this;
// ... 原有事件绑定 ...
// 预览下一个按钮
this.$btnPreviewNext.off("click").on("click", function () {
self.previewNext();
});
// 预览上一个按钮
this.$btnPreviewPrev.off("click").on("click", function () {
self.previewPrev();
});
// 点击文件项预览(只对已上传文件生效)
this.$fileList
.off("click", ".g3-file-item")
.on("click", ".g3-file-item", function (e) {
if ($(e.target).closest(".g3-file-action, .g3-file-checkbox, .g3-file-delete-btn").length) {
return;
}
var id = $(this).data("id");
var file = self.files.find(f => f._id === id);
if (!file) return;
// 只有已上传文件才能预览
if (file.status === 'uploaded') {
self.previewFile(file);
}
if (typeof self.options.onPreview === "function") {
self.options.onPreview([file]);
}
});
}
```
### 步骤 5:更新文件操作后的预览状态
在 `internalDelete()`、`updateFileStatus()` 等方法中调用 `updatePreviewArea()`
## 五、注意事项
1. **预览计数**:只统计 `status === 'uploaded'` 的文件
2. **预览权限**:只有已上传文件才能预览
3. **边界处理**:
- 没有可预览文件时,工具栏按钮禁用
- 处于第一个文件时,上一个按钮禁用
- 处于最后一个文件时,下一个按钮禁用
4. **预览内容**:暂时显示测试内容(文件名、大小、日期等信息)
## 六、测试验证
1. 上传文件后,预览区域显示可预览文件数量
2. 点击已上传文件,预览区域显示文件信息
3. 上下按钮正确切换预览文件
4. 删除文件后,预览区域正确更新
5. 未上传文件点击不触发预览
@@ -0,0 +1,418 @@
# G3File jQuery 到 Vue 3 迁移计划
## 1. 需求分析
用户需要将 `custom-file/index.js` 中的 jQuery 代码迁移到 `custom-file-vue/index.js`,使用 Vue 3 的 `vue.global.prod.js`。要求:
* 代码逻辑不变
* CSS 代码不变
## 2. 原 jQuery 代码结构分析
### 2.1 主要功能模块
| 模块 | 功能描述 |
| ----- | --------------------- |
| 配置与常量 | 默认配置、多语言、图标映射 |
| 工具方法 | 文件图标、图片压缩、大小格式化、日期格式化 |
| UI 渲染 | 主容器、文件列表、操作菜单、任务面板、通知 |
| 事件绑定 | 文件选择、拖拽、粘贴、按钮点击 |
| 文件处理 | 选择、下载、删除、上传 |
| 任务管理 | 批量下载/上传进度跟踪 |
| 通知系统 | 消息提示 |
| 权限控制 | 全局权限和单文件权限 |
### 2.2 核心数据结构
```javascript
// 文件对象
{
id: string,
name: string,
size: number,
uploadDate: string,
url: string,
file: File,
status: 'pending' | 'uploading' | 'uploaded' | 'failed'
}
// 选中文件集合
selectedFiles: Set<string>
// 任务状态
taskStats: { total, success, fail }
```
## 3. Vue 3 迁移方案
### 3.1 组件结构
```
G3File.vue (主组件)
├── 模板:HTML 结构
├── 脚本:响应式数据和方法
└── 样式:引用原 CSS
```
### 3.2 迁移策略
| jQuery 特性 | Vue 3 等价实现 |
| --------------- | ----------------------- |
| `$(selector)` | `ref` + `template refs` |
| `.html()` | `v-html` / 模板插值 |
| `.on('event')` | `@event` 指令 |
| `.off('event')` | Vue 自动管理 |
| `$.extend()` | 展开运算符 |
| `$.fn` 插件 | Vue 组件 |
### 3.3 关键迁移点
1. **DOM 操作 → 模板渲染**
* 主容器 HTML 使用 Vue 模板
* 文件列表使用 `v-for` 渲染
* 条件渲染使用 `v-if`/`v-show`
2. **事件处理**
* 选择文件:`@change`
* 拖拽:`@dragenter`, `@dragover`, `@dragleave`, `@drop`
* 粘贴:`@paste`
* 按钮点击:`@click`
3. **响应式数据**
* `files` → `ref([])`
* `selectedFiles` → `ref(new Set())`
* `actionMenuState` → `reactive({})`
* `tasks` → `ref([])`
4. **生命周期**
* `init()` → `onMounted()`
* `destroy()` → `onUnmounted()`
5. Setup写法
## 4. 迁移步骤
### 步骤 1:创建 Vue 组件结构
```javascript
const { createApp, ref, reactive, onMounted, onUnmounted, computed } = Vue
const G3File = {
template: `...`,
props: {
options: { type: Object, default: () => ({}) }
},
setup(props) {
// 响应式数据
const files = ref([])
const selectedFiles = ref(new Set())
const objectUrls = ref(new Map())
const actionMenuState = reactive({ open: false, fileId: null, anchorEl: null, items: [] })
const tasks = ref([])
const taskStats = reactive({ total: 0, success: 0, fail: 0 })
const notifications = ref([])
// 计算属性
const pendingCount = computed(() => files.value.filter(f => f.status === 'pending').length)
const allSelected = computed(() => files.value.length > 0 && selectedFiles.value.size === files.value.length)
// 方法
const generateUniqueId = () => 'file_' + Math.random().toString(36).slice(2) + Date.now()
const getFileIcon = (filename) => { /* ... */ }
const compressImage = async (file, options) => { /* ... */ }
const isImageFile = (filename) => { /* ... */ }
const formatFileSize = (bytes) => { /* ... */ }
const formatDate = (dateStr) => { /* ... */ }
const getUniqueFileName = (fileName, existing) => { /* ... */ }
const getFileBlob = async (url) => { /* ... */ }
// UI 更新方法
const updateUI = () => { /* ... */ }
const renderFileList = () => { /* ... */ }
// 文件操作方法
const handleFileSelect = async (event) => { /* ... */ }
const handleDownload = (fileList, options) => { /* ... */ }
const handleDelete = (fileList) => { /* ... */ }
const handleUpload = (fileList) => { /* ... */ }
// 任务管理
const addTask = (task) => { /* ... */ }
const updateTask = (taskId, updates) => { /* ... */ }
const finishTask = (taskId, status) => { /* ... */ }
const clearTasks = () => { /* ... */ }
// 通知系统
const showNotification = (options) => { /* ... */ }
const closeNotification = (notificationId) => { /* ... */ }
// 权限控制
const hasPermission = (file, action) => { /* ... */ }
// 生命周期
onMounted(() => {
// 初始化脚本加载
// 绑定全局事件(拖拽、粘贴)
})
onUnmounted(() => {
// 清理 ObjectURL
// 解绑全局事件
})
return {
files,
selectedFiles,
actionMenuState,
tasks,
taskStats,
notifications,
pendingCount,
allSelected,
// ... methods
}
}
}
```
### 步骤 2:模板结构
```html
<div class="g3-file-container" :class="{ 'drag-over': isDragOver }">
<!-- 头部 -->
<div class="header">
<label class="check-all">
<input type="checkbox" v-model="allSelected" />
<span>{{ language.selectAll }}</span>
</label>
<button class="download-all" :disabled="!canBatchDownload" @click="handleBatchDownload">
{{ staticIconMap.download }}
{{ language.downloadBatch }}
</button>
<button class="delete-all" :disabled="!canBatchDelete" @click="handleBatchDelete">
{{ staticIconMap.delete }}
{{ language.delete }}
</button>
</div>
<!-- 文件列表 -->
<div class="main" v-show="files.length > 0">
<div
v-for="file in files"
:key="file.id"
class="g3-file-item"
@click="handleFileClick(file)"
>
<input type="checkbox" v-model="selectedFiles.has(file.id)" />
<label class="g3-checkbox-label"></label>
<div class="g3-file-preview">
<!-- 图片或图标 -->
</div>
<div v-if="file.status === 'pending'" class="g3-file-pending-badge">
{{ language.pending }}
</div>
<div class="g3-file-delete-btn" @click.stop="handleDelete([file])">
{{ staticIconMap.delete }}
</div>
<div class="g3-file-info">
<span class="g3-file-name">{{ file.name }}</span>
<span>{{ formatFileSize(file.size) }}</span>
<span>{{ formatDate(file.uploadDate) }}</span>
</div>
<div class="g3-file-actions">
<div class="g3-file-action preview" @click.stop="handlePreview(file)">
{{ staticIconMap.preview }}
</div>
<div class="g3-file-action download" @click.stop="handleDownload([file], { batch: false })">
{{ staticIconMap.download }}
</div>
<div class="g3-file-action more" @click.stop="openActionMenu($event, file)">
{{ staticIconMap.more }}
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div class="empty-tip" v-show="files.length === 0">
<span>{{ language.emptyTip1 }}</span>
<span>{{ language.emptyTip2 }}</span>
<span>{{ language.emptyTip3 }}</span>
</div>
<!-- 底部 -->
<div class="footer">
<div class="pending-count">
<span>{{ language.pending }}</span>
<span class="pending-number">{{ pendingCount }}</span>
</div>
<div>
<button class="select" :disabled="!hasPermission(null, 'select')" @click="triggerFileInput">
{{ staticIconMap.select }}
{{ language.select }}
</button>
<button class="upload" :disabled="!hasPermission(null, 'upload') || pendingCount === 0" @click="handleUpload(files)">
{{ staticIconMap.upload }}
{{ language.upload }}
</button>
</div>
<input type="file" class="file-input" style="display: none;" multiple @change="handleFileSelect" ref="fileInput" />
</div>
<!-- 操作菜单 -->
<div class="g3-global-menu" :class="{ 'is-open': actionMenuState.open }">
<div
v-for="(item, index) in actionMenuState.items"
:key="index"
class="g3-action-menu-item"
:class="{ 'is-disabled': item.disabled }"
@click="handleActionMenuItemClick(item)"
>
{{ item.title }}
</div>
</div>
<!-- 任务面板 -->
<div class="task-dock" v-show="tasks.length > 0">
<div class="dock-header">
<span class="dock-title">Task Running</span>
<div class="dock-actions">
<button title="close" @click="clearTasks">×</button>
</div>
</div>
<div class="dock-body">
<div class="progress-line">
<span class="count">{{ taskStats.success + taskStats.fail }} / {{ taskStats.total }}</span>
<span class="percent">{{ Math.round(totalProgress) }}%</span>
</div>
<div class="task-name">{{ currentTask?.name }}</div>
<div class="bar">
<div class="bar-inner" :style="{ width: currentTask?.progress + '%' }"></div>
</div>
<div class="task-result">
{{ language.taskTotal }}: {{ taskStats.total }} |
{{ language.taskSuccess }}: {{ taskStats.success }} |
{{ language.taskFail }}: {{ taskStats.fail }}
</div>
</div>
</div>
<!-- 通知容器 -->
<div class="notification-container">
<div
v-for="notification in notifications"
:key="notification.id"
class="notification"
:class="[`notification-${notification.type}`, { 'notification-fade-out': notification.closing }]"
>
<div class="notification-content">
<div v-if="notification.title" class="notification-title">{{ notification.title }}</div>
<div class="notification-message">{{ notification.message }}</div>
</div>
<button v-if="notification.showClose" class="notification-close" @click="closeNotification(notification.id)">×</button>
</div>
</div>
<!-- UI Lock -->
<div class="ui-lock" v-show="isUILocked"></div>
</div>
```
### 步骤 3:工具方法迁移
所有工具方法保持逻辑不变,只是移除 jQuery 依赖:
* `generateUniqueId` - 保持不变
* `getFileIcon` - 保持不变(纯字符串处理)
* `compressImage` - 保持不变(使用原生 API)
* `isImageFile` - 保持不变
* `formatFileSize` - 保持不变
* `formatDate` - 保持不变
* `getUniqueFileName` - 保持不变
* `getFileBlob` - 保持不变(使用 fetch API)
### 步骤 4:事件处理迁移
| jQuery 事件 | Vue 事件 |
| ---------------------------------------------------- | ------------------------------------------------ |
| `$(document).on('paste')` | 在 `onMounted` 中绑定原生事件 |
| `$(document).on('click.g3file_actionmenu')` | Vue 模板事件 |
| `$container.on('dragenter dragover dragleave drop')` | `@dragenter`, `@dragover`, `@dragleave`, `@drop` |
| `$fileInput.on('change')` | `@change` |
### 步骤 5:权限控制迁移
保持原有的权限判断逻辑:
```javascript
const hasPermission = (file, action) => {
const globalPermission = props.options.globalPermission
const filePermissionResolver = props.options.filePermissionResolver
if (typeof filePermissionResolver === 'function') {
const result = filePermissionResolver({ file, action })
if (result !== undefined) return !!result
}
if (typeof globalPermission === 'function') {
return !!globalPermission({ file, action })
} else if (typeof globalPermission === 'object') {
return !!globalPermission[action]
}
return true
}
```
## 5. 文件复制
由于 CSS 代码不需要改变,需要将 `custom-file/index.css` 复制到 `custom-file-vue/index.css`(如果不存在差异)。
## 6. 测试验证
迁移完成后需要验证:
1. 文件列表显示正常
2. 文件选择功能正常(点击、拖拽、粘贴)
3. 文件上传功能正常
4. 文件下载功能正常(单个和批量)
5. 文件删除功能正常
6. 任务面板显示正常
7. 通知系统正常
8. 权限控制正常
9. 多语言支持正常
## 7. 潜在风险
1. **jQuery 插件封装**:原代码使用 `$.fn.G3File` 封装,迁移后需要提供 Vue 组件注册方式
2. **DOM 引用**:需要确保所有模板 ref 正确绑定
3. **全局事件**:需要在 `onUnmounted` 中正确清理
4. **异步操作**:确保所有异步操作(如文件上传、下载)正确处理
## 8. 输出文件
* `custom-file-vue/index.js` - Vue 3 组件实现
* `custom-file-vue/index.css` - 样式文件(保持不变)
@@ -0,0 +1,82 @@
# 批量操作进度通知重构计划
## 需求分析
用户要求修改批量操作(下载、上传、删除等)的通知显示逻辑:
1. **单一通知**:批量操作只显示一个 notification
2. **手动关闭**:通知不会自动关闭,需要用户手动关闭
3. **实时更新**:显示当前正在处理的文件名和进度
4. **结果统计**:操作完成后显示总数、成功数、失败数
5. **多语言支持**:使用现有语言配置,不写死文字
6. **通用设计**:支持批量下载、上传、删除等多种操作类型
7. **UI锁定**:操作进行时自动锁定UI,操作结束后自动解锁(与通知关闭无关)
## 修改内容
### 1. 新增任务组概念
在实例中添加 `currentTaskGroup` 来管理当前批量操作的通知状态:
```javascript
this.currentTaskGroup = null; // 当前任务组 { taskType, notificationId, total }
```
### 2. 修改 addTask 方法
- 检查是否已有同类型的任务组
- 如果没有,创建新的任务组和通知
- 如果有,复用已有的通知
### 3. 修改 updateTask 方法
- 更新通知中的当前文件名和进度百分比
- 显示格式:`正在下载:文件名 (进度%)`
### 4. 修改 finishTask 方法
- 所有任务完成后更新通知显示结果统计
- 解锁 UI(与通知关闭无关)
- 通知保持显示,等待用户手动关闭
### 5. 修改 clearTasks 方法
- 不自动关闭通知(用户手动关闭)
- 只是重置任务状态
### 6. 修改 closeNotification 方法
- 关闭通知时清理对应的任务组
### 7. 更新多语言配置
确保以下语言键存在:
- `batchDownload`: 批量下载
- `batchUpload`: 批量上传
- `batchDelete`: 批量删除
- `downloading`: 正在下载
- `uploading`: 正在上传
- `deleting`: 正在删除
- `downloadResult`: 结果统计格式
## 修改步骤
1. 在 `init` 或构造函数中添加 `currentTaskGroup` 状态
2. 修改 `addTask` 方法支持任务组概念
3. 修改 `updateTask` 方法更新进度显示
4. 修改 `finishTask` 方法显示最终结果并解锁UI
5. 修改 `clearTasks` 方法不自动关闭通知
6. 修改 `closeNotification` 方法清理任务组
7. 验证多语言支持
## 风险评估
| 风险项 | 风险等级 | 处理方式 |
|--------|----------|----------|
| 任务组状态管理 | 中 | 使用单一任务组管理同一类型的批量操作 |
| 通知重复创建 | 低 | 添加检查逻辑,避免重复创建 |
| UI锁定与通知生命周期不一致 | 中 | 分离 UI 锁定和通知关闭逻辑 |
## 兼容性
- 原有 `showNotification` 调用方式保持兼容
- 任务管理方法保持向后兼容
@@ -0,0 +1,108 @@
# Notification 重构计划
## 需求分析
用户要求将现有的 `notification` 和 `dock` 两个组件合并,统一使用 `notification` 来显示所有通知和进度信息。
### 修改目标
1. **统一使用 notification**:删除 dock 组件,所有进度显示改用 notification
2. **下载进度结构不变**:保持现有的任务进度数据结构
3. **开放 notification 内容**:支持更灵活的内容传递
4. **添加 lockUI 选项**:在 showNotification 中增加是否锁定 UI 的选项
***
## 修改内容
### 1. 删除 DOM 结构中的 dock
**文件**: `custom-file/index.js`
删除 `render` 方法中的 task-dock HTML 结构:
```html
<div class="task-dock">...</div>
<div class="ui-lock"></div>
```
### 2. 删除 dock 相关变量
删除 `render` 方法中的:
* `this.$taskDock = this.$container.find(".task-dock");`
* `this.$uiLock = this.$container.find(".ui-lock");`
### 3. 修改 showNotification 方法
增加以下 options:
* `lockUI`: 是否锁定 UI(默认 false)
* `progress`: 进度百分比(0-100),用于显示进度条
* `customContent`: 自定义内容(支持 HTML)
### 4. 修改任务管理方法
修改以下方法使用 notification 显示进度:
* `addTask`: 创建任务时显示通知
* `updateTask`: 更新任务进度时更新通知
* `finishTask`: 完成任务时更新通知状态
* `clearTasks`: 清空任务时关闭通知
### 5. 删除 dock 相关方法
删除以下方法:
* `showTaskDock`
* `hideTaskDock`
* `updateTaskDock`
* `bindTaskDockEvents`
* `lockUI`(改为在 showNotification 中处理)
* `unlockUI`(改为在通知关闭时处理)
* `isUILocked`
### 6. 修改批量下载逻辑
修改 `internalDownloadBatch` 方法,使用 notification 显示下载进度。
***
## 风险评估
| 风险项 | 风险等级 | 处理方式 |
| --------- | ---- | ---------------------------- |
| 任务进度状态丢失 | 低 | 保留 tasks 数组用于状态管理 |
| UI 锁定逻辑变化 | 中 | 在 notification 中实现 lockUI 选项 |
| 通知样式需要调整 | 低 | 保持原有样式,增加进度条样式 |
***
## 兼容性
* 原有 `showNotification` 调用方式保持兼容
* 新增参数为可选参数,不影响现有代码
***
## 实施步骤
1. 修改 `render` 方法,删除 dock 和 ui-lock 的 DOM 结构
2. 修改 `showNotification` 方法,增加 lockUI、progress、customContent 选项
3. 修改任务管理方法,使用 notification 显示进度
4. 删除 dock 相关方法
5. 修改 `internalDownloadBatch` 使用新的通知机制
@@ -0,0 +1,211 @@
# UI 提示系统重构计划
## 需求分析
用户要求重新设计并实现一个轻量 UI 提示系统,包含:
1. **Toast(右上角)** - 瞬时提示,无状态设计
2. **Task Center(右下角任务中心)** - 长任务状态系统,有状态
核心要求:
- 两者完全独立,不共享逻辑
- Task 必须通用化,不绑定具体业务类型
- 实现 UI Lock 机制
- 支持多语言国际化
---
## 架构设计
### 1. 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ UI 提示系统 │
├───────────────────────────┬─────────────────────────────────┤
│ Toast 系统 │ Task Center 系统 │
│ (右上角,无状态) │ (右下角,有状态) │
├───────────────────────────┼─────────────────────────────────┤
│ • 自动消失 │ • 手动关闭 │
│ • 多条堆叠 │ • 单一实例 │
│ • 类型:success/error/ │ • 生命周期独立 │
│ info/warning │ • 进度追踪 │
└───────────────────────────┴─────────────────────────────────┘
```
### 2. Toast API
```js
showToast({
title, // 标题
message, // 消息内容
type: "success | error | info | warning",
duration // 显示时长(毫秒)
});
```
### 3. Task API
```js
// 创建任务
startTask({
id, // 任务ID
title, // 任务标题(国际化key)
total, // 总数量
current, // 当前进度
currentItem // 当前处理项
});
// 更新任务
updateTask({
id, // 任务ID
current, // 当前进度
currentItem // 当前处理项
});
// 完成任务
finishTask({
id, // 任务ID
total, // 总数量
success, // 成功数量
failed // 失败数量
});
// 手动关闭
closeTask(id);
```
---
## 修改内容
### 1. 修改 CSS 文件 (`custom-file/index.css`)
**新增样式:**
- Toast 容器样式(右上角固定定位)
- Toast 单条样式(支持不同类型)
- Toast 动画(进入/退出)
- Task Center 容器样式(右下角固定定位)
- Task Center 进度条样式
- Task Center 结果展示样式
- UI Lock 样式
**删除旧样式:**
- 删除 `.task-dock` 相关样式(如仍存在)
### 2. 修改 JS 文件 (`custom-file/index.js`)
**新增方法:**
| 方法名 | 功能 |
|--------|------|
| `showToast` | 显示 Toast 提示 |
| `startTask` | 创建并启动任务 |
| `updateTask` | 更新任务进度 |
| `finishTask` | 完成任务 |
| `closeTask` | 关闭任务 UI |
| `lockUI` | 锁定 UI |
| `unlockUI` | 解锁 UI |
| `isUILocked` | 检查 UI 是否锁定 |
**修改方法:**
- `render` - 添加 Toast 和 Task Center 的 DOM 结构
- `internalDownloadBatch` - 使用新的 Task API
**删除方法:**
- `addTask`(旧版本)
- `updateTask`(旧版本)
- `finishTask`(旧版本)
- `clearTasks`(旧版本)
- `updateTaskStats`(旧版本)
### 3. 新增多语言配置
```js
languages: {
"zh-CN": {
// ... 现有配置
toast: {
success: "成功",
error: "错误",
info: "提示",
warning: "警告"
},
task: {
title: {
processing: "处理中"
},
total: "总计",
success: "成功",
failed: "失败",
currentItem: "当前项",
close: "关闭"
}
},
"en-US": {
// ... 现有配置
toast: {
success: "Success",
error: "Error",
info: "Info",
warning: "Warning"
},
task: {
title: {
processing: "Processing"
},
total: "Total",
success: "Success",
failed: "Failed",
currentItem: "Current",
close: "Close"
}
}
}
```
---
## 实施步骤
| 步骤 | 任务 | 文件 | 状态 |
|------|------|------|------|
| 1 | 添加 Toast 和 Task Center 的 CSS 样式 | `custom-file/index.css` | 待执行 |
| 2 | 更新多语言配置 | `custom-file/index.js` | 待执行 |
| 3 | 添加 Toast 相关方法 | `custom-file/index.js` | 待执行 |
| 4 | 添加 Task Center 相关方法 | `custom-file/index.js` | 待执行 |
| 5 | 添加 UI Lock 机制 | `custom-file/index.js` | 待执行 |
| 6 | 修改 render 方法添加 DOM 结构 | `custom-file/index.js` | 待执行 |
| 7 | 修改 internalDownloadBatch 使用新 API | `custom-file/index.js` | 待执行 |
| 8 | 删除旧的任务管理方法 | `custom-file/index.js` | 待执行 |
---
## 风险评估
| 风险项 | 风险等级 | 处理方式 |
|--------|----------|----------|
| Task 状态与 UI 不同步 | 中 | 确保 task 对象独立于 UI 存在 |
| Toast 堆叠过多 | 低 | 限制最大显示数量(如5条) |
| UI Lock 状态异常 | 中 | 在任务完成时强制解锁 |
| 多语言文案遗漏 | 低 | 统一使用 `this.language.xxx` 模式 |
---
## 兼容性
- 原有 API 调用保持兼容
- 新增 API 为可选参数
- 不影响现有文件管理功能
---
## 最终目标
实现一个 **"通用任务系统 + Toast 提示系统 + UI Lock + i18n"** 的轻量 UI 架构,支持未来扩展:
- 批量上传
- 批量下载
- 批量删除
- 任意异步任务
无需修改核心结构,仅通过 task 数据驱动。