feat: 收敛AI资源发布审批生命周期
- 统一工作流、知识库、聊天助手的发布、重新发布、下线与删除链路 - 收敛审批编排、生命周期状态机与展示态,补齐审批管理和快照预览 - 调整审批管理权限模型为单入口页面加内部按钮权限
This commit is contained in:
@@ -26,7 +26,11 @@ import {
|
||||
} from 'element-plus';
|
||||
import { tryit } from 'radash';
|
||||
|
||||
import { submitBotDeleteApproval, submitBotPublishApproval } from '#/api';
|
||||
import {
|
||||
submitBotDeleteApproval,
|
||||
submitBotOfflineApproval,
|
||||
submitBotPublishApproval,
|
||||
} from '#/api';
|
||||
import { api } from '#/api/request';
|
||||
import defaultAvatar from '#/assets/ai/bot/defaultBotAvatar.png';
|
||||
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
||||
@@ -34,9 +38,12 @@ import CardList from '#/components/page/CardList.vue';
|
||||
import PageData from '#/components/page/PageData.vue';
|
||||
import PageSide from '#/components/page/PageSide.vue';
|
||||
import {
|
||||
canAiResourceDelete,
|
||||
canAiResourceOffline,
|
||||
canAiResourcePublish,
|
||||
canAiResourceRepublish,
|
||||
isAiResourceApprovalPending,
|
||||
isAiResourcePublished,
|
||||
normalizeAiPublishStatus,
|
||||
resolveAiResourceDisplayStatus,
|
||||
} from '#/views/ai/shared/publish-status';
|
||||
import { useDictStore } from '#/store';
|
||||
|
||||
@@ -105,35 +112,57 @@ const actions: ActionButton[] = [
|
||||
{
|
||||
icon: Promotion,
|
||||
text: (row: BotInfo) =>
|
||||
isAiResourcePublished(row.publishStatus)
|
||||
canAiResourceRepublish(row.displayPublishStatus, row.publishStatus)
|
||||
? $t('button.republish')
|
||||
: $t('button.submitPublishApproval'),
|
||||
: $t('button.publish'),
|
||||
permission: '/api/v1/bot/save',
|
||||
placement: 'inline',
|
||||
visible: (row: BotInfo) =>
|
||||
canAiResourcePublish(row.displayPublishStatus, row.publishStatus) ||
|
||||
canAiResourceRepublish(row.displayPublishStatus, row.publishStatus),
|
||||
onClick(row: BotInfo) {
|
||||
handleSubmitPublishApproval(row);
|
||||
handlePublishAction(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: Promotion,
|
||||
text: $t('button.offline'),
|
||||
permission: '/api/v1/bot/save',
|
||||
placement: 'menu',
|
||||
visible: (row: BotInfo) => canAiResourceOffline(row.displayPublishStatus, row.publishStatus),
|
||||
onClick(row: BotInfo) {
|
||||
handleOfflineAction(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: Delete,
|
||||
text: $t('button.submitDeleteApproval'),
|
||||
text: $t('button.delete'),
|
||||
tone: 'danger',
|
||||
permission: '/api/v1/bot/remove',
|
||||
placement: 'menu',
|
||||
visible: (row: BotInfo) => canAiResourceDelete(row.displayPublishStatus, row.publishStatus),
|
||||
onClick(row: BotInfo) {
|
||||
handleSubmitDeleteApproval(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleSubmitPublishApproval = async (bot: BotInfo) => {
|
||||
if (isAiResourceApprovalPending(bot.publishStatus)) {
|
||||
function isRepublishAction(bot: BotInfo) {
|
||||
return canAiResourceRepublish(bot.displayPublishStatus, bot.publishStatus);
|
||||
}
|
||||
|
||||
const handlePublishAction = async (bot: BotInfo) => {
|
||||
if (
|
||||
isAiResourceApprovalPending(bot.displayPublishStatus, bot.publishStatus)
|
||||
) {
|
||||
ElMessage.warning($t('bot.publishPendingHint'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
$t('bot.submitPublishApprovalConfirm'),
|
||||
isRepublishAction(bot)
|
||||
? $t('bot.submitRepublishApprovalConfirm')
|
||||
: $t('bot.submitPublishApprovalConfirm'),
|
||||
$t('message.noticeTitle'),
|
||||
{
|
||||
confirmButtonText: $t('button.confirm'),
|
||||
@@ -150,8 +179,36 @@ const handleSubmitPublishApproval = async (bot: BotInfo) => {
|
||||
pageDataRef.value?.reload?.();
|
||||
}
|
||||
};
|
||||
const handleOfflineAction = async (bot: BotInfo) => {
|
||||
if (
|
||||
isAiResourceApprovalPending(bot.displayPublishStatus, bot.publishStatus)
|
||||
) {
|
||||
ElMessage.warning($t('bot.publishPendingHint'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
$t('bot.submitOfflineApprovalConfirm'),
|
||||
$t('message.noticeTitle'),
|
||||
{
|
||||
confirmButtonText: $t('button.confirm'),
|
||||
cancelButtonText: $t('button.cancel'),
|
||||
type: 'warning',
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const res = await submitBotOfflineApproval(String(bot.id));
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message || $t('message.saveOkMessage'));
|
||||
pageDataRef.value?.reload?.();
|
||||
}
|
||||
};
|
||||
const handleSubmitDeleteApproval = async (bot: BotInfo) => {
|
||||
if (isAiResourceApprovalPending(bot.publishStatus)) {
|
||||
if (
|
||||
isAiResourceApprovalPending(bot.displayPublishStatus, bot.publishStatus)
|
||||
) {
|
||||
ElMessage.warning($t('bot.deletePendingHint'));
|
||||
return;
|
||||
}
|
||||
@@ -174,8 +231,11 @@ const handleSubmitDeleteApproval = async (bot: BotInfo) => {
|
||||
pageDataRef.value?.reload?.();
|
||||
}
|
||||
};
|
||||
function resolvePublishStatusMeta(status?: string) {
|
||||
switch (normalizeAiPublishStatus(status)) {
|
||||
function resolvePublishStatusMetaByInstance(
|
||||
displayPublishStatus?: string,
|
||||
publishStatus?: string,
|
||||
) {
|
||||
switch (resolveAiResourceDisplayStatus(displayPublishStatus, publishStatus)) {
|
||||
case 'PUBLISHED':
|
||||
return {
|
||||
label: $t('bot.publishStatusPublished'),
|
||||
@@ -186,6 +246,16 @@ function resolvePublishStatusMeta(status?: string) {
|
||||
label: $t('bot.publishStatusPublishPending'),
|
||||
type: 'warning' as const,
|
||||
};
|
||||
case 'OFFLINE_PENDING':
|
||||
return {
|
||||
label: $t('bot.publishStatusOfflinePending'),
|
||||
type: 'warning' as const,
|
||||
};
|
||||
case 'OFFLINE':
|
||||
return {
|
||||
label: $t('bot.publishStatusOffline'),
|
||||
type: 'info' as const,
|
||||
};
|
||||
case 'DELETE_PENDING':
|
||||
return {
|
||||
label: $t('bot.publishStatusDeletePending'),
|
||||
@@ -378,9 +448,9 @@ const getSideList = async () => {
|
||||
size="small"
|
||||
effect="plain"
|
||||
round
|
||||
:type="resolvePublishStatusMeta(item.publishStatus).type"
|
||||
:type="resolvePublishStatusMetaByInstance(item.displayPublishStatus, item.publishStatus).type"
|
||||
>
|
||||
{{ resolvePublishStatusMeta(item.publishStatus).label }}
|
||||
{{ resolvePublishStatusMetaByInstance(item.displayPublishStatus, item.publishStatus).label }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</CardList>
|
||||
|
||||
@@ -42,6 +42,7 @@ import { tryit } from 'radash';
|
||||
import {
|
||||
getPerQuestions,
|
||||
submitBotDeleteApproval,
|
||||
submitBotOfflineApproval,
|
||||
submitBotPublishApproval,
|
||||
updateBotApi,
|
||||
updateBotOptions,
|
||||
@@ -56,10 +57,13 @@ import CommonSelectDataModal from '#/components/commonSelectModal/CommonSelectDa
|
||||
import DictSelect from '#/components/dict/DictSelect.vue';
|
||||
import UploadAvatar from '#/components/upload/UploadAvatar.vue';
|
||||
import {
|
||||
canAiResourceDelete,
|
||||
canAiResourceOffline,
|
||||
canAiResourcePublish,
|
||||
canAiResourceRepublish,
|
||||
isAiResourceApprovalPending,
|
||||
isAiResourceExternallyVisible,
|
||||
isAiResourcePublished,
|
||||
normalizeAiPublishStatus,
|
||||
resolveAiResourceDisplayStatus,
|
||||
} from '#/views/ai/shared/publish-status';
|
||||
|
||||
interface SelectedMcpTool {
|
||||
@@ -163,18 +167,42 @@ const publicChatUrl = computed(() => {
|
||||
const publicChatEmbedUrl = computed(() => {
|
||||
return buildPublicChatUrl(true);
|
||||
});
|
||||
const botDisplayPublishStatus = computed(() =>
|
||||
resolveAiResourceDisplayStatus(
|
||||
botInfo.value?.displayPublishStatus,
|
||||
botInfo.value?.publishStatus,
|
||||
),
|
||||
);
|
||||
const botApprovalPending = computed(() =>
|
||||
isAiResourceApprovalPending(
|
||||
botInfo.value?.displayPublishStatus,
|
||||
botInfo.value?.publishStatus,
|
||||
),
|
||||
);
|
||||
const publishStatusMeta = computed<{
|
||||
description: string;
|
||||
label: string;
|
||||
type: 'danger' | 'info' | 'success' | 'warning';
|
||||
}>(() => {
|
||||
switch (normalizeAiPublishStatus(botInfo.value?.publishStatus)) {
|
||||
switch (botDisplayPublishStatus.value) {
|
||||
case 'PUBLISHED':
|
||||
return {
|
||||
label: $t('bot.publishStatusPublished'),
|
||||
type: 'success',
|
||||
description: $t('bot.publishStatusPublishedDesc'),
|
||||
};
|
||||
case 'OFFLINE_PENDING':
|
||||
return {
|
||||
label: $t('bot.publishStatusOfflinePending'),
|
||||
type: 'warning',
|
||||
description: $t('bot.publishStatusOfflinePendingDesc'),
|
||||
};
|
||||
case 'OFFLINE':
|
||||
return {
|
||||
label: $t('bot.publishStatusOffline'),
|
||||
type: 'info',
|
||||
description: $t('bot.publishStatusOfflineDesc'),
|
||||
};
|
||||
case 'PUBLISH_PENDING':
|
||||
return {
|
||||
label: $t('bot.publishStatusPublishPending'),
|
||||
@@ -199,9 +227,44 @@ const canUsePublicAccess = computed(() =>
|
||||
isAiResourceExternallyVisible(botInfo.value?.publishStatus),
|
||||
);
|
||||
const publishPrimaryActionLabel = computed(() =>
|
||||
isAiResourcePublished(botInfo.value?.publishStatus)
|
||||
canAiResourceRepublish(
|
||||
botInfo.value?.displayPublishStatus,
|
||||
botInfo.value?.publishStatus,
|
||||
)
|
||||
? $t('button.republish')
|
||||
: $t('button.submitPublishApproval'),
|
||||
: $t('button.publish'),
|
||||
);
|
||||
const canShowPublishPrimaryAction = computed(() =>
|
||||
canAiResourcePublish(
|
||||
botInfo.value?.displayPublishStatus,
|
||||
botInfo.value?.publishStatus,
|
||||
) ||
|
||||
canAiResourceRepublish(
|
||||
botInfo.value?.displayPublishStatus,
|
||||
botInfo.value?.publishStatus,
|
||||
),
|
||||
);
|
||||
const secondaryActionLabel = computed(() => {
|
||||
if (
|
||||
canAiResourceOffline(
|
||||
botInfo.value?.displayPublishStatus,
|
||||
botInfo.value?.publishStatus,
|
||||
)
|
||||
) {
|
||||
return $t('button.offline');
|
||||
}
|
||||
if (
|
||||
canAiResourceDelete(
|
||||
botInfo.value?.displayPublishStatus,
|
||||
botInfo.value?.publishStatus,
|
||||
)
|
||||
) {
|
||||
return $t('button.delete');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const canShowSecondaryAction = computed(() =>
|
||||
Boolean(secondaryActionLabel.value),
|
||||
);
|
||||
const iframeCode = computed(() => {
|
||||
if (!publicChatEmbedUrl.value) {
|
||||
@@ -820,17 +883,24 @@ const handleDeletePresetQuestion = (item: any) => {
|
||||
const handlePublishWx = () => {
|
||||
publishWxRef.value.openDialog(botId.value, botInfo.value?.options || {});
|
||||
};
|
||||
const handleSubmitPublishApproval = async () => {
|
||||
const handleLifecycleAction = async () => {
|
||||
if (!botInfo.value) {
|
||||
return;
|
||||
}
|
||||
if (isAiResourceApprovalPending(botInfo.value.publishStatus)) {
|
||||
if (
|
||||
botApprovalPending.value
|
||||
) {
|
||||
ElMessage.warning($t('bot.publishPendingHint'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
$t('bot.submitPublishApprovalConfirm'),
|
||||
canAiResourceRepublish(
|
||||
botInfo.value.displayPublishStatus,
|
||||
botInfo.value.publishStatus,
|
||||
)
|
||||
? $t('bot.submitRepublishApprovalConfirm')
|
||||
: $t('bot.submitPublishApprovalConfirm'),
|
||||
$t('message.noticeTitle'),
|
||||
{
|
||||
confirmButtonText: $t('button.confirm'),
|
||||
@@ -849,17 +919,32 @@ const handleSubmitPublishApproval = async () => {
|
||||
ElMessage.error(res.message || $t('message.saveFailMessage'));
|
||||
}
|
||||
};
|
||||
const handleSubmitDeleteApproval = async () => {
|
||||
const handleSecondaryAction = async () => {
|
||||
if (!botInfo.value) {
|
||||
return;
|
||||
}
|
||||
if (isAiResourceApprovalPending(botInfo.value.publishStatus)) {
|
||||
ElMessage.warning($t('bot.deletePendingHint'));
|
||||
if (
|
||||
botApprovalPending.value
|
||||
) {
|
||||
ElMessage.warning($t('bot.publishPendingHint'));
|
||||
return;
|
||||
}
|
||||
const canOffline = canAiResourceOffline(
|
||||
botInfo.value.displayPublishStatus,
|
||||
botInfo.value.publishStatus,
|
||||
);
|
||||
const canDelete = canAiResourceDelete(
|
||||
botInfo.value.displayPublishStatus,
|
||||
botInfo.value.publishStatus,
|
||||
);
|
||||
if (!canOffline && !canDelete) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
$t('bot.submitDeleteApprovalConfirm'),
|
||||
canOffline
|
||||
? $t('bot.submitOfflineApprovalConfirm')
|
||||
: $t('bot.submitDeleteApprovalConfirm'),
|
||||
$t('message.noticeTitle'),
|
||||
{
|
||||
confirmButtonText: $t('button.confirm'),
|
||||
@@ -870,7 +955,9 @@ const handleSubmitDeleteApproval = async () => {
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const res = await submitBotDeleteApproval(String(botInfo.value.id));
|
||||
const res = canOffline
|
||||
? await submitBotOfflineApproval(String(botInfo.value.id))
|
||||
: await submitBotDeleteApproval(String(botInfo.value.id));
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message || $t('message.saveOkMessage'));
|
||||
getBotDetail();
|
||||
@@ -1485,19 +1572,21 @@ const handleBasicInfoChange = async (
|
||||
</div>
|
||||
<div class="publish-summary-actions">
|
||||
<ElButton
|
||||
v-if="canShowPublishPrimaryAction"
|
||||
type="primary"
|
||||
:disabled="!hasSavePermission"
|
||||
@click="handleSubmitPublishApproval"
|
||||
@click="handleLifecycleAction"
|
||||
>
|
||||
{{ publishPrimaryActionLabel }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="canShowSecondaryAction"
|
||||
plain
|
||||
type="danger"
|
||||
:type="secondaryActionLabel === $t('button.delete') ? 'danger' : 'default'"
|
||||
:disabled="!hasSavePermission"
|
||||
@click="handleSubmitDeleteApproval"
|
||||
@click="handleSecondaryAction"
|
||||
>
|
||||
{{ $t('button.submitDeleteApproval') }}
|
||||
{{ secondaryActionLabel }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user