20260802220544

This commit is contained in:
oneao committed 2026-08-02 22:05:44 +08:00
1 parent c00329b5a4
commit 4fc9040a8a
60 files changed
+6273 -908

No files matched your search

@@ -0,0 +1,234 @@
<script setup>
import { ref, computed, watch, onMounted } from "vue";
import { loadDataApi } from "@/services/api";
import FileCategoryPanel from "./FileCategoryPanel.vue";
import FileListPanel from "./FileListPanel.vue";
import FilePreviewPanel from "./FilePreviewPanel.vue";
import { useFileCategory } from "./composables/useFileCategory";
const props = defineProps({
moduleCode: { type: [String, Number], required: true },
father: { type: [String, Number], required: true },
showTree: { type: Boolean, default: true },
readonly: { type: Boolean, default: false },
initialCateId: { type: String, default: "" },
});
const emit = defineEmits(["uploaded", "deleted", "selected"]);
// ---------- 模块解析 ----------
const moduleId = ref("");
const moduleResolving = ref(false);
const moduleError = ref("");
async function resolveModule() {
const code = props.moduleCode;
if (!code) {
moduleId.value = "";
return;
}
moduleResolving.value = true;
moduleError.value = "";
try {
const res = await loadDataApi(
"s_module",
`b_code = N'${String(code).replaceAll("'", "''")}' AND b_canuse = 1`,
"",
["b_id", "b_code", "b_name"],
);
if (res.data?.length) {
moduleId.value = String(res.data[0].b_id);
} else {
moduleId.value = "";
moduleError.value = `未找到模块 "${code}"`;
}
} catch (e) {
moduleError.value = e?.message || "模块解析失败";
moduleId.value = "";
} finally {
moduleResolving.value = false;
}
}
watch(
() => props.moduleCode,
() => resolveModule(),
);
// ---------- 分类 ----------
const {
treeData: categoryTreeData,
loading: categoryLoading,
selectedKey: categorySelectedKey,
loadCategories,
selectCategory,
} = useFileCategory();
const activeCateId = computed(() => {
const key = categorySelectedKey.value;
if (!key || key === "__fms_tree_root__") return "";
return key;
});
const categoryExpandedKeys = ref(["__fms_tree_root__"]);
function onCategorySelect(key) {
selectCategory(key);
}
// ---------- 预览选中 ----------
const fileListRef = ref(null);
const selectedFile = ref(null);
const currentFileList = computed(() => fileListRef.value?.files || []);
function onPreviewFile(file) {
selectedFile.value = file;
emit("selected", file);
}
function onPreviewPrev() {
const list = currentFileList.value;
const idx = list.findIndex((f) => f.subid === selectedFile.value?.subid);
if (idx > 0) {
selectedFile.value = list[idx - 1];
emit("selected", selectedFile.value);
}
}
function onPreviewNext() {
const list = currentFileList.value;
const idx = list.findIndex((f) => f.subid === selectedFile.value?.subid);
if (idx >= 0 && idx < list.length - 1) {
selectedFile.value = list[idx + 1];
emit("selected", selectedFile.value);
}
}
// ---------- 事件代理 ----------
function onUploaded() {
emit("uploaded");
}
function onDeleted() {
selectedFile.value = null;
emit("deleted");
}
// ---------- 挂载 ----------
onMounted(() => {
resolveModule();
loadCategories();
if (props.initialCateId) {
selectCategory(props.initialCateId);
categoryExpandedKeys.value = ["__fms_tree_root__", String(props.initialCateId)];
}
});
const ready = computed(() => !moduleResolving.value && !moduleError.value);
defineExpose({ moduleId, refreshCategories: loadCategories });
</script>
<template>
<div class="ff-container">
<div v-if="moduleResolving" class="ff-status">
<a-spin size="small" />
<span class="ff-status__text">正在解析模块...</span>
</div>
<div v-else-if="moduleError" class="ff-status ff-status--error">
{{ moduleError }}
</div>
<template v-else>
<a-splitter class="ff-splitter" lazy>
<!-- 左:分类树(固定 220px 侧栏) -->
<a-splitter-panel
v-if="showTree"
:default-size="220"
:min="160"
:max="320"
>
<FileCategoryPanel
:tree-data="categoryTreeData"
:loading="categoryLoading"
:selected-key="categorySelectedKey"
:expanded-keys="categoryExpandedKeys"
@select="onCategorySelect"
@update:expanded-keys="(keys) => (categoryExpandedKeys = keys)"
/>
</a-splitter-panel>
<!-- 中:文件列表(固定 500px) -->
<a-splitter-panel :default-size="500" :min="280">
<div class="ff-center">
<FileListPanel
ref="fileListRef"
:module-code="moduleCode"
:module-id="moduleId"
:father="father"
:cate-id="activeCateId"
:readonly="readonly"
@preview="onPreviewFile"
@uploaded="onUploaded"
@deleted="onDeleted"
/>
</div>
</a-splitter-panel>
<!-- 右:预览(不指定默认宽,自动占剩余空间) -->
<a-splitter-panel :min="440" collapsible>
<FilePreviewPanel
:file="selectedFile"
:file-list="currentFileList"
@prev="onPreviewPrev"
@next="onPreviewNext"
/>
</a-splitter-panel>
</a-splitter>
</template>
</div>
</template>
<style scoped lang="scss">
.ff-container {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
border: 1px solid var(--fms-border);
border-radius: 6px;
background: #fff;
overflow: hidden;
}
.ff-splitter {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
}
.ff-center {
display: flex;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
}
.ff-status {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
gap: 8px;
font-size: 13px;
color: #646a73;
&--error {
color: #cf1322;
}
}
</style>