摘要:上述这篇文章主要是实现的 json 数据导出为 excel ,有讲编辑功能,当时想的是直接用 el-input 实现,并没有实现完整。后面发现了 vxe-table ,但没有切实的使用,所以没有更新那篇文章。这次项目中再次需要用到 excel 识别后编辑的功能
菜鸟所在的公司很多流程都需要用到 excel ,因为之前没有信息化,所以现在很多功能都是要上传 excel 识别,然后对识别内容进行编辑!
这部分菜鸟之前写过一篇文章:vue3 导出数据为 excel 文件 + 导入并识别 excel 文件
上述这篇文章主要是实现的 json 数据导出为 excel ,有讲编辑功能,当时想的是直接用 el-input 实现,并没有实现完整。后面发现了 vxe-table ,但没有切实的使用,所以没有更新那篇文章。这次项目中再次需要用到 excel 识别后编辑的功能,所以特来补充!
vxe-table 是 vxe UI 里面的 table 组件,很实用,其他的组件需要各位读者去开发,因为菜鸟没怎么使用。菜鸟感觉这个基本和 element plus 差不多,只是功能有的更强、有的又偏弱,基本上 element plus 的都可以替代 vxe UI 里面的组件,但菜鸟感觉 vxe UI 的:表单、vxe-grid 各位读者可以好好看看,因为这两个都有通过数据结构生成内容的能力,对喜欢 jsx 的帮助应该很大;然后其他组件的话,感觉也比较有用,有 element #技术分享plus 里面没有的一些功能!
vxe UI 网址:vxeui.com
vxe-table 实用不是因为别的,就因为其使用方式和 element plus 的 el-table 一模一样,但是不同的是,其可以对每一行数据进行编辑!
这里菜鸟感觉有个槽点,按需要引入很奇怪(感觉很超出了认知,是你要用什么自己去引入的这种按需引入,而不是 element plus 那种自动的),主要是使用 vxe-table 以及 vxe-table 中会用到的 vxe-select 、vxe-input ,大部分只需要引入一点就行了!
具体见:vxeui.com/#/start/use…
菜鸟的 main.js 部分
import { VxeUI, VxeSelect, VxeTooltip, VxeInput } from "vxe-pc-ui";import { VxeTable, VxeColumn, VxeColgroup, VxeGrid, VxeToolbar } from "vxe-table";import "vxe-table/styles/cssvar.scss"; import "vxe-pc-ui/styles/cssvar.scss";import zhCN from "vxe-pc-ui/lib/language/zh-CN"; VxeUI.setI18n("zh-CN", zhCN); VxeUI.setLanguage("zh-CN");Function lazyVxe(app) { app.use(VxeSelect); app.use(VxeInput); app.use(VxeTooltip); app.use(VxeTable); app.use(VxeColumn); app.use(VxeColgroup); app.use(VxeGrid); app.use(VxeToolbar); }app.use(lazyVxe);实现代码 -- 父组件
import { upload_or_download_path } from "@/enums/sampleType.js"; import { isEqual, omit } from "lodash-es"; import { sleep } from "@/utils/coroutine.js"; import SampleNucleinCol from "@/components/Lims/SampleCommon/SampleInput/cols/SampleNucleinCol.vue"; import SampleCellCol from "@/components/Lims/SampleCommon/SampleInput/cols/SampleCellCol.vue"; import SampleLibCol from "@/components/Lims/SampleCommon/SampleInput/cols/SampleLibCol.vue"; import SampleSublibraryCol from "@/components/Lims/SampleCommon/SampleInput/cols/SampleSublibraryCol.vue"; import _ from "lodash-es";import domZIndex from "dom-zindex"; domZIndex.setCurrent(10000);const props = defineProps({ sendSampleType: { type: [String, number], default: -1 }, canEdit: { type: boolean, default: true }, canSelect: { type: Boolean, default: true }, height: { type: [String, Number], default: "600" }, otherRules: { type: Object, default: => ({}) } });const emit = defineEmits(["selectChangeEvent", "showSublib", "selectAllEvent"]);const tableRef = ref; const tableData = defineModel("tableData", { default: });const editConfig = ref; if (props.canEdit) { editConfig.value = { trigger: "click", mode: "cell" }; } else { editConfig.value = { trigger: "none", mode: "cell" }; }const curStrategy = computed( => { return ( upload_or_download_path.find( (v) => v.value.toString === `${toValue(props.sendSampleType)}` ) ?? {} ); });const addRow = async => { const newRow = { isChecked: true }; tableData.value.push(newRow); await nextTick; const $table = tableRef.value; if ($table) { if (props.canEdit) { await $table.setEditRow(newRow, "sampleName"); unref(tableRef).setCheckboxRow(newRow, true); } await sleep(0); $table.scrollToRow(newRow); } };const useRules = computed( => { if (_.isFunction(curStrategy.value.rules)) { let rules = curStrategy.value.rules(tableData.value); return { ...rules, ...props.otherRules }; } return { ...props.otherRules }; });const getSelectedList = async => { const selectedList = await unref(tableRef).getCheckboxRecords; return selectedList.map((v) =>omit(v, ["_X_ROW_KEY", "isChecked", "createUserId", "createTime", "updateUserId", "updateTime"]) ); };const addSelectedList = (_sampleList) => { unref(tableRef).setCheckboxRow(_sampleList, true); };const clearSelectEvent = => { const $table = tableRef.value; if ($table) { $table.clearCheckboxRow; } };const resetSelectedList = => { tableData.value = ; };const fullValidEvent = async (bool = true) => { const $table = tableRef.value; if ($table) { const errMap = await $table.validate(bool); return errMap; } };const removeRow = async (row) => { tableData.value = tableData.value.filter((item) => !isEqual(item, row)); };const getRowIndex = (row) => { const $table = tableRef.value; if ($table) { return $table.getRowIndex(row); } };const showSublib = (row) => { emit("showSublib", row); };const selectChangeEvent = (row) => { console.log(row); emit("selectChangeEvent", row.records); }; const selectAllEvent = (checked) => { emit("selectAllEvent", checked); };defineExpose({ getSelectedList, addSelectedList, fullValidEvent, addRow, clearSelectEvent, getRowIndex, resetSelectedList, removeRow }); 当前尚未添加{{ curStrategy.label }}样本数据,请点击右上方的下载{{ curStrategy.label }}模板按钮下载后并填写,填写完成后点击上传{{ curStrategy.label }}模板按钮上传.或点击右上方的新增一行按钮添加{{ curStrategy.label }}样本数据手动添加 子文库{{ row?.orderSampleSubLibraryDTOList?.length || 0 }}.numbericon { border: 1px solid var(--el-color-primary); border-radius: 50%; display: inline-block; font-size: 9px; width: 15px; height: 15px; text-align: center; line-height: 15px; margin-left: 5px; margin-top: 2px; }这里的 upload_or_download_path 就是个枚举,用到了策略模式,不然每一个都要写,很麻烦:
import *import *import *import { sublibEnumsNum } from "@/components/Lims/SampleCommon/SampleInput/common";const rulesObject = { sampleName: [ { required: true, message: "请输入样本名称" } ], latinSpeciesName: [ { required: true, message: "请输入拉丁学名" } ], …… };export const upload_or_download_path = [ { label: "组织", value: 1, height: "600px", downloadApi: => sampleInput.orderSampleDownload(1), uploadApi: sampleInput.orderSampleUpload, associated: sampleSend.sampleAssociatedOrder, detail: sampleListByOrderId.orderSampleList, itemName: "orderSampleDTOList", serverDataToLocal: (res) => { let data = res.data.records; for (let i of data) { i.infective = i.infective + ""; } return data; }, rules: function { return rulesObject; } }, { label: "核酸", value: 2, height: "600px", downloadApi: => sampleInput.orderSampleDownload(2), uploadApi: sampleInput.orderSampleUpload, associated: sampleSend.sampleAssociatedOrder, detail: sampleListByOrderId.orderSampleList, itemName: "orderSampleDTOList", serverDataToLocal: (res) => { let data = res.data.records; for (let i of data) { i.infective = i.infective + ""; } return data; }, rules: function { return rulesObject; } }, { label: "文库", value: 3, height: "500px", downloadApi: sampleInput.orderSampleLibraryDownload, uploadApi: sampleInput.orderSampleLibraryUpload, associated: sampleSend.sampleAssociatedOrderSampleLibrary, detail: sampleListByOrderId.orderSampleListLibrary, itemName: "orderSampleLibraryDTOList", serverDataToLocal: (res) => { let data = res.data.records; for (let i of data) { i.baseBalance = i.baseBalance + ""; i.cyclized = i.cyclized + ""; for (let j of i.orderSampleSubLibraryDTOList) { j.baseBalance = j.baseBalance + ""; j.libraryEndFivePhosphorylation = j.libraryEndFivePhosphorylation + ""; } } return data; }, rules: function (data) { const sampleName = ; sampleName.push({ required: true, message: "请输入文库名称" }); sampleName.push({ validator(e) { let sampleNameArr = {}; for (let i = 0; i 1) { return new Error("存在重名的文库名称"); } } }); return Object.assign({}, rulesObject, { sampleName: sampleName, latinSpeciesName: [ { required: true, message: "请输入来源物种" } ] }); } }, { label: "单细胞", value: 4, height: "450px", downloadApi: sampleInput.orderSampleCellDownload, uploadApi: sampleInput.orderSampleCellUpload, associated: sampleSend.sampleAssociatedOrderSampleCell, detail: sampleListByOrderId.orderSampleListCell, itemName: "orderSampleCellDTOList", serverDataToLocal: (res) => { let data = res.data.records; for (let i of data) { i.sampleRunOut = i.sampleRunOut + ""; i.sampleHasBackup = i.sampleHasBackup + ""; } return data; }, rules: function { return Object.assign({}, rulesObject, { latinSpeciesName: [ { required: true, message: "请输入来源物种" } ] }); } }, { label: "子文库", value: sublibEnumsNum, height: "500px", detail: sampleListByOrderId.orderSampleListSubLibrary, serverDataToLocal: (res) => { let data = res.data; for (let i of data) { i.baseBalance = i.baseBalance + ""; i.libraryEndFivePhosphorylation = i.libraryEndFivePhosphorylation + ""; } return data; }, rules: function { return Object.assign({}, rulesObject, { sampleName: [ { required: true, message: "请输入文库名称" } ] }); } } ];实现代码 -- 子组件
子组件都是列之类的,这里就只放一个了 SampleCellCol.vue ,作为例子
import { dictData, format, activeVal, inactiveVal } from "../common"; import { serverDateFormat } from "@/utils/date.js"; import DictTag from "@/components/DictTag/index.vue";defineProps({ canEdit: { type: Boolean, default: true } });const { send_sample_order_sample_preservation_status, send_sample_order_sample_preservation_medium, send_sample_order_single_cell_platform, send_sample_order_single_cell_project_type, send_sample_order_seq_platform, send_sample_order_single_cell_hand_method } = dictData; {{ format(row, "samplePreservationStatus", "send_sample_order_sample_preservation_status") }}{{ format(row, "samplePreservationMedium", "send_sample_order_sample_preservation_medium") }}{{ row.sampleGatherTime }}{{ format(row, "processWay", "send_sample_order_single_cell_hand_method") }}{{ format(row, "singleCellPlatform", "send_sample_order_single_cell_platform") }}{{ format(row, "singleCellProjectType", "send_sample_order_single_cell_project_type") }}{{ format(row, "seqPlatform", "send_sample_order_seq_platform") }}这里菜鸟感觉不是很难,就按照 element plus 的去搞就行,主要是需要找到对应的文档,具体文档在这里:
可编辑 - 插槽式: vxeui.com/#/component…可编辑 - 配置式: vxeui.com/#/component…校验数据: vxeui.com/#/component…设置选择项: vxeui.com/#/component…然后如果是简单 excel(没有合并的列),甚至都不用后端识别,菜鸟这里有个办法,见文章:工作+学习 常用的一些东西
来源:墨码行者