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 { CreateMaterialDto } from './dto/create-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 { CurrentOrg, OrgPayload } from '../auth/current-org.decorator';
|
||||
|
||||
|
|
@ -36,6 +37,11 @@ export class MaterialsController {
|
|||
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')
|
||||
update(
|
||||
@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) {
|
||||
await this.assertOwned(orgId, id);
|
||||
return this.prisma.material.update({
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
"axios": "^1.7.7",
|
||||
"pinia": "^2.2.4",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.4.5"
|
||||
"vue-router": "^4.4.5",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ export function createMaterial(input: MaterialInput) {
|
|||
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>) {
|
||||
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-form-item>
|
||||
</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>
|
||||
|
||||
<a-table
|
||||
|
|
@ -95,6 +98,7 @@
|
|||
</a-modal>
|
||||
|
||||
<MaterialFormModal :open="formOpen" :material="editing" @ok="onFormOk" @cancel="formOpen = false" />
|
||||
<MaterialImportModal :open="importOpen" @ok="onImported" @cancel="importOpen = false" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
|
|
@ -105,6 +109,7 @@ import { POLLUTANTS, POLLUTANT_LABELS } from '@airpredict/shared';
|
|||
import { listMaterials, deleteMaterial, type Material, type Paged } from '../api/materials';
|
||||
import { toggleFavorite } from '../api/favorites';
|
||||
import MaterialFormModal from '../components/MaterialFormModal.vue';
|
||||
import MaterialImportModal from '../components/MaterialImportModal.vue';
|
||||
|
||||
const pollutants = POLLUTANTS;
|
||||
const labels = POLLUTANT_LABELS;
|
||||
|
|
@ -190,6 +195,13 @@ function onFormOk() {
|
|||
reload();
|
||||
}
|
||||
|
||||
const importOpen = ref(false);
|
||||
function onImported() {
|
||||
importOpen.value = false;
|
||||
scope.value = 'self';
|
||||
reload();
|
||||
}
|
||||
|
||||
async function onDelete(r: Material) {
|
||||
await deleteMaterial(r.id);
|
||||
message.success('已删除');
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ importers:
|
|||
vue-router:
|
||||
specifier: ^4.4.5
|
||||
version: 4.6.4(vue@3.5.35(typescript@5.9.3))
|
||||
xlsx:
|
||||
specifier: ^0.18.5
|
||||
version: 0.18.5
|
||||
devDependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.1.4
|
||||
|
|
@ -823,6 +826,10 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
adler-32@1.3.1:
|
||||
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
|
@ -994,6 +1001,10 @@ packages:
|
|||
caniuse-lite@1.0.30001797:
|
||||
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:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -1043,6 +1054,10 @@ packages:
|
|||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
|
@ -1112,6 +1127,11 @@ packages:
|
|||
typescript:
|
||||
optional: true
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
|
||||
create-require@1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
|
||||
|
|
@ -1357,6 +1377,10 @@ packages:
|
|||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
frac@1.1.2:
|
||||
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
fresh@0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
|
@ -2005,6 +2029,10 @@ packages:
|
|||
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
ssf@0.11.2:
|
||||
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
statuses@2.0.2:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
|
@ -2338,6 +2366,14 @@ packages:
|
|||
engines: {node: '>= 8'}
|
||||
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:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -2350,6 +2386,11 @@ packages:
|
|||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
xlsx@0.18.5:
|
||||
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
|
||||
xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
|
@ -3059,6 +3100,8 @@ snapshots:
|
|||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
adler-32@1.3.1: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
|
|
@ -3268,6 +3311,11 @@ snapshots:
|
|||
|
||||
caniuse-lite@1.0.30001797: {}
|
||||
|
||||
cfb@1.2.2:
|
||||
dependencies:
|
||||
adler-32: 1.3.1
|
||||
crc-32: 1.2.2
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
|
|
@ -3317,6 +3365,8 @@ snapshots:
|
|||
|
||||
clone@1.0.4: {}
|
||||
|
||||
codepage@1.15.0: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
|
@ -3380,6 +3430,8 @@ snapshots:
|
|||
optionalDependencies:
|
||||
typescript: 5.7.2
|
||||
|
||||
crc-32@1.2.2: {}
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
|
|
@ -3648,6 +3700,8 @@ snapshots:
|
|||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
frac@1.1.2: {}
|
||||
|
||||
fresh@0.5.2: {}
|
||||
|
||||
fs-extra@10.1.0:
|
||||
|
|
@ -4328,6 +4382,10 @@ snapshots:
|
|||
|
||||
source-map@0.7.4: {}
|
||||
|
||||
ssf@0.11.2:
|
||||
dependencies:
|
||||
frac: 1.1.2
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
streamsearch@1.1.0: {}
|
||||
|
|
@ -4602,6 +4660,10 @@ snapshots:
|
|||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wmf@1.0.2: {}
|
||||
|
||||
word@0.3.0: {}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
|
|
@ -4620,6 +4682,16 @@ snapshots:
|
|||
string-width: 5.1.2
|
||||
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: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue