1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-09-12 16:40:40 +08:00

chore: 废弃第三方友链提交功能,重构友链相关配置与分类逻辑

1.  移除linksSubmitPlugin相关配置与提交接口代码
2.  重构友链配置数据结构,拆分linkInfo为miniInfo/siteInfo/authorInfo
3.  优化分类页审核模式渲染逻辑,补充postCount默认值处理
4.  更新所有关联页面与组件的数据源读取逻辑
This commit is contained in:
小莫唐尼
2026-09-10 02:32:06 +08:00
parent eb19276a2d
commit be55d9ac2c
9 changed files with 114 additions and 143 deletions
+44 -5
View File
@@ -14,14 +14,41 @@ export interface IImagesConfig {
[key: string]: unknown [key: string]: unknown
} }
/** 插件配置(toolsPlugin/linksSubmitPlugin 等带 Authorization) */ /** 插件配置(toolsPlugin/linksPlugin 等带 Authorization) */
export interface IPluginConfig { export interface IPluginConfig {
votePlugin?: Record<string, unknown> votePlugin?: Record<string, unknown>
toolsPlugin?: { Authorization?: string } & Record<string, unknown> toolsPlugin?: { Authorization?: string } & Record<string, unknown>
linksPlugin?: Record<string, unknown> linksPlugin?: Record<string, unknown>
linksSubmitPlugin?: { Authorization?: string } & Record<string, unknown> /**
/** 链接配置(插件端 spec.linkInfo 直接下发到本键,字段名无映射:displayName/miniProgramCode/link/description/applyRemark/authorName/avatar/website */ * 链接配置(插件端 spec.linkInfo 直接下发到本键,结构 = {miniInfo, siteInfo, authorInfo},字段名无映射;
linkInfo?: Record<string, unknown> * linksSubmitPlugin 旧键已弃用不再下发)
*/
linkInfo?: {
/** 小程序信息(「申请信息」弹窗展示) */
miniInfo?: {
displayName?: string
miniProgramCode?: string
link?: string
description?: string
applyRemark?: string
}
/** 站点信息(对齐 Halo 官方友链提交 APIdisplayName/url/logo/description/email/backlink/feedUrls */
siteInfo?: {
displayName?: string
url?: string
logo?: string
description?: string
email?: string
backlink?: string
feedUrls?: string[]
}
/** 作者信息(小程序端作者区) */
authorInfo?: {
authorName?: string
avatar?: string
website?: string
}
}
doubanPlugin?: { position?: string } & Record<string, unknown> doubanPlugin?: { position?: string } & Record<string, unknown>
[key: string]: unknown [key: string]: unknown
} }
@@ -78,7 +105,7 @@ export interface IPageConfig {
visible?: boolean visible?: boolean
}> }>
/** 首页精选分类引用(插件端「通用配置 → 页面设置 → 首页」配置,固定最多 3 个, /** 首页精选分类引用(插件端「通用配置 → 页面设置 → 首页」配置,固定最多 3 个,
* 快照含名称/封面/排序权重,数组顺序 = 展示顺序;配置模式下直接映射渲染不发请求, * 快照含名称/封面/排序权重/文章数,数组顺序 = 展示顺序;配置模式下直接映射渲染不发请求,
* 未配置/为空时回退默认取数) */ * 未配置/为空时回退默认取数) */
categories?: Array<{ categories?: Array<{
name: string name: string
@@ -86,6 +113,8 @@ export interface IPageConfig {
cover?: string cover?: string
/** 分类排序权重(Halo Category.spec.priority,越大越靠前) */ /** 分类排序权重(Halo Category.spec.priority,越大越靠前) */
priority?: number priority?: number
/** 分类文章数(Halo Category.status.postCount 冗余快照,缺失默认 0 */
postCount?: number
}> }>
} }
categoryConfig?: { type?: string } categoryConfig?: { type?: string }
@@ -123,6 +152,16 @@ export interface IAuditDataResult {
description?: string description?: string
[key: string]: unknown [key: string]: unknown
} }
/** 分类完整快照(插件端公开接口附带:剔除失效、按配置顺序;app 端审核模式分类页免请求映射 ICategory) */
categoryDetails?: Array<{
name: string
title?: string
cover?: string
/** 排序权重(Halo Category.spec.priority) */
priority?: number
/** 文章数(Halo Category.status.postCount,缺失默认 0) */
postCount?: number
}>
} }
/** 应用基础配置(对应旧 DefaultAppConfigs) */ /** 应用基础配置(对应旧 DefaultAppConfigs) */
-20
View File
@@ -377,19 +377,6 @@ export function createVerificationCode() {
/* ==================== 友链提交(linkssubmit.muyin.site) ==================== */ /* ==================== 友链提交(linkssubmit.muyin.site) ==================== */
/**
* 提交友链
*/
export function submitLink(form: ISubmitLinkForm) {
return http.Post<IResponse<{ msg?: string, message?: string }>>('/apis/linkssubmit.muyin.site/v1alpha1/submit', form, {
headers: {
'Authorization': getLinksSubmitAuthorization(),
'Wechat-Session-Id': getOpenid(),
},
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 投票(api.vote.kunkunyu.com) ==================== */ /* ==================== 投票(api.vote.kunkunyu.com) ==================== */
/** /**
@@ -510,13 +497,6 @@ function getToolsAuthorization(): string {
return getAppConfigFromStore().pluginConfig?.toolsPlugin?.Authorization || '' return getAppConfigFromStore().pluginConfig?.toolsPlugin?.Authorization || ''
} }
/**
* 友链提交插件授权头(源自应用配置 pluginConfig.linksSubmitPlugin.Authorization)
*/
function getLinksSubmitAuthorization(): string {
return getAppConfigFromStore().pluginConfig?.linksSubmitPlugin?.Authorization || ''
}
/** /**
* 读取应用配置(store 未就绪时兜底为空) * 读取应用配置(store 未就绪时兜底为空)
* 注:待 src/store/appConfig.ts 建立后改为 useAppConfigStore 读取 * 注:待 src/store/appConfig.ts 建立后改为 useAppConfigStore 读取
@@ -34,7 +34,8 @@
slug: '', slug: '',
cover: checkThumbnailUrl(c.cover), cover: checkThumbnailUrl(c.cover),
priority: c.priority, priority: c.priority,
} },
postCount: c.postCount ?? 0,
} as ICategory)) } as ICategory))
} }
else { else {
@@ -24,20 +24,21 @@ const emit = defineEmits<{
const isShow = ref(false) const isShow = ref(false)
const appConfigStore = useAppConfigStore() const appConfigStore = useAppConfigStore()
/** 小程序申请信息(字段与 uh-links-mini-apply 表单一致,从插件端 linkInfo 配置直接读取,字段名无映射) */ /** 小程序申请信息(字段与 uh-links-mini-apply 表单一致;小程序信息读 linkInfo.miniInfo、作者信息读 linkInfo.authorInfo字段名无映射) */
const miniInfo = computed(() => { const miniInfo = computed(() => {
const cfg = (appConfigStore.configs.pluginConfig?.linkInfo || {}) as Record<string, unknown> const miniCfg = (appConfigStore.configs.pluginConfig?.linkInfo?.miniInfo || {}) as Record<string, unknown>
const str = (key: string, fallback = '') => String(cfg[key] || fallback || '') const authorCfg = (appConfigStore.configs.pluginConfig?.linkInfo?.authorInfo || {}) as Record<string, unknown>
const str = (cfg: Record<string, unknown>, key: string, fallback = '') => String(cfg[key] || fallback || '')
return { return {
displayName: str('displayName'), displayName: str(miniCfg, 'displayName'),
miniProgramCode: str('miniProgramCode'), miniProgramCode: str(miniCfg, 'miniProgramCode'),
link: str('link'), link: str(miniCfg, 'link'),
authorName: str('authorName'), authorName: str(authorCfg, 'authorName'),
avatar: str('avatar'), avatar: str(authorCfg, 'avatar'),
website: str('website'), website: str(authorCfg, 'website'),
description: str('description'), description: str(miniCfg, 'description'),
applyRemark: str('applyRemark'), applyRemark: str(miniCfg, 'applyRemark'),
email: str('email'), email: str(miniCfg, 'email'),
} }
}) })
@@ -5,8 +5,6 @@
* 提交后等待站长审核,通过后展示在「站点」列表中 * 提交后等待站长审核,通过后展示在「站点」列表中
*/ */
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { submitLink } from '@/api/uni-halo'
import type { ISubmitLinkForm } from '@/api/types/uni-halo'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
show?: boolean show?: boolean
@@ -88,34 +86,9 @@ async function handleSubmit() {
if (!validateForm()) if (!validateForm())
return return
submitting.value = true // linksSubmitPlugin 已弃用(2026-09-08),第三方友链自助提交暂未开放;
uni.showLoading({ title: '正在提交...' }) // 后续可对接 Halo 官方 plugin-links link-applications 接口
try { uni.showToast({ icon: 'none', title: '友链申请功能暂未开放,请联系站长' })
const payload: ISubmitLinkForm = {
name: form.value.name.trim(),
url: form.value.url.trim(),
logo: form.value.logo.trim() || undefined,
linkPageUrl: form.value.linkPageUrl.trim() || undefined,
email: form.value.email.trim() || undefined,
rssUrl: form.value.rssUrl.trim() || undefined,
description: form.value.description.trim() || undefined,
}
const res = await submitLink(payload)
const msg = res.data?.msg || res.data?.message || res.message || '提交成功'
uni.showToast({ icon: 'none', title: msg })
if (res.code === 200 || res.code === undefined) {
handleClose(true)
handleResetForm()
}
}
catch (err) {
console.error('友链申请提交失败', err)
uni.showToast({ icon: 'none', title: '提交失败,请稍后重试!' })
}
finally {
submitting.value = false
uni.hideLoading()
}
} }
function handleOnChange(isOpen: boolean) { function handleOnChange(isOpen: boolean) {
@@ -2,7 +2,7 @@
/** /**
* 站点友链信息弹窗(源自旧页面 pages-blog/submit-link 的博客详情弹窗,重设计为底部玻璃弹窗) * 站点友链信息弹窗(源自旧页面 pages-blog/submit-link 的博客详情弹窗,重设计为底部玻璃弹窗)
* 展示本站友链交换信息(博客名片 + 复制交换信息 + 站点缩略图) * 展示本站友链交换信息(博客名片 + 复制交换信息 + 站点缩略图)
* 数据源为 linksSubmitPlugin 配置(即本站申请提交的信息) * 数据源为插件端 linkInfo.siteInfo 配置(字段对齐 Halo 官方友链提交 API:displayName/url/logo/description/email/backlink/feedUrls)
*/ */
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useAppConfigStore } from '@/store/appConfig' import { useAppConfigStore } from '@/store/appConfig'
@@ -21,19 +21,22 @@ const emit = defineEmits<{
const isShow = ref(false) const isShow = ref(false)
const appConfigStore = useAppConfigStore() const appConfigStore = useAppConfigStore()
const blogDetail = computed(() => (appConfigStore.configs.pluginConfig?.linksSubmitPlugin as { const blogDetail = computed(() => (appConfigStore.configs.pluginConfig?.linkInfo?.siteInfo as {
blogName?: string displayName?: string
blogUrl?: string url?: string
blogLogo?: string logo?: string
blogDesc?: string description?: string
email?: string
backlink?: string
feedUrls?: string[]
} | undefined) || {}) } | undefined) || {})
/** 友链交换信息文案(复制用) */ /** 友链交换信息文案(复制用) */
const calcBlogContent = computed(() => ` const calcBlogContent = computed(() => `
博客名称:${blogDetail.value.blogName || ''} 博客名称:${blogDetail.value.displayName || ''}
博客地址:${blogDetail.value.blogUrl || ''} 博客地址:${blogDetail.value.url || ''}
博客logo${checkAvatarUrl(blogDetail.value.blogLogo)} 博客logo${checkAvatarUrl(blogDetail.value.logo)}
博客简介:${blogDetail.value.blogDesc || ''} 博客简介:${blogDetail.value.description || ''}
`) `)
function calcSiteThumbnail(val?: string): string { function calcSiteThumbnail(val?: string): string {
@@ -88,14 +91,14 @@ watch(() => props.show, (val) => {
<view class="flex items-center"> <view class="flex items-center">
<image <image
class="uh-global-card-glass h-20 w-20 shrink-0 rounded-2xl" class="uh-global-card-glass h-20 w-20 shrink-0 rounded-2xl"
:src="checkAvatarUrl(blogDetail.blogLogo)" mode="aspectFill" :src="checkAvatarUrl(blogDetail.logo)" mode="aspectFill"
/> />
<view class="ml-4 flex flex-1 flex-col justify-center gap-y-1"> <view class="ml-4 flex flex-1 flex-col justify-center gap-y-1">
<text class="text-md text-gray-900 font-bold"> <text class="text-md text-gray-900 font-bold">
{{ blogDetail.blogName || '未命名博客' }} {{ blogDetail.displayName || '未命名博客' }}
</text> </text>
<text class="text-xs text-gray-500"> <text class="text-xs text-gray-500">
{{ blogDetail.blogDesc || '这个博主很懒,没写简介~' }} {{ blogDetail.description || '这个博主很懒,没写简介~' }}
</text> </text>
</view> </view>
</view> </view>
@@ -107,8 +110,8 @@ watch(() => props.show, (val) => {
<!-- 站点缩略图 --> <!-- 站点缩略图 -->
<image <image
v-if="blogDetail.blogUrl" class="mt-4 h-[320rpx] w-full rounded-xl" v-if="blogDetail.url" class="mt-4 h-[320rpx] w-full rounded-xl"
:src="calcSiteThumbnail(blogDetail.blogUrl)" mode="aspectFill" :src="calcSiteThumbnail(blogDetail.url)" mode="aspectFill"
/> />
<view class="my-6"> <view class="my-6">
-2
View File
@@ -10,8 +10,6 @@ export const DefaultAppConfigs: IAppConfig = {
votePlugin: {}, votePlugin: {},
toolsPlugin: {}, toolsPlugin: {},
linksPlugin: {}, linksPlugin: {},
// 保留:友链提交授权头(pluginConfig.linksSubmitPlugin.Authorization)与站点信息展示仍读取
linksSubmitPlugin: {},
doubanPlugin: { doubanPlugin: {
position: 'bottom', position: 'bottom',
}, },
+22 -45
View File
@@ -5,7 +5,6 @@
*/ */
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { submitLink } from '@/api/uni-halo'
import { useAppConfigStore } from '@/store/appConfig' import { useAppConfigStore } from '@/store/appConfig'
import { checkAvatarUrl } from '@/utils/url' import { checkAvatarUrl } from '@/utils/url'
@@ -18,11 +17,14 @@ definePage({
const appConfigStore = useAppConfigStore() const appConfigStore = useAppConfigStore()
const haloPluginConfigs = computed(() => appConfigStore.configs.pluginConfig) const haloPluginConfigs = computed(() => appConfigStore.configs.pluginConfig)
const blogDetail = computed(() => (haloPluginConfigs.value?.linksSubmitPlugin as { const blogDetail = computed(() => (haloPluginConfigs.value?.linkInfo?.siteInfo as {
blogName?: string displayName?: string
blogUrl?: string url?: string
blogLogo?: string logo?: string
blogDesc?: string description?: string
email?: string
backlink?: string
feedUrls?: string[]
} | undefined) || {}) } | undefined) || {})
const blogDetailPoupShow = ref(false) const blogDetailPoupShow = ref(false)
@@ -38,10 +40,10 @@ const form = ref({
}) })
const calcBlogContent = computed(() => ` const calcBlogContent = computed(() => `
博客名称:${blogDetail.value.blogName} 博客名称:${blogDetail.value.displayName}
博客地址:${blogDetail.value.blogUrl} 博客地址:${blogDetail.value.url}
博客logo${checkAvatarUrl(blogDetail.value.blogLogo)} 博客logo${checkAvatarUrl(blogDetail.value.logo)}
博客简介:${blogDetail.value.blogDesc} 博客简介:${blogDetail.value.description}
`) `)
function calcSiteThumbnail(val?: string): string { function calcSiteThumbnail(val?: string): string {
@@ -77,34 +79,9 @@ async function handleHandle() {
return return
} }
uni.showLoading({ title: '正在提交...' }) // linksSubmitPlugin 已弃用(2026-09-08),第三方友链自助提交暂未开放;
try { // 后续可对接 Halo 官方 plugin-links link-applications 接口
const res = await submitLink({ uni.showToast({ icon: 'none', title: '友链申请功能暂未开放,请联系站长' })
name: form.value.name,
url: form.value.url,
logo: form.value.logo,
description: form.value.description,
email: form.value.email,
linkPageUrl: form.value.linkPageUrl,
rssUrl: form.value.rssUrl,
})
uni.hideLoading()
const code = res.code
const msg = res.data?.msg || res.data?.message || res.message || '提交成功'
uni.showToast({ icon: 'none', title: msg })
if (code === 200 || code === undefined) {
setTimeout(() => {
uni.navigateTo({
url: '/pages-blog/friend-links/friend-links',
})
}, 1000)
}
}
catch (err) {
console.error(err)
uni.hideLoading()
uni.showToast({ icon: 'none', title: '提交失败,请重试!' })
}
} }
function handleCopyLink() { function handleCopyLink() {
@@ -129,13 +106,13 @@ onLoad(() => {
<view class="app-page box-border min-h-screen w-screen bg-[#fafafd] p-8"> <view class="app-page box-border min-h-screen w-screen bg-[#fafafd] p-8">
<!-- 博客详情卡片 --> <!-- 博客详情卡片 -->
<view class="blog-coupon mb-6 flex items-center rounded-xl p-6" style="background: linear-gradient(135deg, #2196f3, #64b5f6);" @click="blogDetailPoupShow = true"> <view class="blog-coupon mb-6 flex items-center rounded-xl p-6" style="background: linear-gradient(135deg, #2196f3, #64b5f6);" @click="blogDetailPoupShow = true">
<image class="coupon-img h-[80rpx] w-[80rpx] shrink-0 rounded-xl" :src="checkAvatarUrl(blogDetail.blogLogo)" mode="aspectFill" /> <image class="coupon-img h-[80rpx] w-[80rpx] shrink-0 rounded-xl" :src="checkAvatarUrl(blogDetail.logo)" mode="aspectFill" />
<view class="coupon-info ml-5 flex-1"> <view class="coupon-info ml-5 flex-1">
<view class="coupon-title text-[30rpx] text-white font-bold"> <view class="coupon-title text-[30rpx] text-white font-bold">
{{ blogDetail.blogName }} {{ blogDetail.displayName }}
</view> </view>
<view class="coupon-desc mt-1 text-[24rpx] text-white/80"> <view class="coupon-desc mt-1 text-[24rpx] text-white/80">
{{ blogDetail.blogDesc }} {{ blogDetail.description }}
</view> </view>
</view> </view>
<view class="coupon-btn border-2 border-white/60 rounded-3xl px-5 py-1 text-[24rpx] text-white"> <view class="coupon-btn border-2 border-white/60 rounded-3xl px-5 py-1 text-[24rpx] text-white">
@@ -198,20 +175,20 @@ onLoad(() => {
<wd-popup v-model="blogDetailPoupShow" position="center" custom-style="width:640rpx;border-radius:12rpx;"> <wd-popup v-model="blogDetailPoupShow" position="center" custom-style="width:640rpx;border-radius:12rpx;">
<view class="poup p-9"> <view class="poup p-9">
<view class="info flex"> <view class="info flex">
<image class="poup-logo h-[140rpx] w-[140rpx] rounded-3xl" :src="checkAvatarUrl(blogDetail.blogLogo)" mode="aspectFill" /> <image class="poup-logo h-[140rpx] w-[140rpx] rounded-3xl" :src="checkAvatarUrl(blogDetail.logo)" mode="aspectFill" />
<view class="info-detail ml-6 flex flex-1 flex-col justify-center"> <view class="info-detail ml-6 flex flex-1 flex-col justify-center">
<view class="poup-name text-[34rpx] font-bold"> <view class="poup-name text-[34rpx] font-bold">
{{ blogDetail.blogName }} {{ blogDetail.displayName }}
</view> </view>
<view class="poup-tag mt-2.5 text-[24rpx] text-[#999]"> <view class="poup-tag mt-2.5 text-[24rpx] text-[#999]">
{{ blogDetail.blogDesc }} {{ blogDetail.description }}
</view> </view>
</view> </view>
</view> </view>
<view class="poup-desc mt-6 whitespace-pre-wrap text-[28rpx] text-[#555] leading-[1.8]"> <view class="poup-desc mt-6 whitespace-pre-wrap text-[28rpx] text-[#555] leading-[1.8]">
<text>{{ calcBlogContent }}</text> <text>{{ calcBlogContent }}</text>
</view> </view>
<image v-if="blogDetail.blogUrl" class="poup-img mt-6 h-[320rpx] w-[568rpx] rounded-xl" :src="calcSiteThumbnail(blogDetail.blogUrl)" mode="aspectFill" /> <image v-if="blogDetail.url" class="poup-img mt-6 h-[320rpx] w-[568rpx] rounded-xl" :src="calcSiteThumbnail(blogDetail.url)" mode="aspectFill" />
<view class="poup-link my-6 flex justify-center gap-6"> <view class="poup-link my-6 flex justify-center gap-6">
<wd-button size="small" plain type="primary" @click="handleCopyLink"> <wd-button size="small" plain type="primary" @click="handleCopyLink">
复制友链交换信息 复制友链交换信息
+12 -13
View File
@@ -54,21 +54,20 @@
updateLoadingStatus(DataLoadingStatusEnum.Loading) updateLoadingStatus(DataLoadingStatusEnum.Loading)
// 增加延迟,提升用户体验 // 增加延迟,提升用户体验
await sleep(800) await sleep(800)
// 审核模式 // 审核模式:直接使用审核配置分类快照(categoryDetails)映射 ICategory,免请求
if (calcAuditModeEnabled.value) { if (calcAuditModeEnabled.value) {
const auditCategoryNames = appConfigStore.auditData.spec?.categories || [] const auditCategoryDetails = appConfigStore.auditData.categoryDetails || []
try { try {
const res = await getCategoryList({ page: 1, size: 99999 }) dataList.value = auditCategoryDetails.map(item => ({
const filtered = res.data.items metadata: { name: item.name },
.filter(item => auditCategoryNames.includes(item.metadata.name)) spec: {
.map(item => ({ displayName: item.title || item.name,
...item, slug: '',
postCount: item.postCount ?? 0, cover: checkThumbnailUrl(item.cover, true),
spec: { ...item.spec, cover: checkThumbnailUrl(item.spec.cover, true) }, priority: item.priority,
})) },
const orderMap = new Map(auditCategoryNames.map((name, index) => [name, index])) postCount: item.postCount ?? 0,
filtered.sort((a, b) => (orderMap.get(a.metadata.name) ?? 999) - (orderMap.get(b.metadata.name) ?? 999)) } as ICategory))
dataList.value = filtered
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success) updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
loadMoreText.value = t('common.noMore') loadMoreText.value = t('common.noMore')
uni.hideLoading() uni.hideLoading()