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:
@@ -449,6 +449,17 @@ export const kolManageApi = {
|
||||
{},
|
||||
);
|
||||
},
|
||||
selfSubscribePackage(id: number) {
|
||||
return apiClient.post<KolPackageSummary, Record<string, never>>(
|
||||
`/api/tenant/kol/manage/packages/${id}/self-subscription`,
|
||||
{},
|
||||
);
|
||||
},
|
||||
cancelSelfSubscription(id: number) {
|
||||
return apiClient.remove<KolPackageSummary>(
|
||||
`/api/tenant/kol/manage/packages/${id}/self-subscription`,
|
||||
);
|
||||
},
|
||||
deletePackage(id: number) {
|
||||
return apiClient.remove<null>(`/api/tenant/kol/manage/packages/${id}`);
|
||||
},
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
@@ -49,6 +49,7 @@ const subscribeMutation = useMutation({
|
||||
const pkg = computed(() => packageQuery.data.value);
|
||||
const resolvedCoverUrl = computed(() => resolveApiURL(pkg.value?.cover_url));
|
||||
const subscription = computed(() => pkg.value?.subscription);
|
||||
const isOwnPackage = computed(() => Boolean(pkg.value?.owned_by_current_tenant));
|
||||
|
||||
const subscriptionStatusMeta = computed(() => {
|
||||
if (!subscription.value) return null;
|
||||
@@ -67,7 +68,7 @@ const subscriptionStatusMeta = computed(() => {
|
||||
});
|
||||
|
||||
function handleSubscribe() {
|
||||
if (pkg.value) {
|
||||
if (pkg.value && !isOwnPackage.value) {
|
||||
subscribeMutation.mutate(pkg.value.id);
|
||||
}
|
||||
}
|
||||
@@ -182,7 +183,11 @@ function goBack() {
|
||||
<a-col :xs="24" :lg="8">
|
||||
<a-card class="subscription-card">
|
||||
<h3 class="card-title">订阅状态</h3>
|
||||
<div v-if="!subscription" class="not-subscribed">
|
||||
<div v-if="!subscription && isOwnPackage" class="not-subscribed">
|
||||
<p class="status-text">这是你自己发布的订阅包,不能在模版市场申请订阅。</p>
|
||||
<p class="owner-hint">如需自己使用,请回到 KOL 工作台,在订阅包菜单中点击「订阅自己」。</p>
|
||||
</div>
|
||||
<div v-else-if="!subscription" class="not-subscribed">
|
||||
<p class="status-text">您尚未订阅该 KOL 提示词包。</p>
|
||||
<a-button
|
||||
type="primary"
|
||||
@@ -208,7 +213,7 @@ function goBack() {
|
||||
</div>
|
||||
</div>
|
||||
<a-button
|
||||
v-if="subscription.status === 'expired' || subscription.status === 'revoked'"
|
||||
v-if="!isOwnPackage && (subscription.status === 'expired' || subscription.status === 'revoked')"
|
||||
type="primary"
|
||||
block
|
||||
size="large"
|
||||
@@ -471,6 +476,17 @@ function goBack() {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.owner-hint {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin: -8px 0 4px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user