feat: 材料 Excel 批量入库
- 后端 POST /materials/bulk(BulkCreateMaterialsDto,最多2000条)→createMany入自建库 - 前端 MaterialImportModal(自建库"Excel批量导入"):下载模板(xlsx,含表头+示例), 拖拽上传→XLSX解析→按列映射5污染物×3参数→必填校验(标红错误行)→预览→批量导入 - 装 xlsx(SheetJS)前端解析,免后端文件处理 - 实测:3行(2有效1缺类别)→正确解析校验→导入2条到自建库 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d4f8bb3826
commit
d3abadf8eb
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { ArrayMaxSize, ArrayMinSize, IsArray, ValidateNested } from 'class-validator';
|
||||||
|
import { CreateMaterialDto } from './create-material.dto';
|
||||||
|
|
||||||
|
export class BulkCreateMaterialsDto {
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
@ArrayMaxSize(2000)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => CreateMaterialDto)
|
||||||
|
items!: CreateMaterialDto[];
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import { MaterialsService } from './materials.service';
|
||||||
import { QueryMaterialsDto } from './dto/query-materials.dto';
|
import { QueryMaterialsDto } from './dto/query-materials.dto';
|
||||||
import { CreateMaterialDto } from './dto/create-material.dto';
|
import { CreateMaterialDto } from './dto/create-material.dto';
|
||||||
import { UpdateMaterialDto } from './dto/update-material.dto';
|
import { UpdateMaterialDto } from './dto/update-material.dto';
|
||||||
|
import { BulkCreateMaterialsDto } from './dto/bulk-create-material.dto';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
import { CurrentOrg, OrgPayload } from '../auth/current-org.decorator';
|
import { CurrentOrg, OrgPayload } from '../auth/current-org.decorator';
|
||||||
|
|
||||||
|
|
@ -36,6 +37,11 @@ export class MaterialsController {
|
||||||
return this.materials.create(org.id, dto);
|
return this.materials.create(org.id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('bulk')
|
||||||
|
bulk(@CurrentOrg() org: OrgPayload, @Body() dto: BulkCreateMaterialsDto) {
|
||||||
|
return this.materials.createMany(org.id, dto.items);
|
||||||
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
update(
|
update(
|
||||||
@CurrentOrg() org: OrgPayload,
|
@CurrentOrg() org: OrgPayload,
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,27 @@ export class MaterialsService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 批量入库(自建库)。返回成功条数。 */
|
||||||
|
async createMany(orgId: string, items: CreateMaterialDto[]) {
|
||||||
|
const data = items.map((dto) => ({
|
||||||
|
id: this.genId(),
|
||||||
|
name: dto.name,
|
||||||
|
category: dto.category,
|
||||||
|
brand: dto.brand,
|
||||||
|
manufacturer: dto.manufacturer,
|
||||||
|
spec: dto.spec,
|
||||||
|
envGrade: dto.envGrade,
|
||||||
|
healthGrade: dto.healthGrade,
|
||||||
|
usageUnit: dto.usageUnit ?? 'm²',
|
||||||
|
sortOrder: dto.sortOrder ?? 0,
|
||||||
|
emissionParams: dto.emissionParams as unknown as Prisma.InputJsonValue,
|
||||||
|
isPublic: false,
|
||||||
|
ownerOrgId: orgId,
|
||||||
|
}));
|
||||||
|
const res = await this.prisma.material.createMany({ data });
|
||||||
|
return { created: res.count };
|
||||||
|
}
|
||||||
|
|
||||||
async update(orgId: string, id: string, dto: UpdateMaterialDto) {
|
async update(orgId: string, id: string, dto: UpdateMaterialDto) {
|
||||||
await this.assertOwned(orgId, id);
|
await this.assertOwned(orgId, id);
|
||||||
return this.prisma.material.update({
|
return this.prisma.material.update({
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"pinia": "^2.2.4",
|
"pinia": "^2.2.4",
|
||||||
"vue": "^3.5.12",
|
"vue": "^3.5.12",
|
||||||
"vue-router": "^4.4.5"
|
"vue-router": "^4.4.5",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,10 @@ export function createMaterial(input: MaterialInput) {
|
||||||
return http.post<any, Material>('/materials', input);
|
return http.post<any, Material>('/materials', input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bulkCreateMaterials(items: MaterialInput[]) {
|
||||||
|
return http.post<any, { created: number }>('/materials/bulk', { items });
|
||||||
|
}
|
||||||
|
|
||||||
export function updateMaterial(id: string, input: Partial<MaterialInput>) {
|
export function updateMaterial(id: string, input: Partial<MaterialInput>) {
|
||||||
return http.patch<any, Material>(`/materials/${id}`, input);
|
return http.patch<any, Material>(`/materials/${id}`, input);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
<template>
|
||||||
|
<a-modal :open="open" title="Excel 批量入库材料" width="720px" :confirm-loading="saving" @cancel="emit('cancel')">
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="emit('cancel')">取消</a-button>
|
||||||
|
<a-button @click="downloadTemplate">下载模板</a-button>
|
||||||
|
<a-button type="primary" :disabled="!validRows.length || saving" :loading="saving" @click="submit">
|
||||||
|
导入 {{ validRows.length }} 条
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-alert
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
style="margin-bottom: 14px"
|
||||||
|
message="先「下载模板」按列填好,再选文件导入。必填:材料名称、材料类别。15 个散发参数列(甲醛/TVOC/苯/甲苯/二甲苯 各 Y0/Yp/B),不释放填 0。导入的材料进自建库。"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a-upload-dragger
|
||||||
|
:before-upload="onFile"
|
||||||
|
:show-upload-list="false"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
:disabled="saving"
|
||||||
|
>
|
||||||
|
<p class="ant-upload-drag-icon" style="margin-bottom: 6px"><inbox-outlined style="font-size: 32px; color: #b4232a" /></p>
|
||||||
|
<p>点击或拖拽 Excel 文件到此处</p>
|
||||||
|
<p style="color: #999; font-size: 12px">支持 .xlsx / .xls</p>
|
||||||
|
</a-upload-dragger>
|
||||||
|
|
||||||
|
<div v-if="fileName" class="parse-result">
|
||||||
|
<div class="pr-line">
|
||||||
|
已解析 <b>{{ fileName }}</b>:共 {{ rows.length }} 行,
|
||||||
|
<span style="color: #2f8f5b">有效 {{ validRows.length }}</span>
|
||||||
|
<span v-if="errors.length" style="color: #b4232a">,错误 {{ errors.length }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="errors.length" class="errs">
|
||||||
|
<div v-for="(e, i) in errors.slice(0, 8)" :key="i">第 {{ e.row }} 行:{{ e.msg }}</div>
|
||||||
|
<div v-if="errors.length > 8">… 其余 {{ errors.length - 8 }} 条错误</div>
|
||||||
|
</div>
|
||||||
|
<a-table
|
||||||
|
v-if="validRows.length"
|
||||||
|
:columns="previewCols"
|
||||||
|
:data-source="validRows.slice(0, 6)"
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="name"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { InboxOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { POLLUTANTS, POLLUTANT_LABELS, type Pollutant } from '@airpredict/shared';
|
||||||
|
import { bulkCreateMaterials, type MaterialInput } from '../api/materials';
|
||||||
|
|
||||||
|
const props = defineProps<{ open: boolean }>();
|
||||||
|
const emit = defineEmits<{ (e: 'ok', n: number): void; (e: 'cancel'): void }>();
|
||||||
|
|
||||||
|
const saving = ref(false);
|
||||||
|
const fileName = ref('');
|
||||||
|
const rows = ref<any[]>([]);
|
||||||
|
const validRows = ref<MaterialInput[]>([]);
|
||||||
|
const errors = ref<{ row: number; msg: string }[]>([]);
|
||||||
|
|
||||||
|
const previewCols = [
|
||||||
|
{ title: '材料名称', dataIndex: 'name' },
|
||||||
|
{ title: '类别', dataIndex: 'category' },
|
||||||
|
{ title: '品牌', dataIndex: 'brand' },
|
||||||
|
{ title: '甲醛Y0', customRender: ({ record }: any) => record.emissionParams.hcho.y0 },
|
||||||
|
{ title: 'TVOC Y0', customRender: ({ record }: any) => record.emissionParams.tvoc.y0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 列头:基础 + 5污染物×3
|
||||||
|
const COLS = ['材料名称', '材料类别', '材料品牌', '材料厂家', '材料规格', '环保等级', '健康等级', '用量单位', '排序权重'];
|
||||||
|
const PARAM_COLS: { col: string; p: Pollutant; k: 'y0' | 'yp' | 'b' }[] = [];
|
||||||
|
for (const p of POLLUTANTS) {
|
||||||
|
for (const k of ['y0', 'yp', 'b'] as const) {
|
||||||
|
const suffix = k === 'y0' ? 'Y0' : k === 'yp' ? 'Yp' : 'B';
|
||||||
|
PARAM_COLS.push({ col: `${POLLUTANT_LABELS[p].zh}${suffix}`, p, k });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function num(v: any) {
|
||||||
|
const n = Number(v);
|
||||||
|
return Number.isFinite(n) ? n : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFile(file: File) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
try {
|
||||||
|
const wb = XLSX.read(e.target!.result, { type: 'array' });
|
||||||
|
const ws = wb.Sheets[wb.SheetNames[0]];
|
||||||
|
const json = XLSX.utils.sheet_to_json<any>(ws, { defval: '' });
|
||||||
|
parse(file.name, json);
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error('解析失败:' + err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
return false; // 阻止 antd 自动上传
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(name: string, json: any[]) {
|
||||||
|
fileName.value = name;
|
||||||
|
rows.value = json;
|
||||||
|
const valid: MaterialInput[] = [];
|
||||||
|
const errs: { row: number; msg: string }[] = [];
|
||||||
|
json.forEach((r, i) => {
|
||||||
|
const rowNo = i + 2; // 含表头
|
||||||
|
const matName = String(r['材料名称'] ?? '').trim();
|
||||||
|
const category = String(r['材料类别'] ?? '').trim();
|
||||||
|
if (!matName) { errs.push({ row: rowNo, msg: '材料名称为空' }); return; }
|
||||||
|
if (!category) { errs.push({ row: rowNo, msg: '材料类别为空' }); return; }
|
||||||
|
const emissionParams: any = {};
|
||||||
|
for (const p of POLLUTANTS) emissionParams[p] = { y0: 0, yp: 0, b: 0 };
|
||||||
|
for (const pc of PARAM_COLS) emissionParams[pc.p][pc.k] = num(r[pc.col]);
|
||||||
|
valid.push({
|
||||||
|
name: matName,
|
||||||
|
category,
|
||||||
|
brand: String(r['材料品牌'] ?? '').trim() || undefined,
|
||||||
|
manufacturer: String(r['材料厂家'] ?? '').trim() || undefined,
|
||||||
|
spec: String(r['材料规格'] ?? '').trim() || undefined,
|
||||||
|
envGrade: String(r['环保等级'] ?? '').trim() || undefined,
|
||||||
|
healthGrade: String(r['健康等级'] ?? '').trim() || undefined,
|
||||||
|
usageUnit: String(r['用量单位'] ?? '').trim() || 'm²',
|
||||||
|
sortOrder: r['排序权重'] !== '' ? num(r['排序权重']) : 0,
|
||||||
|
emissionParams,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
validRows.value = valid;
|
||||||
|
errors.value = errs;
|
||||||
|
if (!valid.length) message.warning('没有可导入的有效行');
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadTemplate() {
|
||||||
|
const headers = [...COLS, ...PARAM_COLS.map((c) => c.col)];
|
||||||
|
const example: any = {
|
||||||
|
材料名称: '示例·多层实木复合地板', 材料类别: '木地板/实木地板', 材料品牌: '某品牌',
|
||||||
|
材料厂家: '某厂家', 材料规格: '12mm', 环保等级: 'E1', 健康等级: 'B', 用量单位: 'm²', 排序权重: 100,
|
||||||
|
};
|
||||||
|
PARAM_COLS.forEach((c) => (example[c.col] = 0));
|
||||||
|
example['甲醛Y0'] = 0.09; example['甲醛Yp'] = 0.4; example['甲醛B'] = 0.47;
|
||||||
|
const ws = XLSX.utils.json_to_sheet([example], { header: headers });
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, '材料');
|
||||||
|
XLSX.writeFile(wb, '材料批量导入模板.xlsx');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
if (!validRows.value.length) return;
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
const res = await bulkCreateMaterials(validRows.value);
|
||||||
|
message.success(`已导入 ${res.created} 条材料`);
|
||||||
|
emit('ok', res.created);
|
||||||
|
reset();
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reset() { fileName.value = ''; rows.value = []; validRows.value = []; errors.value = []; }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.parse-result { margin-top: 14px; }
|
||||||
|
.pr-line { font-size: 13px; }
|
||||||
|
.errs { margin-top: 8px; background: #fff7f5; border: 1px solid #f0d0c8; border-radius: 6px; padding: 8px 12px; font-size: 12px; color: #b4232a; max-height: 120px; overflow: auto; }
|
||||||
|
</style>
|
||||||
|
|
@ -30,7 +30,10 @@
|
||||||
<a-button type="primary" style="margin-left: 8px" @click="reload">查询</a-button>
|
<a-button type="primary" style="margin-left: 8px" @click="reload">查询</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-button v-if="scope === 'self'" type="primary" @click="openCreate">+ 新建材料</a-button>
|
<div v-if="scope === 'self'" style="display: flex; gap: 8px">
|
||||||
|
<a-button @click="importOpen = true">📥 Excel 批量导入</a-button>
|
||||||
|
<a-button type="primary" @click="openCreate">+ 新建材料</a-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-table
|
<a-table
|
||||||
|
|
@ -95,6 +98,7 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<MaterialFormModal :open="formOpen" :material="editing" @ok="onFormOk" @cancel="formOpen = false" />
|
<MaterialFormModal :open="formOpen" :material="editing" @ok="onFormOk" @cancel="formOpen = false" />
|
||||||
|
<MaterialImportModal :open="importOpen" @ok="onImported" @cancel="importOpen = false" />
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -105,6 +109,7 @@ import { POLLUTANTS, POLLUTANT_LABELS } from '@airpredict/shared';
|
||||||
import { listMaterials, deleteMaterial, type Material, type Paged } from '../api/materials';
|
import { listMaterials, deleteMaterial, type Material, type Paged } from '../api/materials';
|
||||||
import { toggleFavorite } from '../api/favorites';
|
import { toggleFavorite } from '../api/favorites';
|
||||||
import MaterialFormModal from '../components/MaterialFormModal.vue';
|
import MaterialFormModal from '../components/MaterialFormModal.vue';
|
||||||
|
import MaterialImportModal from '../components/MaterialImportModal.vue';
|
||||||
|
|
||||||
const pollutants = POLLUTANTS;
|
const pollutants = POLLUTANTS;
|
||||||
const labels = POLLUTANT_LABELS;
|
const labels = POLLUTANT_LABELS;
|
||||||
|
|
@ -190,6 +195,13 @@ function onFormOk() {
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const importOpen = ref(false);
|
||||||
|
function onImported() {
|
||||||
|
importOpen.value = false;
|
||||||
|
scope.value = 'self';
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
async function onDelete(r: Material) {
|
async function onDelete(r: Material) {
|
||||||
await deleteMaterial(r.id);
|
await deleteMaterial(r.id);
|
||||||
message.success('已删除');
|
message.success('已删除');
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,9 @@ importers:
|
||||||
vue-router:
|
vue-router:
|
||||||
specifier: ^4.4.5
|
specifier: ^4.4.5
|
||||||
version: 4.6.4(vue@3.5.35(typescript@5.9.3))
|
version: 4.6.4(vue@3.5.35(typescript@5.9.3))
|
||||||
|
xlsx:
|
||||||
|
specifier: ^0.18.5
|
||||||
|
version: 0.18.5
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: ^5.1.4
|
specifier: ^5.1.4
|
||||||
|
|
@ -823,6 +826,10 @@ packages:
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
adler-32@1.3.1:
|
||||||
|
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||||
engines: {node: '>= 6.0.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
@ -994,6 +1001,10 @@ packages:
|
||||||
caniuse-lite@1.0.30001797:
|
caniuse-lite@1.0.30001797:
|
||||||
resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==}
|
resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==}
|
||||||
|
|
||||||
|
cfb@1.2.2:
|
||||||
|
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
@ -1043,6 +1054,10 @@ packages:
|
||||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
|
codepage@1.15.0:
|
||||||
|
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
|
|
@ -1112,6 +1127,11 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
crc-32@1.2.2:
|
||||||
|
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
create-require@1.1.1:
|
create-require@1.1.1:
|
||||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||||
|
|
||||||
|
|
@ -1357,6 +1377,10 @@ packages:
|
||||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
frac@1.1.2:
|
||||||
|
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
fresh@0.5.2:
|
fresh@0.5.2:
|
||||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
@ -2005,6 +2029,10 @@ packages:
|
||||||
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
|
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
ssf@0.11.2:
|
||||||
|
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
statuses@2.0.2:
|
statuses@2.0.2:
|
||||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
@ -2338,6 +2366,14 @@ packages:
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
wmf@1.0.2:
|
||||||
|
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
|
word@0.3.0:
|
||||||
|
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
wrap-ansi@6.2.0:
|
wrap-ansi@6.2.0:
|
||||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -2350,6 +2386,11 @@ packages:
|
||||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
xlsx@0.18.5:
|
||||||
|
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
xtend@4.0.2:
|
xtend@4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|
@ -3059,6 +3100,8 @@ snapshots:
|
||||||
|
|
||||||
acorn@8.16.0: {}
|
acorn@8.16.0: {}
|
||||||
|
|
||||||
|
adler-32@1.3.1: {}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
|
|
@ -3268,6 +3311,11 @@ snapshots:
|
||||||
|
|
||||||
caniuse-lite@1.0.30001797: {}
|
caniuse-lite@1.0.30001797: {}
|
||||||
|
|
||||||
|
cfb@1.2.2:
|
||||||
|
dependencies:
|
||||||
|
adler-32: 1.3.1
|
||||||
|
crc-32: 1.2.2
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
|
|
@ -3317,6 +3365,8 @@ snapshots:
|
||||||
|
|
||||||
clone@1.0.4: {}
|
clone@1.0.4: {}
|
||||||
|
|
||||||
|
codepage@1.15.0: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
|
|
@ -3380,6 +3430,8 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.7.2
|
typescript: 5.7.2
|
||||||
|
|
||||||
|
crc-32@1.2.2: {}
|
||||||
|
|
||||||
create-require@1.1.1: {}
|
create-require@1.1.1: {}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
|
|
@ -3648,6 +3700,8 @@ snapshots:
|
||||||
|
|
||||||
forwarded@0.2.0: {}
|
forwarded@0.2.0: {}
|
||||||
|
|
||||||
|
frac@1.1.2: {}
|
||||||
|
|
||||||
fresh@0.5.2: {}
|
fresh@0.5.2: {}
|
||||||
|
|
||||||
fs-extra@10.1.0:
|
fs-extra@10.1.0:
|
||||||
|
|
@ -4328,6 +4382,10 @@ snapshots:
|
||||||
|
|
||||||
source-map@0.7.4: {}
|
source-map@0.7.4: {}
|
||||||
|
|
||||||
|
ssf@0.11.2:
|
||||||
|
dependencies:
|
||||||
|
frac: 1.1.2
|
||||||
|
|
||||||
statuses@2.0.2: {}
|
statuses@2.0.2: {}
|
||||||
|
|
||||||
streamsearch@1.1.0: {}
|
streamsearch@1.1.0: {}
|
||||||
|
|
@ -4602,6 +4660,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
|
wmf@1.0.2: {}
|
||||||
|
|
||||||
|
word@0.3.0: {}
|
||||||
|
|
||||||
wrap-ansi@6.2.0:
|
wrap-ansi@6.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
|
|
@ -4620,6 +4682,16 @@ snapshots:
|
||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
strip-ansi: 7.2.0
|
strip-ansi: 7.2.0
|
||||||
|
|
||||||
|
xlsx@0.18.5:
|
||||||
|
dependencies:
|
||||||
|
adler-32: 1.3.1
|
||||||
|
cfb: 1.2.2
|
||||||
|
codepage: 1.15.0
|
||||||
|
crc-32: 1.2.2
|
||||||
|
ssf: 0.11.2
|
||||||
|
wmf: 1.0.2
|
||||||
|
word: 0.3.0
|
||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue