feat(enterprise-site): add WordPress support and scheduled auto-publish to sites
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:
@@ -54,6 +54,7 @@ interface EnterpriseSiteCard {
|
||||
id: number
|
||||
name: string
|
||||
siteUrl: string
|
||||
cmsTypeId: string
|
||||
cmsType: string
|
||||
status: string
|
||||
statusText: string
|
||||
@@ -265,6 +266,9 @@ const accountCards = computed<PublishAccountCard[]>(() =>
|
||||
const enterpriseSites = computed(() => enterpriseSitesQuery.data.value ?? [])
|
||||
const enterpriseSiteCards = computed<EnterpriseSiteCard[]>(() =>
|
||||
enterpriseSites.value.map((site) => {
|
||||
const siteName =
|
||||
site.name?.trim() || site.site_name?.trim() || site.site_url?.trim() || '企业站点'
|
||||
const cmsType = String(site.cms_type ?? '').trim()
|
||||
const alreadyPublished = successfulEnterpriseSiteIds.value.has(site.id)
|
||||
const activeStatus = activeEnterpriseRecordStatusBySiteId.value.get(site.id)
|
||||
const connectionStatus = enterpriseSiteConnectionStatus(site)
|
||||
@@ -277,9 +281,10 @@ const enterpriseSiteCards = computed<EnterpriseSiteCard[]>(() =>
|
||||
)
|
||||
return {
|
||||
id: site.id,
|
||||
name: site.name,
|
||||
name: siteName,
|
||||
siteUrl: site.site_url,
|
||||
cmsType: cmsTypeLabel(site.cms_type),
|
||||
cmsTypeId: cmsType,
|
||||
cmsType: cmsTypeLabel(cmsType),
|
||||
status: connectionStatus,
|
||||
statusText: alreadyPublished
|
||||
? '已发布'
|
||||
@@ -364,14 +369,18 @@ const selectedEnterpriseSiteCards = computed(() => {
|
||||
})
|
||||
|
||||
const modalTitle = computed(() => detailQuery.data.value?.title || t('article.untitled'))
|
||||
const enterpriseTargetPlatformId = computed(() =>
|
||||
selectedEnterpriseSiteCards.value.length > 0 ? 'pbootcms' : '',
|
||||
const enterpriseTargetPlatformIds = computed(() =>
|
||||
Array.from(
|
||||
new Set(
|
||||
selectedEnterpriseSiteCards.value
|
||||
.map((site) => normalizeEnterprisePlatformId(site.cmsTypeId))
|
||||
.filter((platformId): platformId is string => Boolean(platformId)),
|
||||
),
|
||||
),
|
||||
)
|
||||
const selectedPlatformIds = computed(() =>
|
||||
activeTargetTab.value === 'enterprise_sites'
|
||||
? enterpriseTargetPlatformId.value
|
||||
? [enterpriseTargetPlatformId.value]
|
||||
: []
|
||||
? enterpriseTargetPlatformIds.value
|
||||
: Array.from(new Set(selectedCards.value.map((card) => card.platformId))),
|
||||
)
|
||||
watch([selectedPlatformIds, activeTargetTab], () => {
|
||||
@@ -441,8 +450,8 @@ const publishAdmissionPassed = computed(
|
||||
() => complianceDisplayDecision(publishAdmissionResult.value) === 'pass',
|
||||
)
|
||||
|
||||
function enterpriseSelectPopupContainer(triggerNode: HTMLElement): HTMLElement {
|
||||
return (triggerNode.closest('.ant-modal-content') as HTMLElement | null) ?? document.body
|
||||
function enterpriseSelectPopupContainer(triggerNode?: HTMLElement | null): HTMLElement {
|
||||
return (triggerNode?.closest('.ant-modal-content') as HTMLElement | null) ?? document.body
|
||||
}
|
||||
const mandatoryAdmissionBlocking = computed(
|
||||
() => publishComplianceMandatory.value && !publishAdmissionPassed.value,
|
||||
@@ -878,7 +887,7 @@ async function handleEnterprisePublishSuccess(
|
||||
message: '企业站发布失败',
|
||||
description:
|
||||
formatStoredErrorMessage(failed[0]?.error_message) ||
|
||||
'请检查插件、栏目和 PBootCMS API 配置。',
|
||||
'请检查站点凭证、栏目和 CMS 接口配置。',
|
||||
placement: 'topRight',
|
||||
duration: 6,
|
||||
})
|
||||
@@ -887,7 +896,7 @@ async function handleEnterprisePublishSuccess(
|
||||
message: enterprisePublishType.value === 'draft' ? '企业站草稿已保存' : '企业站发布成功',
|
||||
description:
|
||||
succeeded.length === 1
|
||||
? succeeded[0]?.external_article_url || 'PBootCMS 已返回成功。'
|
||||
? succeeded[0]?.external_article_url || 'CMS 已返回成功。'
|
||||
: `共 ${succeeded.length} 个企业站点已完成。`,
|
||||
placement: 'topRight',
|
||||
duration: 5,
|
||||
@@ -1129,15 +1138,23 @@ function cmsTypeLabel(cmsType: string): string {
|
||||
return 'PBootCMS'
|
||||
case 'wordpress':
|
||||
return 'WordPress'
|
||||
case 'dedecms':
|
||||
return 'DedeCMS'
|
||||
case 'phpcms':
|
||||
return 'PHPCMS'
|
||||
default:
|
||||
return cmsType || 'CMS'
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeEnterprisePlatformId(cmsType: string): string {
|
||||
const normalized = String(cmsType ?? '').trim().toLowerCase()
|
||||
switch (normalized) {
|
||||
case 'pbootcms':
|
||||
return 'pbootcms'
|
||||
case 'wordpress':
|
||||
return 'wordpress'
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
function enterpriseSiteStatusLabel(status: string): string {
|
||||
switch (String(status ?? '').trim()) {
|
||||
case 'connected':
|
||||
@@ -1167,6 +1184,8 @@ function enterpriseSiteStatusColor(status: string): string {
|
||||
function enterpriseCategoryOptions(siteId: number) {
|
||||
return (enterpriseCategoriesBySiteId.value[siteId] ?? []).map((category) => ({
|
||||
label: enterpriseCategoryLabel(category),
|
||||
name: category.name,
|
||||
slug: category.slug?.trim() ?? '',
|
||||
value: category.remote_id,
|
||||
}))
|
||||
}
|
||||
@@ -1182,7 +1201,8 @@ function setEnterpriseCategories(siteId: number, categories: EnterpriseSiteCateg
|
||||
[siteId]: categories,
|
||||
}
|
||||
const current = enterpriseSiteCategoryIds.value[siteId]?.trim()
|
||||
if (!current && categories.length > 0) {
|
||||
const currentExists = categories.some((category) => category.remote_id === current)
|
||||
if ((!current || !currentExists) && categories.length > 0) {
|
||||
enterpriseSiteCategoryIds.value = {
|
||||
...enterpriseSiteCategoryIds.value,
|
||||
[siteId]: categories[0].remote_id,
|
||||
@@ -1618,7 +1638,10 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="enterpriseSiteCards.length" class="publish-modal__list">
|
||||
<div
|
||||
v-if="enterpriseSiteCards.length"
|
||||
class="publish-modal__list publish-modal__list--enterprise"
|
||||
>
|
||||
<article
|
||||
v-for="site in enterpriseSiteCards"
|
||||
:key="site.id"
|
||||
@@ -1758,12 +1781,22 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
:options="enterpriseCategoryOptions(site.id)"
|
||||
:loading="syncEnterpriseCategoriesMutation.isPending.value"
|
||||
:get-popup-container="enterpriseSelectPopupContainer"
|
||||
:dropdown-match-select-width="true"
|
||||
:dropdown-style="{ maxWidth: 'calc(100vw - 96px)' }"
|
||||
popup-class-name="publish-modal__enterprise-select-dropdown"
|
||||
class="publish-modal__enterprise-select"
|
||||
placeholder="选择栏目"
|
||||
@focus="loadEnterpriseCategories(site.id)"
|
||||
@click.stop
|
||||
/>
|
||||
>
|
||||
<template #option="item">
|
||||
<a-tooltip :title="item.label" placement="left">
|
||||
<div class="publish-modal__select-option-text">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-select>
|
||||
<a-button
|
||||
class="publish-modal__sync-btn"
|
||||
:loading="syncEnterpriseCategoriesMutation.isPending.value"
|
||||
@@ -1781,10 +1814,7 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<a-empty
|
||||
v-else
|
||||
description="当前还没有企业自建站点,请先到媒体管理添加 PBootCMS 站点。"
|
||||
/>
|
||||
<a-empty v-else description="当前还没有企业自建站点,请先到媒体管理添加站点连接。" />
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
@@ -2157,6 +2187,12 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.publish-modal__list--enterprise {
|
||||
grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
|
||||
align-items: start;
|
||||
max-height: 520px;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for list */
|
||||
.publish-modal__list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
@@ -2213,6 +2249,7 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
overflow: visible;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.publish-modal__card--enterprise:hover:not(.publish-modal__card--disabled):not(
|
||||
@@ -2283,17 +2320,19 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
|
||||
.publish-modal__config-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.publish-modal__enterprise-select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.publish-modal__enterprise-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.publish-modal__sync-btn {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -2314,6 +2353,24 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
z-index: 3200;
|
||||
}
|
||||
|
||||
:global(.publish-modal__enterprise-select-dropdown .ant-select-item-option) {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
:global(.publish-modal__enterprise-select-dropdown .ant-select-item-option-content) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.publish-modal__select-option-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.publish-modal__site-warning {
|
||||
margin: -4px 16px 14px;
|
||||
color: #b45309;
|
||||
@@ -2454,6 +2511,37 @@ function decisionAlertType(result: ComplianceCheckResult | null | undefined) {
|
||||
border-top-color: #bfdbfe;
|
||||
}
|
||||
|
||||
.publish-modal__card--enterprise .publish-modal__card-identity {
|
||||
align-items: flex-start;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.publish-modal__card--enterprise .publish-modal__identity-copy {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.publish-modal__card--enterprise .publish-modal__identity-copy strong {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.publish-modal__card--enterprise .publish-modal__identity-copy span {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.publish-modal__card--enterprise .publish-modal__platform-line {
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.publish-modal__card--enterprise .publish-modal__platform-text {
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.publish-modal__check {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
||||
Reference in New Issue
Block a user