20260820223837

This commit is contained in:
oneao committed 2026-08-20 22:38:37 +08:00
1 parent 68ac5db038
commit 45854b0570
48 files changed
+7545 -650

No files matched your search

@@ -0,0 +1,346 @@
<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: 1px solid var(--fms-border);
border-radius: var(--fms-control-radius);
background: var(--fms-card);
&--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>