tree-upload-vue3 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -31
- package/dist/App.vue.d.ts +49 -1
- package/dist/components/SchemaTable.vue.d.ts +4 -0
- package/dist/components/SchemaUpload.vue.d.ts +3 -0
- package/dist/components/TreeUpload.vue.d.ts +17 -1
- package/dist/index.d.ts +5 -1
- package/dist/tree-upload-vue3.css +1 -1
- package/dist/tree-upload-vue3.es.js +522 -444
- package/dist/tree-upload-vue3.umd.js +1 -1
- package/dist/types/index.d.ts +6 -0
- package/package.json +20 -2
package/README.md
CHANGED
|
@@ -1,42 +1,179 @@
|
|
|
1
|
-
# Tree Upload
|
|
1
|
+
# Tree Upload Vue 3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
基于 Vue 3 + Element Plus 的 Schema 驱动型树形文件管理组件。支持左侧树形导航、右侧文件上传与列表管理,提供高度可配置的 JSON Schema 接口。
|
|
4
4
|
|
|
5
|
-
## 特性
|
|
5
|
+
## ✨ 特性
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
7
|
+
- **Schema 驱动**: 通过 JSON 配置生成完整界面。
|
|
8
|
+
- **树形导航**:
|
|
9
|
+
- 支持静态数据与 API 动态加载。
|
|
10
|
+
- 支持 CRUD 操作(增删改)。
|
|
11
|
+
- **支持拖拽调整宽度**。
|
|
12
|
+
- **文件管理**:
|
|
13
|
+
- 集成文件上传(拖拽、多选、格式限制、大小限制)。
|
|
14
|
+
- 列表展示(分页、排序、自定义列)。
|
|
15
|
+
- 自动根据配置生成上传提示文案。
|
|
16
|
+
- **校验机制**: 内置 `validate()` 方法,支持验证必填项、最小/最大文件数量等。
|
|
17
|
+
- **生命周期事件**: 暴露上传前/后、删除前/后等关键钩子,支持上下文透传。
|
|
18
|
+
- **文件预览**: 内置多种文件格式预览功能。
|
|
19
|
+
- **权限控制**: 细粒度的按钮级别权限控制。
|
|
20
|
+
- **模式切换**: 支持“查看模式”与“编辑模式”。
|
|
15
21
|
|
|
16
|
-
##
|
|
22
|
+
## 📦 安装
|
|
17
23
|
|
|
24
|
+
```bash
|
|
25
|
+
npm install tree-upload-vue3 element-plus @element-plus/icons-vue file-preview-vue3-ts
|
|
18
26
|
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
|
|
28
|
+
确保你的项目中已经安装了 Vue 3。
|
|
29
|
+
|
|
30
|
+
## 🚀 快速上手
|
|
31
|
+
|
|
32
|
+
在你的 Vue 组件中引入并使用:
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<template>
|
|
36
|
+
<div style="height: 600px;">
|
|
37
|
+
<TreeUpload
|
|
38
|
+
ref="treeUploadRef"
|
|
39
|
+
:schema="mySchema"
|
|
40
|
+
mode="edit"
|
|
41
|
+
@upload-before="handleBeforeUpload"
|
|
42
|
+
@upload-success="handleUploadSuccess"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { reactive, ref } from 'vue';
|
|
49
|
+
import { TreeUpload, type TreeUploadSchema } from 'tree-upload-vue3';
|
|
50
|
+
import 'tree-upload-vue3/dist/tree-upload-vue3.css'; // 引入样式
|
|
51
|
+
|
|
52
|
+
const treeUploadRef = ref();
|
|
53
|
+
|
|
54
|
+
const handleBeforeUpload = ({ file, context }) => {
|
|
55
|
+
console.log('准备上传:', file.name);
|
|
56
|
+
console.log('当前选中节点:', context.variables.currentNode);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleUploadSuccess = (response) => {
|
|
60
|
+
console.log('上传成功:', response);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const mySchema = reactive<TreeUploadSchema>({
|
|
64
|
+
version: '1.0.0',
|
|
65
|
+
tree: {
|
|
66
|
+
dataSource: {
|
|
67
|
+
type: 'static',
|
|
68
|
+
data: [
|
|
69
|
+
{
|
|
70
|
+
id: '1',
|
|
71
|
+
label: '必填文档',
|
|
72
|
+
required: true,
|
|
73
|
+
minCount: 1,
|
|
74
|
+
accept: '.pdf',
|
|
75
|
+
children: []
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
ui: {
|
|
80
|
+
width: 260,
|
|
81
|
+
resizable: true // 开启拖拽调整宽度
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
upload: {
|
|
85
|
+
enabled: true,
|
|
86
|
+
action: 'https://api.example.com/upload',
|
|
87
|
+
multiple: true,
|
|
88
|
+
ui: {
|
|
89
|
+
showTip: true // 自动显示 "支持 .pdf (最少 1 个)" 等提示
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
table: {
|
|
93
|
+
dataSource: { type: 'static', data: [] },
|
|
94
|
+
columns: [
|
|
95
|
+
{ prop: 'name', label: '文件名' },
|
|
96
|
+
{ prop: 'size', label: '大小', formatter: 'fileSize' }
|
|
97
|
+
],
|
|
98
|
+
ui: {
|
|
99
|
+
pagination: { enabled: true, pageSize: 20 }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
</script>
|
|
33
104
|
```
|
|
34
105
|
|
|
35
|
-
##
|
|
106
|
+
## 📖 API
|
|
107
|
+
|
|
108
|
+
### Props
|
|
109
|
+
|
|
110
|
+
| 属性名 | 类型 | 默认值 | 说明 |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `schema` | `TreeUploadSchema` | **必填** | 组件的核心配置对象 |
|
|
113
|
+
| `mode` | `'view' \| 'edit'` | `'edit'` | 视图模式。`view` 模式下隐藏上传和模板区域 |
|
|
114
|
+
|
|
115
|
+
### Events
|
|
116
|
+
|
|
117
|
+
| 事件名 | 参数 | 说明 |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| `upload-before` | `{ file: File, context: SchemaContext }` | 上传前触发,包含文件对象和当前上下文(如选中节点) |
|
|
120
|
+
| `upload-success` | `response: any` | 上传成功后触发 |
|
|
121
|
+
| `upload-error` | `error: any` | 上传失败后触发 |
|
|
122
|
+
| `delete-before` | `file: FileItem` | 用户点击删除确认后,执行删除逻辑前触发 |
|
|
123
|
+
| `delete-after` | `file: FileItem` | 删除逻辑执行完毕后触发 |
|
|
124
|
+
|
|
125
|
+
### Methods
|
|
36
126
|
|
|
37
|
-
|
|
38
|
-
|
|
127
|
+
| 方法名 | 参数 | 返回值 | 说明 |
|
|
128
|
+
| --- | --- | --- | --- |
|
|
129
|
+
| `validate()` | - | `Promise<{ valid: boolean, errors: string[] }>` | 校验整个树结构是否满足配置要求(如 `required`, `minCount`) |
|
|
130
|
+
|
|
131
|
+
### Schema 配置详解
|
|
132
|
+
|
|
133
|
+
#### 1. Tree Schema (`tree`)
|
|
134
|
+
|
|
135
|
+
| 字段 | 类型 | 说明 |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| `dataSource` | `{ type: 'static' \| 'api', ... }` | 数据源配置。`api` 模式需配置 `url`, `method`, `responsePath` |
|
|
138
|
+
| `ui` | `TreeUISchema` | UI 配置,包含 `width`, `resizable`, `showRoot` 等 |
|
|
139
|
+
| `actions` | `TreeActionSchema[]` | 树操作按钮配置(增删改等) |
|
|
140
|
+
|
|
141
|
+
**节点配置 (CategoryNode)**:
|
|
142
|
+
- `required`: 是否必须上传文件。
|
|
143
|
+
- `minCount` / `maxCount`: 文件数量限制。
|
|
144
|
+
- `maxSize`: 单个文件大小限制 (Bytes)。
|
|
145
|
+
- `accept`: 允许的文件类型 (如 `.jpg,.png`)。
|
|
146
|
+
- `meta.templates`: 模板文件列表,选中节点时会自动显示在顶部。
|
|
147
|
+
|
|
148
|
+
#### 2. Upload Schema (`upload`)
|
|
149
|
+
|
|
150
|
+
| 字段 | 类型 | 说明 |
|
|
151
|
+
| --- | --- | --- |
|
|
152
|
+
| `action` | `string` | 上传接口地址 |
|
|
153
|
+
| `accept` | `string` | 全局文件类型限制 (会被节点配置覆盖) |
|
|
154
|
+
| `ui.showTip` | `boolean` | 是否显示动态提示文案 (自动聚合大小、数量、格式限制) |
|
|
155
|
+
|
|
156
|
+
#### 3. Table Schema (`table`)
|
|
157
|
+
|
|
158
|
+
| 字段 | 类型 | 说明 |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| `dataSource` | `{ type: 'static' \| 'api', ... }` | 表格数据源。`api` 模式支持动态参数 (如 `requirementId: '${currentNode.id}'`) |
|
|
161
|
+
| `columns` | `TableColumnSchema[]` | 列定义。支持 `formatter: 'fileSize'` |
|
|
162
|
+
| `ui.pagination` | `{ enabled: boolean, pageSize: number }` | 分页配置 (无数据时自动隐藏) |
|
|
163
|
+
|
|
164
|
+
## 🛠 开发与构建
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# 安装依赖
|
|
168
|
+
npm install
|
|
169
|
+
|
|
170
|
+
# 启动开发服务器
|
|
171
|
+
npm run dev
|
|
172
|
+
|
|
173
|
+
# 构建库文件
|
|
174
|
+
npm run build
|
|
175
|
+
```
|
|
39
176
|
|
|
40
|
-
##
|
|
177
|
+
## 📄 License
|
|
41
178
|
|
|
42
|
-
|
|
179
|
+
MIT
|
package/dist/App.vue.d.ts
CHANGED
|
@@ -1,2 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
import { TreeUploadSchema } from './types';
|
|
2
|
+
declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
|
|
3
|
+
treeUploadRef: import('vue').CreateComponentPublicInstanceWithMixins<Readonly<{
|
|
4
|
+
schema: TreeUploadSchema;
|
|
5
|
+
mode?: "view" | "edit";
|
|
6
|
+
}> & Readonly<{
|
|
7
|
+
"onUpload-success"?: ((...args: any[]) => any) | undefined;
|
|
8
|
+
"onUpload-error"?: ((...args: any[]) => any) | undefined;
|
|
9
|
+
"onUpload-before"?: ((...args: any[]) => any) | undefined;
|
|
10
|
+
"onDelete-before"?: ((...args: any[]) => any) | undefined;
|
|
11
|
+
"onDelete-after"?: ((...args: any[]) => any) | undefined;
|
|
12
|
+
}>, {
|
|
13
|
+
validate: () => Promise<{
|
|
14
|
+
valid: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
17
|
+
"upload-success": (...args: any[]) => void;
|
|
18
|
+
"upload-error": (...args: any[]) => void;
|
|
19
|
+
"upload-before": (...args: any[]) => void;
|
|
20
|
+
"delete-before": (...args: any[]) => void;
|
|
21
|
+
"delete-after": (...args: any[]) => void;
|
|
22
|
+
}, import('vue').PublicProps, {
|
|
23
|
+
mode: "view" | "edit";
|
|
24
|
+
}, false, {}, {}, import('vue').GlobalComponents, import('vue').GlobalDirectives, string, {
|
|
25
|
+
nodeFormRef: unknown;
|
|
26
|
+
}, HTMLDivElement, import('vue').ComponentProvideOptions, {
|
|
27
|
+
P: {};
|
|
28
|
+
B: {};
|
|
29
|
+
D: {};
|
|
30
|
+
C: {};
|
|
31
|
+
M: {};
|
|
32
|
+
Defaults: {};
|
|
33
|
+
}, Readonly<{
|
|
34
|
+
schema: TreeUploadSchema;
|
|
35
|
+
mode?: "view" | "edit";
|
|
36
|
+
}> & Readonly<{
|
|
37
|
+
"onUpload-success"?: ((...args: any[]) => any) | undefined;
|
|
38
|
+
"onUpload-error"?: ((...args: any[]) => any) | undefined;
|
|
39
|
+
"onUpload-before"?: ((...args: any[]) => any) | undefined;
|
|
40
|
+
"onDelete-before"?: ((...args: any[]) => any) | undefined;
|
|
41
|
+
"onDelete-after"?: ((...args: any[]) => any) | undefined;
|
|
42
|
+
}>, {
|
|
43
|
+
validate: () => Promise<{
|
|
44
|
+
valid: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
}, {}, {}, {}, {
|
|
47
|
+
mode: "view" | "edit";
|
|
48
|
+
}> | null;
|
|
49
|
+
}, any>;
|
|
2
50
|
export default _default;
|
|
@@ -6,8 +6,12 @@ type __VLS_Props = {
|
|
|
6
6
|
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
7
7
|
action: (...args: any[]) => void;
|
|
8
8
|
refresh: (...args: any[]) => void;
|
|
9
|
+
"delete-before": (...args: any[]) => void;
|
|
10
|
+
"delete-after": (...args: any[]) => void;
|
|
9
11
|
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
12
|
onAction?: ((...args: any[]) => any) | undefined;
|
|
11
13
|
onRefresh?: ((...args: any[]) => any) | undefined;
|
|
14
|
+
"onDelete-before"?: ((...args: any[]) => any) | undefined;
|
|
15
|
+
"onDelete-after"?: ((...args: any[]) => any) | undefined;
|
|
12
16
|
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
13
17
|
export default _default;
|
|
@@ -2,12 +2,15 @@ import { UploadSchema } from '../types';
|
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
schema: UploadSchema;
|
|
4
4
|
disabled?: boolean;
|
|
5
|
+
fileCount?: number;
|
|
5
6
|
};
|
|
6
7
|
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
7
8
|
"upload-success": (...args: any[]) => void;
|
|
8
9
|
"upload-error": (...args: any[]) => void;
|
|
10
|
+
"upload-before": (...args: any[]) => void;
|
|
9
11
|
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
12
|
"onUpload-success"?: ((...args: any[]) => any) | undefined;
|
|
11
13
|
"onUpload-error"?: ((...args: any[]) => any) | undefined;
|
|
14
|
+
"onUpload-before"?: ((...args: any[]) => any) | undefined;
|
|
12
15
|
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
13
16
|
export default _default;
|
|
@@ -3,7 +3,23 @@ type __VLS_Props = {
|
|
|
3
3
|
schema: TreeUploadSchema;
|
|
4
4
|
mode?: 'view' | 'edit';
|
|
5
5
|
};
|
|
6
|
-
declare const _default: import('vue').DefineComponent<__VLS_Props, {
|
|
6
|
+
declare const _default: import('vue').DefineComponent<__VLS_Props, {
|
|
7
|
+
validate: () => Promise<{
|
|
8
|
+
valid: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
11
|
+
"upload-success": (...args: any[]) => void;
|
|
12
|
+
"upload-error": (...args: any[]) => void;
|
|
13
|
+
"upload-before": (...args: any[]) => void;
|
|
14
|
+
"delete-before": (...args: any[]) => void;
|
|
15
|
+
"delete-after": (...args: any[]) => void;
|
|
16
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
17
|
+
"onUpload-success"?: ((...args: any[]) => any) | undefined;
|
|
18
|
+
"onUpload-error"?: ((...args: any[]) => any) | undefined;
|
|
19
|
+
"onUpload-before"?: ((...args: any[]) => any) | undefined;
|
|
20
|
+
"onDelete-before"?: ((...args: any[]) => any) | undefined;
|
|
21
|
+
"onDelete-after"?: ((...args: any[]) => any) | undefined;
|
|
22
|
+
}>, {
|
|
7
23
|
mode: "view" | "edit";
|
|
8
24
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
|
|
9
25
|
nodeFormRef: unknown;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
1
2
|
import { default as TreeUpload } from './components/TreeUpload.vue';
|
|
2
3
|
import { TreeUploadSchema, CategoryNode, FileItem } from './types';
|
|
3
4
|
export { TreeUpload };
|
|
4
5
|
export type { TreeUploadSchema, CategoryNode, FileItem };
|
|
5
|
-
|
|
6
|
+
declare const _default: {
|
|
7
|
+
install(app: App): void;
|
|
8
|
+
};
|
|
9
|
+
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.schema-tree[data-v-
|
|
1
|
+
.schema-tree[data-v-55bc0bc1]{height:100%;position:relative;display:flex;flex-direction:column}.tree-toolbar[data-v-55bc0bc1]{padding:8px;border-bottom:1px solid var(--el-border-color-lighter);display:flex;gap:8px}.custom-tree-node[data-v-55bc0bc1]{flex:1;display:flex;align-items:center;justify-content:space-between;font-size:14px;padding-right:8px}.custom-tree-node .actions[data-v-55bc0bc1]{display:none}.custom-tree-node:hover .actions[data-v-55bc0bc1]{display:inline-flex}.context-menu[data-v-55bc0bc1]{position:fixed;z-index:1000;background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color);box-shadow:var(--el-box-shadow-light);border-radius:4px;padding:5px 0;min-width:120px}.context-menu-item[data-v-55bc0bc1]{padding:8px 16px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:13px;color:var(--el-text-color-regular)}.context-menu-item[data-v-55bc0bc1]:hover{background-color:var(--el-fill-color-light);color:var(--el-color-primary)}.node-label[data-v-55bc0bc1]{display:flex;align-items:center}.required-star[data-v-55bc0bc1]{color:var(--el-color-danger);margin-right:4px;font-weight:700}.schema-upload[data-v-322cb598]{width:100%}.schema-table[data-v-e3496cb9]{height:100%;display:flex;flex-direction:column}.table-toolbar[data-v-e3496cb9]{margin-bottom:12px}.pagination-wrapper[data-v-e3496cb9]{margin-top:12px;display:flex;justify-content:flex-end}.schema-template[data-v-ea09f00e]{margin-bottom:12px;background-color:var(--el-fill-color-light);padding:8px 12px;border-radius:4px;border:1px solid var(--el-border-color-light)}.template-container[data-v-ea09f00e]{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.label[data-v-ea09f00e]{font-size:13px;color:var(--el-text-color-secondary);white-space:nowrap}.tags-wrapper[data-v-ea09f00e]{display:flex;flex-wrap:wrap;gap:8px}.template-tag[data-v-ea09f00e]{cursor:pointer;border:none;background-color:var(--el-bg-color);transition:all .2s}.template-tag[data-v-ea09f00e]:hover{color:var(--el-color-primary);transform:translateY(-1px);box-shadow:var(--el-box-shadow-lighter)}.tag-content[data-v-ea09f00e]{display:flex;align-items:center;gap:4px}.tpl-name[data-v-ea09f00e]{line-height:1}.preview-content[data-v-6a6a8acf]{height:100%;min-height:500px;display:flex;flex-direction:column}.custom-header[data-v-6a6a8acf]{display:flex;justify-content:space-between;align-items:center;padding-right:0}.header-controls[data-v-6a6a8acf]{display:flex;align-items:center;gap:8px}.tree-upload-container[data-v-1ad270d1]{display:flex;height:100%;border:1px solid var(--el-border-color);background:var(--el-bg-color)}.tree-pane[data-v-1ad270d1]{border-right:1px solid var(--el-border-color);height:100%;overflow-y:auto;padding:10px;box-sizing:border-box;flex-shrink:0}.resize-handle[data-v-1ad270d1]{width:5px;height:100%;cursor:col-resize;background-color:transparent;margin-left:-3px;z-index:10;position:relative;transition:background-color .2s}.resize-handle[data-v-1ad270d1]:hover,.resize-handle[data-v-1ad270d1]:active{background-color:var(--el-color-primary);opacity:.5}.content-pane[data-v-1ad270d1]{flex:1;display:flex;flex-direction:column;padding:16px;overflow:hidden}.top-section[data-v-1ad270d1]{margin-bottom:16px}.table-section[data-v-1ad270d1]{flex:1;overflow:hidden}
|