feat: implement browser extension for media publishing and add backend support for media management

This commit is contained in:
2026-04-03 00:39:15 +08:00
parent 8958cb44c0
commit 32d6a462cd
60 changed files with 8268 additions and 26 deletions
+39 -3
View File
@@ -2,6 +2,7 @@
import {
DeleteOutlined,
EditOutlined,
SendOutlined,
PlusOutlined,
BlockOutlined,
ReloadOutlined,
@@ -22,6 +23,7 @@ import { computed, onBeforeUnmount, reactive, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import PublishArticleModal from "@/components/PublishArticleModal.vue";
import { articlesApi, templatesApi } from "@/lib/api";
import { formatError } from "@/lib/errors";
import {
@@ -37,6 +39,8 @@ const router = useRouter();
const { t } = useI18n();
const pickerOpen = ref(false);
const publishModalOpen = ref(false);
const selectedPublishArticleId = ref<number | null>(null);
const page = ref(1);
const pageSize = ref(10);
const draftGenerationRange = ref<[Dayjs, Dayjs] | null>(null);
@@ -135,8 +139,10 @@ const generateStatusOptions = computed(() => [
const publishStatusOptions = computed(() => [
{ label: getPublishStatusMeta("unpublished").label, value: "unpublished" },
{ label: getPublishStatusMeta("publishing").label, value: "publishing" },
{ label: getPublishStatusMeta("published").label, value: "published" },
{ label: getPublishStatusMeta("publish_failed").label, value: "publish_failed" },
{ label: getPublishStatusMeta("success").label, value: "success" },
{ label: getPublishStatusMeta("partial_success").label, value: "partial_success" },
{ label: getPublishStatusMeta("failed").label, value: "failed" },
{ label: getPublishStatusMeta("pending_review").label, value: "pending_review" },
]);
const articleColumns = computed<TableColumnsType<ArticleListItem>>(() => [
@@ -172,7 +178,7 @@ const articleColumns = computed<TableColumnsType<ArticleListItem>>(() => [
{
title: t("common.actions"),
key: "actions",
width: 120,
width: 156,
align: "right",
},
]);
@@ -243,10 +249,19 @@ function openEditor(article: ArticleListItem): void {
void router.push({ name: "article-editor", params: { id: String(article.id) } });
}
function openPublish(article: ArticleListItem): void {
selectedPublishArticleId.value = article.id;
publishModalOpen.value = true;
}
function canEditArticle(status: string): boolean {
return status === "completed" || status === "draft" || status === "failed";
}
function canPublishArticle(status: string): boolean {
return status === "completed";
}
watch(
() => articleListQuery.data.value?.items ?? [],
(items: ArticleListItem[]) => {
@@ -449,6 +464,22 @@ async function handleDelete(articleId: number): Promise<void> {
<template v-else-if="column.key === 'actions'">
<div class="table-actions-row">
<a-tooltip
:title="canPublishArticle(record.generate_status) ? t('media.publish.title') : t('templates.list.publishDisabled')"
>
<span>
<a-button
type="text"
shape="circle"
size="small"
class="action-btn action-publish"
:disabled="!canPublishArticle(record.generate_status)"
@click="openPublish(record)"
>
<SendOutlined />
</a-button>
</span>
</a-tooltip>
<a-tooltip
:title="canEditArticle(record.generate_status) ? t('templates.list.edit') : t('templates.list.editDisabled')"
>
@@ -516,6 +547,11 @@ async function handleDelete(articleId: number): Promise<void> {
</article>
</div>
</a-modal>
<PublishArticleModal
v-model:open="publishModalOpen"
:article-id="selectedPublishArticleId"
/>
</div>
</template>