diff --git a/client/src/components/AuthorInfo.vue b/client/src/components/AuthorInfo.vue
new file mode 100644
index 0000000..0185cb4
--- /dev/null
+++ b/client/src/components/AuthorInfo.vue
@@ -0,0 +1,134 @@
+
+
+
+
+
+ {{ placeholderLetter }}
+
+
+
+
+
+ {{ displayName }}
+
+
+ {{ displayEmail }}
+
+
+ 用户#{{ userId }}
+
+
+
+
+
+
diff --git a/client/src/components/admin/AdminTableFilter.vue b/client/src/components/admin/AdminTableFilter.vue
new file mode 100644
index 0000000..1efc3aa
--- /dev/null
+++ b/client/src/components/admin/AdminTableFilter.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/admin/ImagePicker.vue b/client/src/components/admin/ImagePicker.vue
new file mode 100644
index 0000000..51cb21b
--- /dev/null
+++ b/client/src/components/admin/ImagePicker.vue
@@ -0,0 +1,189 @@
+
+
+
{{ label }}
+
+
+
+
+
+
+ {{ placeholder }}
+
+
+
+
+
+
+
+ {{ uploading ? '上传中...' : '上传图片' }}
+
+
+ 从素材库选择
+
+
+ 清除
+
+
+
{{ hint }}
+
{{ error }}
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/admin/PostHistoryModal.vue b/client/src/components/admin/PostHistoryModal.vue
index cb0d3df..e5d8889 100644
--- a/client/src/components/admin/PostHistoryModal.vue
+++ b/client/src/components/admin/PostHistoryModal.vue
@@ -43,7 +43,15 @@
v{{ item.version }}
{{ item.modifiedAt }}
-
正文:
@@ -133,6 +151,7 @@ import {
type PostHistoryDiff
} from '../../services/api'
import { useToast } from '../../composables/useToast'
+import AuthorInfo from '../AuthorInfo.vue'
const FIELD_LABELS: Record
= {
title: '标题',
diff --git a/client/src/composables/useLocalTableFilter.ts b/client/src/composables/useLocalTableFilter.ts
new file mode 100644
index 0000000..899b43e
--- /dev/null
+++ b/client/src/composables/useLocalTableFilter.ts
@@ -0,0 +1,50 @@
+import { ref, type Ref } from 'vue'
+
+export function matchKeyword(
+ item: Record,
+ fields: string[],
+ keyword: string
+): boolean {
+ const q = keyword.trim().toLowerCase()
+ if (!q) return true
+ return fields.some((field) => {
+ const value = item[field]
+ if (value == null) return false
+ return String(value).toLowerCase().includes(q)
+ })
+}
+
+export function matchKeywordFn(
+ item: T,
+ keyword: string,
+ getter: (item: T) => string
+): boolean {
+ const q = keyword.trim().toLowerCase()
+ if (!q) return true
+ return getter(item).toLowerCase().includes(q)
+}
+
+/** 关键词输入 + 已应用关键词(点击查询后生效) */
+export function useAppliedKeyword() {
+ const keyword = ref('')
+ const appliedKeyword = ref('')
+
+ const apply = () => {
+ appliedKeyword.value = keyword.value.trim()
+ }
+
+ const reset = () => {
+ keyword.value = ''
+ appliedKeyword.value = ''
+ }
+
+ return { keyword, appliedKeyword, apply, reset }
+}
+
+export function resetRefFilters(filters: Record>, defaults: Record) {
+ Object.keys(defaults).forEach((key) => {
+ if (filters[key]) {
+ filters[key].value = defaults[key]
+ }
+ })
+}
diff --git a/client/src/pages/Blog.vue b/client/src/pages/Blog.vue
index 89b08e3..c101a9b 100644
--- a/client/src/pages/Blog.vue
+++ b/client/src/pages/Blog.vue
@@ -110,6 +110,15 @@
{{ post.categoryName || post.category?.name || '未分类' }}
{{ post.date }}
+
#{{ tag.name }}
@@ -145,6 +154,7 @@ import { useScrollAnimation } from '../composables/useScrollAnimation'
import { useSearchHistory } from '../composables/useSearchHistory'
import CustomSelect from '../components/CustomSelect.vue'
import SearchSuggestDropdown from '../components/SearchSuggestDropdown.vue'
+import AuthorInfo from '../components/AuthorInfo.vue'
const { getHistory, addHistory, removeHistory, clearHistory } = useSearchHistory()
diff --git a/client/src/pages/BlogDetail.vue b/client/src/pages/BlogDetail.vue
index 786153e..38ec6ee 100644
--- a/client/src/pages/BlogDetail.vue
+++ b/client/src/pages/BlogDetail.vue
@@ -152,19 +152,14 @@
{{ post.date }}
{{ post.title }}
-
-
-
-
-
-
+
@@ -265,11 +260,16 @@
-
- {{ recPost.categoryName }}
-
-
{{ recPost.date }}
+
@@ -383,6 +383,7 @@ import { useRoute, useRouter } from 'vue-router'
import { fetchPost, getRecommendedPosts, fetchColumnPosts, fetchSnippet, Post, Snippet, getPublicSettings } from '../services/api'
import { useScrollAnimation } from '../composables/useScrollAnimation'
import Icon from '../components/Icon.vue'
+import AuthorInfo from '../components/AuthorInfo.vue'
import SnippetModal from '../components/SnippetModal.vue'
import MarkdownIt from 'markdown-it'
diff --git a/client/src/pages/ColumnDetail.vue b/client/src/pages/ColumnDetail.vue
index 4bad477..f7bfce5 100644
--- a/client/src/pages/ColumnDetail.vue
+++ b/client/src/pages/ColumnDetail.vue
@@ -38,6 +38,15 @@
{{ post.categoryName || '未分类' }}
{{ post.date }}
+
#{{ tag.name }}
@@ -72,6 +81,7 @@ import { ref, onMounted, nextTick, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { fetchColumn, fetchColumnPosts, Column, Post } from '../services/api'
import { useScrollAnimation } from '../composables/useScrollAnimation'
+import AuthorInfo from '../components/AuthorInfo.vue'
const route = useRoute()
const router = useRouter()
diff --git a/client/src/pages/Home.vue b/client/src/pages/Home.vue
index f12dbe7..3109420 100644
--- a/client/src/pages/Home.vue
+++ b/client/src/pages/Home.vue
@@ -57,6 +57,16 @@
{{ featuredPost.title }}
+