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

refactor: 优化友链接口、页面样式与瞬间详情页代码

1. 更新友链分组接口的返回类型定义
2. 优化免责声明页、友链页的UI样式与代码结构
3. 简化小程序申请组件的表单内边距与弹窗绑定逻辑
4. 重构瞬间详情页的代码缩进与页面布局
This commit is contained in:
小莫唐尼
2026-09-05 03:13:59 +08:00
parent 6a0fda28d4
commit e1c84ff701
5 changed files with 443 additions and 513 deletions
+2 -2
View File
@@ -15,7 +15,7 @@ import type {
ICommentListReq, ICommentListReq,
ICommentListRes, ICommentListRes,
ILink, ILink,
ILinkGroupListRes, ILinkGroup,
ILinkListRes, ILinkListRes,
IMoment, IMoment,
IMomentListReq, IMomentListReq,
@@ -243,7 +243,7 @@ export function getPhotoListByGroupName(params: IPhotoListReq) {
* 友链分组列表 * 友链分组列表
*/ */
export function getFriendLinkGroupList(params: ICategoryListReq) { export function getFriendLinkGroupList(params: ICategoryListReq) {
return http.Get<IResponse<ILinkGroupListRes>>('/apis/api.link.halo.run/v1alpha1/linkgroups', { return http.Get<IResponse<Array<ILinkGroup>>>('/apis/api.link.halo.run/v1alpha1/linkgroups', {
params, params,
meta: { requestFrom: RequestFrom.Halo }, meta: { requestFrom: RequestFrom.Halo },
}) })
@@ -184,8 +184,7 @@
function handleOnChange(isOpen : boolean) { function handleOnChange(isOpen : boolean) {
isShow.value = isOpen isShow.value = isOpen
if (!isOpen) if (!isOpen) { emit('on-close', { isSubmit: false, refresh: false }) }
emit('on-close', { isSubmit: false, refresh: false })
} }
function handleClose(refresh = false) { function handleClose(refresh = false) {
@@ -202,7 +201,7 @@
<template> <template>
<uh-glass-popup v-model="isShow" :z-index="100" position="bottom" <uh-glass-popup v-model="isShow" :z-index="100" position="bottom"
custom-class="!border rounded-lt-2xl rounded-rt-2xl"> custom-class="!border rounded-lt-2xl rounded-rt-2xl" @close="handleClose(false)">
<view class="mb-4 relative w-full flex items-center justify-around box-border px-4 pt-4"> <view class="mb-4 relative w-full flex items-center justify-around box-border px-4 pt-4">
<view class="w-full flex flex-col gap-y-1"> <view class="w-full flex flex-col gap-y-1">
<text class="text-md font-bold">申请收录小程序</text> <text class="text-md font-bold">申请收录小程序</text>
@@ -217,59 +216,61 @@
<view class="mb-5 flex items-center"> <view class="mb-5 flex items-center">
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">名称 *</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">名称 *</text>
<input v-model="form.displayName" <input v-model="form.displayName"
class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-5 text-sm" class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-4 text-sm"
placeholder="请输入小程序名称"> placeholder="请输入小程序名称">
</view> </view>
<view class="mb-5 flex items-center"> <view class="mb-5 flex items-center">
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">太阳码 *</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">太阳码 *</text>
<input v-model="form.miniProgramCode" <input v-model="form.miniProgramCode"
class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-5 text-sm" class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-4 text-sm"
placeholder="小程序码图片链接(必填)"> placeholder="小程序码图片链接(必填)">
</view> </view>
<view class="mb-5 flex items-center"> <view class="mb-5 flex items-center">
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">小程序地址</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">小程序地址</text>
<input v-model="form.link" <input v-model="form.link"
class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-5 text-sm" class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-4 text-sm"
placeholder="跳转链接(选填)"> placeholder="跳转链接(选填)">
</view> </view>
<view class="mb-5 flex items-center"> <view class="mb-5 flex items-center">
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">作者昵称</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">作者昵称</text>
<input v-model="form.authorName" <input v-model="form.authorName"
class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-5 text-sm" placeholder="选填"> class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-4 text-sm" placeholder="选填">
</view> </view>
<view class="mb-5 flex items-center"> <view class="mb-5 flex items-center">
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">作者头像</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">作者头像</text>
<input v-model="form.avatar" <input v-model="form.avatar"
class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-5 text-sm" class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-4 text-sm"
placeholder="头像图片链接(选填)"> placeholder="头像图片链接(选填)">
</view> </view>
<view class="mb-5 flex items-center"> <view class="mb-5 flex items-center">
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">作者网站</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">作者网站</text>
<input v-model="form.website" <input v-model="form.website"
class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-5 text-sm" placeholder="选填"> class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-4 text-sm" placeholder="选填">
</view> </view>
<view class="mb-5"> <view class="mb-5">
<text class="label mb-2 block text-sm text-[#666]">小程序描述</text> <text class="label mb-2 block text-sm text-[#666]">小程序描述</text>
<textarea v-model="form.description" class="w-full h-24 uh-global-card-glass box-border shadow-none border flex-1 rounded-xl p-3 text-sm" <textarea v-model="form.description"
class="w-full h-24 uh-global-card-glass box-border shadow-none border flex-1 rounded-xl p-3 text-sm"
placeholder="介绍一下这个小程序(选填)" :maxlength="200" /> placeholder="介绍一下这个小程序(选填)" :maxlength="200" />
</view> </view>
<view class="mb-5"> <view class="mb-5">
<text class="label mb-2 block text-sm text-[#666]">申请说明</text> <text class="label mb-2 block text-sm text-[#666]">申请说明</text>
<textarea v-model="form.applyRemark" class="w-full h-24 uh-global-card-glass box-border shadow-none border flex-1 rounded-xl p-3 text-sm" <textarea v-model="form.applyRemark"
class="w-full h-24 uh-global-card-glass box-border shadow-none border flex-1 rounded-xl p-3 text-sm"
placeholder="方便管理员了解申请意图(选填)" :maxlength="200" /> placeholder="方便管理员了解申请意图(选填)" :maxlength="200" />
</view> </view>
<view class="mb-5 flex items-center"> <view class="mb-5 flex items-center">
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">通知邮箱</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">通知邮箱</text>
<input v-model="form.email" <input v-model="form.email"
class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-5 text-sm" class="uh-global-card-glass shadow-none border h-9 flex-1 rounded-xl px-4 text-sm"
placeholder="审核结果通知(选填)"> placeholder="审核结果通知(选填)">
</view> </view>
@@ -278,8 +279,8 @@
<text class="label w-[140rpx] shrink-0 text-sm text-[#666]">验证码 *</text> <text class="label w-[140rpx] shrink-0 text-sm text-[#666]">验证码 *</text>
<image :src="captchaSrc" class="captcha-img h-[76rpx] w-[200rpx] shrink-0 rounded-lg" mode="widthFix" <image :src="captchaSrc" class="captcha-img h-[76rpx] w-[200rpx] shrink-0 rounded-lg" mode="widthFix"
@click="handleRefreshCaptcha" /> @click="handleRefreshCaptcha" />
<input v-model="captchaCode" <input v-model="captchaCode" class="input ml-3 h-[72rpx] flex-1 rounded-xl bg-[#f5f5f5] px-4 text-sm"
class="input ml-3 h-[72rpx] flex-1 rounded-xl bg-[#f5f5f5] px-5 text-sm" placeholder="输入图中字符"> placeholder="输入图中字符">
</view> </view>
<view class="my-6"> <view class="my-6">
+5 -8
View File
@@ -40,7 +40,7 @@ function copyText(content: string, tips = '复制成功') {
</script> </script>
<template> <template>
<view class="app-page box-border min-h-screen bg-white p-12 text-[30rpx] text-[#303133] leading-[1.65]"> <view class="box-border min-h-screen bg-page ">
<!-- 自定义导航 --> <!-- 自定义导航 -->
<uh-navbar default-title="免责声明" title-color="text-gray-900" /> <uh-navbar default-title="免责声明" title-color="text-gray-900" />
@@ -48,12 +48,9 @@ function copyText(content: string, tips = '复制成功') {
<view v-if="disclaimersContent" style="min-height: 100%;" v-html="disclaimersContent" /> <view v-if="disclaimersContent" style="min-height: 100%;" v-html="disclaimersContent" />
<!-- 静态写法 --> <!-- 静态写法 -->
<block v-else> <view v-else class="box-border p-4 pt-0 text-sm text-gray-900 leading-7">
<view class="title text-center text-[34rpx] font-bold">
本博客免责声明
</view>
<view class="item mt-6"> <view class="item mt-6">
1博客属于个人非盈利性质的网站所有转载的文章都以遵循原作者的版权声明注明了文章来源 1应用属于个人非盈利性质的网站所有转载的文章都以遵循原作者的版权声明注明了文章来源
</view> </view>
<view class="item mt-6"> <view class="item mt-6">
2如果原文没有版权声明按照目前互联网开放的原则本博客将在不通知作者的情况下转载文章 2如果原文没有版权声明按照目前互联网开放的原则本博客将在不通知作者的情况下转载文章
@@ -63,7 +60,7 @@ function copyText(content: string, tips = '复制成功') {
</view> </view>
<view class="item mt-6"> <view class="item mt-6">
4如果本博客转载的文章不符合作者的版权声明或者作者不想让本博客转载您的文章请邮件告知 4如果本博客转载的文章不符合作者的版权声明或者作者不想让本博客转载您的文章请邮件告知
<text class="email mx-3 text-[#03a9f4]" @click="copyText(bloggerInfo.email, '电子邮箱已复制到剪贴板!')">{{ bloggerInfo.email }}</text> <text class="email mx-3 text-primary" @click="copyText(bloggerInfo.email, '电子邮箱已复制到剪贴板!')">{{ bloggerInfo.email }}</text>
博主将会在第一时间删除相关信息 博主将会在第一时间删除相关信息
</view> </view>
<view class="item mt-6"> <view class="item mt-6">
@@ -81,6 +78,6 @@ function copyText(content: string, tips = '复制成功') {
<view class="item mt-6"> <view class="item mt-6">
9尊重原创知识共享 9尊重原创知识共享
</view> </view>
</block> </view>
</view> </view>
</template> </template>
+71 -70
View File
@@ -81,8 +81,7 @@
/* ---------------- 数据加载 ---------------- */ /* ---------------- 数据加载 ---------------- */
function findLinkGroupDisplayNameByGroupMetadataName(groupName ?: string) : string { function findLinkGroupDisplayNameByGroupMetadataName(groupName ?: string) : string {
if (linkGroupList.value.length === 0) if (linkGroupList.value.length === 0) { return groupName || '未分组' }
return groupName || '未分组'
const found = linkGroupList.value.find(item => item.metadata.name === groupName) const found = linkGroupList.value.find(item => item.metadata.name === groupName)
return found?.spec.displayName || groupName || '未分组' return found?.spec.displayName || groupName || '未分组'
} }
@@ -90,7 +89,7 @@
async function handleGetLinkGroupData() { async function handleGetLinkGroupData() {
try { try {
const res = await getFriendLinkGroupList({ page: 1, size: 0 }) const res = await getFriendLinkGroupList({ page: 1, size: 0 })
linkGroupList.value = res.data.items || [] linkGroupList.value = res.data || []
handleGetData() handleGetData()
} }
catch (err) { catch (err) {
@@ -345,7 +344,7 @@
<!-- 自定义导航 --> <!-- 自定义导航 -->
<uh-navbar default-title="友情链接" title-color="text-gray-900" /> <uh-navbar default-title="友情链接" title-color="text-gray-900" />
<!-- 顶部 tab(吸顶玻璃胶囊 chip,同收藏页) --> <!-- 顶部 -->
<wd-sticky> <wd-sticky>
<scroll-view scroll-x class="w-full whitespace-nowrap"> <scroll-view scroll-x class="w-full whitespace-nowrap">
<view class="flex gap-2 px-3 pb-1 pt-3"> <view class="flex gap-2 px-3 pb-1 pt-3">
@@ -364,7 +363,6 @@
<uh-plugin-unavailable v-if="!sitePluginAvailable" :plugin-id="sitePluginId" :error-text="siteTips" <uh-plugin-unavailable v-if="!sitePluginAvailable" :plugin-id="sitePluginId" :error-text="siteTips"
:checking="siteChecking" @on-refresh="handleSitePluginRefresh" /> :checking="siteChecking" @on-refresh="handleSitePluginRefresh" />
<template v-else> <template v-else>
<!-- 加载/错误/空占位(状态机) -->
<view v-if="siteLoadingStatus !== 'success'"> <view v-if="siteLoadingStatus !== 'success'">
<uh-data-loading :loading-status="siteLoadingStatus" empty-text="啊偶,博主还没有朋友呢~" <uh-data-loading :loading-status="siteLoadingStatus" empty-text="啊偶,博主还没有朋友呢~"
@refresh="handleGetData" /> @refresh="handleGetData" />
@@ -372,51 +370,31 @@
<view v-else class="content pt-4"> <view v-else class="content pt-4">
<!-- 友链列表 --> <!-- 友链列表 -->
<view class="link-list flex flex-col gap-4 px-4 pb-4"> <view class="box-border flex flex-col gap-4 px-4 pb-4">
<view v-for="link in dataList" :key="link.metadata?.name || link.spec.displayName"> <view v-for="link in dataList" :key="link.metadata?.name || link.spec.displayName">
<!-- 色彩版 --> <view class="uh-global-card-glass overflow-hidden box-border flex rounded-xl p-3"
<view v-if="!globalAppSettings.links.useSimple" @click="handleOnLinkEvent(link)">
class="info uh-global-card-glass flex rounded-2xl p-3" @click="handleOnLinkEvent(link)"> <image class="h-16 w-16 shrink-0 rounded-lg" :src="link.spec.logo" mode="aspectFill" />
<image class="link-logo h-[140rpx] w-[140rpx] shrink-0 rounded-xl" :src="link.spec.logo" <view class="overflow-hidden box-border flex flex-1 flex-col justify-center pl-4">
mode="aspectFill" /> <view class="flex items-center text-sm text-gray-900 font-bold">
<view class="info-detail flex flex-1 flex-col justify-center pl-5">
<view class="link-card-name text-[30rpx] text-gray-900 font-bold">
<text <text
class="group-tag mr-3 rounded-md bg-secondary px-1.5 py-0.5 text-[20rpx] text-[#4d7c0f] font-normal">{{ link.spec.groupName || '暂未分组' }}</text> class="shrink-0 mr-3 rounded-md bg-secondary px-1.5 py-0.5 text-xs text-[#4d7c0f] font-normal">{{ link.spec.groupName || '暂未分组' }}</text>
{{ link.spec.displayName }} <text class="flex-1 truncate">{{ link.spec.displayName }}</text>
</view> </view>
<view <view class="mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400">
class="link-card-url mt-1 overflow-hidden text-ellipsis whitespace-nowrap text-[24rpx] text-gray-400">
站点地址{{ link.spec.url }} 站点地址{{ link.spec.url }}
</view> </view>
<view <view class="mt-2 overflow-hidden truncate whitespace-nowrap text-xs text-gray-600">
class="link-card-desc mt-2 overflow-hidden text-ellipsis whitespace-nowrap text-[24rpx] text-gray-600">
博客简介{{ link.spec.description || '这个博主很懒,没写简介~' }} 博客简介{{ link.spec.description || '这个博主很懒,没写简介~' }}
</view> </view>
</view> </view>
</view> </view>
<!-- 简洁版 -->
<view v-else class="link-card uh-global-card-glass flex items-center rounded-2xl p-4"
@click="handleOnLinkEvent(link)">
<image class="logo h-[80rpx] w-[80rpx] shrink-0 border-4 border-white/90 rounded-xl"
:src="link.spec.logo" mode="aspectFill" />
<view class="link-info flex-1 pl-5">
<view class="name text-[30rpx] text-gray-900 font-bold">
{{ link.spec.displayName }}
</view>
<view
class="desc mt-2 overflow-hidden text-ellipsis whitespace-nowrap text-[24rpx] text-gray-400">
{{ link.spec.description }}
</view>
</view>
</view>
</view> </view>
</view> </view>
<!-- 悬浮按钮 --> <!-- 悬浮按钮 -->
<view class="flot-buttons fixed bottom-10 right-4 z-999 flex flex-col gap-1.5"> <view class="flot-buttons fixed bottom-10 right-4 z-999 flex flex-col gap-1.5">
<view <view v-if="!haloPluginConfigs?.linksSubmitPlugin?.enabled"
v-if="!haloPluginConfigs?.linksSubmitPlugin?.enabled"
class="fab-btn uh-global-card-glass h-10 w-10 flex items-center justify-center rounded-full" class="fab-btn uh-global-card-glass h-10 w-10 flex items-center justify-center rounded-full"
@click="toSubmitLinkPage"> @click="toSubmitLinkPage">
<wd-icon name="edit" size="20px" color="#6b7280" /> <wd-icon name="edit" size="20px" color="#6b7280" />
@@ -424,33 +402,44 @@
</view> </view>
<!-- 详情弹窗 --> <!-- 详情弹窗 -->
<wd-popup v-model="detail.show" position="center" custom-style="width:640rpx;border-radius:12rpx;"> <uh-glass-popup v-model="detail.show" position="center" custom-class="w-[90vw] rounded-xl !border">
<view v-if="detail.data" class="poup p-8"> <view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
<view class="info flex"> <view class="w-full flex flex-col gap-y-1">
<text class="text-md font-bold">小程序详情</text>
</view>
<view
class="absolute right-4 top-4 w-6 h-6 uh-global-card-glass shadow-none border rounded-lg text-center"
@click="miniDetail.show = false">
<wd-icon name="close" size="32rpx" class="text-gray-500"></wd-icon>
</view>
</view>
<scroll-view v-if="detail.data" :scroll-y="true" :show-scrollbar="false"
class="box-border p-4 max-h-[60vh]">
<view class="flex">
<image class="poup-logo h-[140rpx] w-[140rpx] shrink-0 rounded-full" <image class="poup-logo h-[140rpx] w-[140rpx] shrink-0 rounded-full"
:src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill" /> :src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill" />
<view class="poup-info ml-6 flex flex-1 flex-col justify-center"> <view class="ml-4 flex flex-1 flex-col gap-y-1 justify-center">
<view class="poup-name text-[34rpx] text-gray-900 font-bold"> <view class="poup-name text-[34rpx] text-gray-900 font-bold">
{{ detail.data.spec.displayName }} {{ detail.data.spec.displayName }}
</view> </view>
<view class="poup-tag mt-2 text-[24rpx] text-gray-400"> <view class="text-xs text-gray-500">
{{ detail.data.spec.groupName }} {{ detail.data.spec.groupName }}
</view> </view>
<view class="poup-link mt-3" @click="handleCopyLink(detail.data)"> <view @click="handleCopyLink(detail.data)">
<text <text
class="poup-url rounded-lg bg-secondary px-2 py-1 text-[24rpx] text-[#4d7c0f]">{{ detail.data.spec.url }}</text> class="rounded-lg bg-secondary px-2 py-1 text-xs text-gray-900">{{ detail.data.spec.url }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="poup-desc mt-5 text-[28rpx] text-gray-600 leading-[1.6]"> <view class="poup-desc mt-4 text-[28rpx] text-gray-600 leading-[1.6]">
博客简介{{ detail.data.spec.description || '这个博主很懒,没写简介~' }} {{ detail.data.spec.description || '这个博主很懒,没写简介~' }}
</view> </view>
<image class="poup-img mt-6 h-[320rpx] w-full rounded-xl" <image class="poup-img mt-4 h-[320rpx] w-full rounded-xl"
:src="calcSiteThumbnail(detail.data.spec.url)" mode="aspectFill" /> :src="calcSiteThumbnail(detail.data.spec.url)" mode="aspectFill" />
</view> </scroll-view>
</wd-popup> </uh-glass-popup>
<view class="load-text py-5 text-center text-[24rpx] text-gray-400"> <view class="load-text py-5 text-center text-xs text-gray-400">
{{ loadMoreText }} {{ loadMoreText }}
</view> </view>
</view> </view>
@@ -462,39 +451,39 @@
<uh-plugin-unavailable v-if="!miniPluginAvailable" :plugin-id="miniPluginId" :error-text="miniTips" <uh-plugin-unavailable v-if="!miniPluginAvailable" :plugin-id="miniPluginId" :error-text="miniTips"
:checking="miniChecking" @on-refresh="handleMiniPluginRefresh" /> :checking="miniChecking" @on-refresh="handleMiniPluginRefresh" />
<template v-else> <template v-else>
<uh-data-loading v-if="miniLoadingStatus !== 'success'" :loading-status="miniLoadingStatus" empty-text="还没有收录的小程序呢~" <uh-data-loading v-if="miniLoadingStatus !== 'success'" :loading-status="miniLoadingStatus"
@refresh="handleGetMiniProgramLinks" /> empty-text="还没有收录的小程序呢~" @refresh="handleGetMiniProgramLinks" />
<view v-else class="content flex flex-1 flex-col"> <view v-else class="content flex flex-1 flex-col">
<!-- 分组列表 --> <!-- 分组列表 -->
<view class="mini-link-list flex-1 px-6 py-4"> <view class="box-border flex-1 p-3">
<view v-for="group in miniGroups" :key="group.groupName || 'ungrouped'" class="group-item mb-8"> <view v-for="group in miniGroups" :key="group.groupName || 'ungrouped'" class="group-item mb-8">
<view class="group-title mb-4 flex items-center"> <view class="mb-4 flex items-center">
<text class="mr-2 inline-block h-[28rpx] w-[8rpx] rounded-full bg-secondary" /> <text class="mr-2 inline-block h-[28rpx] w-[8rpx] rounded-full bg-secondary" />
<text <text
class="text-[30rpx] text-gray-900 font-bold">{{ group.displayName || '未分组' }}</text> class="text-[30rpx] text-gray-900 font-bold">{{ group.displayName || '未分组' }}</text>
<text class="ml-3 text-[24rpx] text-gray-400">{{ group.links.length }}</text> <text class="ml-3 text-xs text-gray-400">{{ group.links.length }}</text>
</view> </view>
<view class="group-cards flex flex-col gap-4"> <view class="group-cards flex flex-col gap-4">
<view v-for="link in group.links" :key="link.metadata?.name" <view v-for="link in group.links" :key="link.metadata?.name"
class="mini-card uh-global-card-glass flex items-center rounded-2xl p-4" class="uh-global-card-glass box-border uh-shadow-xs flex items-center rounded-2xl p-3"
@click="handleOnMiniLinkEvent(link)"> @click="handleOnMiniLinkEvent(link)">
<image class="mini-code h-[120rpx] w-[120rpx] shrink-0 rounded-lg" <image class="h-16 w-16 shrink-0 rounded-lg"
:src="checkImageUrl(link.spec?.miniProgramCode)" mode="aspectFill" /> :src="checkImageUrl(link.spec?.miniProgramCode)" mode="aspectFill" />
<view class="mini-info flex flex-1 flex-col pl-5"> <view class="box-border flex flex-1 flex-col pl-4">
<view <view
class="mini-name overflow-hidden text-ellipsis whitespace-nowrap text-[30rpx] text-gray-900 font-bold"> class="overflow-hidden truncate whitespace-nowrap text-[30rpx] text-gray-900 font-bold">
{{ link.spec?.displayName }} {{ link.spec?.displayName }}
</view> </view>
<view v-if="link.spec?.authorName" <view v-if="link.spec?.authorName"
class="mini-author mt-1 overflow-hidden text-ellipsis whitespace-nowrap text-[24rpx] text-gray-400"> class="mini-author mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400">
{{ link.spec.authorName }} {{ link.spec.authorName }}
</view> </view>
<view <view
class="mini-desc mt-1 overflow-hidden text-ellipsis whitespace-nowrap text-[24rpx] text-gray-500"> class="mini-desc mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-500">
{{ link.spec?.description || '暂无简介~' }} {{ link.spec?.description || '暂无简介~' }}
</view> </view>
</view> </view>
<wd-icon name="arrow-right" size="16px" color="#c8c2b4" /> <wd-icon name="arrow-right" size="36rpx" class="text-primary" />
</view> </view>
</view> </view>
</view> </view>
@@ -502,7 +491,8 @@
<!-- 申请收录悬浮按钮 --> <!-- 申请收录悬浮按钮 -->
<view class="fixed bottom-10 right-4 z-50"> <view class="fixed bottom-10 right-4 z-50">
<view class="box-border flex flex-col w-10 h-10 items-center justify-center rounded-full bg-gray-900" <view
class="box-border flex flex-col w-10 h-10 items-center justify-center rounded-full bg-gray-900"
@click="handleOpenApply"> @click="handleOpenApply">
<text class="text-xs text-white">申请</text> <text class="text-xs text-white">申请</text>
</view> </view>
@@ -510,15 +500,26 @@
</view> </view>
<!-- 小程序详情弹窗 --> <!-- 小程序详情弹窗 -->
<wd-popup v-model="miniDetail.show" position="center" custom-style="width:640rpx;border-radius:12rpx;"> <uh-glass-popup v-model="miniDetail.show" position="center" custom-class="w-[90vw] rounded-xl !border">
<view v-if="miniDetail.data" class="mini-poup p-8"> <view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
<view class="w-full flex flex-col gap-y-1">
<text class="text-md font-bold">小程序详情</text>
</view>
<view
class="absolute right-4 top-4 w-6 h-6 uh-global-card-glass shadow-none border rounded-lg text-center"
@click="miniDetail.show = false">
<wd-icon name="close" size="32rpx" class="text-gray-500"></wd-icon>
</view>
</view>
<scroll-view v-if="miniDetail.data" :scroll-y="true" :show-scrollbar="false"
class="box-border p-4 max-h-[60vh]">
<!-- 太阳码大图(点击预览/长按保存) --> <!-- 太阳码大图(点击预览/长按保存) -->
<view class="code-area flex flex-col items-center"> <view class="code-area flex flex-col items-center">
<image class="code-img h-[320rpx] w-[320rpx] rounded-xl" <image class="code-img h-32 w-32 rounded-xl"
:src="checkImageUrl(miniDetail.data.spec?.miniProgramCode)" mode="aspectFill" :src="checkImageUrl(miniDetail.data.spec?.miniProgramCode)" mode="aspectFill"
@click="handlePreviewMiniProgramCode(miniDetail.data)" @click="handlePreviewMiniProgramCode(miniDetail.data)"
@longpress="handleSaveMiniProgramCode(miniDetail.data)" /> @longpress="handleSaveMiniProgramCode(miniDetail.data)" />
<view class="code-tip mt-3 flex items-center text-[24rpx] text-gray-400"> <view class="code-tip mt-3 flex items-center text-xs text-gray-400">
<wd-icon name="picture" size="14px" color="#a8a294" /> <wd-icon name="picture" size="14px" color="#a8a294" />
<text class="ml-1">点击预览长按保存太阳码</text> <text class="ml-1">点击预览长按保存太阳码</text>
</view> </view>
@@ -551,7 +552,7 @@
<text v-if="miniDetail.data.spec?.authorName" <text v-if="miniDetail.data.spec?.authorName"
class="author-name text-[28rpx] text-gray-900 font-medium">{{ miniDetail.data.spec.authorName }}</text> class="author-name text-[28rpx] text-gray-900 font-medium">{{ miniDetail.data.spec.authorName }}</text>
<text v-if="miniDetail.data.spec?.website" <text v-if="miniDetail.data.spec?.website"
class="author-website mt-1 overflow-hidden text-ellipsis whitespace-nowrap text-[24rpx] text-gray-400" class="author-website mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400"
@click="handleCopyMiniProgramCode(miniDetail.data)"> @click="handleCopyMiniProgramCode(miniDetail.data)">
网站{{ miniDetail.data.spec.website }} 网站{{ miniDetail.data.spec.website }}
</text> </text>
@@ -562,7 +563,7 @@
<view v-if="miniDetail.data.spec?.link" <view v-if="miniDetail.data.spec?.link"
class="mini-link mt-5 flex items-center justify-between rounded-xl bg-secondary p-4"> class="mini-link mt-5 flex items-center justify-between rounded-xl bg-secondary p-4">
<view <view
class="link-text flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-[26rpx] text-[#4d7c0f]"> class="link-text flex-1 overflow-hidden truncate whitespace-nowrap text-[26rpx] text-[#4d7c0f]">
{{ miniDetail.data.spec.link }} {{ miniDetail.data.spec.link }}
</view> </view>
<text class="ml-3 shrink-0 text-[26rpx] text-[#4d7c0f] font-bold" <text class="ml-3 shrink-0 text-[26rpx] text-[#4d7c0f] font-bold"
@@ -579,8 +580,8 @@
</swiper-item> </swiper-item>
</swiper> </swiper>
</view> </view>
</view> </scroll-view>
</wd-popup> </uh-glass-popup>
<!-- 小程序链接申请弹窗 --> <!-- 小程序链接申请弹窗 -->
<uh-links-mini-apply :show="applyShow" @on-close="handleApplyClose" /> <uh-links-mini-apply :show="applyShow" @on-close="handleApplyClose" />
+41 -110
View File
@@ -1,10 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
/**
* 瞬间详情页(源自旧项目 pagesA/moment-detail,新建复刻)
* 展示瞬间内容(mp-html) + 图片/音频/视频附件
* 设计:日记页(大字日期刊头 + 宽松留白),与列表页的信息流形成两种性格
* 试点:useDataLoading 状态机接管加载四态
*/
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { onLoad, onPullDownRefresh, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app' import { onLoad, onPullDownRefresh, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
import { getMomentByName, submitUpvote } from '@/api/halo' import { getMomentByName, submitUpvote } from '@/api/halo'
@@ -24,8 +18,6 @@ definePage({
navigationBarTitleText: '瞬间详情', navigationBarTitleText: '瞬间详情',
enablePullDownRefresh: true, enablePullDownRefresh: true,
navigationStyle: 'custom', navigationStyle: 'custom',
// 下拉/回弹露出的窗口底色对齐页面底色
backgroundColor: '#f6f3ee',
}, },
}) })
@@ -43,7 +35,6 @@ const bloggerInfo = computed(() => {
const calcUseTagRandomColor = computed(() => !!haloConfigs.value.pageConfig?.momentConfig?.useTagRandomColor) const calcUseTagRandomColor = computed(() => !!haloConfigs.value.pageConfig?.momentConfig?.useTagRandomColor)
/** 站点名称(原 startConfig.title 已随启动页下线,改读 appConfig.appInfo.name) */
const siteName = computed(() => { const siteName = computed(() => {
const appInfo = haloConfigs.value.appConfig?.appInfo as { name ?: string } | undefined const appInfo = haloConfigs.value.appConfig?.appInfo as { name ?: string } | undefined
return appInfo?.name || bloggerInfo.value.nickname || 'uni-halo' return appInfo?.name || bloggerInfo.value.nickname || 'uni-halo'
@@ -54,7 +45,6 @@ const queryName = ref('')
const videoContexts = ref<Record<string, UniApp.VideoContext | undefined>>({}) const videoContexts = ref<Record<string, UniApp.VideoContext | undefined>>({})
const currentVideoId = ref<string | null>(null) const currentVideoId = ref<string | null>(null)
/** 瞬间卡片(medium 按类型拆为 images/videos/audios + 正文 tag 清理) */
type MomentCard = IMoment & { type MomentCard = IMoment & {
images ?: { type ?: string, url : string }[] images ?: { type ?: string, url : string }[]
videos ?: { id ?: string, url : string }[] videos ?: { id ?: string, url : string }[]
@@ -68,7 +58,7 @@ function removeTagLinksCompletely(htmlString: string): string {
return htmlString.replace(regex, '') return htmlString.replace(regex, '')
} }
/** 瞬间详情 → 展示卡片(作者兜底 + medium 拆分 + tag 清理) */ /** 瞬间详情 */
function buildMomentCard(res : IMoment) : MomentCard { function buildMomentCard(res : IMoment) : MomentCard {
const medium = (res.spec.content?.medium || []) const medium = (res.spec.content?.medium || [])
.map(x => ({ ...x, url: x.url || '' })) .map(x => ({ ...x, url: x.url || '' }))
@@ -156,8 +146,7 @@ function hasUpvoted(): boolean {
async function handleDoLikes() { async function handleDoLikes() {
const current = moment.value const current = moment.value
if (!current) if (!current) { return }
return
if (hasUpvoted()) { if (hasUpvoted()) {
uni.showToast({ icon: 'none', title: '已经点过赞啦!' }) uni.showToast({ icon: 'none', title: '已经点过赞啦!' })
return return
@@ -284,24 +273,19 @@ onShareTimeline(() => ({
</script> </script>
<template> <template>
<view class="app-page box-border min-h-screen w-screen bg-page px-4 pb-8 pt-4"> <view class="app-page box-border min-h-screen w-screen bg-page pb-safe">
<!-- 自定义导航 --> <!-- 自定义导航 -->
<uh-navbar default-title="瞬间详情" title-color="text-gray-900" /> <uh-navbar default-title="瞬间详情" title-color="text-gray-900" />
<!-- 状态区(加载中/失败可重试/) --> <!-- 状态区(加载中/失败可重试/) -->
<uh-data-loading <uh-data-loading v-if="status !== 'success'" :loading-status="status" min-height="60vh" error-text="瞬间内容加载失败"
v-if="status !== 'success'" empty-text="瞬间不存在或已被删除" @refresh="loadMoment" />
:loading-status="status"
min-height="60vh"
error-text="瞬间内容加载失败"
empty-text="瞬间不存在或已被删除"
@refresh="loadMoment"
/>
<!-- 瞬间内页(日记式:大字日期刊头 + 阅读正文 + 媒体 + 互动脚注) --> <!-- 瞬间内页(日记式:大字日期刊头 + 阅读正文 + 媒体 + 互动脚注) -->
<view v-else-if="moment" class="moment-card uh-shadow-xs overflow-hidden rounded-[28rpx] bg-white"> <view v-else-if="moment" class="box-border px-3 pt-2 pb-12">
<view class="uh-global-card-glass uh-shadow-xs overflow-hidden rounded-2xl box-border pb-4">
<!-- 刊头:大字日期 --> <!-- 刊头:大字日期 -->
<view v-if="moment.spec.releaseTime" class="px-7 pt-7"> <view v-if="moment.spec.releaseTime" class="px-4 pt-4">
<view class="text-[56rpx] text-gray-900 font-bold leading-tight"> <view class="text-[56rpx] text-gray-900 font-bold leading-tight">
{{ calcMastheadDate }} {{ calcMastheadDate }}
</view> </view>
@@ -312,8 +296,9 @@ onShareTimeline(() => ({
</view> </view>
<!-- 作者 --> <!-- 作者 -->
<view class="flex items-center px-7 pt-6"> <view class="flex items-center px-4 pt-6">
<image class="h-[80rpx] w-[80rpx] shrink-0 rounded-full" :src="moment.owner?.avatar || bloggerInfo.avatar" mode="aspectFill" /> <image class="h-[80rpx] w-[80rpx] shrink-0 rounded-full"
:src="checkAvatarUrl(moment.owner?.avatar || bloggerInfo.avatar)" mode="aspectFill" />
<view class="ml-3 flex flex-col"> <view class="ml-3 flex flex-col">
<view class="text-[30rpx] text-[#5c7c0f] font-bold"> <view class="text-[30rpx] text-[#5c7c0f] font-bold">
{{ moment.owner?.displayName || bloggerInfo.nickname }} {{ moment.owner?.displayName || bloggerInfo.nickname }}
@@ -325,124 +310,77 @@ onShareTimeline(() => ({
</view> </view>
<!-- 正文 --> <!-- 正文 -->
<view class="content px-7 pt-5"> <view class="content px-4 mt-6">
<mp-html <mp-html lazy-load :domain="markdownConfig.domain ?? ''"
class="evan-markdown" :loading-img="markdownConfig.loadingGif" scroll-table selectable
lazy-load :tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
:domain="markdownConfig.domain ?? ''" :content="moment.spec.newHtml || ''" :markdown="true" :show-line-number="true"
:loading-img="markdownConfig.loadingGif" :show-language-name="true" copy-by-long-press />
scroll-table
selectable
:tag-style="markdownConfig.tagStyle"
:container-style="markdownConfig.containStyle"
:content="moment.spec.newHtml || ''"
:markdown="true"
:show-line-number="true"
:show-language-name="true"
copy-by-long-press
/>
</view> </view>
<!-- 图片附件 --> <!-- 图片附件 -->
<view v-if="moment.images && moment.images.length !== 0" class="px-7 pt-6"> <view v-if="moment.images && moment.images.length !== 0" class="px-3 mt-4">
<view class="images flex flex-wrap items-start"> <view class="images flex flex-wrap items-start">
<view <view v-for="(image, mediumIndex) in moment.images" :key="mediumIndex"
v-for="(image, mediumIndex) in moment.images"
:key="mediumIndex"
class="image-item box-border p-1.5" class="image-item box-border p-1.5"
:class="moment.images && moment.images.length === 1 ? 'h-[350rpx] w-full' : (moment.images && moment.images.length === 2 ? 'h-[250rpx] w-1/2' : 'h-[200rpx] w-1/3')" :class="moment.images && moment.images.length === 1 ? 'h-[350rpx] w-full' : (moment.images && moment.images.length === 2 ? 'h-[250rpx] w-1/2' : 'h-[200rpx] w-1/3')">
> <image mode="aspectFill" class="image-src h-full w-full rounded-lg" :src="image.url"
<image @click="handlePreview(mediumIndex, moment.images || [])" />
mode="aspectFill"
class="image-src h-full w-full rounded-lg"
:src="image.url"
@click="handlePreview(mediumIndex, moment.images || [])"
/>
</view> </view>
</view> </view>
</view> </view>
<!-- 音频附件 --> <!-- 音频附件 -->
<view v-if="moment.audios && moment.audios.length !== 0" class="px-7 pt-6"> <view v-if="moment.audios && moment.audios.length !== 0" class="px-4 mt-4">
<view class="audio-list flex flex-col gap-3"> <view class="audio-list flex flex-col gap-3">
<uh-audio-player <uh-audio-player v-for="audio in moment.audios" :key="audio.url" :src="audio.url"
v-for="audio in moment.audios" :poster="bloggerInfo.avatar" :name="`来自${siteName}的声音`" :author="bloggerInfo.nickname" />
:key="audio.url"
:src="audio.url"
:poster="bloggerInfo.avatar"
:name="`来自${siteName}的声音`"
:author="bloggerInfo.nickname"
/>
</view> </view>
</view> </view>
<!-- 视频附件 --> <!-- 视频附件 -->
<view v-if="moment.videos && moment.videos.length !== 0" class="px-7 pt-6"> <view v-if="moment.videos && moment.videos.length !== 0" class="px-4 mt-4">
<view class="video-list w-full flex flex-col gap-3"> <view class="video-list w-full flex flex-col gap-3">
<video <video v-for="(video, index) in moment.videos" :id="`video_${video.id}`" :key="index"
v-for="(video, index) in moment.videos" class="video-src h-[400rpx] w-full rounded-xl" :src="video.url" :show-mute-btn="true"
:id="`video_${video.id}`" :controls="true" :show-center-play-btn="true" :enable-progress-gesture="true"
:key="index" @play="onVideoPlay(video.id || '')" @pause="onVideoPause(video.id || '')"
class="video-src h-[400rpx] w-full rounded-xl" @ended="onVideoEnded" />
:src="video.url"
:show-mute-btn="true"
:controls="true"
:show-center-play-btn="true"
:enable-progress-gesture="true"
@play="onVideoPlay(video.id || '')"
@pause="onVideoPause(video.id || '')"
@ended="onVideoEnded"
/>
</view> </view>
</view> </view>
<!-- 标签 --> <!-- 标签 -->
<view v-if="moment.spec.tags && moment.spec.tags.length !== 0" class="tags flex flex-wrap gap-2 px-7 pt-6"> <view v-if="moment.spec.tags && moment.spec.tags.length !== 0"
<view v-for="(tag, tagIndex) in moment.spec.tags" :key="tagIndex" class="rounded-full bg-black/5 px-3 py-1 text-[22rpx] font-bold" :style="{ color: calcTagColors[tagIndex] }"> class="tags flex flex-wrap gap-2 px-4 pt-6">
<view v-for="(tag, tagIndex) in moment.spec.tags" :key="tagIndex"
class="rounded-full bg-primary px-3 py-1 text-xs font-bold" >
# {{ tag }} # {{ tag }}
</view> </view>
</view> </view>
<!-- 互动脚注(点赞/评论) -->
<view v-if="moment.stats" class="mx-7 mt-6 flex items-center justify-end gap-5 border-t border-black/5 py-4 text-[24rpx] text-gray-400">
<view class="flex items-center gap-1">
<wd-icon name="heart" size="13px" color="#f08585" />
<text>{{ moment.stats.upvote || 0 }}</text>
</view>
<view class="flex items-center gap-1">
<wd-icon name="message" size="13px" color="#9aa3b2" />
<text>{{ moment.stats.totalComment || 0 }}</text>
</view> </view>
</view> </view>
<view v-else class="h-7" />
</view>
<!-- 悬浮操作(与文章详情一致:点赞/评论/收藏) --> <!-- 悬浮操作(与文章详情一致:点赞/评论/收藏) -->
<view class="fixed bottom-8 left-1/2 z-10 flex items-center justify-center pb-safe -translate-x-1/2"> <view class="fixed bottom-8 left-1/2 z-10 flex items-center justify-center pb-safe -translate-x-1/2">
<view <view
class="uh-global-card-glass box-border flex items-center justify-center gap-2 border rounded-full p-1 text-primary" class="uh-global-card-glass box-border flex items-center justify-center gap-2 border rounded-full p-1 text-primary">
>
<!-- 点赞 --> <!-- 点赞 -->
<view <view
class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none" class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none"
:class="{ active: hasUpvoted() }" @click="handleDoLikes" :class="{ active: hasUpvoted() }" @click="handleDoLikes">
>
<wd-icon class-prefix="uhemoji-icon" name="-kiss-" size="36rpx" /> <wd-icon class-prefix="uhemoji-icon" name="-kiss-" size="36rpx" />
<text class="shrink-0 text-sm text-gray-900 font-semibold">点赞</text> <text class="shrink-0 text-sm text-gray-900 font-semibold">点赞</text>
</view> </view>
<!-- 评论 --> <!-- 评论 -->
<view <view
class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none" class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none"
@click="handleToComment()" @click="handleToComment()">
>
<wd-icon class-prefix="uhemoji-icon" name="-thinking" size="36rpx" /> <wd-icon class-prefix="uhemoji-icon" name="-thinking" size="36rpx" />
<text class="shrink-0 text-sm text-gray-900 font-semibold">评论</text> <text class="shrink-0 text-sm text-gray-900 font-semibold">评论</text>
</view> </view>
<!-- 收藏 --> <!-- 收藏 -->
<view <view
class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none" class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none"
@click="handleToggleMomentFavorite" @click="handleToggleMomentFavorite">
>
<wd-icon class-prefix="uhemoji-icon" name="-smile-" size="36rpx" /> <wd-icon class-prefix="uhemoji-icon" name="-smile-" size="36rpx" />
<text class="shrink-0 text-sm text-gray-900 font-semibold" <text class="shrink-0 text-sm text-gray-900 font-semibold"
:style="momentFavorited ? { color: '#ffb300' } : ''">{{ momentFavorited ? '已收藏' : '收藏' }}</text> :style="momentFavorited ? { color: '#ffb300' } : ''">{{ momentFavorited ? '已收藏' : '收藏' }}</text>
@@ -450,16 +388,9 @@ onShareTimeline(() => ({
</view> </view>
</view> </view>
<!-- 回顶 -->
<view class="to-top-btn uh-shadow-xs fixed bottom-[100rpx] right-6 z-6 h-[72rpx] w-[72rpx] flex items-center justify-center rounded-full bg-white" @click="handleToTopPage()">
<wd-icon name="arrow-up" size="20px" color="#6b7280" />
</view>
<!-- 评论弹窗 --> <!-- 评论弹窗 -->
<uh-comment-modal <uh-comment-modal v-if="commentModal.show" :show="commentModal.show" :is-comment="commentModal.isComment"
v-if="commentModal.show" :show="commentModal.show" :is-comment="commentModal.isComment"
:title="commentModal.title" :post-name="commentModal.postName" subject-kind="Moment" :title="commentModal.title" :post-name="commentModal.postName" subject-kind="Moment"
@on-close="handleOnCommentModalClose" @on-close="handleOnCommentModalClose" />
/>
</view> </view>
</template> </template>