From d0f0271346b0c41448b32d59ab5484b6aadd1cd7 Mon Sep 17 00:00:00 2001 From: liangxu Date: Tue, 7 Apr 2026 19:38:29 +0800 Subject: [PATCH] feat: enhance article editor and detail components with improved link handling and UI updates --- .../src/components/ArticleDetailDrawer.vue | 89 ++++---- .../src/components/ArticleEditorCanvas.vue | 199 +++++++++++------- .../src/components/MarkdownPreview.vue | 24 ++- .../src/components/PublishArticleModal.vue | 19 +- .../editor-common/EditorActionMenu.vue | 101 +++++++-- .../editor-common/EditorAiAssistPanel.vue | 6 +- .../admin-web/src/views/ArticleEditorView.vue | 14 -- 7 files changed, 281 insertions(+), 171 deletions(-) diff --git a/apps/admin-web/src/components/ArticleDetailDrawer.vue b/apps/admin-web/src/components/ArticleDetailDrawer.vue index b42b901..ddcd6eb 100644 --- a/apps/admin-web/src/components/ArticleDetailDrawer.vue +++ b/apps/admin-web/src/components/ArticleDetailDrawer.vue @@ -1,4 +1,5 @@ @@ -292,14 +289,14 @@ function openPublishLink(record: PublishRecord): void { +
+ +
-
{{
-            displayMarkdown
-          }}
@@ -355,26 +352,27 @@ function openPublishLink(record: PublishRecord): void { {{ formatDateTime(record.published_at) }} @@ -433,8 +431,7 @@ function openPublishLink(record: PublishRecord): void { margin-top: 24px; } -.article-drawer__content, -.article-drawer__markdown { +.article-drawer__content { padding: 20px; background: #f8fafc; border: 1px solid #e5ecf5; @@ -447,13 +444,6 @@ function openPublishLink(record: PublishRecord): void { margin-top: 0; } -.article-drawer__markdown { - margin: 0; - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; - line-height: 1.6; - white-space: pre-wrap; -} - .article-drawer__version-title { display: flex; flex-direction: column; @@ -477,23 +467,30 @@ function openPublishLink(record: PublishRecord): void { font-size: 12px; } -.article-drawer__record-actions { - display: flex; - flex-direction: column; - gap: 8px; - min-width: 0; -} - .article-drawer__record-link { + flex: 1; + min-width: 0; overflow: hidden; color: #1677ff; text-overflow: ellipsis; white-space: nowrap; } -.article-drawer__record-buttons { +.article-drawer__record-link-row { display: flex; - gap: 8px; + align-items: center; + gap: 6px; + min-width: 0; +} + +.article-drawer__record-copy.ant-btn { + flex: none; + color: #8c8c8c; +} + +.article-drawer__record-copy.ant-btn:hover:not(:disabled) { + color: #1677ff; + background: #e6f4ff; } @media (max-width: 960px) { diff --git a/apps/admin-web/src/components/ArticleEditorCanvas.vue b/apps/admin-web/src/components/ArticleEditorCanvas.vue index 49cd6ca..07e8a03 100644 --- a/apps/admin-web/src/components/ArticleEditorCanvas.vue +++ b/apps/admin-web/src/components/ArticleEditorCanvas.vue @@ -318,6 +318,8 @@ let selectedImageLoadHandler: (() => void) | null = null; let aiOptimizeAbortController: AbortController | null = null; let aiOptimizeIgnoreNextWindowPointerDown = false; +const EDITOR_ACTION_MENU_DROPDOWN_SELECTOR = ".editor-action-menu__dropdown-overlay"; + const tableContextHeaderRow = computed(() => tableContextMenu.value.rowIndex === 0); const aiOptimizeStreaming = computed(() => aiOptimizeStatus.value === "generating"); const aiOptimizeHasPreview = computed(() => aiOptimizePreview.value.trim().length > 0); @@ -334,106 +336,135 @@ const aiOptimizeUiText = computed(() => ({ const tableContextMenuGroups = computed(() => [ { - key: "rows", + key: "operations", columns: 4, items: [ { - key: "add-row-before", - label: t("article.editor.tableMenu.addRowBefore"), + key: "rows", + label: "行操作", icon: InsertRowAboveOutlined, - disabled: tableContextHeaderRow.value, + children: [ + { + key: "add-row-before", + label: t("article.editor.tableMenu.addRowBefore"), + icon: InsertRowAboveOutlined, + disabled: tableContextHeaderRow.value, + }, + { + key: "add-row-after", + label: t("article.editor.tableMenu.addRowAfter"), + icon: InsertRowBelowOutlined, + }, + { + key: "delete-row", + label: t("article.editor.tableMenu.deleteRow"), + icon: DeleteOutlined, + danger: true, + disabled: tableContextHeaderRow.value, + }, + ], }, { - key: "add-row-after", - label: t("article.editor.tableMenu.addRowAfter"), - icon: InsertRowBelowOutlined, + key: "columns", + label: "列操作", + icon: InsertRowLeftOutlined, + children: [ + { + key: "add-col-before", + label: t("article.editor.tableMenu.addColBefore"), + icon: InsertRowLeftOutlined, + }, + { + key: "add-col-after", + label: t("article.editor.tableMenu.addColAfter"), + icon: InsertRowRightOutlined, + }, + { + key: "delete-col", + label: t("article.editor.tableMenu.deleteCol"), + icon: DeleteOutlined, + danger: true, + }, + ], }, { - key: "delete-row", - label: t("article.editor.tableMenu.deleteRow"), - icon: DeleteOutlined, - danger: true, - disabled: tableContextHeaderRow.value, + key: "align", + label: "对齐方式", + icon: AlignCenterOutlined, + children: [ + { + key: "align-left", + label: t("article.editor.tableMenu.alignLeft"), + icon: AlignLeftOutlined, + }, + { + key: "align-center", + label: t("article.editor.tableMenu.alignCenter"), + icon: AlignCenterOutlined, + }, + { + key: "align-right", + label: t("article.editor.tableMenu.alignRight"), + icon: AlignRightOutlined, + }, + ], }, { key: "delete-table", label: t("article.editor.tableMenu.deleteTable"), - icon: TableOutlined, - danger: true, - }, - ], - }, - { - key: "columns", - columns: 6, - items: [ - { - key: "add-col-before", - label: t("article.editor.tableMenu.addColBefore"), - icon: InsertRowLeftOutlined, - }, - { - key: "add-col-after", - label: t("article.editor.tableMenu.addColAfter"), - icon: InsertRowRightOutlined, - }, - { - key: "delete-col", - label: t("article.editor.tableMenu.deleteCol"), icon: DeleteOutlined, danger: true, }, - { - key: "align-left", - label: t("article.editor.tableMenu.alignLeft"), - icon: AlignLeftOutlined, - }, - { - key: "align-center", - label: t("article.editor.tableMenu.alignCenter"), - icon: AlignCenterOutlined, - }, - { - key: "align-right", - label: t("article.editor.tableMenu.alignRight"), - icon: AlignRightOutlined, - }, ], }, ]); const imageContextMenuGroups = computed(() => [ { - key: "image", - columns: 3, + key: "operations", + columns: 4, items: [ { - key: "replace-image", - label: t("article.editor.imageMenu.replace"), + key: "image", + label: "图片操作", icon: PictureOutlined, + children: [ + { + key: "replace-image", + label: t("article.editor.imageMenu.replace"), + icon: PictureOutlined, + }, + { + key: "toggle-caption", + label: t("article.editor.imageMenu.caption"), + icon: CodeOutlined, + }, + ], }, { - key: "toggle-caption", - label: t("article.editor.imageMenu.caption"), - icon: CodeOutlined, - }, - { - key: "align-left", - label: t("article.editor.imageMenu.alignLeft"), - icon: AlignLeftOutlined, - active: selectedImage.value.align === "left", - }, - { - key: "align-center", - label: t("article.editor.imageMenu.alignCenter"), + key: "align", + label: "对齐方式", icon: AlignCenterOutlined, - active: selectedImage.value.align === "center", - }, - { - key: "align-right", - label: t("article.editor.imageMenu.alignRight"), - icon: AlignRightOutlined, - active: selectedImage.value.align === "right", + children: [ + { + key: "align-left", + label: t("article.editor.imageMenu.alignLeft"), + icon: AlignLeftOutlined, + active: selectedImage.value.align === "left", + }, + { + key: "align-center", + label: t("article.editor.imageMenu.alignCenter"), + icon: AlignCenterOutlined, + active: selectedImage.value.align === "center", + }, + { + key: "align-right", + label: t("article.editor.imageMenu.alignRight"), + icon: AlignRightOutlined, + active: selectedImage.value.align === "right", + }, + ], }, { key: "reset-size", @@ -1224,6 +1255,10 @@ function handleWindowPointerDown(event: PointerEvent): void { } } + if (isEditorActionMenuDropdownTarget(target)) { + return; + } + if (tablePickerOpen.value) { closeTablePicker(); } @@ -1241,6 +1276,18 @@ function handleWindowPointerDown(event: PointerEvent): void { } } +function isEditorActionMenuDropdownTarget(target: EventTarget | null): boolean { + if (target instanceof Element) { + return target.closest(EDITOR_ACTION_MENU_DROPDOWN_SELECTOR) !== null; + } + + if (target instanceof Node) { + return target.parentElement?.closest(EDITOR_ACTION_MENU_DROPDOWN_SELECTOR) !== null; + } + + return false; +} + function resolveTableContextFromPointer(clientX: number, clientY: number): Omit | null { const editor = getEditor(); if (!editor) { @@ -1870,9 +1917,7 @@ function runTableContextAction(action: TableContextMenuAction): void { :x="tableContextMenu.x" :y="tableContextMenu.y" :groups="tableContextMenuGroups" - variant="tile" - min-width="620px" - max-width="720px" + variant="inline" @select="handleTableContextMenuSelect" /> @@ -1882,8 +1927,6 @@ function runTableContextAction(action: TableContextMenuAction): void { :y="imageContextMenu.y" :groups="imageContextMenuGroups" variant="inline" - min-width="440px" - max-width="min(520px, calc(100vw - 32px))" @select="handleImageContextMenuSelect" /> diff --git a/apps/admin-web/src/components/MarkdownPreview.vue b/apps/admin-web/src/components/MarkdownPreview.vue index 1d7406e..a67108f 100644 --- a/apps/admin-web/src/components/MarkdownPreview.vue +++ b/apps/admin-web/src/components/MarkdownPreview.vue @@ -108,24 +108,36 @@ const html = computed(() => renderer.render(props.content || "")); .markdown-preview :deep(table) { width: 100%; - margin: 0 0 1em; - border-collapse: collapse; - overflow: hidden; + margin: 1em 0; + border-collapse: separate; + border-spacing: 0; border: 1px solid #e5e7eb; - border-radius: 12px; + border-radius: 8px; + overflow: hidden; } .markdown-preview :deep(th), .markdown-preview :deep(td) { - padding: 10px 12px; - border: 1px solid #e5e7eb; + padding: 12px 16px; + border-bottom: 1px solid #e5e7eb; + border-right: 1px solid #e5e7eb; text-align: left; vertical-align: top; } +.markdown-preview :deep(th:last-child), +.markdown-preview :deep(td:last-child) { + border-right: none; +} + +.markdown-preview :deep(tr:last-child td) { + border-bottom: none; +} + .markdown-preview :deep(th) { background: #f9fafb; font-weight: 600; + color: #374151; } .markdown-preview :deep(hr) { diff --git a/apps/admin-web/src/components/PublishArticleModal.vue b/apps/admin-web/src/components/PublishArticleModal.vue index b77074c..04127ef 100644 --- a/apps/admin-web/src/components/PublishArticleModal.vue +++ b/apps/admin-web/src/components/PublishArticleModal.vue @@ -475,7 +475,11 @@ function showPublishFailures(result: PublisherPublishResponse): void { {{ coverRequired ? t("media.publish.messages.coverRequired") : t("media.publish.coverHint") }}

-
+
-
+
- - {{ coverAssetUrl ? t("article.editor.coverReplace") : t("media.publish.coverUpload") }} - - + {{ t("article.editor.coverRemove") }}
-
+
{{ coverFileName || t("article.editor.coverSaved") }}
@@ -733,6 +734,10 @@ function showPublishFailures(result: PublisherPublishResponse): void { margin-top: 14px; } +.publish-modal__cover-body--single { + grid-template-columns: 176px; +} + .publish-modal__cover-preview { display: flex; align-items: center; diff --git a/apps/admin-web/src/components/editor-common/EditorActionMenu.vue b/apps/admin-web/src/components/editor-common/EditorActionMenu.vue index c731aed..4fbd653 100644 --- a/apps/admin-web/src/components/editor-common/EditorActionMenu.vue +++ b/apps/admin-web/src/components/editor-common/EditorActionMenu.vue @@ -1,5 +1,6 @@