|
@@ -161,6 +161,9 @@ export type BuildVehicleCertZipResult = {
|
|
|
zipName: string
|
|
zipName: string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+export type BuildCertZipOptions = BuildVehicleCertZipOptions
|
|
|
|
|
+export type BuildCertZipResult = BuildVehicleCertZipResult
|
|
|
|
|
+
|
|
|
function todayYyyymmdd(): string {
|
|
function todayYyyymmdd(): string {
|
|
|
const d = new Date()
|
|
const d = new Date()
|
|
|
const y = d.getFullYear()
|
|
const y = d.getFullYear()
|
|
@@ -200,43 +203,30 @@ async function defaultSaveAs(blob: Blob, filename: string): Promise<void> {
|
|
|
FileSaver.saveAs(blob, filename)
|
|
FileSaver.saveAs(blob, filename)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export async function buildAndSaveVehicleCertZip(
|
|
|
|
|
- vehicles: VehicleCertZipSource[],
|
|
|
|
|
- options: BuildVehicleCertZipOptions = {}
|
|
|
|
|
-): Promise<BuildVehicleCertZipResult> {
|
|
|
|
|
- const entries = collectVehicleCertZipEntries(vehicles)
|
|
|
|
|
|
|
+export async function packAndSaveCertZip(
|
|
|
|
|
+ entries: Array<{ folderLabel: string; files: VehicleCertZipFile[] }>,
|
|
|
|
|
+ zipName: string,
|
|
|
|
|
+ options: BuildCertZipOptions = {}
|
|
|
|
|
+): Promise<BuildCertZipResult> {
|
|
|
if (!entries.length) {
|
|
if (!entries.length) {
|
|
|
return { okCount: 0, failCount: 0, zipName: '' }
|
|
return { okCount: 0, failCount: 0, zipName: '' }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const yyyymmdd = options.yyyymmdd || todayYyyymmdd()
|
|
|
|
|
- const zipName = buildVehicleCertZipFilename(entries, yyyymmdd)
|
|
|
|
|
const multi = entries.length > 1
|
|
const multi = entries.length > 1
|
|
|
const concurrency = options.concurrency ?? 4
|
|
const concurrency = options.concurrency ?? 4
|
|
|
-
|
|
|
|
|
- type Job = {
|
|
|
|
|
- zipPath: string
|
|
|
|
|
- fileUrl: string
|
|
|
|
|
- fileName: string
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
const used = new Set<string>()
|
|
const used = new Set<string>()
|
|
|
- const jobs: Job[] = []
|
|
|
|
|
|
|
+ const jobs: Array<{ zipPath: string; fileUrl: string; fileName: string }> = []
|
|
|
|
|
+
|
|
|
for (const entry of entries) {
|
|
for (const entry of entries) {
|
|
|
for (const file of entry.files) {
|
|
for (const file of entry.files) {
|
|
|
const leaf = `${file.baseName}${file.ext ? `.${file.ext}` : ''}`
|
|
const leaf = `${file.baseName}${file.ext ? `.${file.ext}` : ''}`
|
|
|
- const rawPath = multi ? `${entry.plateFolder}/${leaf}` : leaf
|
|
|
|
|
|
|
+ const rawPath = multi ? `${entry.folderLabel}/${leaf}` : leaf
|
|
|
const zipPath = uniqueZipPath(used, rawPath)
|
|
const zipPath = uniqueZipPath(used, rawPath)
|
|
|
- jobs.push({
|
|
|
|
|
- zipPath,
|
|
|
|
|
- fileUrl: file.fileUrl,
|
|
|
|
|
- fileName: leaf
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ jobs.push({ zipPath, fileUrl: file.fileUrl, fileName: leaf })
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const fetchBlob = options.fetchBlob ?? defaultFetchBlob
|
|
const fetchBlob = options.fetchBlob ?? defaultFetchBlob
|
|
|
-
|
|
|
|
|
const zip = new JSZip()
|
|
const zip = new JSZip()
|
|
|
let okCount = 0
|
|
let okCount = 0
|
|
|
let failCount = 0
|
|
let failCount = 0
|
|
@@ -257,9 +247,102 @@ export async function buildAndSaveVehicleCertZip(
|
|
|
return { okCount: 0, failCount, zipName: '' }
|
|
return { okCount: 0, failCount, zipName: '' }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const blob = await zip.generateAsync({ type: 'blob' })
|
|
|
|
|
|
|
+ const out = await zip.generateAsync({ type: 'blob' })
|
|
|
const save = options.saveAs ?? defaultSaveAs
|
|
const save = options.saveAs ?? defaultSaveAs
|
|
|
- await save(blob, zipName)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ await save(out, zipName)
|
|
|
return { okCount, failCount, zipName }
|
|
return { okCount, failCount, zipName }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+export async function buildAndSaveVehicleCertZip(
|
|
|
|
|
+ vehicles: VehicleCertZipSource[],
|
|
|
|
|
+ options: BuildVehicleCertZipOptions = {}
|
|
|
|
|
+): Promise<BuildVehicleCertZipResult> {
|
|
|
|
|
+ const collected = collectVehicleCertZipEntries(vehicles)
|
|
|
|
|
+ if (!collected.length) {
|
|
|
|
|
+ return { okCount: 0, failCount: 0, zipName: '' }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const yyyymmdd = options.yyyymmdd || todayYyyymmdd()
|
|
|
|
|
+ const zipName = buildVehicleCertZipFilename(collected, yyyymmdd)
|
|
|
|
|
+ return packAndSaveCertZip(
|
|
|
|
|
+ collected.map((e) => ({ folderLabel: e.plateFolder, files: e.files })),
|
|
|
|
|
+ zipName,
|
|
|
|
|
+ options
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export type PersonnelCertZipSource = {
|
|
|
|
|
+ id: string
|
|
|
|
|
+ personnelName?: string | null
|
|
|
|
|
+ certList?: CertRead[] | null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export type PersonnelCertZipEntry = {
|
|
|
|
|
+ personnelId: string
|
|
|
|
|
+ folderLabel: string
|
|
|
|
|
+ files: VehicleCertZipFile[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function collectPersonnelCertZipEntries(
|
|
|
|
|
+ people: PersonnelCertZipSource[]
|
|
|
|
|
+): PersonnelCertZipEntry[] {
|
|
|
|
|
+ const result: PersonnelCertZipEntry[] = []
|
|
|
|
|
+ const usedFolders = new Set<string>()
|
|
|
|
|
+
|
|
|
|
|
+ for (const person of people) {
|
|
|
|
|
+ const certs = person.certList || []
|
|
|
|
|
+ const files: VehicleCertZipFile[] = []
|
|
|
|
|
+
|
|
|
|
|
+ certs.forEach((cert) => {
|
|
|
|
|
+ const certName = String(cert.certName || '').trim() || '证书'
|
|
|
|
|
+ parseUrls(cert.attachmentUrls).forEach((fileUrl) => {
|
|
|
|
|
+ files.push({
|
|
|
|
|
+ fileUrl,
|
|
|
|
|
+ baseName: certName,
|
|
|
|
|
+ ext: getFileExt(fileUrl)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (!files.length) continue
|
|
|
|
|
+
|
|
|
|
|
+ const rawName = String(person.personnelName || '').trim()
|
|
|
|
|
+ const baseFolder = sanitizeZipPathSegment(rawName || person.id)
|
|
|
|
|
+ const folderLabel = uniqueZipPath(usedFolders, baseFolder)
|
|
|
|
|
+ result.push({
|
|
|
|
|
+ personnelId: person.id,
|
|
|
|
|
+ folderLabel,
|
|
|
|
|
+ files
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildPersonnelCertZipFilename(
|
|
|
|
|
+ entries: Array<{ folderLabel: string }>,
|
|
|
|
|
+ yyyymmdd: string
|
|
|
|
|
+): string {
|
|
|
|
|
+ if (entries.length === 1) {
|
|
|
|
|
+ return `人员证书_${entries[0].folderLabel}.zip`
|
|
|
|
|
+ }
|
|
|
|
|
+ return `人员证书批量_${entries.length}人_${yyyymmdd}.zip`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export async function buildAndSavePersonnelCertZip(
|
|
|
|
|
+ people: PersonnelCertZipSource[],
|
|
|
|
|
+ options: BuildCertZipOptions = {}
|
|
|
|
|
+): Promise<BuildCertZipResult> {
|
|
|
|
|
+ const collected = collectPersonnelCertZipEntries(people)
|
|
|
|
|
+ if (!collected.length) {
|
|
|
|
|
+ return { okCount: 0, failCount: 0, zipName: '' }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const yyyymmdd = options.yyyymmdd || todayYyyymmdd()
|
|
|
|
|
+ const zipName = buildPersonnelCertZipFilename(collected, yyyymmdd)
|
|
|
|
|
+ return packAndSaveCertZip(
|
|
|
|
|
+ collected.map((e) => ({ folderLabel: e.folderLabel, files: e.files })),
|
|
|
|
|
+ zipName,
|
|
|
|
|
+ options
|
|
|
|
|
+ )
|
|
|
|
|
+}
|