mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
9cd21be138
1. 新增友链底部悬浮操作组件uh-links-actions 2. 新增站点友链信息弹窗组件uh-links-site-info 3. 新增站点友链申请表单组件uh-links-site-apply 4. 新增小程序友链信息弹窗组件uh-links-mini-info 5. 优化投票页面:替换提示文案、添加插件检测回调、新增防抖sleep工具、调整样式与交互细节
712 lines
26 KiB
Vue
712 lines
26 KiB
Vue
<script lang="ts" setup>
|
||
import { computed, ref, watch } from 'vue'
|
||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||
import { getFriendLinkGroupList, getFriendLinkList } from '@/api/halo'
|
||
import { getMiniProgramLinkGroupedList } from '@/api/uni-halo'
|
||
import { useAppConfigStore } from '@/store/appConfig'
|
||
import { useSettingStore } from '@/store/setting'
|
||
import { sleep } from '@/utils/common'
|
||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||
import type { ILink, ILinkGroup } from '@/api/types/halo'
|
||
import type { IMiniProgramLink, IMiniProgramLinkGroupVo } from '@/api/types/uni-halo'
|
||
|
||
definePage({
|
||
style: {
|
||
navigationBarTitleText: '友情链接',
|
||
enablePullDownRefresh: true,
|
||
navigationStyle: 'custom',
|
||
},
|
||
})
|
||
|
||
const appConfigStore = useAppConfigStore()
|
||
const settingStore = useSettingStore()
|
||
|
||
const haloPluginConfigs = computed(() => appConfigStore.configs.pluginConfig)
|
||
const globalAppSettings = computed(() => settingStore.settings)
|
||
|
||
/* ---------------- 依赖插件(参考 gallery 对象传参模式) ---------------- */
|
||
/** 站点 tab:PluginLinks */
|
||
const { pluginId: sitePluginId, checking: siteChecking, tips: siteTips, available: sitePluginAvailable, check: checkSitePluginAvailable } = usePluginAvailable({
|
||
pluginId: NeedPluginIds.PluginLinks,
|
||
tips: '啊偶,功能正在维护中...',
|
||
callback: (isAvailable) => {
|
||
if (!isAvailable)
|
||
return
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration: 0,
|
||
})
|
||
handleGetLinkGroupData()
|
||
},
|
||
})
|
||
/** 小程序 tab:plugin-uni-halo */
|
||
const { pluginId: miniPluginId, checking: miniChecking, tips: miniTips, available: miniPluginAvailable, check: checkMiniPluginAvailable } = usePluginAvailable({
|
||
pluginId: NeedPluginIds.PluginUniHalo,
|
||
tips: '啊偶,功能正在维护中...',
|
||
callback: (isAvailable) => {
|
||
if (!isAvailable)
|
||
return
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration: 0,
|
||
})
|
||
handleGetMiniProgramLinks()
|
||
},
|
||
})
|
||
|
||
/** 重新检测站点插件:可用则拉取友链数据(供 uh-plugin-unavailable 刷新按钮) */
|
||
async function handleSitePluginRefresh() {
|
||
if (await checkSitePluginAvailable())
|
||
handleGetLinkGroupData()
|
||
}
|
||
|
||
/** 重新检测小程序插件:可用则拉取小程序链接数据(供 uh-plugin-unavailable 刷新按钮) */
|
||
async function handleMiniPluginRefresh() {
|
||
if (await checkMiniPluginAvailable())
|
||
handleGetMiniProgramLinks()
|
||
}
|
||
|
||
/* ---------------- tabs ---------------- */
|
||
const activeTabIndex = ref(0)
|
||
|
||
/** 顶部 tab 定义(同收藏页胶囊 chip;审核模式下小程序 tab 隐藏) */
|
||
const friendLinkTabs = computed(() => [
|
||
{ key: 'site', label: '站点' },
|
||
...(appConfigStore.auditModeEnabled ? [] : [{ key: 'mini', label: '小程序' }]),
|
||
])
|
||
|
||
function handleOnTabChange(e: { index: number }) {
|
||
activeTabIndex.value = e.index
|
||
}
|
||
|
||
// 审核模式下小程序 tab 隐藏,强制停留在站点 tab
|
||
watch(() => appConfigStore.auditModeEnabled, (enabled) => {
|
||
if (enabled)
|
||
activeTabIndex.value = 0
|
||
})
|
||
|
||
/* ==================== 站点 tab(plugin-links) ==================== */
|
||
/* ---------------- 状态 ---------------- */
|
||
const { loadingStatus: siteLoadingStatus, updateLoadingStatus: updateSiteLoadingStatus } = useDataLoadingStatus()
|
||
const queryParams = ref({ size: 10, page: 1 })
|
||
const detail = ref<{ show: boolean, data: ILink | null }>({ show: false, data: null })
|
||
const hasNext = ref(false)
|
||
const isLoadMore = ref(false)
|
||
const loadMoreText = ref('')
|
||
const linkGroupList = ref<ILinkGroup[]>([])
|
||
const dataList = ref<ILink[]>([])
|
||
|
||
/* ---------------- 数据加载 ---------------- */
|
||
function findLinkGroupDisplayNameByGroupMetadataName(groupName?: string): string {
|
||
if (linkGroupList.value.length === 0)
|
||
return groupName || '未分组'
|
||
const found = linkGroupList.value.find(item => item.metadata.name === groupName)
|
||
return found?.spec.displayName || groupName || '未分组'
|
||
}
|
||
|
||
async function handleGetLinkGroupData() {
|
||
try {
|
||
const res = await getFriendLinkGroupList({ page: 1, size: 0 })
|
||
linkGroupList.value = res.data || []
|
||
handleGetData()
|
||
}
|
||
catch (err) {
|
||
console.error(err)
|
||
updateSiteLoadingStatus(DataLoadingStatusEnum.Error)
|
||
}
|
||
}
|
||
|
||
async function handleGetData() {
|
||
if (!isLoadMore.value) {
|
||
updateSiteLoadingStatus(DataLoadingStatusEnum.Loading)
|
||
}
|
||
loadMoreText.value = ''
|
||
|
||
try {
|
||
const res = await getFriendLinkList({ ...queryParams.value })
|
||
hasNext.value = res.data.hasNext
|
||
// 审核模式:站点链接仅展示选中 LinkGroup 分组内的
|
||
let items = res.data.items
|
||
if (appConfigStore.auditModeEnabled) {
|
||
const auditGroupNames = appConfigStore.auditData.spec?.linkGroups || []
|
||
items = items.filter(item => item.spec.groupName && auditGroupNames.includes(item.spec.groupName))
|
||
}
|
||
const list = items.map(item => ({
|
||
...item,
|
||
spec: {
|
||
...item.spec,
|
||
logo: checkAvatarUrl(item.spec.logo),
|
||
groupName: findLinkGroupDisplayNameByGroupMetadataName(item.spec.groupName),
|
||
},
|
||
}))
|
||
// 分页加载时累加,首次/刷新时覆盖(修复重复数据)
|
||
dataList.value = isLoadMore.value ? dataList.value.concat(list) : list
|
||
await sleep(600)
|
||
updateSiteLoadingStatus(
|
||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||
)
|
||
loadMoreText.value = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
||
}
|
||
catch (err) {
|
||
console.error(err)
|
||
updateSiteLoadingStatus(DataLoadingStatusEnum.Error)
|
||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||
}
|
||
finally {
|
||
uni.stopPullDownRefresh()
|
||
}
|
||
}
|
||
|
||
/** 站点友链按分组聚合(无分组链接归入「未分组」,参考小程序 tab 分组展示) */
|
||
const siteGroups = computed(() => {
|
||
const map = new Map<string, ILink[]>()
|
||
for (const link of dataList.value) {
|
||
const key = link.spec.groupName || '未分组'
|
||
if (!map.has(key))
|
||
map.set(key, [])
|
||
map.get(key)!.push(link)
|
||
}
|
||
return Array.from(map.entries()).map(([groupName, links]) => ({ groupName, links }))
|
||
})
|
||
|
||
/* ---------------- 站点交互 ---------------- */
|
||
function handleOnLinkEvent(link: ILink) {
|
||
detail.value = { show: true, data: link }
|
||
}
|
||
|
||
function handleCopyLink(link: ILink) {
|
||
uni.setClipboardData({
|
||
data: `${link.spec.displayName}:${link.spec.url}`,
|
||
showToast: false,
|
||
success: () => {
|
||
uni.showToast({ icon: 'none', title: '链接复制成功!' })
|
||
},
|
||
fail: () => {
|
||
uni.showToast({ icon: 'none', title: '复制失败!' })
|
||
},
|
||
})
|
||
}
|
||
|
||
/** 站点友链申请弹窗 */
|
||
const siteApplyShow = ref(false)
|
||
/** 站点友链信息弹窗 */
|
||
const infoShow = ref(false)
|
||
|
||
function handleOpenSiteApply() {
|
||
siteApplyShow.value = true
|
||
}
|
||
|
||
function handleSiteApplyClose(data: { isSubmit: boolean, refresh: boolean }) {
|
||
siteApplyShow.value = false
|
||
}
|
||
|
||
function handleOpenInfo() {
|
||
infoShow.value = true
|
||
}
|
||
|
||
function handleInfoClose() {
|
||
infoShow.value = false
|
||
}
|
||
|
||
/** 小程序友链信息弹窗(展示小程序申请提交的信息) */
|
||
const miniInfoShow = ref(false)
|
||
|
||
function handleOpenMiniInfo() {
|
||
miniInfoShow.value = true
|
||
}
|
||
|
||
function handleMiniInfoClose() {
|
||
miniInfoShow.value = false
|
||
}
|
||
|
||
function handleToTopPage(duration = 500) {
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration,
|
||
fail: (err) => {
|
||
console.error('回顶失败', err)
|
||
},
|
||
})
|
||
}
|
||
|
||
/* ==================== 小程序 tab(plugin-uni-halo) ==================== */
|
||
/* ---------------- 状态 ---------------- */
|
||
const { loadingStatus: miniLoadingStatus, updateLoadingStatus: updateMiniLoadingStatus } = useDataLoadingStatus()
|
||
const miniGroups = ref<IMiniProgramLinkGroupVo[]>([])
|
||
const miniDetail = ref<{ show: boolean, data: IMiniProgramLink | null }>({ show: false, data: null })
|
||
const applyShow = ref(false)
|
||
|
||
/* ---------------- 数据加载 ---------------- */
|
||
async function handleGetMiniProgramLinks() {
|
||
updateMiniLoadingStatus(DataLoadingStatusEnum.Loading)
|
||
try {
|
||
const res = await getMiniProgramLinkGroupedList()
|
||
miniGroups.value = res.data || []
|
||
await sleep(600)
|
||
updateMiniLoadingStatus(
|
||
miniGroups.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||
)
|
||
}
|
||
catch (err) {
|
||
console.error(err)
|
||
updateMiniLoadingStatus(DataLoadingStatusEnum.Error)
|
||
}
|
||
finally {
|
||
uni.stopPullDownRefresh()
|
||
}
|
||
}
|
||
|
||
function handleOnMiniLinkEvent(link: IMiniProgramLink) {
|
||
miniDetail.value = { show: true, data: link }
|
||
}
|
||
|
||
function handleOpenApply() {
|
||
applyShow.value = true
|
||
}
|
||
|
||
function handleApplyClose(data: { isSubmit: boolean, refresh: boolean }) {
|
||
applyShow.value = false
|
||
if (data.refresh)
|
||
handleGetMiniProgramLinks()
|
||
}
|
||
|
||
/** 复制小程序地址 */
|
||
function handleCopyMiniProgramCode(link: IMiniProgramLink) {
|
||
const url = link.spec?.link
|
||
if (!url) {
|
||
uni.showToast({ icon: 'none', title: '该小程序未填写跳转地址' })
|
||
return
|
||
}
|
||
uni.setClipboardData({
|
||
data: url,
|
||
showToast: false,
|
||
success: () => {
|
||
uni.showToast({ icon: 'none', title: '地址复制成功!' })
|
||
},
|
||
fail: () => {
|
||
uni.showToast({ icon: 'none', title: '复制失败!' })
|
||
},
|
||
})
|
||
}
|
||
|
||
/** 预览太阳码(支持长按识别/保存) */
|
||
function handlePreviewMiniProgramCode(link: IMiniProgramLink) {
|
||
const code = link.spec?.miniProgramCode
|
||
if (!code)
|
||
return
|
||
uni.previewImage({
|
||
urls: [checkImageUrl(code)],
|
||
current: checkImageUrl(code),
|
||
})
|
||
}
|
||
|
||
/** 保存太阳码到相册 */
|
||
function handleSaveMiniProgramCode(link: IMiniProgramLink) {
|
||
const code = link.spec?.miniProgramCode
|
||
if (!code)
|
||
return
|
||
uni.showLoading({ title: '保存中...' })
|
||
uni.downloadFile({
|
||
url: checkImageUrl(code),
|
||
success: (res) => {
|
||
uni.saveImageToPhotosAlbum({
|
||
filePath: res.tempFilePath,
|
||
success: () => {
|
||
uni.showToast({ icon: 'none', title: '已保存到相册' })
|
||
},
|
||
fail: () => {
|
||
uni.showModal({
|
||
title: '保存失败',
|
||
content: '请检查相册权限后重试',
|
||
showCancel: false,
|
||
})
|
||
},
|
||
complete: () => {
|
||
uni.hideLoading()
|
||
},
|
||
})
|
||
},
|
||
fail: () => {
|
||
uni.hideLoading()
|
||
uni.showToast({ icon: 'none', title: '图片下载失败' })
|
||
},
|
||
})
|
||
}
|
||
|
||
/* ---------------- 生命周期 ---------------- */
|
||
onLoad(async () => {
|
||
await Promise.all([
|
||
checkSitePluginAvailable(),
|
||
checkMiniPluginAvailable(),
|
||
])
|
||
if (sitePluginAvailable.value)
|
||
handleGetLinkGroupData()
|
||
if (miniPluginAvailable.value)
|
||
handleGetMiniProgramLinks()
|
||
if (!sitePluginAvailable.value && !miniPluginAvailable.value)
|
||
uni.stopPullDownRefresh()
|
||
})
|
||
|
||
onPullDownRefresh(() => {
|
||
if (activeTabIndex.value === 0) {
|
||
if (!sitePluginAvailable.value) {
|
||
uni.stopPullDownRefresh()
|
||
return
|
||
}
|
||
isLoadMore.value = false
|
||
queryParams.value.page = 1
|
||
dataList.value = []
|
||
handleGetData()
|
||
}
|
||
else {
|
||
if (!miniPluginAvailable.value) {
|
||
uni.stopPullDownRefresh()
|
||
return
|
||
}
|
||
handleGetMiniProgramLinks()
|
||
}
|
||
})
|
||
|
||
onReachBottom(() => {
|
||
if (activeTabIndex.value === 0) {
|
||
if (!sitePluginAvailable.value)
|
||
return
|
||
if (hasNext.value) {
|
||
queryParams.value.page += 1
|
||
isLoadMore.value = true
|
||
handleGetData()
|
||
}
|
||
else {
|
||
uni.showToast({ icon: 'none', title: '没有更多数据了' })
|
||
}
|
||
}
|
||
else {
|
||
uni.showToast({ icon: 'none', title: '没有更多数据了' })
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
||
<!-- 自定义导航 -->
|
||
<uh-navbar default-title="友情链接" title-color="text-gray-900" />
|
||
|
||
<!-- 顶部 -->
|
||
<wd-sticky>
|
||
<scroll-view scroll-x class="w-full whitespace-nowrap">
|
||
<view class="flex gap-2 px-3 pb-1 pt-3">
|
||
<view
|
||
v-for="(tab, index) in friendLinkTabs" :key="tab.key"
|
||
class="uh-global-card-glass uh-shadow-xs inline-block border rounded-2xl px-5 py-1.5 text-sm"
|
||
:class="activeTabIndex === index ? 'bg-primary font-bold' : 'text-gray-500'"
|
||
@click="handleOnTabChange({ index })"
|
||
>
|
||
{{ tab.label }}
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</wd-sticky>
|
||
|
||
<!-- ==================== 站点 tab ==================== -->
|
||
<template v-if="activeTabIndex === 0">
|
||
<uh-plugin-unavailable
|
||
v-if="!sitePluginAvailable" :plugin-id="sitePluginId" :error-text="siteTips"
|
||
:checking="siteChecking" @on-refresh="handleSitePluginRefresh"
|
||
/>
|
||
<template v-else>
|
||
<view v-if="siteLoadingStatus !== 'success'">
|
||
<uh-data-loading
|
||
:loading-status="siteLoadingStatus" empty-text="啊偶,博主还没有朋友呢~"
|
||
@refresh="handleGetData"
|
||
/>
|
||
</view>
|
||
|
||
<view v-else class="content pt-4">
|
||
<!-- 友链分组列表(参考小程序 tab 分组展示) -->
|
||
<view class="box-border px-4 pb-4">
|
||
<view v-for="group in siteGroups" :key="group.groupName" class="group-item mb-4">
|
||
<view class="mb-3 flex items-center">
|
||
<text class="mr-2 inline-block h-4 w-1 rounded-full bg-secondary" />
|
||
<text class="text-sm text-gray-900 font-bold">{{ group.groupName }}</text>
|
||
<text class="ml-3 text-xs text-gray-400">({{ group.links.length }})</text>
|
||
</view>
|
||
<view class="group-cards flex flex-col gap-3">
|
||
<view
|
||
v-for="link in group.links" :key="link.metadata?.name || link.spec.displayName"
|
||
class="uh-global-card-glass box-border flex overflow-hidden rounded-xl p-3"
|
||
@click="handleOnLinkEvent(link)"
|
||
>
|
||
<image class="h-16 w-16 shrink-0 rounded-lg" :src="link.spec.logo" mode="aspectFill" />
|
||
<view class="box-border flex flex-1 flex-col justify-center overflow-hidden pl-4">
|
||
<view class="flex items-center text-sm text-gray-900 font-bold">
|
||
<text class="flex-1 truncate">{{ link.spec.displayName }}</text>
|
||
</view>
|
||
<view class="mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400">
|
||
站点地址:{{ link.spec.url }}
|
||
</view>
|
||
<view class="mt-2 overflow-hidden truncate whitespace-nowrap text-xs text-gray-600">
|
||
博客简介:{{ link.spec.description || '这个博主很懒,没写简介~' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部悬浮操作栏(通用组件) -->
|
||
<uh-links-actions :actions="['apply', 'info']" @apply="handleOpenSiteApply" @info="handleOpenInfo" />
|
||
|
||
<!-- 详情弹窗 -->
|
||
<uh-glass-popup
|
||
v-model="detail.show" position="bottom" :z-index="999"
|
||
custom-class="rounded-xl !border"
|
||
>
|
||
<view class="relative box-border w-full flex items-center justify-around px-4 pt-4">
|
||
<view class="w-full flex flex-col gap-y-1">
|
||
<text class="text-md font-bold">站点详情</text>
|
||
</view>
|
||
<view
|
||
class="uh-global-card-glass absolute right-4 top-4 h-6 w-6 border rounded-lg text-center shadow-none"
|
||
@click="miniDetail.show = false"
|
||
>
|
||
<wd-icon name="close" size="32rpx" class="text-gray-500" />
|
||
</view>
|
||
</view>
|
||
<scroll-view
|
||
v-if="detail.data" :scroll-y="true" :show-scrollbar="false"
|
||
class="box-border max-h-[60vh] p-4"
|
||
>
|
||
<view class="flex">
|
||
<image
|
||
class="uh-global-card-glass h-20 w-20 shrink-0 rounded-2xl"
|
||
:src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill"
|
||
/>
|
||
<view class="ml-4 flex flex-1 flex-col justify-center gap-y-1.5">
|
||
<view class="text-md text-gray-900 font-bold">
|
||
{{ detail.data.spec.displayName }}
|
||
</view>
|
||
<view class="flex items-center gap-x-2">
|
||
<text
|
||
class="uh-global-card-glass uh-shadow-xs border rounded-lg bg-secondary px-2 py-0.5 text-xs text-gray-500 text-gray-900"
|
||
>
|
||
{{ detail.data.spec.groupName }}
|
||
</text>
|
||
<text
|
||
class="uh-global-card-glass uh-shadow-xs border rounded-lg bg-secondary px-2 py-0.5 text-xs text-gray-500 text-gray-900"
|
||
>
|
||
复制地址
|
||
</text>
|
||
</view>
|
||
<view @click="handleCopyLink(detail.data)">
|
||
<text class="text-xs text-gray-900">{{ detail.data.spec.url }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="poup-desc mt-4 text-xs text-gray-600 leading-5">
|
||
{{ detail.data.spec.description || '这个博主很懒,没写简介~' }}
|
||
</view>
|
||
</scroll-view>
|
||
</uh-glass-popup>
|
||
|
||
<view class="load-text py-5 text-center text-xs text-gray-400">
|
||
{{ loadMoreText }}
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</template>
|
||
|
||
<!-- ==================== 小程序 tab ==================== -->
|
||
<template v-else>
|
||
<uh-plugin-unavailable
|
||
v-if="!miniPluginAvailable" :plugin-id="miniPluginId" :error-text="miniTips"
|
||
:checking="miniChecking" @on-refresh="handleMiniPluginRefresh"
|
||
/>
|
||
<template v-else>
|
||
<uh-data-loading
|
||
v-if="miniLoadingStatus !== 'success'" :loading-status="miniLoadingStatus"
|
||
empty-text="还没有收录的小程序呢~" @refresh="handleGetMiniProgramLinks"
|
||
/>
|
||
<view v-else class="content flex flex-1 flex-col">
|
||
<!-- 分组列表 -->
|
||
<view class="box-border flex-1 p-3">
|
||
<view v-for="group in miniGroups" :key="group.groupName || 'ungrouped'" class="group-item mb-4">
|
||
<view class="mb-3 flex items-center">
|
||
<text class="mr-2 inline-block h-4 w-1 rounded-full bg-secondary" />
|
||
<text
|
||
class="text-sm text-gray-900 font-bold"
|
||
>
|
||
{{ group.displayName || '未分组' }}
|
||
</text>
|
||
<text class="ml-3 text-xs text-gray-400">({{ group.links.length }})</text>
|
||
</view>
|
||
<view class="group-cards flex flex-col gap-4">
|
||
<view
|
||
v-for="link in group.links" :key="link.metadata?.name"
|
||
class="uh-global-card-glass uh-shadow-xs box-border flex items-center rounded-2xl p-3"
|
||
@click="handleOnMiniLinkEvent(link)"
|
||
>
|
||
<image
|
||
class="h-16 w-16 shrink-0 rounded-lg"
|
||
:src="checkImageUrl(link.spec?.miniProgramCode)" mode="aspectFill"
|
||
/>
|
||
<view class="box-border flex flex-1 flex-col pl-4">
|
||
<view
|
||
class="overflow-hidden truncate whitespace-nowrap text-[30rpx] text-gray-900 font-bold"
|
||
>
|
||
{{ link.spec?.displayName }}
|
||
</view>
|
||
<view
|
||
v-if="link.spec?.authorName"
|
||
class="mini-author mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400"
|
||
>
|
||
{{ link.spec.authorName }}
|
||
</view>
|
||
<view
|
||
class="mini-desc mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-500"
|
||
>
|
||
{{ link.spec?.description || '暂无简介~' }}
|
||
</view>
|
||
</view>
|
||
<wd-icon name="arrow-right" size="36rpx" class="text-primary" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部悬浮操作栏(通用组件) -->
|
||
<uh-links-actions :actions="['apply', 'info']" @apply="handleOpenApply" @info="handleOpenMiniInfo" />
|
||
</view>
|
||
|
||
<!-- 小程序详情弹窗 -->
|
||
<uh-glass-popup
|
||
v-model="miniDetail.show" :z-index="999" position="bottom"
|
||
custom-class="!rounded-xl !border"
|
||
>
|
||
<view class="relative box-border w-full flex items-center justify-around px-4 pt-4">
|
||
<view class="w-full flex flex-col gap-y-1">
|
||
<text class="text-md font-bold">小程序详情</text>
|
||
</view>
|
||
<view
|
||
class="uh-global-card-glass absolute right-4 top-4 h-6 w-6 border rounded-lg text-center shadow-none"
|
||
@click="miniDetail.show = false"
|
||
>
|
||
<wd-icon name="close" size="32rpx" class="text-gray-500" />
|
||
</view>
|
||
</view>
|
||
<scroll-view
|
||
v-if="miniDetail.data" :scroll-y="true" :show-scrollbar="false"
|
||
class="box-border max-h-[60vh] p-4"
|
||
>
|
||
<!-- 太阳码大图(点击预览/长按保存) -->
|
||
<view class="code-area flex flex-col items-center">
|
||
<image
|
||
class="code-img h-32 w-32 rounded-xl"
|
||
:src="checkImageUrl(miniDetail.data.spec?.miniProgramCode)" mode="aspectFill"
|
||
@click="handlePreviewMiniProgramCode(miniDetail.data)"
|
||
@longpress="handleSaveMiniProgramCode(miniDetail.data)"
|
||
/>
|
||
<view class="code-tip mt-3 flex items-center text-xs text-gray-400">
|
||
<wd-icon name="picture" size="14px" color="#a8a294" />
|
||
<text class="ml-1">点击预览,长按保存太阳码</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 名称与分组 -->
|
||
<view class="mini-head mt-5 flex items-center">
|
||
<text
|
||
class="mini-name text-[34rpx] text-gray-900 font-bold"
|
||
>
|
||
{{ miniDetail.data.spec?.displayName }}
|
||
</text>
|
||
<text
|
||
v-if="miniDetail.data.spec?.groupName"
|
||
class="group-tag ml-3 rounded-md bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f]"
|
||
>
|
||
{{ miniDetail.data.spec.groupName }}
|
||
</text>
|
||
</view>
|
||
|
||
<!-- 描述 -->
|
||
<view
|
||
v-if="miniDetail.data.spec?.description"
|
||
class="mini-desc mt-4 text-[28rpx] text-gray-600 leading-[1.6]"
|
||
>
|
||
{{ miniDetail.data.spec.description }}
|
||
</view>
|
||
|
||
<!-- 作者信息 -->
|
||
<view
|
||
v-if="miniDetail.data.spec?.authorName || miniDetail.data.spec?.avatar || miniDetail.data.spec?.website"
|
||
class="mini-author-info mt-5 flex items-center rounded-xl bg-[#f6f3ee] p-4"
|
||
>
|
||
<image
|
||
v-if="miniDetail.data.spec?.avatar"
|
||
class="author-avatar h-[72rpx] w-[72rpx] shrink-0 rounded-full"
|
||
:src="checkAvatarUrl(miniDetail.data.spec.avatar)" mode="aspectFill"
|
||
/>
|
||
<view class="author-detail ml-4 flex flex-1 flex-col">
|
||
<text
|
||
v-if="miniDetail.data.spec?.authorName"
|
||
class="author-name text-[28rpx] text-gray-900 font-medium"
|
||
>
|
||
{{ miniDetail.data.spec.authorName }}
|
||
</text>
|
||
<text
|
||
v-if="miniDetail.data.spec?.website"
|
||
class="author-website mt-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400"
|
||
@click="handleCopyMiniProgramCode(miniDetail.data)"
|
||
>
|
||
网站:{{ miniDetail.data.spec.website }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 小程序地址 -->
|
||
<view
|
||
v-if="miniDetail.data.spec?.link"
|
||
class="mini-link mt-5 flex items-center justify-between rounded-xl bg-secondary p-4"
|
||
>
|
||
<view
|
||
class="link-text flex-1 overflow-hidden truncate whitespace-nowrap text-[26rpx] text-[#4d7c0f]"
|
||
>
|
||
{{ miniDetail.data.spec.link }}
|
||
</view>
|
||
<text
|
||
class="ml-3 shrink-0 text-[26rpx] text-[#4d7c0f] font-bold"
|
||
@click="handleCopyMiniProgramCode(miniDetail.data)"
|
||
>
|
||
复制
|
||
</text>
|
||
</view>
|
||
|
||
<!-- 预览图轮播 -->
|
||
<view v-if="miniDetail.data.spec?.screenshots?.length" class="mini-screenshots mt-6">
|
||
<swiper class="screenshots-swiper h-[360rpx] w-full" indicator-dots circular>
|
||
<swiper-item v-for="(img, idx) in miniDetail.data.spec.screenshots" :key="idx">
|
||
<image
|
||
class="screenshot-img h-full w-full rounded-xl" :src="checkImageUrl(img)"
|
||
mode="aspectFill"
|
||
@click="handlePreviewMiniProgramCode({ spec: { miniProgramCode: img } } as IMiniProgramLink)"
|
||
/>
|
||
</swiper-item>
|
||
</swiper>
|
||
</view>
|
||
</scroll-view>
|
||
</uh-glass-popup>
|
||
|
||
<!-- 小程序链接申请弹窗 -->
|
||
<uh-links-mini-apply :show="applyShow" @on-close="handleApplyClose" />
|
||
</template>
|
||
</template>
|
||
|
||
<!-- 站点友链申请弹窗 -->
|
||
<uh-links-site-apply :show="siteApplyShow" @on-close="handleSiteApplyClose" />
|
||
<!-- 站点友链信息弹窗 -->
|
||
<uh-links-site-info :show="infoShow" @on-close="handleInfoClose" />
|
||
<!-- 小程序友链信息弹窗(展示小程序申请提交的信息) -->
|
||
<uh-links-mini-info :show="miniInfoShow" @on-close="handleMiniInfoClose" />
|
||
</view>
|
||
</template>
|