树状结构支持数据导入

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)"
class="options-value"
/>
<a class="options-delete" @click="addTreeNode(value)">
<Icon icon="ant-design:folder-add-outlined" />
</a>
<Tooltip title="添加子级选项" placement="top">
<a class="options-delete" @click="addTreeNode(value)">
<Icon icon="ant-design:folder-add-outlined" />
</a>
</Tooltip>
<a class="options-delete" @click="deleteTreeNode(value)">
<Icon icon="ant-design:delete-outlined" />
</a>
@ -46,6 +48,10 @@
<Icon icon="ant-design:file-add-outlined" />
添加父级选项
</a>
<a @click="showModal">
<Icon icon="ant-design:import-outlined" />
导入数据
</a>
</div>
<div v-else-if="['Transfer'].includes(formConfig.currentItem!.component)">
<div v-for="(item, index) of formConfig.currentItem!.componentProps![key]" :key="index">
@ -92,30 +98,64 @@
</a>
</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>
<script lang="ts">
import { defineComponent, reactive, toRefs, ref, unref, watch } from 'vue';
import { useFormDesignState } from '../../../hooks/useFormDesignState';
import { BasicTree, TreeItem, TreeActionType } from '@/components/Tree';
import { remove } from '../../../utils';
import message from '../../../utils/message';
import { Input } from 'ant-design-vue';
import Icon from '@/components/Icon/Icon.vue';
import { remove, formItemsForEach, generateKey } from '../../../utils';
import { CodeEditor, MODE } from '@/components/CodeEditor';
import { Upload, Modal, Input, Tooltip } from 'ant-design-vue';
import { options_json, treeData_json } from '../../VFormDesign/config/formItemPropsScript';
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({
name: 'FormOptions',
components: { Input, Icon, BasicTree },
components: { Input, Icon, BasicTree, CodeEditor, Upload, Modal, Tooltip },
// props: {},
setup() {
const state = reactive({});
const { formConfig } = useFormDesignState();
let key: string = '';
// ref
//
const state = reactive({
visible: false,
json: ``,
});
//
const treeDataAndOptions = ref<TreeItem[]>([]);
watch(
@ -124,10 +164,12 @@
if (formConfig.value.currentItem?.component == 'TreeSelect') {
//
treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.treeData;
state.json = treeData_json;
key = 'treeData';
} else if (formConfig.value.currentItem?.component == 'Cascader') {
//
treeDataAndOptions.value = formConfig.value.currentItem?.componentProps?.options;
state.json = options_json;
key = 'options';
} else if (formConfig.value.currentItem?.component == 'Transfer') {
// 穿
@ -142,11 +184,11 @@
//
const treeDataRef = ref<Nullable<TreeActionType>>(null);
function getTree(): any {
const getTree = (): any => {
return unref(treeDataRef);
}
};
//
function addTreeNode(value) {
const addTreeNode = (value) => {
let length = getLength(1, treeDataAndOptions.value);
getTree().insertNodeByKey({
parentKey: value,
@ -158,14 +200,14 @@
push: 'push',
});
refresh();
}
};
//
function deleteTreeNode(value) {
const deleteTreeNode = (value) => {
getTree().deleteNodeByKey(value);
refresh();
}
};
//
function getLength(length, treeDataOrOptions) {
const getLength = (length, treeDataOrOptions) => {
treeDataOrOptions?.forEach((to) => {
if (to.children) {
length = getLength(length, to.children);
@ -176,24 +218,84 @@
}
});
return length;
}
};
// labelvalue
function updateLabelOrValue(type, label, value, newLabelOrValue) {
const updateLabelOrValue = (type, label, value, newLabelOrValue) => {
if (type == 'label') {
getTree().updateNodeByKey(value, { label: newLabelOrValue, value: 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();
// 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);
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 = () => {
if (!formConfig.value.currentItem?.componentProps?.[key])
@ -230,7 +332,6 @@
const deleteOptions = (index: number) => {
remove(formConfig.value.currentItem?.componentProps?.[key], index);
};
const addGridOptions = () => {
formConfig.value.currentItem?.['columns']?.push({
span: 12,
@ -242,7 +343,6 @@
remove(formConfig.value.currentItem!['columns']!, index);
};
const updateTransferDisabled = (index: number, flag: boolean) => {
formConfig.value.currentItem.componentProps[key][index].disabled = flag;
};
@ -260,6 +360,11 @@
deleteTreeNode,
updateLabelOrValue,
updateTransferDisabled,
handleImportJson,
beforeUpload,
handleCancel,
showModal,
MODE,
};
},
});
@ -290,4 +395,8 @@
}
}
}
.upload-button {
margin: 0 10px;
}
</style>

View File

@ -58,3 +58,42 @@ url:api地址, params:参数
let resPut = await utils.httpPut(url, 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"
}
]
}`;