Files
workspace/code/fms/fms-vue/src/views/file-demo/index.vue
T
2026-08-26 17:31:31 +08:00

347 lines
8.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref } from 'vue'
import { RotateCcw, FileUp, ExternalLink, Inbox } from '@lucide/vue'
import Button from '@/components/ui/button/button.vue'
import Input from '@/components/ui/input/input.vue'
import Checkbox from '@/components/ui/checkbox/checkbox.vue'
import FmsFile from '@/components/fms-file/FmsFile.vue'
import FmsFileModal from '@/components/fms-file/FmsFileModal.vue'
import { useFileModal } from '@/components/fms-file/useFileModal'
defineOptions({ name: 'FileDemo' })
/* ---------- 参数配置 ---------- */
const moduleCode = ref('b_port')
const father = ref('1001')
const showTree = ref(true)
/* 内嵌示例:key 变化强制重挂载(参数修改后点「刷新」生效) */
const embeddedKey = ref(0)
function reloadEmbedded() {
embeddedKey.value++
}
/* ---------- 弹窗示例 ---------- */
// 声明式
const modalOpen = ref(false)
// 命令式
const fileModal = useFileModal()
function openImperative() {
fileModal.open({
moduleCode: moduleCode.value,
father: father.value,
showTree: showTree.value,
onUploaded: () => log('uploaded', '命令式弹窗:文件已上传'),
onDeleted: () => log('deleted', '命令式弹窗:文件已删除'),
onSelected: (f) => log('selected', `命令式弹窗选中:${f.mx_filename}`),
})
}
/* ---------- 事件日志 ---------- */
const events = ref([])
function log(type, payload) {
events.value.unshift({ type, payload, time: new Date().toLocaleTimeString() })
if (events.value.length > 20) events.value.pop()
}
function clearEvents() {
events.value = []
}
</script>
<template>
<div class="file-demo">
<header class="file-demo__head">
<h1 class="file-demo__title">FmsFile 附件管理组件演示</h1>
<p class="file-demo__sub">
支持上传(本地 / OSS 自动匹配)、拖拽 / 粘贴、分类筛选、网格 / 列表视图、预览与删除。下方参数可切换后点「刷新」重新加载。
</p>
</header>
<div class="file-demo__grid">
<!-- 左列:参数 + 弹窗 + 事件 -->
<div class="file-demo__side">
<!-- 参数配置 -->
<section class="file-demo__card">
<h2 class="file-demo__card-title">参数配置</h2>
<div class="file-demo__field">
<label class="file-demo__label">模块编码 moduleCode</label>
<Input v-model="moduleCode" placeholder="如 b_port" />
</div>
<div class="file-demo__field">
<label class="file-demo__label">业务 ID father</label>
<Input v-model="father" placeholder="如 1001" />
</div>
<div class="file-demo__row">
<Checkbox v-model="showTree">显示分类树</Checkbox>
<Button type="outline" @click="reloadEmbedded">
<RotateCcw />
刷新内嵌
</Button>
</div>
</section>
<!-- 弹窗用法 -->
<section class="file-demo__card">
<h2 class="file-demo__card-title">弹窗用法</h2>
<div class="file-demo__btns">
<Button type="primary" @click="modalOpen = true">
<FileUp />
声明式弹窗
</Button>
<Button type="outline" @click="openImperative">
<ExternalLink />
命令式弹窗
</Button>
</div>
<p class="file-demo__tip">
命令式:<code>useFileModal().open({ moduleCode, father })</code>,支持
onUploaded / onDeleted / onSelected 回调。
</p>
</section>
<!-- 事件日志 -->
<section class="file-demo__card file-demo__card--grow">
<div class="file-demo__card-head">
<h2 class="file-demo__card-title">事件日志</h2>
<Button type="ghost" @click="clearEvents">清空</Button>
</div>
<ul v-if="events.length" class="file-demo__events">
<li v-for="(ev, i) in events" :key="i" class="file-demo__event">
<span class="file-demo__event-time">{{ ev.time }}</span>
<span class="file-demo__event-tag" :class="`is-${ev.type}`">{{ ev.type }}</span>
<span class="file-demo__event-text">{{ ev.payload }}</span>
</li>
</ul>
<div v-else class="file-demo__empty">
<Inbox :size="28" :stroke-width="1" />
<span>暂无事件,去上传 / 删除 / 选中文件试试</span>
</div>
</section>
</div>
<!-- 右列:内嵌用法 -->
<section class="file-demo__card file-demo__main">
<div class="file-demo__card-head">
<h2 class="file-demo__card-title">内嵌用法</h2>
<span class="file-demo__badge">moduleCode={{ moduleCode }} · father={{ father }}</span>
</div>
<div class="file-demo__embedded">
<FmsFile
:key="embeddedKey"
:module-code="moduleCode"
:father="father"
:show-tree="showTree"
@uploaded="log('uploaded', '内嵌:文件已上传')"
@deleted="log('deleted', '内嵌:文件已删除')"
@selected="(f) => log('selected', `内嵌选中:${f.mx_filename}`)"
/>
</div>
</section>
</div>
<!-- 弹窗(声明式) -->
<FmsFileModal
v-model:open="modalOpen"
:module-code="moduleCode"
:father="father"
:show-tree="showTree"
@uploaded="log('uploaded', '声明式弹窗:文件已上传')"
@deleted="log('deleted', '声明式弹窗:文件已删除')"
@selected="(f) => log('selected', `声明式弹窗选中:${f.mx_filename}`)"
/>
</div>
</template>
<style scoped lang="scss">
.file-demo {
display: flex;
flex-direction: column;
gap: 12px;
color: var(--fms-text);
}
.file-demo__head {
.file-demo__title {
margin: 0 0 4px;
font-size: 18px;
font-weight: 600;
}
.file-demo__sub {
margin: 0;
font-size: 13px;
line-height: 20px;
color: var(--fms-text-secondary);
}
}
.file-demo__grid {
display: grid;
grid-template-columns: 320px minmax(0, 1fr);
gap: 12px;
align-items: start;
}
.file-demo__side {
display: flex;
flex-direction: column;
gap: 12px;
}
.file-demo__card {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
border-radius: var(--fms-control-radius);
background: var(--fms-card);
box-shadow: var(--fms-shadow);
&--grow {
flex: 1;
min-height: 180px;
}
}
.file-demo__card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.file-demo__card-title {
margin: 0;
font-size: 14px;
font-weight: 600;
}
.file-demo__field {
display: flex;
flex-direction: column;
gap: 4px;
}
.file-demo__label {
font-size: 12px;
color: var(--fms-text-secondary);
}
.file-demo__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.file-demo__btns {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.file-demo__tip {
margin: 0;
font-size: 12px;
line-height: 18px;
color: var(--fms-text-secondary);
code {
padding: 1px 5px;
border-radius: 4px;
background: var(--fms-secondary-hover);
font-size: 11px;
}
}
.file-demo__events {
display: flex;
flex-direction: column;
gap: 4px;
margin: 0;
padding: 0;
list-style: none;
overflow: auto;
max-height: 320px;
}
.file-demo__event {
display: flex;
align-items: baseline;
gap: 6px;
padding: 4px 6px;
border-radius: 4px;
background: var(--fms-secondary-hover);
font-size: 12px;
line-height: 16px;
&-time {
flex: none;
font-variant-numeric: tabular-nums;
color: var(--fms-text-secondary);
font-size: 11px;
}
&-tag {
flex: none;
padding: 0 4px;
border-radius: 3px;
font-size: 10px;
font-weight: 600;
color: var(--fms-primary);
background: color-mix(in srgb, var(--fms-primary) 12%, transparent);
&.is-uploaded {
color: var(--fms-success);
background: color-mix(in srgb, var(--fms-success) 12%, transparent);
}
&.is-deleted {
color: var(--fms-danger);
background: color-mix(in srgb, var(--fms-danger) 12%, transparent);
}
}
&-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.file-demo__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
flex: 1;
min-height: 120px;
font-size: 12px;
color: var(--fms-text-secondary);
}
/* 内嵌主区 */
.file-demo__main {
min-width: 0;
}
.file-demo__badge {
padding: 2px 8px;
border-radius: 999px;
background: var(--fms-secondary-hover);
color: var(--fms-text-secondary);
font-size: 11px;
font-variant-numeric: tabular-nums;
}
.file-demo__embedded {
height: 640px;
min-height: 0;
border: 1px solid var(--fms-border);
border-radius: var(--fms-control-radius);
overflow: hidden;
}
</style>