Files
YschcmsFront/src/views/cms/LinkManage.vue
T
chenxing bcd639cfc2 feat(B2): 附件管理改"未使用"语义+用途列;banner/link 上传带 scene
- AttachmentManage: orphanCount→unusedCount(零残留),用途列(文章/轮播/友链/站点配置/未使用)
- BannerManage/LinkManage 上传 action 传 scene;ArticleList 删除提示文案同步
- types AttachmentVo 增 refType;vitest 24/24+build 绿
2026-09-08 15:14:07 +08:00

242 lines
7.5 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 lang="ts">
import { computed, onMounted, reactive, ref, watch } from "vue";
import { Message } from "view-ui-plus";
import Utils from "@/utils/index";
import MyConfirm from "@/components/MyConfirm";
import { linkApi, uploadUrl } from "@/api/module/cms";
import { useCmsStore } from "@/store";
import { LinkVo } from "@/types/cms";
const store = useCmsStore();
const rows = ref<LinkVo[]>([]);
const loading = ref(false);
const tableHeight = Utils.useTableHeight({ isOperat: true });
const columns = [
{ title: "分组", key: "groupName", width: 140 },
{
title: "Logo",
key: "logoUrl",
width: 90,
align: "center",
render: (h: any, p: any) =>
p.row.logoUrl
? h("img", { src: p.row.logoUrl, style: { maxHeight: "36px", maxWidth: "72px" } })
: h("span", { style: { color: "#999" } }, "-"),
},
{ title: "名称", key: "name", minWidth: 200, tooltip: true },
{ title: "链接地址", key: "linkUrl", minWidth: 260, tooltip: true },
{ title: "排序", key: "sortNo", width: 80, align: "center" },
{
title: "状态",
key: "status",
width: 90,
align: "center",
render: (h: any, p: any) =>
h(Tag, { color: p.row.status === 1 ? "success" : "default" }, () =>
p.row.status === 1 ? "显示" : "隐藏"
),
},
{
title: "操作",
key: "action",
width: 120,
fixed: "right",
align: "center",
render: (h: any, p: any) => [
Utils.operatingHandle(h, "编辑", "icon-edit", () => openEdit(p.row)),
Utils.operatingHandle(h, "删除", "icon-ashbin", () => doDelete(p.row)),
],
},
];
const modalVisible = ref(false);
const saving = ref(false);
const formRef = ref();
const form = reactive({
id: undefined as number | undefined,
groupName: "default",
name: "",
logoUrl: "",
linkUrl: "",
sortNo: 0,
status: 1,
});
const rules = {
name: [{ required: true, message: "链接名称不得为空", trigger: "blur" }],
linkUrl: [{ required: true, message: "链接地址不得为空", trigger: "blur" }],
};
/** 分组 Tab(从数据聚合) */
const groups = computed(() => {
const set = new Set<string>();
rows.value.forEach(r => set.add(r.groupName));
return Array.from(set);
});
const activeGroup = ref<string>("");
const visibleRows = computed(() =>
activeGroup.value ? rows.value.filter(r => r.groupName === activeGroup.value) : rows.value
);
async function run() {
if (!store.siteId) return;
loading.value = true;
try {
rows.value = (await linkApi.list(store.siteId).send()) || [];
} finally {
loading.value = false;
}
}
function openCreate() {
Object.assign(form, {
id: undefined,
groupName: activeGroup.value || "default",
name: "",
logoUrl: "",
linkUrl: "",
sortNo: 0,
status: 1,
});
modalVisible.value = true;
}
function openEdit(row: LinkVo) {
Object.assign(form, {
id: row.id,
groupName: row.groupName,
name: row.name,
logoUrl: row.logoUrl || "",
linkUrl: row.linkUrl,
sortNo: row.sortNo,
status: row.status,
});
modalVisible.value = true;
}
function logoUploaded(res: any) {
if (res.code === 0 && res.data) {
form.logoUrl = res.data.url;
} else {
Message.error(res.msg || "Logo 上传失败");
}
}
async function save() {
const valid = await formRef.value?.validate();
if (!valid) return;
saving.value = true;
try {
await linkApi
.save({
id: form.id,
siteId: store.siteId!,
groupName: form.groupName || "default",
name: form.name,
logoUrl: form.logoUrl || undefined,
linkUrl: form.linkUrl,
sortNo: form.sortNo,
status: form.status,
})
.send();
Message.success("保存成功!");
modalVisible.value = false;
run();
} finally {
saving.value = false;
}
}
function doDelete(row: LinkVo) {
MyConfirm({
title: "删除友链",
msg: `确定删除「${row.name}」吗?`,
async onOk() {
await linkApi.delete({ id: row.id!, siteId: store.siteId! }).send();
Message.success("操作成功!");
run();
},
});
}
onMounted(run);
// 站点上下文切换即时重载(docs/09 §3.3)
watch(() => store.siteId, () => run());
</script>
<template>
<div class="page-wrapper">
<div class="main-container">
<div class="fix">
<Button class="fl" type="primary" size="large" @click="openCreate">
<Icon type="ios-add" />
新增友链
</Button>
<Tabs v-if="groups.length" class="fr" :value="activeGroup" @on-click="(g: string) => (activeGroup = g)">
<TabPane v-for="g in groups" :key="g" :name="g" :label="`${g}(${rows.filter(r => r.groupName === g).length})`" />
</Tabs>
</div>
<Table
:columns="columns"
:data="visibleRows"
:height="tableHeight"
:loading="loading"
no-data-text="暂无友链"
/>
<Modal v-model="modalVisible" :title="form.id ? '编辑友链' : '新增友链'" :mask-closable="false" width="520px">
<Form ref="formRef" :model="form" :rules="rules" :label-width="100">
<FormItem label="分组">
<Input v-model="form.groupName" :maxlength="64" placeholder="上级部门/兄弟院校/合作单位…" />
</FormItem>
<FormItem label="名称" prop="name">
<Input v-model="form.name" :maxlength="128" />
</FormItem>
<FormItem label="Logo(可选)">
<Upload
:action="uploadUrl() + '?siteId=' + store.siteId + '&scene=link'"
with-credentials
:show-upload-list="false"
:format="['jpg', 'jpeg', 'png', 'gif', 'webp']"
:on-success="logoUploaded"
>
<Button icon="ios-cloud-upload-outline">上传 Logo</Button>
</Upload>
<img v-if="form.logoUrl" :src="form.logoUrl" class="img-preview" />
</FormItem>
<FormItem label="链接地址" prop="linkUrl">
<Input v-model="form.linkUrl" :maxlength="500" placeholder="http(s):// 开头" />
</FormItem>
<FormItem label="排序">
<InputNumber v-model="form.sortNo" :min="0" />
</FormItem>
<FormItem label="状态">
<RadioGroup v-model="form.status">
<Radio :label="1">显示</Radio>
<Radio :label="0">隐藏</Radio>
</RadioGroup>
</FormItem>
</Form>
<template #footer>
<Button @click="modalVisible = false">取消</Button>
<Button type="primary" :loading="saving" @click="save">保存</Button>
</template>
</Modal>
</div>
</div>
</template>
<style scoped lang="scss">
.img-preview {
display: block;
max-width: 200px;
max-height: 64px;
margin-top: 8px;
border: 1px solid #e5e6eb;
}
</style>