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

feat: 联系博主页对接博主介绍/社交动态列表,下线友链提交页

- contact.vue:博主介绍(intro 富文本)用 mp-html 完整配置渲染(domain/loading-img/
  tag-style/container-style/markdown,对齐 moments/文章详情);社交信息改读
  authorConfig.social.items 动态列表(priority 排序、visible 过滤、未配置空态、
  不回退默认值),去 enabled 开关;收信日期改 formatTime 实时显示
- about.vue:社交入口判定改按社交项列表(存在可见且有内容即展示)
- 友链提交功能调整:submit-link 页面下线,mini-info/site-info 与提交开关
  (submissionEnabled)随插件端新结构对接

Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>
This commit is contained in:
小莫唐尼
2026-09-10 19:13:42 +08:00
parent 0c1c71a290
commit 4eae337a8e
9 changed files with 209 additions and 375 deletions
+32 -10
View File
@@ -18,10 +18,14 @@ export interface IImagesConfig {
export interface IPluginConfig {
toolsPlugin?: { Authorization?: string } & Record<string, unknown>
/**
* 链接配置(插件端 spec.linkInfo 直接下发到本键,结构 = {miniInfo, siteInfo, authorInfo},字段名无映射;
* linksSubmitPlugin 旧键已弃用不再下发;votePlugin/linksPlugin 已下线(2026-09-10app 端改插件启用检测判定)
* 友链信息(插件端 spec.linkInfo 直接下发到本键,结构 = {submissionEnabled, miniInfo, siteInfo},字段名无映射;
* linksSubmitPlugin 旧键已弃用不再下发;votePlugin/linksPlugin 已下线(2026-09-10app 端改插件启用检测判定)
* 2026-09-11 起新增基本配置 submissionEnabled(公开提交申请开关,原 setting linkConfig);authorInfo 已下线,
* 作者信息改读 authorConfig.blogger
*/
linkInfo?: {
/** 基本配置:是否开放公开提交申请(默认 true;false 时隐藏「提交申请」入口) */
submissionEnabled?: boolean
/** 小程序信息(「申请信息」弹窗展示) */
miniInfo?: {
displayName?: string
@@ -30,22 +34,15 @@ export interface IPluginConfig {
description?: string
applyRemark?: string
}
/** 站点信息(对齐 Halo 官方友链提交 APIdisplayName/url/logo/description/email/backlink/feedUrls */
/** 站点信息(对齐 Halo 官方友链提交 APIdisplayName/url/logo/description/backlink/feedUrls2026-09-11 起不再含 email */
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>
[key: string]: unknown
@@ -121,6 +118,23 @@ export interface IPageConfig {
aboutConfig?: {
bgImageUrl?: string
waveImageUrl?: string
/** 页脚版权(2026-09-10 起插件端从应用设置迁入,显示于关于页页脚) */
copyrightConfig?: {
enabled?: boolean
content?: string
}
}
/** 免责声明页(2026-09-10 起插件端从应用设置迁入:不再有 enabled 开关,按内容非空展示) */
disclaimers?: {
content?: string
}
/** 文章详情页配置(2026-09-10 起插件端从应用设置迁入:评论开关与版权文案) */
postDetailConfig?: {
showComment?: boolean
copyrightEnabled?: boolean
copyrightAuthor?: string
copyrightDesc?: string
copyrightViolation?: string
}
[key: string]: unknown
}
@@ -169,8 +183,16 @@ export interface IAppConfig {
personalToken?: string
}
}
/**
* 恋爱配置(2026-09-10 起插件端 loveEnabled 总开关下线,入口显隐由模块入口开关
* ourStory/lovePhoto/loveDaily.enabled)与 navList 统一管理,客户端按模块开关判定)
*/
loveConfig?: Record<string, unknown>
imagesConfig?: IImagesConfig
/**
* 博主与社交(插件端 GeneralConfig.profile.blogger/social 重建;blogger 含
* nickname/avatar/email/description/website(官网地址 2026-09-10 新增))
*/
authorConfig?: Record<string, unknown>
appConfig?: Record<string, unknown>
pluginConfig?: IPluginConfig
@@ -24,18 +24,20 @@ const emit = defineEmits<{
const isShow = ref(false)
const appConfigStore = useAppConfigStore()
/** 小程序申请信息(字段与 uh-links-mini-apply 表单一致;小程序信息读 linkInfo.miniInfo、作者信息读 linkInfo.authorInfo,字段名无映射) */
/** 小程序申请信息(字段与 uh-links-mini-apply 表单一致;小程序信息读 linkInfo.miniInfo
* 作者信息 2026-09-10 起改读应用设置-博主资料 authorConfig.bloggerlinkInfo.authorInfo 已下线),字段名无映射) */
const miniInfo = computed(() => {
const miniCfg = (appConfigStore.configs.pluginConfig?.linkInfo?.miniInfo || {}) as Record<string, unknown>
const authorCfg = (appConfigStore.configs.pluginConfig?.linkInfo?.authorInfo || {}) as Record<string, unknown>
const blogger = (appConfigStore.configs.authorConfig?.blogger || {}) as Record<string, unknown>
const str = (cfg: Record<string, unknown>, key: string, fallback = '') => String(cfg[key] || fallback || '')
return {
displayName: str(miniCfg, 'displayName'),
miniProgramCode: str(miniCfg, 'miniProgramCode'),
link: str(miniCfg, 'link'),
authorName: str(authorCfg, 'authorName'),
avatar: str(authorCfg, 'avatar'),
website: str(authorCfg, 'website'),
// 作者信息:博主资料 nickname/avatar/website(官网地址 2026-09-10 新增)
authorName: str(blogger, 'nickname'),
avatar: str(blogger, 'avatar'),
website: str(blogger, 'website'),
description: str(miniCfg, 'description'),
applyRemark: str(miniCfg, 'applyRemark'),
email: str(miniCfg, 'email'),
@@ -26,7 +26,6 @@ const blogDetail = computed(() => (appConfigStore.configs.pluginConfig?.linkInfo
url?: string
logo?: string
description?: string
email?: string
backlink?: string
feedUrls?: string[]
} | undefined) || {})
@@ -66,7 +66,7 @@
})
/* ---------------- 计算属性 ---------------- */
const postDetailConfig = computed(() => (haloConfigs.value.basicConfig as { postDetailConfig ?: Record<string, unknown> } | undefined)?.postDetailConfig)
const postDetailConfig = computed(() => (haloConfigs.value.pageConfig as { postDetailConfig ?: Record<string, unknown> } | undefined)?.postDetailConfig)
const bloggerInfo = computed(() => {
const blogger = haloConfigs.value.authorConfig?.blogger as { nickname ?: string, avatar ?: string } | undefined
+139 -132
View File
@@ -1,148 +1,155 @@
<script lang="ts" setup>
import { computed, ref, watch, watchEffect } from 'vue'
import { useAppConfigStore } from '@/store/appConfig'
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
import { checkAvatarUrl } from '@/utils/url'
import { computed, onUnmounted, ref, watch, watchEffect } from 'vue'
import { useAppConfigStore } from '@/store/appConfig'
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
import { checkAvatarUrl } from '@/utils/url'
import { formatTime } from '@/utils/formatTime'
import { markdownConfig } from '@/config/markdown'
definePage({
style: {
navigationBarTitleText: '联系博主',
navigationStyle: 'custom',
},
})
definePage({
style: {
navigationBarTitleText: '联系博主',
navigationStyle: 'custom',
},
})
const appConfigStore = useAppConfigStore()
const authorConfig = computed(() => appConfigStore.configs.authorConfig)
interface ISoical {
key ?: string
name : string
content : string
icon ?: string,
color : string,
bgColor : string,
priority ?: number,
visible ?: boolean,
}
const bloggerInfo = computed(() => {
const blogger = authorConfig.value?.blogger as { nickname?: string, avatar?: string, description?: string } | undefined
return {
nickname: blogger?.nickname || '',
avatar: checkAvatarUrl(blogger?.avatar),
description: blogger?.description || '',
}
})
const appConfigStore = useAppConfigStore()
const authorConfig = computed(() => appConfigStore.configs.authorConfig)
const socialConfig = computed(() => (authorConfig.value?.social as Record<string, unknown> | undefined) || {})
const bloggerInfo = computed(() => {
const blogger = authorConfig.value?.blogger as { nickname ?: string, avatar ?: string, description ?: string, intro ?: string } | undefined
return {
nickname: blogger?.nickname || '',
avatar: checkAvatarUrl(blogger?.avatar),
description: blogger?.description || '',
intro: blogger?.intro || '',
}
})
/** 联系方式列表(从配置填充) */
const result = ref<{ key: string, name: string, value: string }[]>([
{ key: 'qq', name: '企鹅号', value: '' },
{ key: 'wechat', name: '微信号', value: '' },
{ key: 'github', name: 'Github', value: '' },
{ key: 'gitee', name: 'Gitee', value: '' },
{ key: 'bilibili', name: 'Bilibili', value: '' },
{ key: 'csdn', name: 'CSDN', value: '' },
{ key: 'blog', name: '博客地址', value: '' },
{ key: 'juejin', name: '掘金地址', value: '' },
{ key: 'weibo', name: '微博地址', value: '' },
{ key: 'email', name: '邮箱地址', value: '' },
])
const socialConfig = computed(() => (authorConfig.value?.social as { items ?: ISoical[] } | undefined) || {})
/** 平台瓦片(品牌色 + 字符);未覆盖的平台回退中性色与名称首字 */
const platformMeta: Record<string, { color: string, letter: string }> = {
qq: { color: '#12b7f5', letter: 'Q' },
wechat: { color: '#07c160', letter: '微' },
github: { color: '#24292f', letter: 'G' },
gitee: { color: '#c71d23', letter: 'G' },
bilibili: { color: '#fb7299', letter: 'B' },
csdn: { color: '#fc5531', letter: 'C' },
blog: { color: '#3f51b5', letter: '博' },
juejin: { color: '#1e80ff', letter: '掘' },
weibo: { color: '#e6162d', letter: 'W' },
email: { color: '#f57c00', letter: '@' },
}
const socialList = computed<Array<ISoical>>(() => {
const configured = socialConfig.value.items
if (!configured || !configured.length) {
return []
}
return [...configured]
.filter(item => item.visible !== false)
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))
})
const calcIsNotEmpty = computed(() => result.value.some(item => item.value !== ''))
const isNotEmpty = computed(() => socialList.value.some(item => item.visible && !!item.content))
/* ---------------- 加载状态机(本地配置,状态直接推导) ---------------- */
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
watchEffect(() => {
updateLoadingStatus(calcIsNotEmpty.value ? DataLoadingStatusEnum.Success : DataLoadingStatusEnum.Empty)
})
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
watchEffect(() => {
updateLoadingStatus(isNotEmpty.value ? DataLoadingStatusEnum.Success : DataLoadingStatusEnum.Empty)
})
function handleGetData() {
for (const key in socialConfig.value) {
if (key === 'enabled')
continue
const item = result.value.find(x => x.key === key)
if (item) {
item.value = String(socialConfig.value[key] || '')
}
}
}
function handleOnClick(item : ISoical) {
if (!item.content) { return }
uni.setClipboardData({
data: item.content,
showToast: false,
success: () => {
uni.showToast({ icon: 'none', title: `${item.name} 已复制!` })
},
})
}
function handleOnClick(item: { value: string, name: string }) {
if (!item.value)
return
uni.setClipboardData({
data: item.value,
showToast: false,
success: () => {
uni.showToast({ icon: 'none', title: `${item.name} 已复制!` })
},
})
}
watch(socialConfig, () => {
handleGetData()
}, { deep: true, immediate: true })
/* ---------------- 实时时间(收信日期,formatTime 工具每秒刷新) ---------------- */
const nowText = ref('')
let clock : ReturnType<typeof setInterval> | null = null
function refreshNowText() {
nowText.value = formatTime({ d: Date.now(), f: 'yyyy年MM月dd日' })
}
refreshNowText()
clock = setInterval(refreshNowText, 1000)
onUnmounted(() => {
if (clock) {
clearInterval(clock)
}
})
</script>
<template>
<view class="box-border min-h-screen w-screen overflow-hidden bg-page px-4 pb-10 pt-2">
<!-- 自定义导航 -->
<uh-navbar default-title="联系博主" title-color="text-gray-900" />
<view class="box-border min-h-screen w-screen overflow-hidden bg-page px-4 pb-10 pt-2">
<!-- 自定义导航 -->
<uh-navbar default-title="联系博主" title-color="text-gray-900" />
<!-- Hero 名片卡(主题色光斑透过毛玻璃形成柔和渐变) -->
<view class="hero-wrap relative">
<view class="absolute h-[220rpx] w-[220rpx] rounded-full bg-[rgba(185,228,36,0.32)] -right-8 -top-8" />
<view class="absolute top-[150rpx] h-[180rpx] w-[180rpx] rounded-full bg-[rgba(215,249,76,0.45)] -left-10" />
<view class="hero-card uh-global-card-glass uh-shadow-xs relative flex flex-col items-center rounded-2xl px-6 pb-6 pt-10">
<view class="avatar relative box-border h-[150rpx] w-[150rpx] overflow-hidden border-5 border-white/90 rounded-full shadow-lg">
<image class="avatar-img h-full w-full" :src="bloggerInfo.avatar" mode="aspectFill" />
</view>
<view class="nickname mt-4 text-[38rpx] text-gray-900 font-bold">
{{ bloggerInfo.nickname }}
</view>
<view class="desc mt-2 px-4 text-center text-[26rpx] text-gray-500 leading-relaxed">
{{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }}
</view>
<view class="mt-5 w-full flex items-center justify-center gap-2 border-t border-black/5 pt-4">
<wd-icon name="copy" size="26rpx" color="#a8a294" />
<text class="text-2xs text-gray-400">点击卡片即可复制对应内容</text>
</view>
</view>
</view>
<view class="fixed -right-8 top-8 h-28 w-28 rounded-full bg-[rgba(185,228,36,0.32)] blur-xl" />
<view class="fixed -left-10 top-36 h-24 w-24 rounded-full bg-[rgba(215,249,76,0.45)] blur-xl" />
<!-- 联系方式(状态机:无联系方式 empty ) -->
<uh-data-loading
v-if="loadingStatus !== 'success'" :loading-status="loadingStatus" min-height="30vh"
empty-text="暂无联系方式" empty-sub-text="" @refresh="handleGetData"
/>
<template v-else>
<uh-section-title class="mb-3 mt-6">
联系方式
</uh-section-title>
<view class="flex flex-col gap-3">
<view
v-for="item in result.filter(i => i.value)"
:key="item.key"
class="box-border uh-global-card-glass uh-shadow-xs flex items-center gap-3 rounded-2xl px-4 py-3"
@click="handleOnClick(item)"
>
<view class="tile h-[76rpx] w-[76rpx] flex shrink-0 items-center justify-center rounded-xl" :style="{ backgroundColor: platformMeta[item.key]?.color || '#8a8a7a' }">
<text class="text-[30rpx] text-white font-bold">{{ platformMeta[item.key]?.letter || item.name.slice(0, 1) }}</text>
</view>
<view class="min-w-0 flex flex-1 flex-col">
<text class="text-xs text-gray-500">{{ item.name }}</text>
<view class="mt-1 break-all text-[26rpx] text-gray-900 leading-snug">
{{ item.value }}
</view>
</view>
<wd-icon name="copy" size="28rpx" class="shrink-0 text-gray-400" />
</view>
</view>
</template>
</view>
</template>
<view
class="box-border relative flex items-center justify-between uh-global-card-glass uh-shadow-xs rounded-xl p-4">
<view class="flex-1 relative flex flex-col rounded-2xl">
<view class="flex items-center gap-x-2 text-xs text-primary">
TO <text class="text-sm">·</text>
</view>
<view class="mt-2 text-md text-gray-900 tracking-wider">
素未谋面的朋友
</view>
<view class="mt-2 text-xs text-black/60">
展信安见字如面
</view>
<view class="mt-3 text-xs text-black/60">
· {{ bloggerInfo.nickname }}
</view>
</view>
<view
class="shrink-0 uh-global-card-glass uh-shadow-xs border relative rotate-3 box-border w-22 overflow-hidden flex flex-col gap-y-1 items-center justify-center p-2">
<image class="h-18 w-full" :src="bloggerInfo.avatar" mode="aspectFill" />
<text class="text-xs font-normal truncate">{{ bloggerInfo.nickname }}</text>
</view>
</view>
<view class="mt-4 flex flex-col gap-y-6 box-border uh-global-card-glass uh-shadow-xs rounded-xl px-4 py-3">
<uh-data-loading v-if="loadingStatus !== 'success'" :loading-status="loadingStatus" min-height="65vh"
empty-text="暂无联系方式" empty-sub-text="" />
<template v-else>
<view class="w-full flex flex-col gap-y-3">
<view class="text-md text-gray-900">
展信安
</view>
<mp-html v-if="bloggerInfo.intro" lazy-load :domain="markdownConfig.domain ?? ''"
:loading-img="markdownConfig.loadingGif" scroll-table selectable
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
:content="bloggerInfo.intro" :markdown="true" :show-line-number="true"
:show-language-name="true" copy-by-long-press />
<view class="mt-2 w-full flex items-end flex-col gap-y-3">
<text class="text-md font-semibold text-gray-900">{{ bloggerInfo.nickname }}</text>
<text class="text-xs text-black/60">{{ nowText }}</text>
</view>
</view>
<view class="box-border pt-4 w-full flex flex-col gap-y-2 border-t border-t-dashed border-gray-200">
<view v-for="item in socialList" :key="item.name" class="flex items-center gap-3"
@click="handleOnClick(item)">
<view
class="uh-global-card-glass uh-shadow-xs border h-10 w-10 flex shrink-0 items-center justify-center rounded-xl"
:style="{ backgroundColor: item.bgColor ,color:item.color }">
<text class="text-sm text-gray-500 font-bold"> {{ item.icon }} </text>
</view>
<view class="min-w-0 flex flex-1 flex-col">
<text class="text-xs text-gray-500">{{ item.name }}</text>
<view class="mt-1 break-all text-sm text-gray-900 leading-snug">
{{ item.content }}
</view>
</view>
<wd-icon name="copy" size="28rpx" class="shrink-0 text-primary" />
</view>
</view>
</template>
</view>
</view>
</template>
+2 -2
View File
@@ -16,8 +16,8 @@
const haloConfigs = computed(() => appConfigStore.configs)
const disclaimersContent = computed(() => {
const basicConfig = haloConfigs.value.basicConfig as { disclaimers ?: { content ?: string } } | undefined
return basicConfig?.disclaimers?.content || ''
const pageConfig = haloConfigs.value.pageConfig as { disclaimers ?: { content ?: string } } | undefined
return pageConfig?.disclaimers?.content || ''
})
const bloggerInfo = computed(() => {
+5 -2
View File
@@ -68,6 +68,9 @@ async function handleMiniPluginRefresh() {
handleGetMiniProgramLinks()
}
/** 是否开放公开提交申请(通用配置-友链信息-基本配置 submissionEnabled2026-09-11 由 setting linkConfig 迁入;默认 true */
const submissionEnabled = computed(() => (haloPluginConfigs.value?.linkInfo?.submissionEnabled ?? true))
/* ---------------- tabs ---------------- */
const activeTabIndex = ref(0)
@@ -575,8 +578,8 @@ onReachBottom(() => {
</view>
</view>
<!-- 底部悬浮操作栏(通用组件) -->
<uh-links-actions :actions="['apply', 'info']" @apply="handleOpenApply" @info="handleOpenMiniInfo" />
<!-- 底部悬浮操作栏(通用组件)提交申请入口按 submissionEnabled 显隐(2026-09-11) -->
<uh-links-actions :actions="submissionEnabled ? ['apply', 'info'] : ['info']" @apply="handleOpenApply" @info="handleOpenMiniInfo" />
</view>
<!-- 小程序详情弹窗 -->
-209
View File
@@ -1,209 +0,0 @@
<script lang="ts" setup>
/**
* 友链申请页(源自旧项目 pagesA/submit-link,新建复刻)
* 提交友链交换信息表单
*/
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useAppConfigStore } from '@/store/appConfig'
import { checkAvatarUrl } from '@/utils/url'
definePage({
style: {
navigationBarTitleText: '友链申请',
},
})
const appConfigStore = useAppConfigStore()
const haloPluginConfigs = computed(() => appConfigStore.configs.pluginConfig)
const blogDetail = computed(() => (haloPluginConfigs.value?.linkInfo?.siteInfo as {
displayName?: string
url?: string
logo?: string
description?: string
email?: string
backlink?: string
feedUrls?: string[]
} | undefined) || {})
const blogDetailPoupShow = ref(false)
const form = ref({
url: '',
name: '',
logo: '',
linkPageUrl: '',
email: '',
rssUrl: '',
description: '',
})
const calcBlogContent = computed(() => `
博客名称:${blogDetail.value.displayName}
博客地址:${blogDetail.value.url}
博客logo${checkAvatarUrl(blogDetail.value.logo)}
博客简介:${blogDetail.value.description}
`)
function calcSiteThumbnail(val?: string): string {
if (!val)
return ''
const _val = val.endsWith('/') ? val : `${val}/`
return `https://image.thum.io/get/width/1000/crop/800/${_val}`
}
function checkIsUrl(url: string): boolean {
return /^https?:\/\//i.test(url)
}
function checkIsEmail(email: string): boolean {
return /^[\w.-]+@[\w-]+(?:\.[\w-]+)+$/.test(email)
}
async function handleHandle() {
if (!form.value.name.trim()) {
uni.showToast({ icon: 'none', title: '请输入网站名称!' })
return
}
if (!checkIsUrl(form.value.url)) {
uni.showToast({ icon: 'none', title: '请输入正确的网站地址!' })
return
}
if (form.value.logo && !checkIsUrl(form.value.logo)) {
uni.showToast({ icon: 'none', title: '请输入正确的Logo地址!' })
return
}
if (form.value.email && !checkIsEmail(form.value.email)) {
uni.showToast({ icon: 'none', title: '请输入正确的邮箱地址!' })
return
}
// linksSubmitPlugin 已弃用(2026-09-08),第三方友链自助提交暂未开放;
// 后续可对接 Halo 官方 plugin-links link-applications 接口
uni.showToast({ icon: 'none', title: '友链申请功能暂未开放,请联系站长' })
}
function handleCopyLink() {
uni.setClipboardData({
data: calcBlogContent.value,
showToast: false,
success: () => {
uni.showToast({ icon: 'none', title: '复制成功!' })
},
fail: () => {
uni.showToast({ icon: 'none', title: '复制失败!' })
},
})
}
onLoad(() => {
uni.setNavigationBarTitle({ title: '友链申请' })
})
</script>
<template>
<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">
<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-title text-[30rpx] text-white font-bold">
{{ blogDetail.displayName }}
</view>
<view class="coupon-desc mt-1 text-[24rpx] text-white/80">
{{ blogDetail.description }}
</view>
</view>
<view class="coupon-btn border-2 border-white/60 rounded-3xl px-5 py-1 text-[24rpx] text-white">
友链详情
</view>
</view>
<!-- 友链信息提交表单 -->
<view class="form-wrap rounded-xl bg-white p-6 shadow-sm">
<view class="form-title mb-6 text-[26rpx] font-bold">
友链信息提交
</view>
<view class="form-item flex items-center border-b-2 border-[#f5f5f5] py-5">
<text class="label w-[160rpx] shrink-0 text-[26rpx] text-[#333]">名称</text>
<input v-model="form.name" class="input flex-1 text-[26rpx]" placeholder="请输入网站名称">
</view>
<view class="form-item flex items-center border-b-2 border-[#f5f5f5] py-5">
<text class="label w-[160rpx] shrink-0 text-[26rpx] text-[#333]">网址</text>
<input v-model="form.url" class="input flex-1 text-[26rpx]" placeholder="请输入网站地址">
</view>
<view class="form-item flex items-center border-b-2 border-[#f5f5f5] py-5">
<text class="label w-[160rpx] shrink-0 text-[26rpx] text-[#333]">Logo</text>
<input v-model="form.logo" class="input flex-1 text-[26rpx]" placeholder="请输入网站Logo">
</view>
<view class="form-item flex items-center border-b-2 border-[#f5f5f5] py-5">
<text class="label w-[160rpx] shrink-0 text-[26rpx] text-[#333]">邮箱</text>
<input v-model="form.email" class="input flex-1 text-[26rpx]" placeholder="请输入邮箱">
</view>
<view class="form-item flex items-center border-b-2 border-[#f5f5f5] py-5">
<text class="label w-[160rpx] shrink-0 text-[26rpx] text-[#333]">友链页面</text>
<input v-model="form.linkPageUrl" class="input flex-1 text-[26rpx]" placeholder="请输入友链页面地址">
</view>
<view class="form-tip py-1 pl-[160rpx] text-[22rpx] text-[#999]">
贵站友情链接页面地址即包含本站链接的页面
</view>
<view class="form-item flex items-center border-b-2 border-[#f5f5f5] py-5">
<text class="label w-[160rpx] shrink-0 text-[26rpx] text-[#333]">RSS地址</text>
<input v-model="form.rssUrl" class="input flex-1 text-[26rpx]" placeholder="请输入RSS地址">
</view>
<view class="form-tip py-1 pl-[160rpx] text-[22rpx] text-[#999]">
用于抓取文章
</view>
<view class="form-item flex items-center border-b-2 border-[#f5f5f5] py-5">
<text class="label w-[160rpx] shrink-0 text-[26rpx] text-[#333]">网站描述</text>
<textarea v-model="form.description" class="textarea h-[100rpx] flex-1 text-[26rpx]" :maxlength="30" placeholder="请输入网站描述,不超过30字符" />
</view>
<view class="submit-btn mt-6">
<wd-button type="primary" block size="medium" @click="handleHandle">
提交数据
</wd-button>
<view class="submit-tip py-8 text-center text-[24rpx] text-[#999]">
友链申请
</view>
</view>
</view>
<!-- 博客详情弹窗 -->
<wd-popup v-model="blogDetailPoupShow" position="center" custom-style="width:640rpx;border-radius:12rpx;">
<view class="poup p-9">
<view class="info flex">
<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="poup-name text-[34rpx] font-bold">
{{ blogDetail.displayName }}
</view>
<view class="poup-tag mt-2.5 text-[24rpx] text-[#999]">
{{ blogDetail.description }}
</view>
</view>
</view>
<view class="poup-desc mt-6 whitespace-pre-wrap text-[28rpx] text-[#555] leading-[1.8]">
<text>{{ calcBlogContent }}</text>
</view>
<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">
<wd-button size="small" plain type="primary" @click="handleCopyLink">
复制友链交换信息
</wd-button>
<wd-button size="small" plain @click="blogDetailPoupShow = false">
关闭
</wd-button>
</view>
</view>
</wd-popup>
</view>
</template>
<style scoped>
.app-page {
/* 布局全部由 UnoCSS 原子类实现 */
}
</style>
+23 -13
View File
@@ -53,7 +53,7 @@
})
const pageConfig = computed(() => haloConfigs.value.pageConfig?.aboutConfig as
| { bgImageUrl ?: string, waveImageUrl ?: string }
| { bgImageUrl ?: string, waveImageUrl ?: string, copyrightConfig ?: { enabled ?: boolean, content ?: string } }
| undefined)
const calcProfileStyle = computed(() => ({
@@ -62,18 +62,26 @@
const calcWaveUrl = computed(() => checkImageUrl(pageConfig.value?.waveImageUrl))
const basicConfig = computed(() => haloConfigs.value.basicConfig as
| {
copyrightConfig ?: { enabled ?: boolean, content ?: string }
disclaimers ?: { enabled ?: boolean }
showAboutSystem ?: boolean
}
| undefined)
/** 免责声明(2026-09-10 起插件端从 basicConfig 迁入 pageConfig.disclaimers,无 enabled 开关,按内容判定入口显隐) */
const pageDisclaimers = computed(() => (haloConfigs.value.pageConfig as { disclaimers ?: { content ?: string } } | undefined)?.disclaimers)
const copyrightConfig = computed(() => basicConfig.value?.copyrightConfig)
/** 页脚版权(2026-09-10 起插件端从 basicConfig.copyrightConfig 迁入 pageConfig.aboutConfig.copyrightConfig */
const copyrightConfig = computed(() => pageConfig.value?.copyrightConfig)
const loveEnabled = computed(() => !!(haloConfigs.value.loveConfig as { loveEnabled ?: boolean } | undefined)?.loveEnabled)
const socialEnabled = computed(() => !!(haloConfigs.value.authorConfig?.social as { enabled ?: boolean } | undefined)?.enabled)
/** 恋爱入口:总开关 loveEnabled 2026-09-10 下线,改按模块入口开关判定(任一开启即展示) */
const loveEnabled = computed(() => {
const love = haloConfigs.value.loveConfig as
| { ourStory ?: { enabled ?: boolean }, lovePhoto ?: { enabled ?: boolean }, loveDaily ?: { enabled ?: boolean } }
| undefined
return !!(love?.ourStory?.enabled || love?.lovePhoto?.enabled || love?.loveDaily?.enabled)
})
/** 社交信息入口:2026-09-10 起去 enabled 开关,改按社交项列表判定(存在可见且有内容的社交项即展示) */
const socialEnabled = computed(() => {
const social = haloConfigs.value.authorConfig?.social as
| { items ?: Array<{ visible ?: boolean, content ?: string }> }
| undefined
return !!(social?.items?.some(item => item.visible !== false && !!item.content))
})
/* ---------------- 状态 ---------------- */
const statisticsShowMore = ref(false)
@@ -280,7 +288,8 @@
bgColor: 'rgba(121, 85, 72, 0.95)',
rightText: '博客内容免责声明',
path: '/pages-blog/disclaimers/disclaimers',
show: !!basicConfig.value?.disclaimers?.enabled,
// 2026-09-10 起无 enabled 开关,按内容非空判定入口显隐
show: !!pageDisclaimers.value?.content,
// show: true,
group: 'more',
},
@@ -304,7 +313,8 @@
bgColor: 'rgba(96, 125, 139, 0.95)',
rightText: '小莫唐尼开源项目',
path: '/pages-blog/about/about',
show: !!basicConfig.value?.showAboutSystem,
// showAboutSystem 2026-09-10 下线(入口由插件端功能入口 myPageConfig 统一管理),默认展示
show: true,
// show: true,
group: 'more',
},