235 lines
5.7 KiB
Vue
235 lines
5.7 KiB
Vue
<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>
|