树状结构支持数据导入

dianlixunjian
滕嵩 2024-06-25 17:08:28 +08:00
parent d61c8dd12a
commit eeff8ef4f4
2 changed files with 177 additions and 29 deletions

View File

@ -33,9 +33,11 @@
@input="updateLabelOrValue('value', label, value, $event.target.value)" @input="updateLabelOrValue('value', label, value, $event.target.value)"
class="options-value" class="options-value"
/> />
<a class="options-delete" @click="addTreeNode(value)"> <Tooltip title="添加子级选项" placement="top">
<Icon icon="ant-design:folder-add-outlined" /> <a class="options-delete" @click="addTreeNode(value)">
</a> <Icon icon="ant-design:folder-add-outlined" />
</a>
</Tooltip>
<a class="options-delete" @click="deleteTreeNode(value)"> <a class="options-delete" @click="deleteTreeNode(value)">
<Icon icon="ant-design:delete-outlined" /> <Icon icon="ant-design:delete-outlined" />
</a> </a>
@ -46,6 +48,10 @@
<Icon icon="ant-design:file-add-outlined" /> <Icon icon="ant-design:file-add-outlined" />
添加父级选项 添加父级选项
</a> </a>
<a @click="showModal">
<Icon icon="ant-design:import-outlined" />
导入数据
</a>
</div> </div>
<div v-else-if="['Transfer'].includes(formConfig.currentItem!.component)"> <div v-else-if="['Transfer'].includes(formConfig.currentItem!.component)">
<div v-for="(item, index) of formConfig.currentItem!.componentProps![key]" :key="index"> <div v-for="(item, index) of formConfig.currentItem!.componentProps![key]" :key="index">
@ -92,30 +98,64 @@
</a> </a>
</div> </div>
</div> </div>
<Modal
title="树状结构数据导入"
:open="visible"
@ok="handleImportJson"
@cancel="handleCancel"
cancelText="关闭"
:destroyOnClose="true"
wrapClassName="v-code-modal"
style="top: 20px"
:width="850"
>
<p class="hint-box">导入格式如下:</p>
<div class="v-json-box">
<CodeEditor v-model:value="json" ref="myEditor" :mode="MODE.JSON" />
</div>
<template #footer>
<a-button @click="handleCancel"></a-button>
<Upload
class="upload-button"
:beforeUpload="beforeUpload"
:showUploadList="false"
accept="application/json"
>
<a-button type="primary">导入json文件</a-button>
</Upload>
<a-button type="primary" @click="handleImportJson"></a-button>
</template>
</Modal>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, toRefs, ref, unref, watch } from 'vue'; import { defineComponent, reactive, toRefs, ref, unref, watch } from 'vue';
import { useFormDesignState } from '../../../hooks/useFormDesignState'; import { useFormDesignState } from '../../../hooks/useFormDesignState';
import { BasicTree, TreeItem, TreeActionType } from '@/components/Tree'; import { BasicTree, TreeItem, TreeActionType } from '@/components/Tree';
import { remove } from '../../../utils'; import { remove, formItemsForEach, generateKey } from '../../../utils';
import message from '../../../utils/message'; import { CodeEditor, MODE } from '@/components/CodeEditor';
import { Input } from 'ant-design-vue'; import { Upload, Modal, Input, Tooltip } from 'ant-design-vue';
import { options_json, treeData_json } from '../../VFormDesign/config/formItemPropsScript';
import Icon from '@/components/Icon/Icon.vue';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { cloneDeep } from 'lodash-es';
import Icon from '@/components/Icon/Icon.vue';
import message from '../../../utils/message';
export default defineComponent({ export default defineComponent({
name: 'FormOptions', name: 'FormOptions',
components: { Input, Icon, BasicTree }, components: { Input, Icon, BasicTree, CodeEditor, Upload, Modal, Tooltip },
// props: {}, // props: {},
setup() { setup() {
const state = reactive({});
const { formConfig } = useFormDesignState(); const { formConfig } = useFormDesignState();
let key: string = ''; let key: string = '';
// ref //
const state = reactive({
visible: false,
json: ``,
});
//
const treeDataAndOptions = ref<TreeItem[]>([]); const treeDataAndOptions = ref<TreeItem[]>([]);
watch( watch(
@ -124,10 +164,12 @@
if (formConfig.value.currentItem?.component == 'TreeSelect') { if (formConfig.value.currentItem?.component == 'TreeSelect') {
// //
treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.treeData; treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.treeData;
state.json = treeData_json;
key = 'treeData'; key = 'treeData';
} else if (formConfig.value.currentItem?.component == 'Cascader') { } else if (formConfig.value.currentItem?.component == 'Cascader') {
// //
treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.options; treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.options;
state.json = options_json;
key = 'options'; key = 'options';
} else if (formConfig.value.currentItem?.component == 'Transfer') { } else if (formConfig.value.currentItem?.component == 'Transfer') {
// 穿 // 穿
@ -142,11 +184,11 @@
// //
const treeDataRef = ref<Nullable<TreeActionType>>(null); const treeDataRef = ref<Nullable<TreeActionType>>(null);
function getTree(): any { const getTree = (): any => {
return unref(treeDataRef); return unref(treeDataRef);
} };
// //
function addTreeNode(value) { const addTreeNode = (value) => {
let length = getLength(1, treeDataAndOptions.value); let length = getLength(1, treeDataAndOptions.value);
getTree().insertNodeByKey({ getTree().insertNodeByKey({
parentKey: value, parentKey: value,
@ -158,14 +200,14 @@
push: 'push', push: 'push',
}); });
refresh(); refresh();
} };
// //
function deleteTreeNode(value) { const deleteTreeNode = (value) => {
getTree().deleteNodeByKey(value); getTree().deleteNodeByKey(value);
refresh(); refresh();
} };
// //
function getLength(length, treeDataOrOptions) { const getLength = (length, treeDataOrOptions) => {
treeDataOrOptions?.forEach((to) => { treeDataOrOptions?.forEach((to) => {
if (to.children) { if (to.children) {
length = getLength(length, to.children); length = getLength(length, to.children);
@ -176,24 +218,84 @@
} }
}); });
return length; return length;
} };
// labelvalue // labelvalue
function updateLabelOrValue(type, label, value, newLabelOrValue) { const updateLabelOrValue = (type, label, value, newLabelOrValue) => {
if (type == 'label') { if (type == 'label') {
getTree().updateNodeByKey(value, { label: newLabelOrValue, value: value }); getTree().updateNodeByKey(value, { label: newLabelOrValue, value: value });
} }
if (type == 'value') { if (type == 'value') {
getTree().updateNodeByKey(value, { label: label, value: newLabelOrValue }); if (checkRepeat(true, getTree().getTreeData(), newLabelOrValue)) {
getTree().updateNodeByKey(value, { label: label, value: newLabelOrValue });
} else {
message.warning('不能赋给重复的值');
}
} }
refresh(); refresh();
// console.log(getTree().getTreeData()); };
} // labelvalue-
const checkRepeat = (flag, treeData, newValue) => {
treeData.forEach((tree) => {
if (tree.value == newValue) {
flag = false;
} else if (tree.children) {
return checkRepeat(flag, tree.children, newValue);
}
});
return flag;
};
// //
function refresh() { const refresh = () => {
getTree().expandAll(true); getTree().expandAll(true);
treeDataAndOptions.value = getTree().getTreeData(); formConfig.value.currentItem.componentProps[key] = treeDataAndOptions.value =
formConfig.value.currentItem.componentProps[key] = treeDataAndOptions.value; getTree().getTreeData();
} };
//
const handleCancel = () => {
state.visible = false;
};
//
const showModal = () => {
state.visible = true;
};
// JSON
const handleImportJson = () => {
try {
const editorJsonData = JSON.parse(state.json);
editorJsonData[key] &&
formItemsForEach(editorJsonData[key], (formItem) => {
generateKey(formItem);
});
//
cloneDeep(getTree().getTreeData())?.forEach((item) => {
deleteTreeNode(item.value);
});
//
editorJsonData[key]?.forEach((item) => {
getTree().insertNodeByKey({
parentKey: null,
node: item,
push: 'push',
});
});
refresh();
handleCancel();
message.success('导入成功');
} catch {
message.error('导入失败,数据格式不对');
}
};
// json
const beforeUpload = (e: File) => {
const reader = new FileReader();
reader.readAsText(e);
reader.onload = function () {
state.json = this.result as string;
handleImportJson();
};
return false;
};
const addOptions = () => { const addOptions = () => {
if (!formConfig.value.currentItem?.componentProps?.[key]) if (!formConfig.value.currentItem?.componentProps?.[key])
@ -230,7 +332,6 @@
const deleteOptions = (index: number) => { const deleteOptions = (index: number) => {
remove(formConfig.value.currentItem?.componentProps?.[key], index); remove(formConfig.value.currentItem?.componentProps?.[key], index);
}; };
const addGridOptions = () => { const addGridOptions = () => {
formConfig.value.currentItem?.['columns']?.push({ formConfig.value.currentItem?.['columns']?.push({
span: 12, span: 12,
@ -242,7 +343,6 @@
remove(formConfig.value.currentItem!['columns']!, index); remove(formConfig.value.currentItem!['columns']!, index);
}; };
const updateTransferDisabled = (index: number, flag: boolean) => { const updateTransferDisabled = (index: number, flag: boolean) => {
formConfig.value.currentItem.componentProps[key][index].disabled = flag; formConfig.value.currentItem.componentProps[key][index].disabled = flag;
}; };
@ -260,6 +360,11 @@
deleteTreeNode, deleteTreeNode,
updateLabelOrValue, updateLabelOrValue,
updateTransferDisabled, updateTransferDisabled,
handleImportJson,
beforeUpload,
handleCancel,
showModal,
MODE,
}; };
}, },
}); });
@ -290,4 +395,8 @@
} }
} }
} }
.upload-button {
margin: 0 10px;
}
</style> </style>

View File

@ -58,3 +58,42 @@ url:api地址, params:参数
let resPut = await utils.httpPut(url, params); let resPut = await utils.httpPut(url, params);
url:api, params: url:api, params:
`; `;
export const options_json = `{
"options": [
{
"label": "选项1",
"value": "1",
"children": [
{
"label": "选项3",
"value": "3"
}
]
},
{
"label": "选项2",
"value": "2"
}
]
}`;
export const treeData_json = `{
"treeData": [
{
"label": "选项1",
"value": "1",
"children": [
{
"label": "选项3",
"value": "3"
}
]
},
{
"label": "选项2",
"value": "2"
}
]
}`;