const express = require('express'); const router = express.Router(); const path = require('path'); const fs = require('fs'); const sanitizeSegment = (seg) => String(seg || '').replace(/[^a-zA-Z0-9_\-]/g, ''); router.get('/list', (req, res) => { try { const series = sanitizeSegment(req.query.series); const sub = sanitizeSegment(req.query.sub); if (!series || !sub) { return res.status(400).json({ error: '缺少必要参数 series 或 sub' }); } const modelsDocsDir = path.join(__dirname, '..', 'uploads', 'models', series, sub, 'docs'); const legacyDocsDir = path.join(__dirname, '..', 'uploads', 'docs', series, sub); const docsDir = fs.existsSync(modelsDocsDir) ? modelsDocsDir : legacyDocsDir; if (!fs.existsSync(docsDir)) { return res.json({ success: true, data: [] }); } const entries = fs.readdirSync(docsDir, { withFileTypes: true }); const files = entries .filter((e) => e.isFile()) .filter((e) => /\.(pdf|doc|docx|xls|xlsx|ppt|pptx)$/i.test(e.name)) .map((e) => { const fullPath = path.join(docsDir, e.name); let size = 0; try { size = fs.statSync(fullPath).size; } catch {} const base = docsDir === modelsDocsDir ? `/uploads/models/${series}/${sub}/docs` : `/uploads/docs/${series}/${sub}`; const url = `${base}/${e.name}`; return { name: e.name, url, size }; }); res.json({ success: true, data: files }); } catch (error) { console.error('获取资料列表错误:', error); res.status(500).json({ error: '服务器内部错误' }); } }); // 图片列表(支持每机型统一文件夹结构) router.get('/images', (req, res) => { try { const series = sanitizeSegment(req.query.series); const sub = sanitizeSegment(req.query.sub); if (!series || !sub) { return res.status(400).json({ error: '缺少必要参数 series 或 sub' }); } const candidates = [ path.join(__dirname, '..', 'uploads', 'models', series, sub, 'image'), path.join(__dirname, '..', 'uploads', 'models', series, sub, 'images'), path.join(__dirname, '..', 'uploads', 'images', series, sub), path.join(__dirname, '..', 'uploads', 'image', series, sub) ]; const imgDir = candidates.find((p) => fs.existsSync(p)); if (!fs.existsSync(imgDir)) { return res.json({ success: true, data: [] }); } const entries = fs.readdirSync(imgDir, { withFileTypes: true }); const files = entries .filter((e) => e.isFile()) .filter((e) => /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(e.name)) .map((e) => { const fullPath = path.join(imgDir, e.name); let size = 0; try { size = fs.statSync(fullPath).size; } catch {} let base = `/uploads/images/${series}/${sub}`; if (imgDir.includes(path.join('uploads', 'models'))) { if (imgDir.endsWith(path.join(series, sub, 'image'))) { base = `/uploads/models/${series}/${sub}/image`; } else if (imgDir.endsWith(path.join(series, sub, 'images'))) { base = `/uploads/models/${series}/${sub}/images`; } } else if (imgDir.includes(path.join('uploads', 'image'))) { base = `/uploads/image/${series}/${sub}`; } const url = `${base}/${e.name}`; return { name: e.name, url, size }; }); res.json({ success: true, data: files }); } catch (error) { console.error('获取图片列表错误:', error); res.status(500).json({ error: '服务器内部错误' }); } }); module.exports = router; // 使用相同 router 添加规格读取接口 router.get('/specs', (req, res) => { try { const series = sanitizeSegment(req.query.series); const sub = sanitizeSegment(req.query.sub); if (!series || !sub) { return res.status(400).json({ error: '缺少必要参数 series 或 sub' }); } const dirCandidates = [ path.join(__dirname, '..', 'uploads', 'models', series, sub, 'specs'), path.join(__dirname, '..', 'uploads', 'specs', series, sub) ]; const parseCsvFile = (filePath) => { const raw = fs.readFileSync(filePath, 'utf8'); const lines = raw.replace(/^\uFEFF/, '').split(/\r?\n/).filter((l) => l.trim().length); const normalize = (s) => String(s || '').trim().replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1'); const parseLine = (l) => { const replaced = l.replace(/;/g, ';').replace(/,/g, ',').replace(/\uff0c/g, ',').replace(/\uff1b/g, ';'); let tokens = replaced.match(/"([^"]*)"|[^,;\t]+/g) || []; if (tokens.length < 2) { const parts = replaced.split(/:|:/); if (parts.length >= 2) { tokens = [parts[0], parts.slice(1).join(':')]; } } const key = normalize(tokens[0] || ''); const val = normalize(tokens[1] || ''); return { key, value: val }; }; return lines.map(parseLine).filter((r) => r.key.length); }; const dirPath = dirCandidates.find((p) => fs.existsSync(p) && fs.statSync(p).isDirectory()); if (dirPath) { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); const csvFiles = entries.filter((e) => e.isFile() && /\.csv$/i.test(String(e.name).trim())); if (csvFiles.length) { const sections = csvFiles.map((e) => { const fp = path.join(dirPath, e.name); const title = e.name.replace(/\.csv$/i, ''); return { title, rows: parseCsvFile(fp) }; }); return res.json({ success: true, data: sections }); } } const fileCandidates = [ path.join(__dirname, '..', 'uploads', 'models', series, sub, 'specs', 'specs.csv'), path.join(__dirname, '..', 'uploads', 'models', series, sub, 'specs.csv'), path.join(__dirname, '..', 'uploads', 'specs', series, `${sub}.csv`), path.join(__dirname, '..', 'uploads', 'specs', series, sub, 'specs.csv') ]; const filePath = fileCandidates.find((p) => fs.existsSync(p)); if (!filePath) { return res.json({ success: true, data: [] }); } const specs = parseCsvFile(filePath); res.json({ success: true, data: specs }); } catch (error) { console.error('读取规格CSV错误:', error); res.status(500).json({ error: '服务器内部错误' }); } });