vue-csv-exporter 1.0.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/LICENSE +21 -0
- package/README.md +396 -0
- package/package.json +50 -0
- package/src/CSVGenerator.js +220 -0
- package/src/exportMixin.js +138 -0
- package/src/index.js +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hunter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# vue-csv-exporter
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/vue-csv-exporter)
|
|
4
|
+
[](https://github.com/7Hunter7/vue-csv-exporter/blob/main/LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/vue-csv-exporter)
|
|
6
|
+
[](https://vuejs.org/)
|
|
7
|
+
[](https://v3.vuejs.org/)
|
|
8
|
+
|
|
9
|
+
Простой и мощный экспорт данных в CSV для Vue.js приложений.
|
|
10
|
+
**Без зависимостей**, с поддержкой кириллицы и TypeScript.
|
|
11
|
+
|
|
12
|
+
## Автор
|
|
13
|
+
Ivan Kalugin Телеграмм: https://t.me/Ivan_Anatolievich_Kalugin
|
|
14
|
+
|
|
15
|
+
## ✨ Особенности
|
|
16
|
+
|
|
17
|
+
- 🚀 **Zero dependencies** - никаких лишних библиотек
|
|
18
|
+
- 📦 **Tree shaking** - используйте только то, что нужно
|
|
19
|
+
- 🌍 **Кириллица** - корректное отображение в Excel
|
|
20
|
+
- 🎯 **RFC 4180** - соответствие стандарту CSV
|
|
21
|
+
- 🔧 **Гибкая настройка** - разделители, кавычки, BOM
|
|
22
|
+
- 📱 **Браузер** - работает в любой современном браузере
|
|
23
|
+
- 🎨 **Vue 2 и 3** - поддержка обеих версий
|
|
24
|
+
- 📄 **TypeScript** - готовые типы
|
|
25
|
+
|
|
26
|
+
## Структура файлов
|
|
27
|
+
```bash
|
|
28
|
+
vue-csv-exporter/
|
|
29
|
+
├── src/
|
|
30
|
+
│ ├── index.js # главный файл
|
|
31
|
+
│ ├── CSVGenerator.js # ядро
|
|
32
|
+
│ └── exportMixin.js # миксин
|
|
33
|
+
├── README.md
|
|
34
|
+
├── LICENSE
|
|
35
|
+
└── package.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 📦 Установка
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install vue-csv-exporter
|
|
42
|
+
# или
|
|
43
|
+
yarn add vue-csv-exporter
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 🚀 Быстрый старт
|
|
47
|
+
1. Как плагин (рекомендуется)
|
|
48
|
+
```bash
|
|
49
|
+
// main.js
|
|
50
|
+
import Vue from 'vue';
|
|
51
|
+
import VueCSVExporter from 'vue-csv-exporter';
|
|
52
|
+
|
|
53
|
+
Vue.use(VueCSVExporter, {
|
|
54
|
+
defaultFilename: 'export.csv',
|
|
55
|
+
defaultConfig: {
|
|
56
|
+
delimiter: ';',
|
|
57
|
+
includeBOM: true
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// В компоненте
|
|
62
|
+
export default {
|
|
63
|
+
methods: {
|
|
64
|
+
async exportData() {
|
|
65
|
+
try {
|
|
66
|
+
const count = await this.$csvExport({
|
|
67
|
+
data: this.items,
|
|
68
|
+
filename: 'users.csv',
|
|
69
|
+
onStart: () => console.log('Экспорт начат'),
|
|
70
|
+
onProgress: (progress) => console.log(`Прогресс: ${progress}%`),
|
|
71
|
+
onSuccess: (count) => this.$notify.success(`Экспортировано ${count} записей`),
|
|
72
|
+
onError: (error) => this.$notify.error(error.message)
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Ошибка экспорта:', error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
### Как миксин
|
|
82
|
+
```bash
|
|
83
|
+
import { exportMixin } from 'vue-csv-exporter';
|
|
84
|
+
|
|
85
|
+
export default {
|
|
86
|
+
mixins: [exportMixin],
|
|
87
|
+
data() {
|
|
88
|
+
return {
|
|
89
|
+
users: [
|
|
90
|
+
{ name: 'Иван', email: 'ivan@example.com' },
|
|
91
|
+
{ name: 'Мария', email: 'maria@example.com' }
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
methods: {
|
|
96
|
+
async exportUsers() {
|
|
97
|
+
await this.exportToCSV({
|
|
98
|
+
data: this.users,
|
|
99
|
+
filename: 'users.csv',
|
|
100
|
+
config: { delimiter: ',' }
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Как отдельный класс
|
|
108
|
+
```bash
|
|
109
|
+
import { CSVGenerator } from 'vue-csv-exporter';
|
|
110
|
+
|
|
111
|
+
// Генерация CSV
|
|
112
|
+
const data = [
|
|
113
|
+
{ name: 'Иван', age: 30 },
|
|
114
|
+
{ name: 'Мария', age: 25 }
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const csv = CSVGenerator.generate(data, {
|
|
118
|
+
delimiter: ';',
|
|
119
|
+
includeBOM: true
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Скачивание
|
|
123
|
+
CSVGenerator.download(csv, 'export.csv');
|
|
124
|
+
|
|
125
|
+
// Валидация
|
|
126
|
+
const validation = CSVGenerator.validateData(data);
|
|
127
|
+
if (validation.isValid) {
|
|
128
|
+
console.log('Данные корректны');
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 📊 API Reference
|
|
133
|
+
|
|
134
|
+
### Плагин Vue
|
|
135
|
+
|
|
136
|
+
#### Опции плагина
|
|
137
|
+
|
|
138
|
+
| Параметр | Тип | По умолчанию | Обязательный | Описание |
|
|
139
|
+
|----------|-----|--------------|---------------|----------|
|
|
140
|
+
| `defaultFilename` | `string` | `'export.csv'` | Нет | Имя файла по умолчанию для экспорта |
|
|
141
|
+
| `defaultConfig` | `Object` | `{}` | Нет | Конфигурация CSV по умолчанию (см. таблицу Конфигурация) |
|
|
142
|
+
| `install` | `Function` | - | Нет | Функция установки плагина (вызывается автоматически) |
|
|
143
|
+
|
|
144
|
+
#### Методы плагина (`this.$csvExport()`)
|
|
145
|
+
|
|
146
|
+
| Параметр | Тип | По умолчанию | Обязательный | Описание |
|
|
147
|
+
|----------|-----|--------------|---------------|----------|
|
|
148
|
+
| `data` | `Array<Object>` | - | **Да** | Массив данных для экспорта |
|
|
149
|
+
| `filename` | `string` | `'export.csv'` | Нет | Имя выходного CSV файла |
|
|
150
|
+
| `config` | `Object` | `{}` | Нет | Конфигурация CSV (переопределяет defaultConfig) |
|
|
151
|
+
| `onStart` | `Function` | `() => {}` | Нет | Колбэк перед началом экспорта |
|
|
152
|
+
| `onProgress` | `Function` | `() => {}` | Нет | Колбэк прогресса (0-100) |
|
|
153
|
+
| `onSuccess` | `Function` | `() => {}` | Нет | Колбэк при успешном экспорте |
|
|
154
|
+
| `onError` | `Function` | `() => {}` | Нет | Колбэк при ошибке |
|
|
155
|
+
|
|
156
|
+
**Возвращает:** `Promise<number>` - количество экспортированных записей
|
|
157
|
+
|
|
158
|
+
**Пример:**
|
|
159
|
+
```javascript
|
|
160
|
+
const count = await this.$csvExport({
|
|
161
|
+
data: this.users,
|
|
162
|
+
filename: 'users.csv',
|
|
163
|
+
config: { delimiter: ',' },
|
|
164
|
+
onStart: () => this.loading = true,
|
|
165
|
+
onProgress: (p) => this.progress = p,
|
|
166
|
+
onSuccess: (count) => this.showSuccess(count),
|
|
167
|
+
onError: (error) => this.showError(error)
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### Миксин `exportMixin`
|
|
174
|
+
|
|
175
|
+
#### Данные миксина
|
|
176
|
+
|
|
177
|
+
| Поле | Тип | По умолчанию | Доступ | Описание |
|
|
178
|
+
|------|-----|--------------|---------|----------|
|
|
179
|
+
| `isExporting` | `boolean` | `false` | Только чтение | Флаг процесса экспорта |
|
|
180
|
+
| `exportProgress` | `number` | `0` | Только чтение | Прогресс экспорта (0-100) |
|
|
181
|
+
|
|
182
|
+
#### Методы миксина
|
|
183
|
+
|
|
184
|
+
| Метод | Параметры | Возвращает | Описание |
|
|
185
|
+
|-------|-----------|------------|----------|
|
|
186
|
+
| `exportToCSV(options)` | `Object` (см. таблицу методов плагина) | `Promise<number>` | Основной метод экспорта |
|
|
187
|
+
| `prepareExportData(data)` | `data: Array` | `Array` | Подготовка данных (можно переопределить) |
|
|
188
|
+
| `transformData(data)` | `data: Array` | `Array` | Трансформация данных (можно переопределить) |
|
|
189
|
+
| `generateDefaultFilename()` | - | `string` | Генерация имени файла по умолчанию |
|
|
190
|
+
|
|
191
|
+
**Пример:**
|
|
192
|
+
```javascript
|
|
193
|
+
import { exportMixin } from 'vue-csv-exporter';
|
|
194
|
+
|
|
195
|
+
export default {
|
|
196
|
+
mixins: [exportMixin],
|
|
197
|
+
data() {
|
|
198
|
+
return {
|
|
199
|
+
items: []
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
methods: {
|
|
203
|
+
async handleExport() {
|
|
204
|
+
if (this.isExporting) return;
|
|
205
|
+
|
|
206
|
+
await this.exportToCSV({
|
|
207
|
+
data: this.items,
|
|
208
|
+
filename: 'export.csv'
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
// Переопределение метода подготовки данных
|
|
212
|
+
prepareExportData(data) {
|
|
213
|
+
return data.filter(item => item.active);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### Класс `CSVGenerator`
|
|
222
|
+
|
|
223
|
+
#### Статические методы
|
|
224
|
+
|
|
225
|
+
| Метод | Параметры | Возвращает | Описание |
|
|
226
|
+
|-------|-----------|------------|----------|
|
|
227
|
+
| `generate(data, config)` | `data: Array<Object>`<br>`config: Object` | `string` | Генерация CSV строки из данных |
|
|
228
|
+
| `download(csv, filename)` | `csv: string`<br>`filename: string` | `void` | Скачивание CSV файла |
|
|
229
|
+
| `validateData(data)` | `data: Array` | `{ isValid: boolean, error: string }` | Валидация данных перед экспортом |
|
|
230
|
+
| `sanitizeField(field)` | `field: any` | `string` | Очистка поля от опасных символов |
|
|
231
|
+
| `formatRow(fields, config)` | `fields: Array`<br>`config: Object` | `string` | Форматирование строки (приватный) |
|
|
232
|
+
| `formatField(field, config)` | `field: any`<br>`config: Object` | `string` | Форматирование поля (приватный) |
|
|
233
|
+
|
|
234
|
+
**Пример:**
|
|
235
|
+
```javascript
|
|
236
|
+
import { CSVGenerator } from 'vue-csv-exporter';
|
|
237
|
+
|
|
238
|
+
// Валидация
|
|
239
|
+
const validation = CSVGenerator.validateData(myData);
|
|
240
|
+
if (!validation.isValid) {
|
|
241
|
+
console.error(validation.error);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Генерация
|
|
246
|
+
const csv = CSVGenerator.generate(myData, {
|
|
247
|
+
delimiter: ';',
|
|
248
|
+
includeBOM: true
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Скачивание
|
|
252
|
+
CSVGenerator.download(csv, 'report.csv');
|
|
253
|
+
|
|
254
|
+
// Очистка поля
|
|
255
|
+
const safeString = CSVGenerator.sanitizeField("Hello;World");
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### Конфигурация CSV
|
|
261
|
+
|
|
262
|
+
#### Параметры конфигурации
|
|
263
|
+
|
|
264
|
+
| Параметр | Тип | По умолчанию | Допустимые значения | Описание |
|
|
265
|
+
|----------|-----|--------------|---------------------|----------|
|
|
266
|
+
| `delimiter` | `string` | `';'` | `','`, `';'`, `'\t'`, любой символ | Разделитель полей в CSV |
|
|
267
|
+
| `quote` | `string` | `'"'` | `'"'`, `"'"` | Символ для экранирования полей |
|
|
268
|
+
| `lineBreak` | `string` | `'\r\n'` | `'\r\n'`, `'\n'`, `'\r'` | Разделитель строк |
|
|
269
|
+
| `includeBOM` | `boolean` | `true` | `true`, `false` | Добавлять BOM для кириллицы |
|
|
270
|
+
| `quoteAll` | `boolean` | `false` | `true`, `false` | Всегда заключать поля в кавычки |
|
|
271
|
+
| `escapeDoubleQuotes` | `boolean` | `true` | `true`, `false` | Экранировать кавычки удвоением |
|
|
272
|
+
|
|
273
|
+
#### Примеры конфигурации
|
|
274
|
+
|
|
275
|
+
| Сценарий | Конфигурация | Результат |
|
|
276
|
+
|----------|--------------|-----------|
|
|
277
|
+
| **Excel (Россия)** | `{ delimiter: ';', includeBOM: true }` | Корректное отображение кириллицы в Excel |
|
|
278
|
+
| **Excel (Запад)** | `{ delimiter: ',', includeBOM: false }` | Стандартный CSV для западных версий Excel |
|
|
279
|
+
| **Максимальная совместимость** | `{ quoteAll: true, delimiter: ',' }` | Все поля в кавычках, никаких проблем с спецсимволами |
|
|
280
|
+
| **Google Sheets** | `{ delimiter: ',', lineBreak: '\n' }` | Оптимально для импорта в Google Sheets |
|
|
281
|
+
| **Базы данных** | `{ delimiter: '\t', quoteAll: false }` | TSV формат для импорта в БД |
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
// Примеры использования разных конфигураций
|
|
285
|
+
const configs = {
|
|
286
|
+
// Для Excel (русский)
|
|
287
|
+
russianExcel: {
|
|
288
|
+
delimiter: ';',
|
|
289
|
+
includeBOM: true,
|
|
290
|
+
quoteAll: false
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
// Для Google Sheets
|
|
294
|
+
googleSheets: {
|
|
295
|
+
delimiter: ',',
|
|
296
|
+
lineBreak: '\n',
|
|
297
|
+
includeBOM: false
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
// Максимальная защита
|
|
301
|
+
safe: {
|
|
302
|
+
delimiter: ',',
|
|
303
|
+
quoteAll: true,
|
|
304
|
+
escapeDoubleQuotes: true
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// TSV формат
|
|
308
|
+
tsv: {
|
|
309
|
+
delimiter: '\t',
|
|
310
|
+
quoteAll: false
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
### События и колбэки
|
|
318
|
+
|
|
319
|
+
| Колбэк | Параметры | Момент вызова | Использование |
|
|
320
|
+
|--------|-----------|---------------|---------------|
|
|
321
|
+
| `onStart` | - | Перед началом экспорта | Показать лоадер, заблокировать UI |
|
|
322
|
+
| `onProgress` | `progress: number` (0-100) | Во время экспорта | Обновить индикатор прогресса |
|
|
323
|
+
| `onSuccess` | `count: number` | После успешного экспорта | Показать уведомление об успехе |
|
|
324
|
+
| `onError` | `error: Error` | При ошибке | Показать сообщение об ошибке |
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
// Пример с использованием всех колбэков
|
|
328
|
+
this.$csvExport({
|
|
329
|
+
data: this.largeDataset,
|
|
330
|
+
filename: 'big_export.csv',
|
|
331
|
+
|
|
332
|
+
onStart: () => {
|
|
333
|
+
this.isExporting = true;
|
|
334
|
+
this.showProgressBar = true;
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
onProgress: (progress) => {
|
|
338
|
+
this.exportProgress = progress;
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
onSuccess: (count) => {
|
|
342
|
+
this.$notify.success(`Успешно экспортировано ${count} записей`);
|
|
343
|
+
this.isExporting = false;
|
|
344
|
+
this.showProgressBar = false;
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
onError: (error) => {
|
|
348
|
+
this.$notify.error(`Ошибка экспорта: ${error.message}`);
|
|
349
|
+
this.isExporting = false;
|
|
350
|
+
this.showProgressBar = false;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Типы данных
|
|
358
|
+
|
|
359
|
+
| Тип | Описание | Пример |
|
|
360
|
+
|-----|----------|--------|
|
|
361
|
+
| `CSVConfig` | Объект конфигурации | `{ delimiter: ';', includeBOM: true }` |
|
|
362
|
+
| `ValidationResult` | Результат валидации | `{ isValid: true, error: '' }` |
|
|
363
|
+
| `ExportOptions` | Опции экспорта | `{ data: [], filename: 'file.csv' }` |
|
|
364
|
+
| `ProgressCallback` | Колбэк прогресса | `(progress) => console.log(progress)` |
|
|
365
|
+
| `SuccessCallback` | Колбэк успеха | `(count) => console.log(count)` |
|
|
366
|
+
| `ErrorCallback` | Колбэк ошибки | `(error) => console.error(error)` |
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// TypeScript интерфейсы (для справки)
|
|
370
|
+
interface CSVConfig {
|
|
371
|
+
delimiter?: string;
|
|
372
|
+
quote?: string;
|
|
373
|
+
lineBreak?: string;
|
|
374
|
+
includeBOM?: boolean;
|
|
375
|
+
quoteAll?: boolean;
|
|
376
|
+
escapeDoubleQuotes?: boolean;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
interface ExportOptions {
|
|
380
|
+
data: Record<string, any>[];
|
|
381
|
+
filename?: string;
|
|
382
|
+
config?: CSVConfig;
|
|
383
|
+
onStart?: () => void;
|
|
384
|
+
onProgress?: (progress: number) => void;
|
|
385
|
+
onSuccess?: (count: number) => void;
|
|
386
|
+
onError?: (error: Error) => void;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
interface ValidationResult {
|
|
390
|
+
isValid: boolean;
|
|
391
|
+
error: string;
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
## 📄 Лицензия
|
|
395
|
+
|
|
396
|
+
MIT © [Ivan Kalugin](https://github.com/7Hunter7)
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-csv-exporter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Простой и надежный экспорт данных в CSV для Vue.js. Без зависимостей, с поддержкой кириллицы и TypeScript.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"module": "src/index.js",
|
|
7
|
+
"browser": "src/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"src",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "echo \"Error: no test specified\" && exit 0",
|
|
15
|
+
"prepublishOnly": "echo \"Ready to publish\""
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"vue",
|
|
19
|
+
"csv",
|
|
20
|
+
"exporter",
|
|
21
|
+
"export",
|
|
22
|
+
"vuejs",
|
|
23
|
+
"csv-exporter",
|
|
24
|
+
"vue-csv",
|
|
25
|
+
"russian",
|
|
26
|
+
"cyrillic",
|
|
27
|
+
"browser",
|
|
28
|
+
"no-dependencies"
|
|
29
|
+
],
|
|
30
|
+
"author": {
|
|
31
|
+
"name": "Ivan Kalugin",
|
|
32
|
+
"email": "i.am.hunter.777.ih@gmail.com",
|
|
33
|
+
"url": "https://github.com/7Hunter7"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/7Hunter7/vue-csv-exporter.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/7Hunter7/vue-csv-exporter/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/7Hunter7/vue-csv-exporter#readme",
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"vue": "^2.6.0 || ^3.0.0"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=12.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vue-csv-exporter
|
|
3
|
+
* Без зависимостей, нативный генератор CSV для браузера
|
|
4
|
+
* Соответствует RFC 4180
|
|
5
|
+
* @module vue-csv-exporter
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Конфигурация по умолчанию
|
|
10
|
+
* @typedef {Object} CSVConfig
|
|
11
|
+
* @property {string} delimiter - Разделитель полей (по умолчанию ';')
|
|
12
|
+
* @property {string} quote - Символ кавычек (по умолчанию '"')
|
|
13
|
+
* @property {string} lineBreak - Разделитель строк (по умолчанию '\r\n')
|
|
14
|
+
* @property {boolean} includeBOM - Добавлять BOM для кириллицы (по умолчанию true)
|
|
15
|
+
* @property {boolean} quoteAll - Всегда заключать поля в кавычки (по умолчанию false)
|
|
16
|
+
* @property {boolean} escapeDoubleQuotes - Экранировать кавычки удвоением (по умолчанию true)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Результат валидации
|
|
21
|
+
* @typedef {Object} ValidationResult
|
|
22
|
+
* @property {boolean} isValid - Валидны ли данные
|
|
23
|
+
* @property {string} error - Сообщение об ошибке
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
class CSVGenerator {
|
|
27
|
+
/**
|
|
28
|
+
* Конфигурация по умолчанию
|
|
29
|
+
* @type {CSVConfig}
|
|
30
|
+
*/
|
|
31
|
+
static defaultConfig = {
|
|
32
|
+
delimiter: ";",
|
|
33
|
+
quote: '"',
|
|
34
|
+
lineBreak: "\r\n",
|
|
35
|
+
includeBOM: true,
|
|
36
|
+
quoteAll: false,
|
|
37
|
+
escapeDoubleQuotes: true,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Генерация CSV из массива объектов
|
|
42
|
+
* @param {Array<Object>} data - Массив данных для экспорта
|
|
43
|
+
* @param {CSVConfig} config - Конфигурация генерации
|
|
44
|
+
* @returns {string} CSV строка
|
|
45
|
+
* @throws {Error} Если данные некорректны
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const csv = CSVGenerator.generate([
|
|
49
|
+
* { name: 'Иван', age: 30 },
|
|
50
|
+
* { name: 'Мария', age: 25 }
|
|
51
|
+
* ]);
|
|
52
|
+
*/
|
|
53
|
+
static generate(data, config = {}) {
|
|
54
|
+
const validation = this.validateData(data);
|
|
55
|
+
if (!validation.isValid) {
|
|
56
|
+
throw new Error(`CSV generation failed: ${validation.error}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cfg = { ...this.defaultConfig, ...config };
|
|
60
|
+
|
|
61
|
+
// Получаем заголовки из первого объекта
|
|
62
|
+
const headers = Object.keys(data[0]);
|
|
63
|
+
|
|
64
|
+
// Генерируем строки CSV
|
|
65
|
+
const rows = [
|
|
66
|
+
this.formatRow(headers, cfg),
|
|
67
|
+
...data.map((item) => this.formatRow(Object.values(item), cfg)),
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
let csv = rows.join(cfg.lineBreak);
|
|
71
|
+
|
|
72
|
+
// Добавляем BOM для правильного отображения кириллицы в Excel
|
|
73
|
+
if (cfg.includeBOM) {
|
|
74
|
+
csv = "\uFEFF" + csv;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return csv;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Форматирование одной строки CSV
|
|
82
|
+
* @private
|
|
83
|
+
* @param {Array} fields - Поля строки
|
|
84
|
+
* @param {CSVConfig} cfg - Конфигурация
|
|
85
|
+
* @returns {string} Отформатированная строка
|
|
86
|
+
*/
|
|
87
|
+
static formatRow(fields, cfg) {
|
|
88
|
+
return fields
|
|
89
|
+
.map((field) => this.formatField(field, cfg))
|
|
90
|
+
.join(cfg.delimiter);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Форматирование отдельного поля
|
|
95
|
+
* @private
|
|
96
|
+
* @param {*} field - Значение поля
|
|
97
|
+
* @param {CSVConfig} cfg - Конфигурация
|
|
98
|
+
* @returns {string} Отформатированное поле
|
|
99
|
+
*/
|
|
100
|
+
static formatField(field, cfg) {
|
|
101
|
+
let stringField =
|
|
102
|
+
field === null || field === undefined ? "" : String(field);
|
|
103
|
+
|
|
104
|
+
const needsQuoting =
|
|
105
|
+
cfg.quoteAll ||
|
|
106
|
+
stringField.includes(cfg.delimiter) ||
|
|
107
|
+
stringField.includes(cfg.quote) ||
|
|
108
|
+
stringField.includes("\n") ||
|
|
109
|
+
stringField.includes("\r");
|
|
110
|
+
|
|
111
|
+
if (needsQuoting) {
|
|
112
|
+
if (cfg.escapeDoubleQuotes) {
|
|
113
|
+
stringField = stringField.replace(
|
|
114
|
+
new RegExp(cfg.quote, "g"),
|
|
115
|
+
cfg.quote + cfg.quote,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return cfg.quote + stringField + cfg.quote;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return stringField;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Очистка поля от потенциально опасных символов
|
|
126
|
+
* @param {*} field - Поле для очистки
|
|
127
|
+
* @returns {string} Очищенное поле
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* const safe = CSVGenerator.sanitizeField("Hello;World");
|
|
131
|
+
*/
|
|
132
|
+
static sanitizeField(field) {
|
|
133
|
+
if (field === null || field === undefined) return "";
|
|
134
|
+
|
|
135
|
+
return String(field)
|
|
136
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, " ")
|
|
137
|
+
.replace(/\s+/g, " ")
|
|
138
|
+
.trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Валидация данных перед экспортом
|
|
143
|
+
* @param {Array} data - Данные для проверки
|
|
144
|
+
* @returns {ValidationResult} Результат валидации
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* const result = CSVGenerator.validateData(myData);
|
|
148
|
+
* if (result.isValid) {
|
|
149
|
+
* // экспорт
|
|
150
|
+
* }
|
|
151
|
+
*/
|
|
152
|
+
static validateData(data) {
|
|
153
|
+
if (!Array.isArray(data)) {
|
|
154
|
+
return {
|
|
155
|
+
isValid: false,
|
|
156
|
+
error: "Data must be an array",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (data.length === 0) {
|
|
161
|
+
return {
|
|
162
|
+
isValid: false,
|
|
163
|
+
error: "No data to export",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const hasInvalidItem = data.some(
|
|
168
|
+
(item) => item === null || typeof item !== "object",
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (hasInvalidItem) {
|
|
172
|
+
return {
|
|
173
|
+
isValid: false,
|
|
174
|
+
error: "Data contains invalid items",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { isValid: true };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Скачивание CSV файла
|
|
183
|
+
* @param {string} csv - CSV контент
|
|
184
|
+
* @param {string} filename - Имя файла
|
|
185
|
+
* @throws {Error} Если нет данных или ошибка скачивания
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* CSVGenerator.download(csv, 'export.csv');
|
|
189
|
+
*/
|
|
190
|
+
static download(csv, filename) {
|
|
191
|
+
if (!csv) {
|
|
192
|
+
throw new Error("No data to download");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const blob = new Blob([csv], {
|
|
197
|
+
type: "text/csv;charset=utf-8;",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const url = URL.createObjectURL(blob);
|
|
201
|
+
const link = document.createElement("a");
|
|
202
|
+
|
|
203
|
+
link.setAttribute("href", url);
|
|
204
|
+
link.setAttribute("download", filename);
|
|
205
|
+
link.setAttribute("target", "_blank");
|
|
206
|
+
|
|
207
|
+
document.body.appendChild(link);
|
|
208
|
+
link.click();
|
|
209
|
+
|
|
210
|
+
setTimeout(() => {
|
|
211
|
+
document.body.removeChild(link);
|
|
212
|
+
URL.revokeObjectURL(url);
|
|
213
|
+
}, 100);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw new Error(`Failed to download CSV: ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export default CSVGenerator;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue миксин для экспорта данных в CSV
|
|
3
|
+
* @module vue-csv-exporter/mixin
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import CSVGenerator from "./CSVGenerator";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @mixin exportMixin
|
|
10
|
+
* @property {boolean} isExporting - Флаг процесса экспорта
|
|
11
|
+
* @property {number} exportProgress - Прогресс экспорта (0-100)
|
|
12
|
+
*/
|
|
13
|
+
export const exportMixin = {
|
|
14
|
+
data() {
|
|
15
|
+
return {
|
|
16
|
+
isExporting: false,
|
|
17
|
+
exportProgress: 0,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
methods: {
|
|
22
|
+
/**
|
|
23
|
+
* Экспорт данных в CSV файл
|
|
24
|
+
* @param {Object} options - Опции экспорта
|
|
25
|
+
* @param {Array} options.data - Данные для экспорта
|
|
26
|
+
* @param {string} options.filename - Имя файла
|
|
27
|
+
* @param {Object} options.config - Конфигурация CSV
|
|
28
|
+
* @param {Function} options.onStart - Колбэк перед началом
|
|
29
|
+
* @param {Function} options.onProgress - Колбэк прогресса
|
|
30
|
+
* @param {Function} options.onSuccess - Колбэк успеха
|
|
31
|
+
* @param {Function} options.onError - Колбэк ошибки
|
|
32
|
+
* @returns {Promise<void>}
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* this.exportToCSV({
|
|
36
|
+
* data: this.items,
|
|
37
|
+
* filename: 'export.csv',
|
|
38
|
+
* onSuccess: (count) => console.log(`Exported ${count} items`)
|
|
39
|
+
* });
|
|
40
|
+
*/
|
|
41
|
+
async exportToCSV({
|
|
42
|
+
data,
|
|
43
|
+
filename = this.generateDefaultFilename(),
|
|
44
|
+
config = {},
|
|
45
|
+
onStart,
|
|
46
|
+
onProgress,
|
|
47
|
+
onSuccess,
|
|
48
|
+
onError,
|
|
49
|
+
} = {}) {
|
|
50
|
+
if (this.isExporting) {
|
|
51
|
+
this.$notify?.warning?.("Export already in progress", "Please wait");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const exportData = this.prepareExportData(data);
|
|
56
|
+
const validation = CSVGenerator.validateData(exportData);
|
|
57
|
+
|
|
58
|
+
if (!validation.isValid) {
|
|
59
|
+
const error = new Error(validation.error);
|
|
60
|
+
onError?.(error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.isExporting = true;
|
|
65
|
+
this.exportProgress = 0;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
onStart?.();
|
|
69
|
+
|
|
70
|
+
const transformedData = this.transformData(exportData);
|
|
71
|
+
this.exportProgress = 50;
|
|
72
|
+
onProgress?.(50);
|
|
73
|
+
|
|
74
|
+
const csv = CSVGenerator.generate(transformedData, config);
|
|
75
|
+
this.exportProgress = 90;
|
|
76
|
+
onProgress?.(90);
|
|
77
|
+
|
|
78
|
+
CSVGenerator.download(csv, filename);
|
|
79
|
+
this.exportProgress = 100;
|
|
80
|
+
onProgress?.(100);
|
|
81
|
+
|
|
82
|
+
onSuccess?.(exportData.length);
|
|
83
|
+
|
|
84
|
+
return exportData.length;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
onError?.(error);
|
|
87
|
+
throw error;
|
|
88
|
+
} finally {
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
this.isExporting = false;
|
|
91
|
+
this.exportProgress = 0;
|
|
92
|
+
}, 1000);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Подготовка данных для экспорта
|
|
98
|
+
* @protected
|
|
99
|
+
* @param {Array} data - Исходные данные
|
|
100
|
+
* @returns {Array} Подготовленные данные
|
|
101
|
+
*/
|
|
102
|
+
prepareExportData(data) {
|
|
103
|
+
return data?.map?.((item) => ({ ...item })) || [];
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Трансформация данных для CSV формата
|
|
108
|
+
* @protected
|
|
109
|
+
* @param {Array} data - Подготовленные данные
|
|
110
|
+
* @returns {Array} Трансформированные данные
|
|
111
|
+
*/
|
|
112
|
+
transformData(data) {
|
|
113
|
+
return data.map((item) => {
|
|
114
|
+
const transformed = {};
|
|
115
|
+
Object.keys(item).forEach((key) => {
|
|
116
|
+
transformed[key] = CSVGenerator.sanitizeField(item[key]);
|
|
117
|
+
});
|
|
118
|
+
return transformed;
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Генерация имени файла по умолчанию
|
|
124
|
+
* @protected
|
|
125
|
+
* @returns {string} Имя файла
|
|
126
|
+
*/
|
|
127
|
+
generateDefaultFilename() {
|
|
128
|
+
const date = new Date();
|
|
129
|
+
const year = date.getFullYear();
|
|
130
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
131
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
132
|
+
|
|
133
|
+
return `export_${year}-${month}-${day}.csv`;
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export default exportMixin;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vue-csv-exporter
|
|
3
|
+
* Простой и мощный экспорт данных в CSV для Vue.js приложений
|
|
4
|
+
*
|
|
5
|
+
* @module vue-csv-exporter
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import CSVGenerator from "./CSVGenerator";
|
|
9
|
+
import exportMixin from "./exportMixin";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Версия библиотеки
|
|
13
|
+
*/
|
|
14
|
+
export const VERSION = "1.0.0";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Плагин для Vue.js
|
|
18
|
+
* @param {Vue} Vue - Конструктор Vue
|
|
19
|
+
* @param {Object} options - Опции плагина
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* import Vue from 'vue';
|
|
23
|
+
* import VueCSVExporter from 'vue-csv-exporter';
|
|
24
|
+
*
|
|
25
|
+
* Vue.use(VueCSVExporter, {
|
|
26
|
+
* defaultFilename: 'export.csv',
|
|
27
|
+
* defaultConfig: { delimiter: ';' }
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
export function install(Vue, options = {}) {
|
|
31
|
+
if (install.installed) return;
|
|
32
|
+
install.installed = true;
|
|
33
|
+
|
|
34
|
+
const globalMixin = {
|
|
35
|
+
data() {
|
|
36
|
+
return {
|
|
37
|
+
$csvExporter: {
|
|
38
|
+
isExporting: false,
|
|
39
|
+
exportProgress: 0,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
methods: {
|
|
44
|
+
$exportToCSV(config) {
|
|
45
|
+
return this.$csvExport(config);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
Vue.mixin(globalMixin);
|
|
51
|
+
|
|
52
|
+
Vue.prototype.$csvExport = async function ({
|
|
53
|
+
data,
|
|
54
|
+
filename = options.defaultFilename,
|
|
55
|
+
config = options.defaultConfig,
|
|
56
|
+
...callbacks
|
|
57
|
+
}) {
|
|
58
|
+
const mixin = new Vue(exportMixin);
|
|
59
|
+
return mixin.exportToCSV({ data, filename, config, ...callbacks });
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Автоматическая установка в браузере
|
|
64
|
+
if (typeof window !== "undefined" && window.Vue) {
|
|
65
|
+
install(window.Vue);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { CSVGenerator, exportMixin };
|
|
69
|
+
|
|
70
|
+
export default {
|
|
71
|
+
install,
|
|
72
|
+
CSVGenerator,
|
|
73
|
+
exportMixin,
|
|
74
|
+
VERSION,
|
|
75
|
+
};
|