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
@@ -3,6 +3,7 @@ import {
LoadingOutlined,
AppstoreOutlined,
ClockCircleOutlined,
SendOutlined,
EditOutlined,
DeleteOutlined,
} from "@ant-design/icons-vue";
@@ -15,6 +16,7 @@ import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import ArticleDetailDrawer from "@/components/ArticleDetailDrawer.vue";
import PublishArticleModal from "@/components/PublishArticleModal.vue";
import { articlesApi, promptRulesApi } from "@/lib/api";
import {
formatDateTime,
@@ -55,6 +57,8 @@ const appliedFilters = reactive<{
const articleDrawerOpen = ref(false);
const selectedArticleId = ref<number | null>(null);
const publishModalOpen = ref(false);
const selectedPublishArticleId = ref<number | null>(null);
const rulesQuery = useQuery({
queryKey: ["promptRules", "simple"],
@@ -75,8 +79,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 articleParams = computed<ArticleListParams>(() => {
@@ -117,7 +123,7 @@ const columns = computed<TableColumnsType<ArticleListItem>>(() => [
{ title: t("common.generateStatus"), dataIndex: "generate_status", key: "generate_status", width: 128 },
{ title: t("common.publishStatus"), dataIndex: "publish_status", key: "publish_status", width: 128 },
{ title: t("common.wordCount"), dataIndex: "word_count", key: "word_count", width: 100 },
{ title: t("common.actions"), key: "actions", width: 120, fixed: "right", align: "right" },
{ title: t("common.actions"), key: "actions", width: 156, fixed: "right", align: "right" },
]);
function applyFilters(): void {
@@ -148,10 +154,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";
}
function canPublishArticle(status: string): boolean {
return status === "completed";
}
function getDisplayTitle(article: ArticleListItem): string {
if ((article.generate_status === "generating" || article.generate_status === "running") && !article.title) {
return t("custom.article.titleGenerating");
@@ -327,6 +342,20 @@ onBeforeUnmount(() => {
</template>
<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="t('common.edit')">
<span>
<a-button
@@ -365,6 +394,11 @@ onBeforeUnmount(() => {
:article-id="selectedArticleId"
@close="articleDrawerOpen = false"
/>
<PublishArticleModal
v-model:open="publishModalOpen"
:article-id="selectedPublishArticleId"
/>
</div>
</template>