feat(tenant/kol): let KOLs self-subscribe to their own published packages

Add /kol/manage/packages/:id/self-subscription POST/DELETE on the tenant
side so a KOL can grant themselves access to their own published package
without going through the marketplace approval flow. Reject self-subscribe
through the marketplace with a clear error, expose owned_by_current_tenant
on package responses, and surface a self_subscription block on KolPackage
Response. The admin-web KOL workspace gets 订阅自己 / 取消订阅 entries with
matching messaging in the marketplace detail view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 22:06:57 +08:00
parent 088dbb0ec7
commit 4dd8a1bb0e
9 changed files with 472 additions and 77 deletions
@@ -11,6 +11,8 @@ import {
CloudUploadOutlined,
InboxOutlined,
DeleteOutlined,
CheckCircleOutlined,
StopOutlined,
} from "@ant-design/icons-vue";
import KolPackageFormModal from "@/components/kol/KolPackageFormModal.vue";
@@ -79,6 +81,24 @@ const archivePackageMutation = useMutation({
onError: (error) => message.error(formatError(error)),
});
const selfSubscribePackageMutation = useMutation({
mutationFn: (id: number) => kolManageApi.selfSubscribePackage(id),
onSuccess: () => {
message.success("已订阅自己的订阅包");
invalidateKolPackageAccess();
},
onError: (error) => message.error(formatError(error)),
});
const cancelSelfSubscriptionMutation = useMutation({
mutationFn: (id: number) => kolManageApi.cancelSelfSubscription(id),
onSuccess: () => {
message.success("已取消订阅自己的订阅包");
invalidateKolPackageAccess();
},
onError: (error) => message.error(formatError(error)),
});
const deletePackageMutation = useMutation({
mutationFn: (id: number) => kolManageApi.deletePackage(id),
onSuccess: (_, id) => {
@@ -150,6 +170,12 @@ const deletePromptMutation = useMutation({
},
});
function invalidateKolPackageAccess() {
void queryClient.invalidateQueries({ queryKey: ["kol", "packages"] });
void queryClient.invalidateQueries({ queryKey: ["kol", "my-subscription-prompts"] });
void queryClient.invalidateQueries({ queryKey: ["workspace"] });
}
const packageInitialValue = computed<Partial<CreateKolPackageRequest> | undefined>(() => {
if (!editingPackage.value) return undefined;
return {
@@ -220,6 +246,18 @@ function packageStatusLabel(status: string) {
if (status === "archived") return t("kol.manage.packageStatus.archived");
return t("kol.manage.packageStatus.draft");
}
function isSelfSubscribed(pkg: KolPackageSummary) {
return pkg.self_subscription?.status === "active";
}
function canSelfSubscribe(pkg: KolPackageSummary) {
return pkg.status === "published" && !isSelfSubscribed(pkg);
}
function canCancelSelfSubscription(pkg: KolPackageSummary) {
return pkg.self_subscription?.status === "active";
}
</script>
<template>
@@ -267,6 +305,7 @@ function packageStatusLabel(status: string) {
>
{{ packageStatusLabel(item.status) }}
</a-tag>
<a-tag v-if="isSelfSubscribed(item)" color="blue" size="small">自己已订阅</a-tag>
<span>{{ t("kol.marketplace.prompts", { count: item.prompt_count }) }}</span>
</div>
</div>
@@ -290,6 +329,19 @@ function packageStatusLabel(status: string) {
>
<InboxOutlined /> {{ t('kol.manage.archivePackage') }}
</a-menu-item>
<a-menu-item
v-if="canSelfSubscribe(item)"
@click="selfSubscribePackageMutation.mutate(item.id)"
>
<CheckCircleOutlined /> 订阅自己
</a-menu-item>
<a-menu-item
v-if="canCancelSelfSubscription(item)"
danger
@click="cancelSelfSubscriptionMutation.mutate(item.id)"
>
<StopOutlined /> 取消订阅
</a-menu-item>
<a-menu-divider />
<a-menu-item danger @click="handleDeletePackage(item.id)">
<DeleteOutlined /> {{ t('common.delete') }}