@@ -66,6 +78,11 @@ import { authState, clearToken, loadCurrentUser } from '~/utils/auth'
import { fetchUnreadCount, notificationState } from '~/utils/notification'
import { useIsMobile } from '~/utils/screen'
import { themeState, cycleTheme, ThemeMode } from '~/utils/theme'
+import { toast } from '~/main'
+import { getToken } from '~/utils/auth'
+const config = useRuntimeConfig()
+const API_BASE_URL = config.public.apiBaseUrl
+const WEBSITE_BASE_URL = config.public.websiteBaseUrl
const props = defineProps({
showMenuBtn: {
@@ -82,6 +99,7 @@ const showSearch = ref(false)
const searchDropdown = ref(null)
const userMenu = ref(null)
const menuBtn = ref(null)
+const isCopying = ref(false)
const search = () => {
showSearch.value = true
@@ -100,6 +118,41 @@ const goToLogin = () => {
const goToSettings = () => {
navigateTo('/settings', { replace: true })
}
+
+const copyInviteLink = async () => {
+ isCopying.value = true
+ const token = getToken()
+ if (!token) {
+ toast.error('请先登录')
+ return
+ }
+ try {
+ const res = await fetch(`${API_BASE_URL}/api/invite/generate`, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ if (res.ok) {
+ const data = await res.json()
+ const inviteLink = data.token ? `${WEBSITE_BASE_URL}/signup?invite_token=${data.token}` : ''
+ await navigator.clipboard.writeText(inviteLink)
+ toast.success('邀请链接已复制')
+ } else {
+ const data = await res.json().catch(() => ({}))
+ toast.error(data.error || '生成邀请链接失败')
+ }
+ } catch (e) {
+ toast.error('生成邀请链接失败')
+ } finally {
+ isCopying.value = false
+ }
+}
+
+const copyRssLink = async () => {
+ const rssLink = `${API_BASE_URL}/api/rss`
+ await navigator.clipboard.writeText(rssLink)
+ toast.success('RSS链接已复制')
+}
+
const goToProfile = async () => {
if (!authState.loggedIn) {
navigateTo('/login', { replace: true })
@@ -224,7 +277,7 @@ onMounted(async () => {
margin-left: auto;
flex-direction: row;
align-items: center;
- gap: 20px;
+ gap: 30px;
}
.auth-btns {
@@ -315,11 +368,26 @@ onMounted(async () => {
cursor: pointer;
}
+.invite_text {
+ font-size: 12px;
+ cursor: pointer;
+ color: var(--primary-color);
+}
+
+.invite_text:hover {
+ text-decoration: underline;
+}
+
+.rss-icon,
.new-post-icon {
font-size: 18px;
cursor: pointer;
}
+.rss-icon {
+ text-shadow: 0 0 10px var(--primary-color);
+}
+
@media (max-width: 1200px) {
.header-content {
padding-left: 15px;
diff --git a/frontend_nuxt/pages/posts/[id]/index.vue b/frontend_nuxt/pages/posts/[id]/index.vue
index b87a251e5..55f69a1a0 100644
--- a/frontend_nuxt/pages/posts/[id]/index.vue
+++ b/frontend_nuxt/pages/posts/[id]/index.vue
@@ -268,6 +268,7 @@ const postReactions = ref([])
const comments = ref([])
const status = ref('PUBLISHED')
const pinnedAt = ref(null)
+const rssExcluded = ref(false)
const isWaitingPostingComment = ref(false)
const postTime = ref('')
const postItems = ref([])
@@ -356,6 +357,11 @@ const articleMenuItems = computed(() => {
} else {
items.push({ text: '置顶', onClick: () => pinPost() })
}
+ if (rssExcluded.value) {
+ items.push({ text: '取消rss不推荐', onClick: () => includeRss() })
+ } else {
+ items.push({ text: 'rss不推荐', onClick: () => excludeRss() })
+ }
}
if (isAdmin.value && status.value === 'PENDING') {
items.push({ text: '通过审核', onClick: () => approvePost() })
@@ -480,6 +486,7 @@ watchEffect(() => {
subscribed.value = !!data.subscribed
status.value = data.status
pinnedAt.value = data.pinnedAt
+ rssExcluded.value = data.rssExcluded
postTime.value = TimeManager.format(data.createdAt)
lottery.value = data.lottery || null
if (lottery.value && lottery.value.endTime) startCountdown()
@@ -645,6 +652,36 @@ const unpinPost = async () => {
}
}
+const excludeRss = async () => {
+ const token = getToken()
+ if (!token) return
+ const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/rss-exclude`, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ if (res.ok) {
+ rssExcluded.value = true
+ toast.success('已标记为rss不推荐')
+ } else {
+ toast.error('操作失败')
+ }
+}
+
+const includeRss = async () => {
+ const token = getToken()
+ if (!token) return
+ const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/rss-include`, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ if (res.ok) {
+ rssExcluded.value = false
+ toast.success('已取消rss不推荐')
+ } else {
+ toast.error('操作失败')
+ }
+}
+
const editPost = () => {
navigateTo(`/posts/${postId}/edit`, { replace: true })
}