feat(B2): 附件管理改"未使用"语义+用途列;banner/link 上传带 scene

- AttachmentManage: orphanCount→unusedCount(零残留),用途列(文章/轮播/友链/站点配置/未使用)
- BannerManage/LinkManage 上传 action 传 scene;ArticleList 删除提示文案同步
- types AttachmentVo 增 refType;vitest 24/24+build 绿
This commit is contained in:
chenxing
2026-09-08 15:14:07 +08:00
parent 3990811ed5
commit bcd639cfc2
6 changed files with 24 additions and 20 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ class ArticleApi {
class AttachmentApi {
page(siteId: number, orphanOnly: boolean, page: number, pageSize: number) {
return get<PageResult<AttachmentVo> & { orphanCount: number }>(`${ATTACHMENT}/page`, {
return get<PageResult<AttachmentVo> & { unusedCount: number }>(`${ATTACHMENT}/page`, {
siteId,
orphanOnly,
page,
+3
View File
@@ -173,11 +173,14 @@ export interface AttachmentVo {
id: number;
siteId: number;
articleId?: number;
/** 场景引用类型(B2):banner/link/site;空=无场景引用 */
refType?: string;
fileName: string;
url: string;
fileSize?: number;
mimeType?: string;
createTime?: string;
/** 未使用(B2 语义:无文章引用且无场景引用) */
orphan: boolean;
}
+1 -1
View File
@@ -174,7 +174,7 @@ function doDelete(row: ArticlePageItem) {
MyConfirm({
title: "删除文章",
msg: `确定删除「${row.title}」吗?`,
info: "删除后正文图片将转为孤儿附件,可在附件管理中清理",
info: "删除后正文图片将转为未使用附件,可在附件管理中清理",
async onOk() {
await articleApi.delete({ id: row.id, siteId: store.siteId! }).send();
Message.success("操作成功!");
+17 -16
View File
@@ -11,7 +11,7 @@ const store = useCmsStore();
const rows = ref<AttachmentVo[]>([]);
const total = ref(0);
const orphanCount = ref(0);
const unusedCount = ref(0);
const loading = ref(false);
const pageNum = ref(1);
const pageSize = ref(parseInt(localStorage.getItem("customPageCount") || "10"));
@@ -32,16 +32,17 @@ const columns = [
},
{ title: "大小", key: "fileSize", width: 100, render: (h: any, p: any) => h("span", Utils.getFileSize(p.row.fileSize || 0)) },
{
title: "关联文章",
key: "articleId",
width: 100,
title: "用途",
key: "usage",
width: 110,
align: "center",
render: (h: any, p: any) =>
h(
Tag,
{ color: p.row.orphan ? "warning" : "default" },
() => (p.row.orphan ? "孤儿" : p.row.articleId)
),
render: (h: any, p: any) => {
// B2 引用模型:文章(articleId)优先,其次场景引用(banner/link/site),均无=未使用
const usage = p.row.articleId
? "文章"
: { banner: "轮播", link: "友链", site: "站点配置" }[p.row.refType as string] || "未使用";
return h(Tag, { color: usage === "未使用" ? "warning" : "default" }, () => usage);
},
},
{ title: "上传时间", key: "createTime", width: 170 },
{
@@ -68,7 +69,7 @@ async function run() {
.send();
rows.value = page?.list || [];
total.value = page?.total || 0;
orphanCount.value = (page as any)?.orphanCount || 0;
unusedCount.value = (page as any)?.unusedCount || 0;
} finally {
loading.value = false;
}
@@ -95,9 +96,9 @@ function doDelete(row: AttachmentVo) {
function cleanOrphans() {
MyConfirm({
title: "清理孤儿附件",
msg: `当前共有 ${orphanCount.value} 条孤儿附件记录(关联文章已删除),仅清理列表页中勾选的记录。`,
info: "为避免误删被封面/轮播引用的文件,MVP 不提供一键全清",
title: "查看未使用附件",
msg: `当前共有 ${unusedCount.value} 条未使用附件记录(无文章引用且无场景引用),点击确定切换到未使用列表。`,
info: "为避免误删被引用的文件,MVP 不提供一键全清;旧数据在重新保存对应内容前显示为未使用属正常现象",
onOk() {
filter.orphanOnly = true;
pageNum.value = 1;
@@ -140,11 +141,11 @@ watch(() => store.siteId, () => {
<Button type="primary" size="large" icon="ios-cloud-upload-outline">上传附件</Button>
</Upload>
<Button class="fl" style="margin-left: 8px" size="large" @click="cleanOrphans">
孤儿附件({{ orphanCount }})
未使用附件({{ unusedCount }})
</Button>
</div>
<div class="search-wrap">
<Checkbox v-model="filter.orphanOnly" @on-change="pageNum = 1; run()">仅看孤儿附件</Checkbox>
<Checkbox v-model="filter.orphanOnly" @on-change="pageNum = 1; run()">仅看未使用附件</Checkbox>
</div>
<Table
:columns="columns"
+1 -1
View File
@@ -207,7 +207,7 @@ watch(() => store.siteId, () => run());
</FormItem>
<FormItem label="轮播图片" required>
<Upload
:action="uploadUrl() + '?siteId=' + store.siteId"
:action="uploadUrl() + '?siteId=' + store.siteId + '&scene=banner'"
with-credentials
:show-upload-list="false"
:format="['jpg', 'jpeg', 'png', 'gif', 'webp']"
+1 -1
View File
@@ -198,7 +198,7 @@ watch(() => store.siteId, () => run());
</FormItem>
<FormItem label="Logo(可选)">
<Upload
:action="uploadUrl() + '?siteId=' + store.siteId"
:action="uploadUrl() + '?siteId=' + store.siteId + '&scene=link'"
with-credentials
:show-upload-list="false"
:format="['jpg', 'jpeg', 'png', 'gif', 'webp']"