-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from ywywZhou/import-export
feat: 决策表支持导入导出 --story=119248396
- Loading branch information
Showing
7 changed files
with
562 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
170 changes: 170 additions & 0 deletions
170
frontend/src/components/DecisionTable/ImportExport/ExportBtn.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
<template> | ||
<bk-button | ||
text | ||
theme="primary" | ||
:disabled="isDisabled" | ||
@click="handleClick"> | ||
{{ '导出' }} | ||
</bk-button> | ||
</template> | ||
<script> | ||
import XLSX from 'xlsx-js-style'; | ||
import moment from 'moment-timezone'; | ||
import { getCellText } from './dataTransfer.js'; | ||
export default { | ||
props: { | ||
name: { | ||
type: String, | ||
default: '', | ||
}, | ||
data: { | ||
type: Object, | ||
default: () => ({}), | ||
}, | ||
}, | ||
data() { | ||
return { | ||
}; | ||
}, | ||
computed: { | ||
isDisabled() { | ||
return !this.data.records.length; | ||
}, | ||
}, | ||
methods: { | ||
handleClick() { | ||
const { inputs, outputs, records } = this.data; | ||
// 设置单元格的样式 | ||
const cellStyles = { | ||
alignment: { | ||
horizontal: 'center', // 水平居中 | ||
vertical: 'center', // 垂直居中 | ||
}, | ||
border: { | ||
top: { style: 'thin', color: { rgb: '000000' } }, | ||
bottom: { style: 'thin', color: { rgb: '000000' } }, | ||
left: { style: 'thin', color: { rgb: '000000' } }, | ||
right: { style: 'thin', color: { rgb: '000000' } }, | ||
}, | ||
}; | ||
// 定义表头和注释 | ||
const headers = [ | ||
{ | ||
label: 'Input', | ||
children: inputs.map(item => ({ label: `${item.name}(${item.id})`, description: JSON.stringify(item) })), | ||
}, | ||
{ | ||
label: 'Output', | ||
children: outputs.map(item => ({ label: `${item.name}(${item.id})`, description: JSON.stringify(item) })), | ||
}, | ||
]; | ||
const data = records.reduce((acc, cur) => { | ||
// 暂时过滤【条件组合】类型!!! | ||
if (cur.inputs.type !== 'common') return acc; | ||
const arr = []; | ||
cur.inputs.conditions.forEach((item) => { | ||
arr.push({ v: getCellText(item), t: 's', s: cellStyles }); | ||
}); | ||
outputs.forEach((item) => { | ||
arr.push({ v: cur.outputs[item.id], t: 's', s: cellStyles }); | ||
}); | ||
acc.push(arr); | ||
return acc; | ||
}, []); | ||
const wb = XLSX.utils.book_new(); | ||
// 定义工作表数据 | ||
const wsData = []; | ||
// 表头 | ||
const topHeader = []; | ||
const subHeader = []; | ||
const comments = []; | ||
headers.forEach((header, headerIndex) => { | ||
topHeader.push({ | ||
v: header.label, | ||
t: 's', | ||
s: { | ||
...cellStyles, | ||
font: { bold: true, sz: 16 }, | ||
fill: { fgColor: { rgb: '9FE3FF' } }, | ||
}, | ||
}); | ||
for (let i = 1; i < header.children.length; i++) { | ||
topHeader.push(null); | ||
} | ||
header.children.forEach((child, childIndex) => { | ||
subHeader.push({ | ||
v: child.label, | ||
t: 's', | ||
s: { | ||
...cellStyles, | ||
font: { bold: true }, | ||
fill: { fgColor: { rgb: '9FE3FF' } }, | ||
}, | ||
}); | ||
comments.push({ | ||
cell: XLSX.utils.encode_cell({ c: headerIndex * header.children.length + childIndex, r: 1 }), | ||
comment: child.description, | ||
}); | ||
}); | ||
}); | ||
wsData.push(topHeader); | ||
wsData.push(subHeader); | ||
// 填充数据 | ||
wsData.push(...data); | ||
// 创建工作表 | ||
const ws = XLSX.utils.aoa_to_sheet(wsData); | ||
// 合并单元格设置 | ||
let colIndex = 0; | ||
headers.forEach((header) => { | ||
ws['!merges'] = ws['!merges'] || []; | ||
ws['!merges'].push({ | ||
s: { r: 0, c: colIndex }, | ||
e: { r: 0, c: colIndex + header.children.length - 1 }, | ||
}); | ||
colIndex += header.children.length; | ||
}); | ||
// 添加注释 | ||
comments.forEach(({ cell, comment }) => { | ||
if (!ws[cell].c) ws[cell].c = []; | ||
ws[cell].c.hidden = true; | ||
ws[cell].c.push({ t: comment }); | ||
}); | ||
// 调整列高宽 | ||
ws['!cols'] = subHeader.map(() => ({ wch: 40 })); | ||
ws['!rows'] = [ | ||
{ hpx: 40 }, | ||
{ hpx: 25 }, | ||
...data.map(() => ({ hpx: 20 })), | ||
]; | ||
// 将工作表添加到工作簿中 | ||
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); | ||
// 导出工作簿 | ||
XLSX.writeFile(wb, `${this.name || 'Decision'}_${moment().format('YYYYMMDDHHmmss')}.xlsx`); | ||
}, | ||
}, | ||
}; | ||
</script> | ||
<style lang="scss" scoped> | ||
.bk-button-text { | ||
font-size: 12px; | ||
color: #63656e; | ||
&.is-disabled { | ||
color: #dcdee5; | ||
} | ||
} | ||
</style> |
191 changes: 191 additions & 0 deletions
191
frontend/src/components/DecisionTable/ImportExport/ImportBtn.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
<template> | ||
<div> | ||
<label | ||
:for="isDisabled ? '' : 'xls-file'" | ||
:class="['file-label', { 'is-disabled': isDisabled }]"> | ||
{{ '导入' }} | ||
</label> | ||
<input | ||
id="xls-file" | ||
ref="fileInput" | ||
type="file" | ||
accept=".xlsx, .xls" | ||
style="display: none;" | ||
@change="handleFile"> | ||
</div> | ||
</template> | ||
<script> | ||
import * as XLSX from 'xlsx'; | ||
import tools from '@/utils/tools.js'; | ||
import { validateFiled, parseValue, validateValue, getValueRight } from './dataTransfer.js'; | ||
export default { | ||
props: { | ||
data: { | ||
type: Object, | ||
default: () => ({}), | ||
}, | ||
}, | ||
computed: { | ||
isDisabled() { | ||
const { inputs, outputs, records } = this.data; | ||
return inputs.length > 0 || outputs.length > 0 || records.length > 0; | ||
}, | ||
}, | ||
methods: { | ||
handleFile(event) { | ||
const file = event.target.files[0]; | ||
const reader = new FileReader(); | ||
reader.onload = this.processFile; | ||
reader.onerror = () => { | ||
this.showMessage('读取文件失败'); | ||
}; | ||
reader.readAsArrayBuffer(file); | ||
// 处理完文件后,重置文件输入字段 | ||
this.$refs.fileInput.value = ''; | ||
}, | ||
processFile(e) { | ||
const data = new Uint8Array(e.target.result); | ||
const workbook = XLSX.read(data, { type: 'array' }); | ||
// 读取第一个工作表 | ||
const firstSheetName = workbook.SheetNames[0]; | ||
const worksheet = workbook.Sheets[firstSheetName]; | ||
// 将工作表转换为JSON | ||
let jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); | ||
jsonData = jsonData.filter(row => (!row.every(cell => !cell))); // 过滤空行 | ||
// 表格至少三层结构 类型、字段、数据 | ||
if (jsonData.length < 3) { | ||
this.showMessage('数据结构不对'); | ||
return; | ||
} | ||
const sheetValue = Object.values(worksheet); | ||
this.parseSheetData(jsonData, sheetValue); | ||
}, | ||
parseSheetData(jsonData, sheetValue) { | ||
const inputs = []; | ||
const outputs = []; | ||
const records = []; | ||
const result = jsonData.some((row, rIndex) => { | ||
// 类型 | ||
if (rIndex === 0) return false; | ||
// 字段 | ||
if (rIndex === 1) { | ||
const { header, result } = this.getHeader(row, sheetValue); | ||
if (!result) return true; | ||
// 校验header | ||
const message = validateFiled(header); | ||
if (message) { | ||
this.showMessage(message); | ||
return true; | ||
} | ||
header.forEach((item) => { | ||
if (item.from === 'inputs') { | ||
inputs.push(item); | ||
} else { | ||
outputs.push(item); | ||
} | ||
}); | ||
return false; | ||
} | ||
// 数据 | ||
const header = [...inputs, ...outputs]; | ||
const record = this.getRecord(row, header); | ||
if (!record.result) return true; | ||
delete record.result; | ||
records.push(record); | ||
return false; | ||
}); | ||
if (result) return; | ||
this.$emit('updateData', { inputs, outputs, records }); | ||
}, | ||
getHeader(row, sheetValue) { | ||
const header = []; | ||
const result = row.every((cell) => { | ||
const comment = sheetValue.find(value => Object.prototype.toString.call(value) === '[object Object]' && value.v === cell); | ||
if (!comment || !comment.c) { | ||
this.showMessage(`表格【${cell}】列缺少相应的配置注释`); | ||
return false; | ||
} | ||
const { t } = comment.c[0]; | ||
if (!tools.checkIsJSON(t)) { | ||
this.showMessage(`表格【${cell}】列的配置注释不是json格式`); | ||
return false; | ||
} | ||
header.push(JSON.parse(t)); | ||
return true; | ||
}); | ||
return { header, result }; | ||
}, | ||
getRecord(row, header) { | ||
const inputs = { | ||
conditions: [], | ||
type: 'common', | ||
}; | ||
const outputs = {}; | ||
const result = header.every((col, colIndex) => { | ||
// 解析value和操作方式 | ||
const { value, type } = parseValue(row[colIndex]); | ||
// 校验value | ||
const message = validateValue(value, col); | ||
if (message) { | ||
this.showMessage(message); | ||
return false; | ||
} | ||
// 生成record | ||
if (col.from === 'outputs') { | ||
outputs[col.id] = value; | ||
} | ||
if (col.from === 'inputs') { | ||
inputs.conditions.push({ | ||
compare: type, | ||
right: getValueRight(value, type, col), | ||
}); | ||
} | ||
return true; | ||
}); | ||
return { inputs, outputs, result }; | ||
}, | ||
showMessage(message, theme = 'error') { | ||
this.$bkMessage({ message, theme }); | ||
}, | ||
}, | ||
}; | ||
</script> | ||
<style lang="scss" scoped> | ||
.file-label { | ||
line-height: 24px; | ||
font-size: 12px; | ||
color: #63656e; | ||
cursor: pointer; | ||
&:hover { | ||
color: #3a84ff; | ||
} | ||
&.is-disabled { | ||
color: #dcdee5; | ||
cursor: not-allowed; | ||
} | ||
} | ||
</style> |
Oops, something went wrong.