添加 CSV 编辑工具(close #12)

This commit is contained in:
2022-03-18 11:09:16 +08:00
parent 5f9c6eeb0f
commit 3283aea435
3 changed files with 445 additions and 5 deletions

View File

@@ -35,11 +35,6 @@ const navTools = {
version: '2',
enabled: true
},
'simple': {
title: '简易计算器',
component: 'CalcSimple',
enabled: false
},
}
},
generator: {
@@ -121,6 +116,13 @@ const navTools = {
other: {
title: '其他',
list: {
'edit-csv': {
title: 'CSV 编辑工具',
component: 'OtherEditCSV',
update: '2022-03-17',
version: '1',
enabled: true,
},
'new-window': {
title: '新窗口(小窗)中打开',
component: 'OtherNewWindow',

View File

@@ -0,0 +1,430 @@
<docs>
- [DataGridXL](https://www.datagridxl.com/)
- [@datagridxl/datagridxl2](https://www.npmjs.com/package/@datagridxl/datagridxl2)
- [Papa Parse](https://www.papaparse.com/)
- [papaparse](https://www.npmjs.com/package/papaparse)
- [@types/papaparse](https://www.npmjs.com/package/@types/papaparse)
</docs>
<template>
<div class="tool-page">
<div class="actions">
<div class="title">导入 / 导出</div>
<div class="content">
<file-upload accept=".csv" @changed="importCSV">
<el-button
type="success"
size="small"
plain
>导入 CSV</el-button>
</file-upload>
<el-button
type="success"
size="small"
plain
@click="exportCSV()"
>导出 CSV</el-button>
<el-button
type="primary"
size="small"
plain
@click="initExample()"
>示例数据</el-button>
<el-button
type="danger"
size="small"
plain
@click="reset(false)"
>初始化</el-button>
<el-button
type="danger"
size="small"
plain
@click="reset(true)"
>清空数据</el-button>
</div>
</div>
<div class="actions">
<div class="title">操作</div>
<div class="content">
<el-button
type="success"
size="small"
plain
@click="actionUndo()"
>撤销</el-button>
<el-button
type="success"
size="small"
plain
@click="actionRedo()"
>还原</el-button>
<el-button
type="success"
size="small"
plain
@click="insertEmptyRows()"
>插入新行</el-button>
<el-button
type="success"
size="small"
plain
@click="insertEmptyCols()"
>插入新列</el-button>
</div>
</div>
<div class="editor">
<div class="title">编辑区</div>
<div class="content">
<div ref="dgxl" class="grid"></div>
</div>
</div>
<div class="credits">
<div class="title">Credits</div>
<div class="content">
<div class="link-item">
<el-link
type="success"
target="_blank"
href="https://www.datagridxl.com/"
>DataGridXL - 数据表格</el-link>
</div>
<div class="link-item">
<el-link
type="success"
target="_blank"
href="https://www.papaparse.com/"
>Papa Parse - CSV 数据解析库</el-link>
</div>
</div>
</div>
</div>
</template>
<script>
import DataGridXL from '@datagridxl/datagridxl2';
import Papa from 'papaparse';
import FileUpload from '@/components/FileUpload.vue';
// https://www.datagridxl.com/docs/localization
const gridLocale = {
'Copy' : '复制',
'Cut' : '剪切',
'Paste' : '粘贴',
'Delete Row(s)' : '删除行',
'Hide Row' : '隐藏行',
'Hide $n Rows' : '隐藏 $n 列',
'Insert Row (up)' : '插入行(上方)',
'Insert Row (down)' : '插入行(下方)',
'Insert $n Rows (up)' : '插入 $n 行(上方)',
'Insert $n Rows (down)' : '插入 $n 行(下方)',
'Delete Column(s)' : '删除列',
'Hide Column' : '隐藏列',
'Hide $n Columns' : '隐藏 $n 列',
'Insert Column (left)' : '插入列(左侧)',
'Insert Column (right)' : '插入列(右侧)',
'Insert $n Columns (left)' : '插入 $n 列(左侧)',
'Insert $n Columns (right)' : '插入 $n 列(右侧)',
'Deselect' : '取消选择',
'Search' : '搜索',
'Sort A to Z' : '从 A 到 Z 排序',
'Sort Z to A' : '从 Z 到 A 排序',
'$n from $total' : '总数 $total 中的 $n',
'Paste not available.': '粘贴不可用。',
'Cannot delete all columns.': '部分列无法删除。',
'Cannot delete all rows.': '部分行无法删除。',
'Cannot hide all columns.': '部分列无法隐藏。',
'Cannot hide all rows.': '部分行无法隐藏。',
'Cannot delete all non-frozen columns.': '无法删除冻结的列。',
'Cannot delete all non-frozen rows.': '无法删除冻结的行',
'Cannot move columns in or out of the frozen section. Try turning off frozen columns first.': '无法将列移入或移出冻结的区域。',
'Cannot move rows in or out of the frozen section. Try turning off frozen rows first.': '无法将行移入或移出冻结的区域。',
};
// https://www.datagridxl.com/docs/themes
const gridTheme = {
// Header
'header' : '#F8F9FA',
'header|text' : '#000000',
'header:highlight' : '#E8EAED',
'header:selected' : '#2196F3',
'header:selected|text' : '#FFFFFF',
'header-icon' : '#000000',
};
export default {
name: 'EditCSV',
components: {
FileUpload,
},
data() {
return {
gridInstance: null,
}
},
mounted () {
this.initGrid();
},
methods: {
/**
* @description 初始化表格
* @param {object} [config] 配置选项
* @param {number[][]|string[][]} [config.datas] 初始数据
* @param {number} [config.rows] 初始行数
* @param {number} [config.cols] 初始列数
*/
initGrid(config = {}) {
const el = this.$refs['dgxl'];
if (!el) {
console.error('初始化失败,元素不存在!');
return;
}
const {
datas = null,
rows = 5,
cols = 5,
} = config;
// https://www.datagridxl.com/docs/passing-data
const data = (datas || DataGridXL.createEmptyData(rows, cols));
const options = {
data,
fontFamily: 'sans-serif',
fontSize: 14,
locale: gridLocale,
theme: gridTheme,
};
const instance = new DataGridXL(el, options);
this.gridInstance = instance;
},
/** 初始化示例数据 */
initExample() {
const csvStr = 'a,b,c,d,e\n1,2,3,4,5\n10,20,30,40,50\n"","","","",""';
const parsed = Papa.parse(csvStr);
const msg = '是否载入示例数据?';
this.$confirm(msg).then(() => {
this.initGrid({
datas: parsed.data,
});
}).catch(() => {});
},
/**
* @description 调用 DataGridXL 实例的方法
* @param {string} methodName 方法名称
* @param {array} params 调用时传递的参数
* @returns {boolean} 成功时返回 true失败时返回 false
*/
callGrid(methodName = '', params = []) {
const { gridInstance: instance } = this;
if (instance && instance[methodName]) {
instance[methodName](...params);
} else {
console.error('DataGridXL 实例不存在。');
return false;
}
},
/** 还原 */
actionRedo() {
this.callGrid('redo');
},
/** 撤销 */
actionUndo() {
this.callGrid('undo');
},
/** 清空数据 */
clearCellValues() {
const { gridInstance: instance } = this;
if (instance) {
const data = instance.getData();
const start = {
x: 0,
y: 0,
};
const end = {
x: (data[0].length -1), // 列
y: (data.length - 1), // 行
};
instance.clearCellValues([start, end]);
}
},
/** 导出 CSV */
exportCSV() {
this.callGrid('downloadDataAsCSV');
},
/** 导入 CSV */
importCSV(datas) {
const files = datas.list;
if (!(files && files.constructor === FileList)) {
return;
}
if (files.length > 0) {
Papa.parse(files[0], {
complete: (results) => {
this.$message({
duration: 3000,
message: '文件解析成功。',
type: 'success',
});
this.initGrid({ datas: results.data });
},
error: (error) => {
this.$message({
duration: 3000,
message: '文件解析失败!',
type: 'error',
});
console.error('[文件解析失败]', error);
},
});
}
},
insertEmptyCols() {
this.callGrid('insertEmptyCols');
},
insertEmptyRows() {
this.callGrid('insertEmptyRows');
},
/**
* @description 重置数据
* @param {boolean} isClean 是否为清除,否则为初始化
*/
reset(isClean = false) {
const msg = (
isClean ? '是否清空表格数据?' : '是否重置为初始状态?'
);
this.$confirm(msg).then(() => {
if (isClean) {
this.clearCellValues();
} else {
this.initGrid();
}
}).catch(() => {});
},
},
}
</script>
<style lang="less" scoped>
.actions .content > * {
margin: 0 0.25em;
}
.editor {
width: 100%;
height: auto;
::-webkit-scrollbar-track {
background-color: #FFF;
}
::-webkit-scrollbar-thumb {
border-radius: 0;
}
.grid {
width: 100%;
height: 400px;
border: 0.125rem solid #2196f3;
border-radius: 0.375rem;
}
/deep/ button {
border: none !important;
background-color: @colorPrimary !important;
background-image: none !important;
svg {
fill: #FFF !important;
}
}
/deep/ .dgxl-inputInfo {
padding: 0.25em;
z-index: 10;
background-color: #FFF;
line-height: 1;
}
/deep/ .dgxl-inputWrapper {
border-color: #CCC !important;
&.dgxl-hasFocus {
box-shadow: none !important;
border-color: @colorPrimary !important;
.dgxl-inputInfo {
display: none !important;
}
}
}
/deep/ .dgxl-part-credits {
display: none !important;
}
}
</style>

View File

@@ -42,6 +42,14 @@ module.exports = defineConfig({
});
},
configureWebpack: {
resolve: {
fallback: {
'stream': false
}
}
},
devServer: {
host: '0.0.0.0',
port: 9005,