添加 CSV 编辑工具(close #12)
This commit is contained in:
@@ -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',
|
||||
|
430
src/components/Tools/OtherEditCSV.vue
Normal file
430
src/components/Tools/OtherEditCSV.vue
Normal 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>
|
@@ -42,6 +42,14 @@ module.exports = defineConfig({
|
||||
});
|
||||
},
|
||||
|
||||
configureWebpack: {
|
||||
resolve: {
|
||||
fallback: {
|
||||
'stream': false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
port: 9005,
|
||||
|
Reference in New Issue
Block a user