This commit is contained in:
oneao committed 2026-10-11 22:14:13 +08:00
1 parent 32d423b759
commit 78eb811ff7
102 files changed
+12067 -209

No files matched your search

@@ -0,0 +1,80 @@
<script setup lang="ts">
// 最小用法:只喂 files + categories,上传 / 删除交给业务(组件不发请求)
import { ref } from 'vue'
import { G3Attachment } from '@g3soft/ui'
import type { AttachmentCategory, AttachmentItem, AttachmentUploadContext } from '@g3soft/ui'
// 分类(可选):不给 categories 就只渲染「列表 + 预览」两栏
const categories: AttachmentCategory[] = [
{ id: 'contract', name: '合同文件' },
{ id: 'invoice', name: '发票凭证' },
{ id: 'photo', name: '现场照片' },
]
// 内联 SVG,保证文档站离线也能看到图片预览
const PHOTO = `data:image/svg+xml;utf8,${encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="320" height="200"><rect width="320" height="200" fill="#e5e7eb"/><text x="160" y="108" font-size="18" text-anchor="middle" fill="#6b7280">现场照片</text></svg>',
)}`
const files = ref<AttachmentItem[]>([
{ id: 1, name: '2026年度框架合同.pdf', size: 512_000, categoryId: 'contract' },
{ id: 2, name: '供应商报价明细.xlsx', size: 86_000, categoryId: 'invoice' },
{ id: 3, name: '现场照片.jpg', size: 1_240_000, categoryId: 'photo', url: PHOTO },
{ id: 4, name: '验收纪要.docx', size: 24_000, categoryId: 'contract' },
])
const lastEvent = ref('等待操作…')
// 没传 upload-request 时,组件把待上传文件抛出来,落库由业务决定
function onUpload(incoming: File[], context: AttachmentUploadContext) {
files.value = [
...files.value,
...incoming.map((file, index) => ({
id: `local-${Date.now()}-${index}`,
name: file.name,
size: file.size,
categoryId: context.categoryId,
// 真实项目换成接口返回的地址;示例用 blob 地址即时预览
url: URL.createObjectURL(file),
})),
]
lastEvent.value = `upload:${incoming.map((file) => file.name).join('、')}`
}
function onRemove(items: AttachmentItem[]) {
const removed = new Set(items.map((item) => item.id))
files.value = files.value.filter((file) => !removed.has(file.id))
lastEvent.value = `remove:${items.map((item) => item.name).join('、')}`
}
function onDownload(items: AttachmentItem[]) {
lastEvent.value = `download:${items.map((item) => item.name).join('、')}`
}
function onInvalid(info: { message: string }) {
lastEvent.value = `invalid:${info.message}`
}
</script>
<template>
<div>
<G3Attachment
v-model:files="files"
:categories="categories"
:height="560"
@upload="onUpload"
@remove="onRemove"
@download="onDownload"
@invalid="onInvalid"
/>
<p class="hint">{{ lastEvent }}</p>
</div>
</template>
<style scoped>
.hint {
margin: 8px 0 0;
color: #8c8c8c;
font-size: 12px;
}
</style>
@@ -0,0 +1,58 @@
<script setup lang="ts">
// 弹窗用法:业务自己用 G3Modal 包一层,附件组件并不知道「弹窗」的存在
import { ref } from 'vue'
import { G3Attachment, G3Button, G3Modal } from '@g3soft/ui'
import type { AttachmentItem, AttachmentUploadContext } from '@g3soft/ui'
const open = ref(false)
// 从空列表开始:正好看到空态投放区 → 入队(底栏变"脏")→ 上传的完整过程
const files = ref<AttachmentItem[]>([])
const lastEvent = ref('')
function onUpload(incoming: File[], context: AttachmentUploadContext) {
files.value = [
...files.value,
...incoming.map((file, index) => ({
id: `local-${Date.now()}-${index}`,
name: file.name,
size: file.size,
categoryId: context.categoryId,
url: URL.createObjectURL(file),
})),
]
lastEvent.value = `已加入 ${incoming.length} 个文件`
}
function onRemove(items: AttachmentItem[]) {
const removed = new Set(items.map((item) => item.id))
files.value = files.value.filter((file) => !removed.has(file.id))
lastEvent.value = `已删除 ${items.length} 个文件`
}
</script>
<template>
<div>
<G3Button @click="open = true">打开附件管理</G3Button>
<p class="hint">{{ lastEvent }}</p>
<!-- 弹窗高度用 vh,附件组件再按 100% 撑满;不给 categories 就只有列表一栏 -->
<G3Modal v-model:open="open" title="附件管理" width="80%" :fullscreen="false">
<G3Attachment
v-model:files="files"
height="60vh"
@upload="onUpload"
@remove="onRemove"
/>
</G3Modal>
</div>
</template>
<style scoped>
.hint {
margin: 8px 0 0;
color: #8c8c8c;
font-size: 12px;
}
</style>
@@ -0,0 +1,20 @@
<script setup lang="ts">
// 只读:隐藏上传 / 删除入口,只保留浏览与下载
import { G3Attachment } from '@g3soft/ui'
import type { AttachmentItem } from '@g3soft/ui'
const PHOTO = `data:image/svg+xml;utf8,${encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="320" height="200"><rect width="320" height="200" fill="#dbeafe"/><text x="160" y="108" font-size="18" text-anchor="middle" fill="#1d4ed8">现场照片</text></svg>',
)}`
const files: AttachmentItem[] = [
{ id: 1, name: '立项批复.pdf', size: 320_000 },
{ id: 2, name: '现场照片.jpg', size: 1_024_000, url: PHOTO },
{ id: 3, name: '预算表.xlsx', size: 64_000 },
]
</script>
<template>
<!-- readonly 同时关掉「全选 / 批量删除 / 底栏上传」,拖拽与粘贴也会失效 -->
<G3Attachment :files="files" readonly :height="420" />
</template>
@@ -0,0 +1,61 @@
<script setup lang="ts">
// 多选与批量操作:勾选后工具栏浮出「下载 / 删除」,行为全部走事件
import { ref } from 'vue'
import { G3Attachment } from '@g3soft/ui'
import type { AttachmentItem } from '@g3soft/ui'
const files = ref<AttachmentItem[]>([
{ id: 'a1', name: '设备清单.xlsx', size: 128_000 },
{ id: 'a2', name: '施工方案.pdf', size: 2_048_000 },
{ id: 'a3', name: '进度汇报.pptx', size: 640_000 },
{ id: 'a4', name: '材料验收单.docx', size: 32_000 },
{ id: 'a5', name: '现场记录.zip', size: 8_192_000 },
])
const selected = ref<AttachmentItem[]>([])
const lastEvent = ref('')
function onSelect(items: AttachmentItem[]) {
selected.value = items
}
function onRemove(items: AttachmentItem[]) {
const removed = new Set(items.map((item) => item.id))
files.value = files.value.filter((file) => !removed.has(file.id))
lastEvent.value = `remove:${items.map((item) => item.name).join('、')}`
}
function onDownload(items: AttachmentItem[]) {
// 真实项目里在这里调接口拿签名地址再下载
lastEvent.value = `download:${items.map((item) => item.name).join('、')}`
}
</script>
<template>
<div>
<!-- 每页 3 条演示分页;builtin-download 关掉后「下载」只上报事件 -->
<G3Attachment
v-model:files="files"
:height="420"
:builtin-download="false"
:show-size-changer="true"
:page-size="3"
:page-size-options="[3, 5]"
@select="onSelect"
@remove="onRemove"
@download="onDownload"
/>
<p class="hint">
已选 {{ selected.length }} 个<span v-if="selected.length">:{{ selected.map((item) => item.name).join('、') }}</span>
<template v-if="lastEvent"> | {{ lastEvent }}</template>
</p>
</div>
</template>
<style scoped>
.hint {
margin: 8px 0 0;
color: #8c8c8c;
font-size: 12px;
}
</style>