feat: 物料导入前端接口与 CSV 导出工具
- materialsApi 增加 importFile(silent,校验明细由页面弹窗展示)与 downloadImportTemplate(走原生 fetch,响应是文件流) - 下载接口绕开 axios 拦截器,自行携带令牌与当前公司头 - 新增 exportCsv:buildCsv 带 UTF-8 BOM、按 RFC 4180 转义;downloadCsv 触发下载 - BOM 不用内联零宽字符,改用 JS 转义序列写法,源码里可见可改
This commit is contained in:
parent
7b926ff60a
commit
444c2ed1af
|
|
@ -1,4 +1,16 @@
|
||||||
import request from './index'
|
import request from './index'
|
||||||
|
import { getOrganizationId } from '../utils/organizationContext'
|
||||||
|
|
||||||
|
// 下载接口绕开 axios,得自己带令牌与当前公司,否则 401 / 400。
|
||||||
|
// 令牌沿用拦截器同款读法(localStorage),避免在 API 模块里依赖 Pinia 激活时序。
|
||||||
|
function downloadHeaders() {
|
||||||
|
const token = localStorage.getItem('wmms_token')
|
||||||
|
const organizationId = getOrganizationId()
|
||||||
|
return {
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
...(organizationId ? { 'X-Organization-ID': String(organizationId) } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const materialsApi = {
|
export const materialsApi = {
|
||||||
getAll(params = {}) {
|
getAll(params = {}) {
|
||||||
|
|
@ -23,5 +35,18 @@ export const materialsApi = {
|
||||||
return request.post('/upload', formData, {
|
return request.post('/upload', formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' }
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
importFile(formData) {
|
||||||
|
// silent:校验失败的明细要自己弹窗展示,不让拦截器先弹一句笼统提示
|
||||||
|
return request.post('/material-import', formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
downloadImportTemplate() {
|
||||||
|
// 下载走原生 fetch:响应是文件流,axios 拦截器只认 JSON 的 code 字段
|
||||||
|
return fetch('/api/material-import/template', {
|
||||||
|
headers: downloadHeaders()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
frontend/src/utils/exportCsv.js
Normal file
27
frontend/src/utils/exportCsv.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 生成 CSV 文本。带 UTF-8 BOM(写成转义而不是内联零宽字符,源码里才看得见),
|
||||||
|
// Excel 打开中文才不会乱码;字段里的逗号、引号、换行按 RFC 4180 用双引号包裹并转义。
|
||||||
|
export function buildCsv(rows) {
|
||||||
|
const lines = rows.map(row => row.map(escapeCsvCell).join(','))
|
||||||
|
return '\uFEFF' + lines.map(line => line + '\r\n').join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeCsvCell(value) {
|
||||||
|
const text = value === null || value === undefined ? '' : String(value)
|
||||||
|
if (/[",\r\n]/.test(text)) {
|
||||||
|
return `"${text.replace(/"/g, '""')}"`
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发浏览器下载。用 Blob + a[download],不依赖任何第三方库。
|
||||||
|
export function downloadCsv(fileName, csv) {
|
||||||
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
21
frontend/src/utils/exportCsv.test.js
Normal file
21
frontend/src/utils/exportCsv.test.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
import { buildCsv } from './exportCsv'
|
||||||
|
|
||||||
|
// BOM 用转义写,不内联那个零宽字符:内联的话看不见也改不动,
|
||||||
|
// 一旦被编辑器或编码转换吃掉,测试就会以一个极难看懂的方式失败。
|
||||||
|
const BOM = '\uFEFF'
|
||||||
|
|
||||||
|
describe('buildCsv', () => {
|
||||||
|
test('用逗号分隔并在行尾换行', () => {
|
||||||
|
const csv = buildCsv([
|
||||||
|
['行号', '失败原因'],
|
||||||
|
[3, '物料名称不能为空']
|
||||||
|
])
|
||||||
|
expect(csv).toBe(BOM + '行号,失败原因\r\n3,物料名称不能为空\r\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('包含逗号、引号、换行的字段用双引号包裹并转义', () => {
|
||||||
|
const csv = buildCsv([['规格', '备注'], ['M8,30', '含"引号"'], ['两\n行', '']])
|
||||||
|
expect(csv).toBe(BOM + '规格,备注\r\n"M8,30","含""引号"""\r\n"两\n行",\r\n')
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user