<script lang="ts" setup>
|
import type { TreeActionType } from '../../../core';
|
import type { JnpfAreaSelectProps } from './props';
|
|
import { computed, onMounted, reactive, ref, toRefs, unref, watch } from 'vue';
|
|
import { useAttrs } from '@jnpf/hooks';
|
import { BasicTree } from '@jnpf/ui';
|
import { ModalClose, ModalFooter } from '@jnpf/ui/modal';
|
|
import { $t } from '@vben/locales';
|
|
import { globalShareState } from '@vben-core/shared/global-state';
|
|
import { Modal as AModal, Form, Select, TabPane, Tabs, Tag } from 'ant-design-vue';
|
import { cloneDeep, pick } from 'lodash-es';
|
|
defineOptions({ inheritAttrs: false, name: 'JnpfAreaSelect' });
|
|
const props = withDefaults(defineProps<JnpfAreaSelectProps>(), {
|
allowClear: true,
|
disabled: false,
|
level: 2,
|
multiple: false,
|
placeholder: '请选择',
|
});
|
|
const emit = defineEmits(['update:value', 'change']);
|
|
interface State {
|
activeKey: string;
|
innerValue: any[] | string | undefined;
|
loading: boolean;
|
options: any[];
|
selectedData: any[];
|
selectedIds: any[];
|
treeData: any[];
|
visible: boolean;
|
}
|
|
const api = globalShareState.getApi();
|
const { getAreaByIds, getAreaSelector } = api;
|
|
const attrs: any = useAttrs({ excludeDefaultKeys: false });
|
const treeRef = ref<Nullable<TreeActionType>>(null);
|
const nodeId = ref('-1');
|
const formItemContext = Form.useInjectFormItemContext();
|
const state = reactive<State>({
|
activeKey: '1',
|
innerValue: [],
|
loading: false,
|
options: [],
|
selectedData: [],
|
selectedIds: [],
|
treeData: [],
|
visible: false,
|
});
|
const { activeKey, innerValue, loading, options, selectedData, selectedIds, treeData, visible } = toRefs(state);
|
|
const getSelectBindValue: any = computed(() => ({
|
...pick(props, ['placeholder', 'disabled', 'size', 'allowClear']),
|
class: unref(attrs).class ? `w-full ${unref(attrs).class}` : 'w-full',
|
fieldNames: { label: 'fullName', value: 'id' },
|
mode: props.multiple ? 'multiple' : '',
|
open: false,
|
showArrow: true,
|
showSearch: false,
|
style: Reflect.has(unref(attrs), 'style') ? unref(attrs).style : {},
|
}));
|
|
watch(
|
() => unref(props.value),
|
() => {
|
setValue();
|
},
|
{ immediate: true },
|
);
|
|
function setValue() {
|
if (!props.value || props.value.length === 0) return setNullValue();
|
const ids = props.multiple ? (props.value as any[]) : [props.value];
|
getAreaByIds(unref(ids)).then((res) => {
|
if (!props.value || props.value.length === 0) return setNullValue();
|
state.selectedIds = cloneDeep(ids);
|
const selectedList: any[] = res.data.map((o, i) => {
|
const nodePath: any[] = [];
|
for (const [j, element] of o.entries()) {
|
const item = { fullName: element, id: ids[i][j] };
|
nodePath.push(item);
|
}
|
return {
|
fullName: o.join('/'),
|
id: ids[i].join(''),
|
ids: ids[i],
|
nodePath,
|
};
|
});
|
const innerIds = unref(selectedIds).map((o) => o.join(''));
|
state.innerValue = props.multiple ? innerIds : innerIds[0];
|
state.options = cloneDeep(selectedList);
|
state.selectedData = cloneDeep(selectedList);
|
});
|
}
|
function setNullValue() {
|
state.innerValue = props.multiple ? [] : undefined;
|
state.options = [];
|
state.selectedIds = [];
|
state.selectedData = [];
|
}
|
function onChange(_val, option) {
|
if (option) {
|
state.selectedData = option;
|
state.selectedIds = option.map((o) => o.ids);
|
} else {
|
state.selectedData = [];
|
state.selectedIds = [];
|
}
|
handleSubmit();
|
}
|
function openSelectModal() {
|
if (props.disabled) return;
|
state.visible = true;
|
setValue();
|
}
|
function handleCancel() {
|
state.visible = false;
|
}
|
function getNodePath(node): any[] {
|
let fullPath: any[] = [];
|
const currNode = { ...node.dataRef };
|
fullPath.push(currNode);
|
if (node.parent) {
|
const nodes = node.parent.nodes;
|
fullPath = [...nodes, ...fullPath];
|
}
|
return fullPath;
|
}
|
function handleSelect(_keys, { node }) {
|
if (!node.isLeaf || node.disabled) return;
|
const nodePath: any[] = getNodePath(node).map((o) => ({ fullName: o.fullName, id: o.id }));
|
const currId = nodePath.map((o) => o.id);
|
if (currId.length !== props.level + 1) return;
|
const data = {
|
fullName: nodePath.map((o) => o.fullName).join('/'),
|
id: currId.join(''),
|
ids: currId,
|
nodePath,
|
};
|
if (props.multiple) {
|
const boo = state.selectedIds.some((o) => o.join(',') === currId.join(','));
|
if (boo) return;
|
state.selectedIds.push(currId);
|
state.selectedData.push(data);
|
} else {
|
state.selectedIds = [currId];
|
state.selectedData = [data];
|
}
|
}
|
function removeAll() {
|
state.selectedIds = [];
|
state.selectedData = [];
|
}
|
function removeData(index: number) {
|
state.selectedIds.splice(index, 1);
|
state.selectedData.splice(index, 1);
|
}
|
function getTree() {
|
const tree = unref(treeRef);
|
if (!tree) {
|
throw new Error('tree is null!');
|
}
|
return tree;
|
}
|
function handleSubmit() {
|
const ids = state.selectedData.map((o) => o.id);
|
state.options = state.selectedData;
|
state.innerValue = props.multiple ? ids : ids[0];
|
const nodePathData = state.selectedData.map((o) => o.nodePath);
|
if (props.multiple) {
|
emit('update:value', state.selectedIds);
|
emit('change', state.selectedIds, unref(nodePathData));
|
} else {
|
emit('update:value', state.selectedIds[0] || []);
|
emit('change', state.selectedIds[0] || [], unref(nodePathData)[0]);
|
}
|
formItemContext.onFieldChange();
|
handleCancel();
|
}
|
function onLoadData(node) {
|
const level = node.parent ? node.parent.level + 2 : 1;
|
nodeId.value = node.id;
|
return new Promise((resolve: (value?: unknown) => void) => {
|
getAreaSelector(nodeId.value).then((res) => {
|
const list = res.data.list.map((value) => ({
|
...value,
|
isLeaf: level >= props.level ? true : value.isLeaf,
|
}));
|
getTree().updateNodeByKey(node.eventKey, { isLeaf: list.length === 0, children: list });
|
resolve();
|
});
|
});
|
}
|
async function initData() {
|
state.loading = true;
|
getAreaSelector(nodeId.value).then((res) => {
|
const list = res.data.list.map((value) => ({
|
...value,
|
isLeaf: props.level <= 0 ? true : value.isLeaf,
|
}));
|
state.treeData = list;
|
state.loading = false;
|
});
|
}
|
|
onMounted(() => {
|
initData();
|
});
|
</script>
|
|
<template>
|
<Select v-bind="getSelectBindValue" v-model:value="innerValue" :options="options" @change="onChange" @click="openSelectModal" />
|
<AModal
|
v-model:open="visible"
|
:keyboard="false"
|
:mask-closable="false"
|
:title="$t('component.jnpf.areaSelect.modalTitle')"
|
:width="800"
|
centered
|
class="common-select-modal"
|
@ok="handleSubmit">
|
<template #closeIcon>
|
<ModalClose :can-fullscreen="false" @cancel="handleCancel" />
|
</template>
|
<template #footer>
|
<ModalFooter @cancel="handleCancel" @ok="handleSubmit">
|
<template #insertFooter v-if="multiple">
|
<div class="float-left">
|
<span class="mr-[10px]">{{ $t('component.jnpf.common.selected') }}({{ selectedData.length }})</span>
|
<span class="remove-all-btn" @click="removeAll">{{ $t('component.jnpf.common.clearAll') }}</span>
|
</div>
|
</template>
|
</ModalFooter>
|
</template>
|
<div class="common-select-modal-main">
|
<div class="selected-pane">
|
<Tag v-for="(item, i) in selectedData" :key="item.id" :closable="true" class="selected-pane-tag" @close="removeData(i)">
|
{{ item.fullName }}
|
</Tag>
|
</div>
|
<div class="select-pane">
|
<div class="select-pane-tool">
|
<Tabs v-model:active-key="activeKey" :tab-bar-gutter="20" class="select-pane-tool-tabs">
|
<TabPane key="1" tab="全部数据" />
|
</Tabs>
|
<div class="select-pane-main">
|
<BasicTree
|
ref="treeRef"
|
class="tree-main select-pane-item"
|
:load-data="onLoadData"
|
:loading="loading"
|
:tree-data="treeData"
|
click-row-to-expand
|
@select="handleSelect" />
|
</div>
|
</div>
|
</div>
|
</div>
|
</AModal>
|
</template>
|