<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" destroyOnClose width="60%" @ok="handleSubmit" @visible-change="visibleChange">
<BasicForm @register="registerForm">
<template #title="{ model, field }">
<div class="item">
<div class="title-view">
<div class="tag"></div>
<div class="title">{{ model[field] }}</div>
</div>
</div>
</template>
<template #getTreeBtn>
<a-button type="primary" @click="createTree">获取目录树</a-button>
</template>
<template #getTree>
<div class="item">
<a-card :bordered="false" style="height: 100%">
<a-spin :spinning="loading">
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
checkable
:clickRowToExpand="true"
:treeData="treeData"
:selectedKeys="selectedKeys"
:checkStrictly="true"
:load-data="loadChildrenTreeData"
:checkedKeys="checkedKeysRef"
v-model:expandedKeys="expandedKeys"
@check="onCheck"
@select="onSelect"
@expand="onExpand"
>
<template #title="{ key: treeKey, title }">
<span>{{ title }}</span>
</template>
</a-tree>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
</a-card>
</div>
</template>
<template #ziduan>
<editTable :taskType="2" :data="dataSource" @save="saveData" />
</template>
</BasicForm>
</BasicModal>
</template>
<script setup lang="ts">
import { ref, onMounted, defineEmits } from 'vue';
import { formSchema } from '../user.data';
import { BasicForm, useForm } from '/@/components/Form/index';
import { message } from 'ant-design-vue';
import { useUserStore } from '/@/store/modules/user';
import { dirList, saveOrUpdate } from '../user.api';
import { BasicModal, useModalInner } from '/@/components/Modal';
import editTable from '../../database/components/editTable.vue';
// 状态定义
const treeData = ref<any[]>([]);
const checkedKeysRef = ref<string[]>([]);
const selectedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>([]);
const loading = ref<boolean>(false);
const treeReloading = ref<boolean>(false);
const paths = ref<string[]>([]);
const dataSource = ref();
const fieldInfoObj = ref();
const isUpdate = ref(false);
const title = ref();
const editDeviceName = ref();
const emit = defineEmits(['success']);
const deviceTypeOptions = ref();
// 初始化
onMounted(async () => {
deviceTypeOptions.value = useUserStore().getAllDictItems['dc_device_type'] || [];
});
// 表单配置
const [registerForm, { resetFields, setFieldsValue, validate, getFieldsValue }] = useForm({
labelWidth: 100,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: { span: 12 },
});
// 递归查找 treeData 中匹配 path 的 key
function findKeysByPaths(treeNodes: any[], pathsToMatch: string[]) {
const keys: string[] = [];
treeNodes.forEach((node) => {
if (pathsToMatch.includes(node.path)) {
keys.push(node.key);
}
if (node.children && node.children.length > 0) {
keys.push(...findKeysByPaths(node.children, pathsToMatch));
}
});
return keys;
}
// 查找 treeData 中匹配 path 的节点
function findNodeByPath(treeNodes: any[], path: string) {
for (const node of treeNodes) {
if (node.path === path) {
return node;
}
if (node.children && node.children.length > 0) {
const found = findNodeByPath(node.children, path);
if (found) return found;
}
}
return null;
}
// 递归加载路径上的所有节点
async function loadPathNodes(path: string, parentNode: any = null) {
console.log('loadPathNodes: Processing path:', path);
const segments = path.split('/').filter((seg) => seg);
let currentPath = '';
let currentNodes = parentNode ? parentNode.children : treeData.value;
let currentParent = parentNode;
for (let i = 0; i < segments.length; i++) {
currentPath = i === 0 ? `/${segments[0]}` : `${currentPath}/${segments[i]}`;
let node = findNodeByPath(currentNodes, currentPath);
console.log('loadPathNodes: Current path:', currentPath, 'Node found:', !!node);
if (!node && currentParent && currentParent.izLeaf) {
console.log('loadPathNodes: Loading children for:', currentParent.path);
await loadChildrenTreeData({ dataRef: currentParent });
currentNodes = currentParent.children || [];
node = findNodeByPath(currentNodes, currentPath);
}
if (node) {
expandedKeys.value = [...new Set([...expandedKeys.value, node.key])];
if (node.izLeaf && i < segments.length - 1) {
console.log('loadPathNodes: Preloading children for:', node.path);
await loadChildrenTreeData({ dataRef: node });
}
currentParent = node;
currentNodes = node.children || [];
} else {
console.warn('loadPathNodes: Node not found for path:', currentPath);
break;
}
}
treeData.value = [...treeData.value]; // 强制更新 treeData
console.log('loadPathNodes: Updated treeData:', treeData.value);
}
// 解析并加载所有路径的父节点
async function expandParentNodes(pathsToExpand: string[]) {
console.log('expandParentNodes: Processing paths:', pathsToExpand);
expandedKeys.value = [];
for (const path of pathsToExpand) {
await loadPathNodes(path);
}
console.log('expandParentNodes: Final expandedKeys:', expandedKeys.value);
treeData.value = [...treeData.value]; // 确保响应式更新
}
// 模态框初始化
const [registerModal, { closeModal }] = useModalInner(async (data) => {
await resetFields();
isUpdate.value = data.isUpdate;
if (data.isUpdate) {
editDeviceName.value = data.record.deviceName;
paths.value = data.record.configInfoObj.paths || [];
fieldInfoObj.value = dataSource.value = data.record.fieldInfoObj || [];
title.value = '修改任务';
await setFieldsValue({
...data.record,
...data.record.configInfoObj,
});
await new Promise((resolve) => setTimeout(resolve, 10));
await setFieldsValue({ deviceId: data.record.deviceId });
console.log('Modal init: paths:', paths.value);
await handleTree();
if (paths.value.length > 0) {
await expandParentNodes(paths.value);
checkedKeysRef.value = findKeysByPaths(treeData.value, paths.value);
console.log('Modal init: Backfill - paths:', paths.value, 'checkedKeys:', checkedKeysRef.value);
}
} else {
fieldInfoObj.value = dataSource.value = [];
paths.value = [];
checkedKeysRef.value = [];
title.value = '创建任务';
await setFieldsValue({
...data.record,
});
}
});
// 将数据格式化为树结构
function formatTreeData(item: any, index: number) {
return {
title: item.name,
key: `${item.path}-${item.name}-${index}`,
izLeaf: item.izLeaf,
isLeaf: !item.izLeaf,
path: item.path,
parentPath: item.parentPath,
children: [], // 初始化 children 为空数组
};
}
// 获取目录树
async function handleTree() {
console.log('handleTree: Starting, current paths:', paths.value);
const savedPaths = [...paths.value]; // 保存当前路径
const savedExpandedKeys = [...expandedKeys.value]; // 保存当前展开状态
let values = await getFieldsValue();
let params = {
host: values.host,
port: values.port,
username: values.username,
password: values.password,
os: values.os,
path: '/',
};
console.log('handleTree: dirList params:', params);
if (values.host && values.port && values.username && values.password && values.os) {
try {
loading.value = true;
let data = await dirList({ ...params });
console.log('handleTree: dirList result:', data);
if (!data || data.length === 0) {
message.error('目录树数据为空,请检查FTP服务器配置');
treeData.value = [];
paths.value = [];
checkedKeysRef.value = [];
expandedKeys.value = [];
return;
}
treeData.value = data.map((item, index) => formatTreeData(item, index));
console.log('handleTree: Initial treeData:', treeData.value);
autoExpandParentNode();
if (isUpdate.value && savedPaths.length > 0) {
paths.value = savedPaths; // 恢复路径
console.log('handleTree: Restoring paths:', paths.value);
await expandParentNodes(paths.value); // 重新加载深层路径
checkedKeysRef.value = findKeysByPaths(treeData.value, paths.value);
expandedKeys.value = [...new Set([...expandedKeys.value, ...savedExpandedKeys])]; // 恢复展开状态
console.log('handleTree: After restore - paths:', paths.value, 'checkedKeys:', checkedKeysRef.value, 'expandedKeys:', expandedKeys.value);
}
} catch (error) {
console.error('handleTree: Error:', error);
message.error('获取目录树失败:' + error.message);
treeData.value = [];
paths.value = [];
checkedKeysRef.value = [];
expandedKeys.value = [];
} finally {
loading.value = false;
}
} else {
message.info('请填写完整FTP服务器配置再获取目录树');
}
}
// 自动展开父节点
function autoExpandParentNode() {
let item = treeData.value[0];
if (item && item.izLeaf) {
expandedKeys.value = [...new Set([...expandedKeys.value, item.key])];
}
console.log('autoExpandParentNode: expandedKeys:', expandedKeys.value);
reloadTree();
}
// 重新加载树
async function reloadTree() {
treeReloading.value = true;
await new Promise((resolve) => setTimeout(resolve, 0));
treeReloading.value = false;
}
// 动态加载子节点
async function loadChildrenTreeData(treeNode: any) {
try {
console.log('loadChildrenTreeData: Triggered for path:', treeNode.dataRef.path);
if (treeNode.dataRef.izLeaf) {
let values = await getFieldsValue();
let params = {
host: values.host,
port: values.port,
username: values.username,
password: values.password,
os: values.os,
path: treeNode.dataRef.path,
};
console.log('loadChildrenTreeData: dirList params:', params);
const result = await dirList(params);
console.log('loadChildrenTreeData: dirList result:', result);
if (result.length === 0) {
treeNode.dataRef.izLeaf = false;
treeNode.dataRef.isLeaf = true;
} else {
treeNode.dataRef.children = result.map((item, index) => formatTreeData(item, index));
treeNode.dataRef.isLeaf = false; // 确保有子节点时不标记为叶子节点
}
treeData.value = [...treeData.value]; // 深拷贝触发响应式更新
if (isUpdate.value && paths.value.length > 0) {
checkedKeysRef.value = findKeysByPaths(treeData.value, paths.value);
console.log('loadChildrenTreeData: After load - checkedKeys:', checkedKeysRef.value);
}
} else {
console.log('loadChildrenTreeData: Node is not a folder, izLeaf:', treeNode.dataRef.izLeaf);
}
} catch (e) {
console.error('loadChildrenTreeData: Error:', e);
message.error('加载子节点失败:' + e.message);
}
return Promise.resolve();
}
// 展开事件
async function onExpand(expandedKeys: string[], { expanded, node }: { expanded: boolean; node: any }) {
expandedKeys.value = expandedKeys;
console.log('onExpand: Node:', node.path, 'Expanded:', expanded, 'izLeaf:', node.izLeaf);
if (expanded && node.izLeaf && (!node.children || !node.children.length)) {
await loadChildrenTreeData({ dataRef: node });
if (isUpdate.value && paths.value.length > 0) {
for (const path of paths.value) {
if (path.startsWith(node.path) && path !== node.path) {
console.log('onExpand: Reloading deep path:', path);
await loadPathNodes(path, node);
}
}
}
}
}
// 勾选事件
function onCheck(checkedKeys: any, info: any) {
checkedKeysRef.value = Array.isArray(checkedKeys) ? checkedKeys : checkedKeys.checked;
paths.value = info.checkedNodes.map((node: any) => node.path);
console.log('onCheck: checkedKeys:', checkedKeysRef.value, 'paths:', paths.value);
}
// 选择事件
async function onSelect(selKeys: string[], event: any) {
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
selectedKeys.value = [selKeys[0]];
}
}
// 存储表格数据
function saveData(data: any[]) {
if (data.length > 0) {
fieldInfoObj.value = data.map(({ key, ...rest }) => rest);
} else {
fieldInfoObj.value = data;
}
}
// 提交事件
async function handleSubmit() {
if (!Array.isArray(paths.value) || paths.value.length === 0 || (paths.value.length === 1 && paths.value[0] === '')) {
message.info('请选择文件目录,文件目录不能为空');
return;
}
try {
let values = await validate();
const fieldInfo = JSON.stringify(fieldInfoObj.value);
for (const obj of fieldInfoObj.value) {
for (const key of Object.keys(obj)) {
const value = obj[key];
if (value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0)) {
message.info('表格数据不能为空');
return;
}
}
}
let configInfoObj = {
host: values.host,
port: values.port,
username: values.username,
password: values.password,
os: values.os,
paths: paths.value,
fileNameMatchingRules: values.fileNameMatchingRules,
matchKey: values.matchKey,
fileType: values.fileType,
readRow: values.readRow,
};
const configInfo = JSON.stringify(configInfoObj);
let params = {
...values,
fieldInfo: fieldInfo,
fieldInfoObj: fieldInfoObj.value,
configInfoObj: configInfoObj,
configInfo: configInfo,
};
await saveOrUpdate(params, unref(isUpdate));
closeModal();
emit('success');
} finally {
}
}
// 模态框可见性变化
function visibleChange(visible: boolean) {
if (!visible) {
treeData.value = [];
checkedKeysRef.value = [];
paths.value = [];
expandedKeys.value = [];
selectedKeys.value = [];
}
}
async function createTree() {
console.log('createTree: Starting fresh tree generation');
const values = await getFieldsValue();
const params = {
host: values.host,
port: values.port,
username: values.username,
password: values.password,
os: values.os,
path: '/', // 始终从根目录开始
};
if (!values.host || !values.port || !values.username || !values.password || !values.os) {
message.info('请填写完整FTP服务器配置再获取目录树');
return;
}
try {
loading.value = true;
// 清空所有缓存数据
treeData.value = [];
checkedKeysRef.value = [];
expandedKeys.value = [];
selectedKeys.value = [];
const data = await dirList(params);
console.log('createTree: Fresh tree data:', data);
if (!data || data.length === 0) {
message.error('目录树数据为空,请检查FTP服务器配置');
return;
}
// 生成全新树结构
treeData.value = data.map((item, index) => formatTreeData(item, index));
console.log('createTree: New treeData:', treeData.value);
// 自动展开根节点(可选)
if (treeData.value.length > 0) {
expandedKeys.value = [treeData.value[0].key];
}
} catch (error) {
console.error('createTree: Error:', error);
message.error('获取目录树失败:' + error.message);
} finally {
loading.value = false;
reloadTree(); // 确保UI刷新
}
}
</script>
<style scoped lang="less">
.item {
margin-left: 10px;
position: relative;
.title-view {
padding-bottom: 20px 20px 0 20px;
display: flex;
align-items: center;
.tag {
width: 5px;
height: 18px;
border-radius: 2rpx;
background: #40a9ff;
}
.title {
padding-left: 7px;
font-size: 14px;
font-weight: bold;
}
}
}
</style>当树树存在数据时,表单的values.host,values.port,values.username,values.password某一项发生修改则提示FTP服务器配置,重新获取目录树,并清空树的数据和paths.value,给我修改后的全部代码,用中文回答
最新发布