feat(enterprise-site): add WordPress support and scheduled auto-publish to sites
Frontend CI / Frontend (push) Successful in 4m17s
Backend CI / Backend (push) Failing after 6m42s

Add WordPress as an enterprise-site CMS type alongside pbootcms, and let
schedule tasks auto-publish generated articles to enterprise sites.

- WordPress connection/publisher service and tests; register wordpress
  media platform and relax cms_type CHECK constraint
- New publish_enterprise_site_targets JSONB column on schedule_tasks with
  array type constraint; thread targets through scheduler dispatch,
  prompt/KOL generation, and worker
- Admin UI: enterprise-site targets in generate/schedule/publish flows,
  KOL package form, MediaView, and i18n strings

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 19:44:24 +08:00
parent 41f5623791
commit c7bad83496
30 changed files with 2946 additions and 696 deletions
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { DeleteOutlined, PictureOutlined } from '@ant-design/icons-vue'
import { CloseOutlined, DeleteOutlined, PictureOutlined } from '@ant-design/icons-vue'
import type { CreateKolPackageRequest } from '@geo/shared-types'
import { message } from 'ant-design-vue'
import { computed, reactive, ref, watch } from 'vue'
@@ -52,6 +52,13 @@ watch(
const coverPreviewUrl = computed(() => resolveApiURL(formState.cover_url))
const coverFileName = computed(() => deriveCoverFileName(formState.cover_url))
const tagOptions = computed(() =>
Array.from(new Set(formState.tags.map((tag) => tag.trim()).filter(Boolean))).map((tag) => ({
label: tag,
value: tag,
title: null,
})),
)
function handleOk() {
if (!formState.name.trim()) {
@@ -70,6 +77,21 @@ function handleRemoveCover() {
formState.cover_url = ''
}
function getModalPopupContainer(triggerNode: HTMLElement): HTMLElement {
return (triggerNode.closest('.ant-modal-content') as HTMLElement | null) ?? document.body
}
function preventTagMouseDown(event: MouseEvent): void {
event.preventDefault()
event.stopPropagation()
}
function handleTagClose(onClose: (event?: MouseEvent) => void, event: MouseEvent): void {
event.preventDefault()
event.stopPropagation()
onClose(event)
}
function handleCoverConfirmed(payload: { url: string }) {
formState.cover_url = resolveApiURL(payload.url)
}
@@ -111,8 +133,29 @@ async function uploadCover(file: File) {
v-model:value="formState.tags"
mode="tags"
style="width: 100%"
:options="tagOptions"
:placeholder="t('kol.manage.packageForm.tagsPlaceholder')"
/>
:get-popup-container="getModalPopupContainer"
popup-class-name="kol-package-tags-dropdown"
>
<template #tagRender="{ label, closable, onClose }">
<span class="package-tag" @mousedown="preventTagMouseDown">
<span class="package-tag__label">{{ label }}</span>
<button
v-if="closable"
type="button"
class="package-tag__remove"
:aria-label="t('common.delete')"
@click="handleTagClose(onClose, $event)"
>
<CloseOutlined />
</button>
</span>
</template>
<template #option="{ label }">
<span class="package-tag-option">{{ label }}</span>
</template>
</a-select>
</a-form-item>
<a-form-item :label="t('kol.manage.packageForm.priceNote')">
@@ -240,4 +283,53 @@ async function uploadCover(file: File) {
display: flex;
gap: 8px;
}
.package-tag {
display: inline-flex;
max-width: 100%;
align-items: center;
gap: 4px;
margin-inline-end: 4px;
padding: 1px 4px 1px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background: #f5f5f5;
color: #1f2937;
line-height: 20px;
}
.package-tag__label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.package-tag__remove {
display: inline-flex;
width: 16px;
height: 16px;
align-items: center;
justify-content: center;
border: 0;
padding: 0;
background: transparent;
color: #667085;
cursor: pointer;
}
.package-tag__remove:hover {
color: #1f2937;
}
:global(.kol-package-tags-dropdown .ant-select-item-option) {
max-width: 100%;
}
:global(.kol-package-tags-dropdown .ant-select-item-option-content),
.package-tag-option {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>