mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-07-27 12:30:41 +08:00
feat: 完成uni-halo小程序基础功能迭代
本次提交完成多项核心功能升级与优化: 1. 新增页面背景色变量page3并替换部分页面背景样式 2. 新增通用列表分页请求基础接口类型 3. 调整首页默认跳转路径为首页 4. 重构配置结构,新增插件配置模块 5. 新增瞬间、友链、相册相关API接口与类型定义 6. 重构useRequest导出方式,修复hook导出问题 7. 移除部分调试日志代码 8. 新增padStart兼容polyfill工具函数 9. 新增文章卡片多种布局样式支持 10. 优化友链页面UI展示与交互逻辑 11. 新增瞬间页面时间轴展示模式与时间解析工具 12. 新增相册页面多种浏览模式切换功能
This commit is contained in:
+119
-13
@@ -3,6 +3,7 @@ import useRequest from '@/hooks/useRequest'
|
||||
import { queryPhotoGroups, queryPhotos } from '@/api/halo-plugin/photo'
|
||||
import type { IPhoto, IPhotoGroup } from '@/api/halo-plugin/photo'
|
||||
import { completeUrl } from '@/utils/url'
|
||||
import random from '@/utils/base/random'
|
||||
|
||||
defineOptions({
|
||||
name: 'Gallery',
|
||||
@@ -28,7 +29,7 @@ interface IGalleryGroup {
|
||||
const groupBanner = reactive({
|
||||
current: 0,
|
||||
})
|
||||
const galleryStyle = ref('list')
|
||||
const galleryStyle = ref('mix')
|
||||
const showListPanel = reactive({
|
||||
show: false,
|
||||
metadataName: '',
|
||||
@@ -46,7 +47,7 @@ function onGroupBannerChange(e: any) {
|
||||
|
||||
function getGroupImageRotateClass(index: number) {
|
||||
if (index === 0) {
|
||||
return 'z-30 border-2 border-solid border-white uh-shadow-card group-hover:scale-95'
|
||||
return 'z-30 border-2 border-solid border-white uh-shadow-md group-hover:scale-95'
|
||||
}
|
||||
if (index === 1) {
|
||||
return 'z-20 -rotate-6 group-hover:-rotate-22 border-2 border-solid border-white uh-shadow-card'
|
||||
@@ -107,11 +108,12 @@ function getGalleryPhotosByGroups() {
|
||||
}
|
||||
// 执行对每个分组查询图片
|
||||
galleryGroups.value.forEach(async (group) => {
|
||||
const randomSize = random(1, 4)
|
||||
const queryParams = {
|
||||
group: undefined,
|
||||
fieldSelector: undefined,
|
||||
page: 1,
|
||||
size: 4,
|
||||
size: randomSize,
|
||||
}
|
||||
if (group.metadataName.trim()) {
|
||||
queryParams.group = group.metadataName
|
||||
@@ -178,7 +180,28 @@ onPageScroll(() => { })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen w-full flex flex-col bg-page">
|
||||
<view class="bg-page3 min-h-screen w-full flex flex-col">
|
||||
<!-- 测试 -->
|
||||
<view class="box-border w-full p-4 pb-0">
|
||||
<view class="uh-shadow-card box-border w-full flex items-center gap-x-4 rounded-xl bg-white p-4 text-sm">
|
||||
<view class="shrink-0">
|
||||
{{ galleryStyle }}:
|
||||
</view>
|
||||
<view class="box-border shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="galleryStyle = 'card'">
|
||||
卡片
|
||||
</view>
|
||||
<view class="box-border shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="galleryStyle = 'list'">
|
||||
列表
|
||||
</view>
|
||||
<view class="box-border shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="galleryStyle = 'mix'">
|
||||
混排
|
||||
</view>
|
||||
<view class="box-border shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="galleryStyle = 'timeline'">
|
||||
时间轴
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分组列表:常态显示 -->
|
||||
<view v-if="galleryGroups.length !== 0" class="box-border w-full">
|
||||
<!-- 顶部区域固定区域 -->
|
||||
@@ -196,7 +219,10 @@ onPageScroll(() => { })
|
||||
class="absolute bottom-0 left-0 right-0 z-10 h-2/5 from-transparent via-page/30 to-page bg-gradient-to-b"
|
||||
/>
|
||||
</view>
|
||||
<view v-if="galleryGroups.length > 1 && galleryGroups[0].photos.length >= 4" class="relative z-10 box-border w-full flex items-center justify-center gap-x-2 overflow-hidden p-4 -mt-12">
|
||||
<view
|
||||
v-if="galleryGroups.length > 1 && galleryGroups[0].photos.length >= 4"
|
||||
class="relative z-10 box-border w-full flex items-center justify-center gap-x-2 overflow-hidden p-4 -mt-12"
|
||||
>
|
||||
<view class="uh-shadow-card box-border w-full flex items-center justify-center gap-x-4 rounded-xl bg-white p-4">
|
||||
<image
|
||||
v-for="(photo, index) in galleryGroups[0].photos.slice(0, 5)" :key="photo.metadata.name"
|
||||
@@ -216,11 +242,40 @@ onPageScroll(() => { })
|
||||
<text class="text-sm text-gray-900 font-bold">我的相册</text>
|
||||
<text class="text-[10px] text-gray-600">共 30 组记录</text>
|
||||
</view>
|
||||
|
||||
<!-- 卡片模式 -->
|
||||
<view v-if="galleryStyle === 'card'" class="grid grid-cols-2 mt-4 w-full gap-4">
|
||||
<view
|
||||
v-for="group in galleryGroups" :key="group.metadataName"
|
||||
class="group relative box-border h-36 w-full flex items-center justify-center p-1"
|
||||
@click="handleShowListPanel(group)"
|
||||
>
|
||||
<!-- 预览图区域 -->
|
||||
<view class="relative h-full w-full">
|
||||
<view
|
||||
v-for="(photo, index) in group.photos.slice(0, 1)" :key="photo.metadata.name"
|
||||
class="uh-shadow-md relative left-0 top-0 z-30 box-border h-full w-full origin-center overflow-hidden border-2 border-white rounded-2xl border-solid"
|
||||
>
|
||||
<image :src="completeUrl(photo.spec.url)" mode="scaleToFill" class="h-full w-full transition-all duration-300 group-hover:scale-110" />
|
||||
<!-- 文字区域 -->
|
||||
<view
|
||||
v-if="index === 0"
|
||||
class="absolute bottom-0 left-0 right-0 z-40 w-full flex flex-col gap-y-0.5 from-transparent to-black/90 bg-gradient-to-b px-2 py-1.5"
|
||||
>
|
||||
<text class="text-xs text-white font-bold">{{ group.displayName }}</text>
|
||||
<text class="text-xs text-white/80">{{ group.photoCount }} 张图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 混合模式 -->
|
||||
<view v-if="galleryStyle === 'mix'" class="grid grid-cols-2 mt-4 w-full gap-4">
|
||||
<view
|
||||
v-for="group in galleryGroups" :key="group.metadataName"
|
||||
class="group relative box-border h-36 w-full flex items-center justify-center p-1"
|
||||
@click="handleShowListPanel(group)"
|
||||
>
|
||||
<!-- 预览图区域 -->
|
||||
<view class="relative h-full w-full">
|
||||
@@ -229,14 +284,14 @@ onPageScroll(() => { })
|
||||
class="absolute left-0 top-0 box-border h-full w-full origin-center overflow-hidden rounded-2xl transition-all duration-300"
|
||||
:class="[getGroupImageRotateClass(index)]"
|
||||
>
|
||||
<image :src="photo.spec.url" mode="scaleToFill" class="h-full w-full" />
|
||||
<image :src="completeUrl(photo.spec.url)" mode="scaleToFill" class="h-full w-full" />
|
||||
<!-- 文字区域 -->
|
||||
<view
|
||||
v-if="index === 0"
|
||||
class="absolute bottom-0 left-0 right-0 z-40 w-full flex flex-col gap-y-0.5 from-transparent to-black/90 bg-gradient-to-b px-2 py-1.5"
|
||||
>
|
||||
<text class="text-xs text-white font-bold">{{ group.displayName }}</text>
|
||||
<text class="text-[8px] text-white/80">{{ group.photoCount }} 张图片</text>
|
||||
<text class="text-xs text-white/80">{{ group.photoCount }} 张图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -248,7 +303,7 @@ onPageScroll(() => { })
|
||||
<template v-for="group in galleryGroups" :key="group.metadataName">
|
||||
<view
|
||||
v-if="group.photoCount > 0"
|
||||
class="box-border flex flex-col gap-y-2 overflow-hidden rounded-xl bg-white p-4 pt-3"
|
||||
class="uh-shadow-card box-border flex flex-col gap-y-2 overflow-hidden rounded-xl bg-white p-4 pt-3"
|
||||
@click="handleShowListPanel(group)"
|
||||
>
|
||||
<!-- 顶部区域 -->
|
||||
@@ -257,7 +312,7 @@ onPageScroll(() => { })
|
||||
<view class="hidden h-2 w-2 rounded-full bg-gray-900" /> {{ group.displayName }}
|
||||
</view>
|
||||
<view class="shrink-0">
|
||||
<text class="text-[10px] text-gray-400">{{ group.photoCount }} 张</text>
|
||||
<text class="text-xs text-gray-400">{{ group.photoCount }} 张</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -279,6 +334,59 @@ onPageScroll(() => { })
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 时间轴卡片模式 -->
|
||||
<view v-else-if="galleryStyle === 'timeline'" class="mt-2 w-full flex flex-col gap-y-4">
|
||||
<template v-for="(group, groupIndex) in galleryGroups" :key="group.metadataName">
|
||||
<view v-if="group.photoCount > 0" class="flex">
|
||||
<!-- 左边区域 -->
|
||||
<view class="relative h-auto w-4 flex shrink-0 items-center justify-center">
|
||||
<view
|
||||
class="uh-shadow-md h-full w-px bg-gray-300" :class="{
|
||||
'mt-6 h-[110%]!': groupIndex !== galleryGroups.length - 1,
|
||||
}"
|
||||
/>
|
||||
<view
|
||||
class="uh-shadow-md absolute left-1/2 top-0 box-border h-4 w-4 border-2 border-white rounded-full border-solid bg-gray-900"
|
||||
:style="{
|
||||
transform: 'translateX(-50%)',
|
||||
}"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="box-border flex flex-1 flex-col gap-y-2 overflow-hidden pl-4"
|
||||
@click="handleShowListPanel(group)"
|
||||
>
|
||||
<!-- 顶部区域 -->
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex items-center gap-x-2 text-sm font-bold">
|
||||
<view class="hidden h-2 w-2 rounded-full bg-gray-900" /> {{ group.displayName }}
|
||||
</view>
|
||||
<view class="shrink-0">
|
||||
<text class="text-[10px] text-gray-400">{{ group.photoCount }} 张</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图片列表 -->
|
||||
<view
|
||||
class="uh-shadow-card grid gap-0.5 overflow-hidden rounded-lg bg-white p-4"
|
||||
:class="[getGalleryListContainerClass(group.photos.slice(0, 4).length)]"
|
||||
>
|
||||
<view
|
||||
v-for="(photo, index) in group.photos.slice(0, 4)" :key="photo.metadata.name"
|
||||
class="overflow-hidden border-2 border-white rounded-lg border-solid"
|
||||
:class="getGalleryListItemClass(index, group.photos.slice(0, 4).length)"
|
||||
>
|
||||
<image
|
||||
:src="completeUrl(photo.spec.url)" mode="aspectFill"
|
||||
class="h-full w-full object-cover transition-transform duration-500 hover:scale-105"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 分组列表:无数据显示 -->
|
||||
@@ -292,10 +400,8 @@ onPageScroll(() => { })
|
||||
|
||||
<!-- 相册列表:根据分组显示 -->
|
||||
<uh-gallery-panel
|
||||
v-if="showListPanel.show"
|
||||
:group-metadata-name="showListPanel.metadataName"
|
||||
:group-display-name="showListPanel.displayName"
|
||||
@close="showListPanel.show = false"
|
||||
v-if="showListPanel.show" :group-metadata-name="showListPanel.metadataName"
|
||||
:group-display-name="showListPanel.displayName" @close="showListPanel.show = false"
|
||||
/>
|
||||
|
||||
<!-- 底部导航占位 -->
|
||||
|
||||
+30
-4
@@ -23,6 +23,11 @@ definePage({
|
||||
},
|
||||
})
|
||||
|
||||
type PostCardStyle = 'noCover' | 'leftCover' | 'bottomCover' | 'rightCover' | 'topCover'
|
||||
const postCardStyle = ref<PostCardStyle>('leftCover')
|
||||
|
||||
const showPostCardModeSelector = ref(false)
|
||||
|
||||
const exampleImageUrls = reactive({
|
||||
banner: 'https://fastly.picsum.photos/id/353/800/800.jpg?hmac=RaDuQ92sSXj4q5vOYna9G00J7KrBUC3eBS0slCWYZXA',
|
||||
avatar: 'https://www.xiaoxiaomo.cn/logo.jpg',
|
||||
@@ -122,7 +127,7 @@ onPageScroll(() => { })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen bg-page2">
|
||||
<view class="bg-page3 min-h-screen">
|
||||
<!-- 顶部banner -->
|
||||
<view v-if="false" class="relative h-72 w-full">
|
||||
<image class="h-72 w-full object-cover" :src="exampleImageUrls.banner" />
|
||||
@@ -164,7 +169,28 @@ onPageScroll(() => { })
|
||||
|
||||
<!-- 作者卡片 -->
|
||||
<view class="relative z-10 box-border px-4 -mt-12">
|
||||
<profile-card />
|
||||
<profile-card @click="showPostCardModeSelector = !showPostCardModeSelector" />
|
||||
</view>
|
||||
|
||||
<!-- 测试 -->
|
||||
<view v-if="showPostCardModeSelector" class="box-border w-full p-4 pb-0">
|
||||
<view class="uh-shadow-card box-border w-full flex items-center gap-x-2 rounded-xl bg-white p-4 text-sm">
|
||||
<view class="box-border flex-1 shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="postCardStyle = 'noCover'">
|
||||
无封面
|
||||
</view>
|
||||
<view class="box-border flex-1 shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="postCardStyle = 'leftCover'">
|
||||
左封面
|
||||
</view>
|
||||
<view class="box-border flex-1 shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="postCardStyle = 'topCover'">
|
||||
上封面
|
||||
</view>
|
||||
<view class="box-border flex-1 shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="postCardStyle = 'rightCover'">
|
||||
右封面
|
||||
</view>
|
||||
<view class="box-border flex-1 shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="postCardStyle = 'bottomCover'">
|
||||
下封面
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 金刚区 -->
|
||||
@@ -172,7 +198,7 @@ onPageScroll(() => { })
|
||||
<view class="flex items-center justify-around gap-x-4">
|
||||
<view
|
||||
v-for="item in quickList" :key="item.pagePath"
|
||||
class="w-1/5 flex flex-col items-center justify-center gap-y-1"
|
||||
class="flex flex-1 flex-col items-center justify-center gap-y-1"
|
||||
>
|
||||
<view class="uh-shadow-xs flex items-center justify-center rounded-2xl bg-white p-3.5">
|
||||
<wd-icon :name="item.icon" :size="24" />
|
||||
@@ -211,7 +237,7 @@ onPageScroll(() => { })
|
||||
<wd-empty :icon-size="60" icon="no-result" tip="暂无数据" />
|
||||
</view>
|
||||
<template v-else>
|
||||
<post-card v-for="post in postList" :key="post.metadata.name" :post="post" type="topCover" />
|
||||
<post-card v-for="post in postList" :key="post.metadata.name" :post="post" :type="postCardStyle" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -14,8 +14,9 @@ definePage({
|
||||
onLoad(() => {
|
||||
console.log('index onLoad')
|
||||
// 默认跳转首页
|
||||
// uni.switchTab({ url: '/pages/home/home' })
|
||||
uni.switchTab({ url: '/pages/gallery/gallery' })
|
||||
uni.switchTab({ url: '/pages/home/home' })
|
||||
// uni.switchTab({ url: '/pages/links/links' })
|
||||
// uni.switchTab({ url: '/pages/moments/moments' })
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
+97
-26
@@ -1,13 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import useRequest from '@/hooks/useRequest'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRequest } from '@/hooks/useRequest'
|
||||
import { random } from '@/utils/base/random'
|
||||
import { queryLinkGroups, queryLinks } from '@/api/halo-plugin/link'
|
||||
import { cover } from '@/utils/imageHelper'
|
||||
import { copy } from '@/utils/base/clipboard'
|
||||
import { getRect } from '@/utils/base/getRect'
|
||||
import { useListData } from '@/hooks/useListData'
|
||||
import type { IPaginationParams } from '@/hooks/useListData'
|
||||
import { debounce } from '@/utils/base/debounce'
|
||||
import { useUniHaloConfigStore } from '@/store/config'
|
||||
import type { IPaginationParams } from '@/hooks/useListData'
|
||||
import type { ILink, ILinkGroup, LinkTypes } from '@/api/halo-plugin/link'
|
||||
import type { IHaloListResponseBase } from '@/api/types/halo'
|
||||
|
||||
defineOptions({
|
||||
name: 'Links',
|
||||
@@ -21,10 +26,13 @@ definePage({
|
||||
},
|
||||
})
|
||||
|
||||
const { loading: loadingGroups, error: errorGroups, data: dataGroups, run: runGroups } = useRequest<any>(queryLinkGroups, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
const { loading, error, data, run } = useRequest<any, IPaginationParams>(queryLinks, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
const { config } = storeToRefs(useUniHaloConfigStore())
|
||||
const { reset } = useUniHaloConfigStore()
|
||||
|
||||
const { list: links, refresh } = useListData<any>({
|
||||
const { loading: loadingGroups, error: errorGroups, data: dataGroups, run: runGroups } = useRequest<ILinkGroup[]>(queryLinkGroups, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
const { loading, error, data, run } = useRequest<IHaloListResponseBase<ILink>, IPaginationParams>(queryLinks, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
|
||||
const { list: links, refresh } = useListData<ILink>({
|
||||
params: {
|
||||
page: 1,
|
||||
size: 0,
|
||||
@@ -53,12 +61,20 @@ interface ILinkGroupData {
|
||||
name: string
|
||||
index: string
|
||||
priority: number
|
||||
links: any[]
|
||||
links: ILink[]
|
||||
}
|
||||
|
||||
const linkTypes: LinkTypes[] = ['mpweixin', 'website', 'android', 'ios']
|
||||
|
||||
const linkGroups = computed(() => {
|
||||
const groupedLinks = links.value.reduce((acc, link) => {
|
||||
const groupName = link.spec.groupName
|
||||
|
||||
// 处理站点类型
|
||||
link.uh = {
|
||||
type: linkTypes[random(0, linkTypes.length - 1)],
|
||||
}
|
||||
|
||||
const group = dataGroups.value?.find(item => item.metadata.name === groupName)
|
||||
let _groupName = '未分组'
|
||||
if (group) {
|
||||
@@ -74,7 +90,7 @@ const linkGroups = computed(() => {
|
||||
}
|
||||
acc[_groupName].links.push(toRaw(link))
|
||||
return acc
|
||||
}, {} as Record<string, any[]>)
|
||||
}, {} as Record<string, ILinkGroupData>)
|
||||
// // 分组还需要根据 分组中的 spec.priority 字段进行排序
|
||||
return Object.values(groupedLinks)
|
||||
.map((item) => {
|
||||
@@ -149,39 +165,54 @@ onLoad(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen w-full flex flex-col bg-page2">
|
||||
<!-- <view>
|
||||
<view>分组数据</view>
|
||||
<view>{{ dataGroups }}</view>
|
||||
</view> -->
|
||||
|
||||
<view class="bg-page3 min-h-screen w-full flex flex-col">
|
||||
<!-- 分组列表 -->
|
||||
<view v-if="linkGroups.length !== 0" class="box-border w-full flex flex-col gap-y-4 overflow-hidden p-4 pb-0">
|
||||
<template v-for="(item, index) in linkGroups" :key="item.name">
|
||||
<view v-if="linkGroups.length !== 0" class="box-border w-full flex flex-col gap-y-6 overflow-hidden p-4 pb-0">
|
||||
<view v-for="(item, index) in linkGroups" :key="item.name" class="flex flex-col gap-y-4">
|
||||
<view
|
||||
v-if="linkGroups.length !== 1" class="flex items-center justify-between text-sm"
|
||||
:class="[`group-start-${index}`]"
|
||||
>
|
||||
<text class="font-bold">{{ item.name }}</text>
|
||||
<view class="flex items-center gap-x-1 font-bold">
|
||||
<wd-icon name="folder" :size="16" />
|
||||
<text class="font-bold">{{ item.name }}</text>
|
||||
</view>
|
||||
<text class="text-xs text-gray-600"> {{ item.links.length }} 条记录 </text>
|
||||
</view>
|
||||
|
||||
<!-- 分组卡片 -->
|
||||
<view
|
||||
v-for="(link, linkIndex) in item.links" :key="link.metadata.name"
|
||||
class="uh-shadow-card box-border w-full flex flex-col items-center justify-center gap-y-2 overflow-hidden border-2 border-white rounded-xl border-solid bg-white p-4"
|
||||
class="uh-shadow-card relative box-border w-full flex flex-col items-center justify-center gap-y-2 overflow-hidden rounded-xl bg-white p-4"
|
||||
:class="[
|
||||
linkIndex === item.links.length - 1 ? `group-end-${index}` : '',
|
||||
]"
|
||||
]" :style="{
|
||||
boxShadow: '0 16rpx 60rpx rgba(0, 0, 0, 0.06)',
|
||||
}"
|
||||
>
|
||||
<!-- 右上角装饰 -->
|
||||
<view
|
||||
class="uh-blur-3xl absolute right-0 top-0 h-12 w-12 rounded-full" :class="[
|
||||
{
|
||||
'bg-[#10B981]/30': link.uh.type === 'mpweixin',
|
||||
'bg-[#007AFF]/30': link.uh.type === 'website',
|
||||
'bg-[#FF9800]/30': link.uh.type === 'android',
|
||||
'bg-[#FF5722]/30': link.uh.type === 'ios',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
|
||||
<!-- 顶部信息 -->
|
||||
<view class="box-border w-full flex items-center justify-between gap-x-3">
|
||||
<view
|
||||
class="uh-shadow-md box-border h-10 w-10 shrink-0 overflow-hidden border-2 border-white rounded-xl border-solid bg-white"
|
||||
class="uh-shadow-md box-border h-11 w-11 shrink-0 overflow-hidden border-2 border-white rounded-lg border-solid bg-white"
|
||||
:style="{
|
||||
boxShadow: '0 16rpx 40rpx rgba(0, 0, 0, 0.055)',
|
||||
}"
|
||||
>
|
||||
<image :src="cover(link.spec.logo)" mode="aspectFill" class="h-full w-full" />
|
||||
</view>
|
||||
<view class="flex flex-1 flex-col gap-y-0.5">
|
||||
<view class="box-border flex flex-1 flex-col gap-y-0.5 pl-1">
|
||||
<view class="line-clamp-1 text-sm text-gray-900 font-bold">
|
||||
{{ link.spec.displayName }}
|
||||
</view>
|
||||
@@ -189,22 +220,58 @@ onLoad(async () => {
|
||||
{{ link.spec.url }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="shrink-0">
|
||||
<view v-if="false" class="shrink-0">
|
||||
<view
|
||||
class="uh-shadow-md rounded-3xl bg-[#1A1A1A] px-4 py-1.5 text-xs text-white"
|
||||
class="uh-shadow-md rounded-xl bg-gray-900 px-4 py-1.5 text-xs text-white"
|
||||
@click="copy(link.spec.url, true)"
|
||||
>
|
||||
复制
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部信息 -->
|
||||
<view class="line-clamp-2 w-full text-xs text-gray-500 leading-relaxed">
|
||||
<!-- 站点描述 -->
|
||||
<view v-if="link.spec.description" class="line-clamp-2 mt-1 w-full text-xs text-gray-500 leading-relaxed">
|
||||
{{ link.spec.description }}
|
||||
<!-- {{ '你好,我是 UNI HALO v3.0 新版本,开源免费 Halo 博客小程序,支持多端编译。' }} -->
|
||||
</view>
|
||||
<!-- 底部操作区域 -->
|
||||
<view class="w-full flex items-center justify-between">
|
||||
<view class="flex shrink-0 items-center gap-x-2">
|
||||
<view
|
||||
class="box-border flex items-center justify-center border border-gray-200 rounded-lg border-solid p-2 text-gray-600"
|
||||
>
|
||||
<wd-icon name="share-alt" :size="14" />
|
||||
</view>
|
||||
<view
|
||||
class="box-border flex items-center justify-center border border-gray-200 rounded-lg border-solid p-2 text-gray-600"
|
||||
>
|
||||
<wd-icon name="copy" :size="14" />
|
||||
</view>
|
||||
<view
|
||||
v-if="false"
|
||||
class="box-border flex items-center justify-center border border-gray-200 rounded-lg border-solid p-2 py-1.5 text-xs text-gray-600"
|
||||
>
|
||||
网站
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-2 flex shrink-0 items-center justify-end gap-x-2">
|
||||
<button
|
||||
v-if="config.base.isIndividual"
|
||||
class="uh-shadow-md rounded-lg bg-gray-900 px-4 py-2 text-xs text-white outline-0 border-none!"
|
||||
@click="copy(link.spec.url, true)"
|
||||
>
|
||||
<wd-icon name="copy" :size="14" /> 复制
|
||||
</button>
|
||||
<button
|
||||
v-if="!config.base.isIndividual"
|
||||
class="uh-shadow-md rounded-lg bg-gray-900 px-4 py-2 text-xs text-white outline-0 border-none!"
|
||||
>
|
||||
<wd-icon name="Launch" :size="14" /> 访问
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="w-full flex flex-1 items-center justify-center">
|
||||
<wd-empty icon="no-content" tip="暂无友链">
|
||||
@@ -216,7 +283,7 @@ onLoad(async () => {
|
||||
|
||||
<!-- 导航 -->
|
||||
<view
|
||||
v-if="!useBottomGroupNav && allGroupRects.length !== 0" class="fixed right-2 top-1/2 z-50" :style="{
|
||||
v-if="!useBottomGroupNav && allGroupRects.length !== 0" class="fixed right-2 top-1/2 z-50 hidden!" :style="{
|
||||
transform: 'translateY(-50%)',
|
||||
}"
|
||||
>
|
||||
@@ -278,4 +345,8 @@ onLoad(async () => {
|
||||
.uh-shadow-card {
|
||||
box-shadow: 0 16rpx 60rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.uh-blur-3xl {
|
||||
filter: blur(64px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -93,7 +93,7 @@ function radioChange(evt) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="box-border min-h-screen w-full flex flex-col items-center justify-center gap-y-6 bg-page2 px-4">
|
||||
<view class="bg-page3 box-border min-h-screen w-full flex flex-col items-center justify-center gap-y-6 px-4">
|
||||
<view class="mt-3 break-all px-3 text-center text-green-500">
|
||||
{{ userInfo.username ? '已登录' : '未登录' }}
|
||||
</view>
|
||||
|
||||
+106
-59
@@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { cover } from '@/utils/imageHelper'
|
||||
import { copy } from '@/utils/base/clipboard'
|
||||
import { queryMoments } from '@/api/halo-plugin/moment'
|
||||
import { timeFrom } from '@/utils/base/timeFrom'
|
||||
import { timeFormat } from '@/utils/base/timeFormat'
|
||||
import { parseDateTime } from '@/utils/base/time'
|
||||
import type { IMoment, IMomentListRequest } from '@/api/halo-plugin/moment'
|
||||
import type { IHaloListResponseBase } from '@/api/types/halo'
|
||||
|
||||
defineOptions({
|
||||
name: 'Moments',
|
||||
@@ -17,9 +17,18 @@ definePage({
|
||||
},
|
||||
})
|
||||
|
||||
const { loading, error, data, run } = useRequest<any, IPaginationParams>(queryMoments, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
const useTimeline = ref(true)
|
||||
|
||||
const { list: moments, refresh } = useListData<any>({
|
||||
const { loading, error, data, run } = useRequest<IHaloListResponseBase<IMoment>, IMomentListRequest>(queryMoments, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
|
||||
function formatDataItems(items: IMoment[]) {
|
||||
return items.map((item) => {
|
||||
item.spec._parseReleaseTime = parseDateTime(item.spec.releaseTime)
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
const { list: moments, refresh } = useListData<IMoment>({
|
||||
params: {
|
||||
page: 1,
|
||||
size: 10,
|
||||
@@ -30,7 +39,7 @@ const { list: moments, refresh } = useListData<any>({
|
||||
updateData({
|
||||
error: error.value,
|
||||
hasNext: data.value?.hasNext,
|
||||
items: data.value?.items ?? [],
|
||||
items: formatDataItems(data.value?.items ?? []),
|
||||
})
|
||||
},
|
||||
onReachBottom: async (params, updateData) => {
|
||||
@@ -38,7 +47,7 @@ const { list: moments, refresh } = useListData<any>({
|
||||
updateData({
|
||||
error: error.value,
|
||||
hasNext: data.value?.hasNext,
|
||||
items: data.value?.items ?? [],
|
||||
items: formatDataItems(data.value?.items ?? []),
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -54,86 +63,115 @@ onPageScroll(() => { })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen w-full flex flex-col bg-page2">
|
||||
<view class="bg-page3 min-h-screen w-full flex flex-col">
|
||||
<!-- 测试 -->
|
||||
<view class="box-border w-full p-4 pb-0">
|
||||
<view class="uh-shadow-card box-border w-full flex items-center gap-x-4 rounded-xl bg-white p-4 text-sm">
|
||||
<view class="shrink-0">
|
||||
模式:【{{ useTimeline }}】
|
||||
</view>
|
||||
<view class="box-border shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="useTimeline = false">
|
||||
纯列表
|
||||
</view>
|
||||
<view class="box-border shrink-0 rounded-lg bg-gray-900 px-2 py-1 text-xs text-white" @click="useTimeline = true">
|
||||
时间轴
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 瞬间列表 -->
|
||||
<view v-if="moments.length !== 0" class="box-border w-full flex flex-col gap-y-4 overflow-hidden p-4 pb-0">
|
||||
<view v-for="moment in moments" :key="moment.metadata.name" class="flex">
|
||||
<view v-for="(moment, index) in moments" :key="moment.metadata.name" class="flex">
|
||||
<!-- 左边区域 -->
|
||||
<view class="relative h-auto w-4 flex shrink-0 items-center justify-center">
|
||||
<view class="uh-shadow-md h-full w-2px bg-white" />
|
||||
<view v-if="useTimeline" class="relative h-auto w-4 flex shrink-0 items-center justify-center">
|
||||
<view
|
||||
class="uh-shadow-md absolute left-1/2 top-0 box-border h-4 w-4 border-2 border-white rounded-full border-solid bg-[#1A1A1A]"
|
||||
class="uh-shadow-md h-full w-px bg-gray-300" :class="{
|
||||
'mt-6 h-[110%]!': index !== moments.length - 1,
|
||||
}"
|
||||
/>
|
||||
<view
|
||||
class="uh-shadow-md absolute left-1/2 top-0 box-border h-4 w-4 border-2 border-white rounded-full border-solid bg-gray-900"
|
||||
:style="{
|
||||
transform: 'translateX(-50%)',
|
||||
}"
|
||||
/>
|
||||
</view>
|
||||
<!-- 瞬间卡片 -->
|
||||
<view class="box-border flex-1 pl-2">
|
||||
<view class="mb-2 text-sm text-gray-900 font-semibold">
|
||||
{{ timeFormat(moment.spec.releaseTime) }}
|
||||
</view>
|
||||
<view
|
||||
class="box-border flex-1"
|
||||
:class="{ 'pl-2': useTimeline }"
|
||||
>
|
||||
<view
|
||||
class="uh-shadow-card box-border w-full flex flex-1 flex-col items-center justify-center gap-y-2 overflow-hidden border-2 border-white rounded-xl border-solid bg-white p-4"
|
||||
class="uh-shadow-card group relative box-border w-full flex flex-1 flex-col items-center justify-center gap-y-2 overflow-hidden border-2 border-white rounded-xl border-solid bg-white p-4"
|
||||
>
|
||||
<!-- 用户信息 -->
|
||||
<view class="box-border w-full flex items-center justify-between gap-x-4">
|
||||
<view class="w-full flex items-center justify-between">
|
||||
<!-- 时间信息 -->
|
||||
<view class="box-border w-full flex flex-col gap-y-1">
|
||||
<view class="text-xs text-gray-400 uppercase">
|
||||
{{ moment.spec._parseReleaseTime.year }}
|
||||
{{ moment.spec._parseReleaseTime.week.en }}
|
||||
</view>
|
||||
<view class="flex items-center gap-x-2 text-sm text-gray-900 font-bold">
|
||||
<text>{{ moment.spec._parseReleaseTime.month }}月{{ moment.spec._parseReleaseTime.day }}日</text>
|
||||
<view class="h-1.5 w-1.5 rounded-full bg-gray-900" />
|
||||
<text> {{ moment.spec._parseReleaseTime.week.zh }} </text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 右侧操作 -->
|
||||
<view
|
||||
class="uh-shadow-md box-border h-8 w-8 shrink-0 overflow-hidden border-2 border-white rounded-xl border-solid bg-white"
|
||||
class="uh-shadow-md shrink-0 rounded-lg bg-[#1A1A1A] px-4 py-1.5 text-xs text-white opacity-0 group-hover:opacity-100!"
|
||||
>
|
||||
<image :src="cover(moment.owner.avatar)" mode="aspectFill" class="h-full w-full" />
|
||||
</view>
|
||||
<view class="flex flex-1 flex-col gap-y-1">
|
||||
<view class="text-sm text-gray-900 font-bold">
|
||||
{{ moment.owner.displayName }}
|
||||
</view>
|
||||
<view v-if="false" class="text-xs text-gray-400">
|
||||
{{ timeFrom(moment.spec.releaseTime) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="shrink-0">
|
||||
<view
|
||||
class="uh-shadow-md rounded-3xl bg-[#1A1A1A] px-4 py-1.5 text-xs text-white"
|
||||
>
|
||||
详情
|
||||
</view>
|
||||
复制
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧年份装饰 -->
|
||||
<view v-if="false" class="absolute right-4 top-1.5">
|
||||
<text class="outline-text text-11 text-gray-900 font-bold opacity-0 transition-all duration-300 group-hover:opacity-100!">
|
||||
{{ moment.spec._parseReleaseTime.year }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 瞬间内容 -->
|
||||
<view class="line-clamp-6 w-full text-xs text-gray-900 leading-relaxed">
|
||||
<view class="line-clamp-6 mt-2 w-full text-sm text-gray-900 leading-5 tracking-wide">
|
||||
<rich-text :nodes="moment.spec.content.raw" />
|
||||
</view>
|
||||
|
||||
<!-- 瞬间附件 -->
|
||||
<view class="w-full">
|
||||
<!-- 如果有附件 -->
|
||||
<view class="mt-2 hidden w-full">
|
||||
<!-- 如果有附件 -->
|
||||
</view>
|
||||
|
||||
<!-- 标签 -->
|
||||
<view v-if="false && moment.spec.tags.length !== 0" class="mt-2 w-full flex items-center gap-x-2">
|
||||
<view v-for="tag in moment.spec.tags" :key="tag" class="text-xs text-gray-600">
|
||||
{{ tag }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<view class="h-1px w-full bg-gray-50" />
|
||||
<view class="mb-1 mt-2 h-1px w-full bg-gray-50" />
|
||||
<!-- 底部信息:标签、时间、点赞数、评论数 -->
|
||||
<view class="w-full flex items-center justify-between gap-x-2 text-xs text-gray-500 leading-relaxed">
|
||||
<template v-if="moment.spec.tags.length !== 0">
|
||||
<view class="shrink-0">
|
||||
<view
|
||||
v-for="tag in moment.spec.tags" :key="tag"
|
||||
class="text-xs text-gray-600"
|
||||
>
|
||||
{{ tag }}
|
||||
</view>
|
||||
<!-- 用户信息 -->
|
||||
<view class="box-border w-full flex items-center justify-between gap-x-2">
|
||||
<view
|
||||
class="uh-shadow-md box-border h-6 w-6 shrink-0 overflow-hidden border-2 border-white rounded-lg border-solid bg-white"
|
||||
>
|
||||
<image :src="cover(moment.owner.avatar)" mode="aspectFill" class="h-full w-full" />
|
||||
</view>
|
||||
<view class="shrink-0 text-xs text-gray-600">
|
||||
·
|
||||
<view class="flex-1 text-sm text-gray-600">
|
||||
{{ moment.owner.displayName }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view class="text-[11px] text-gray-600">
|
||||
点赞 {{ moment.stats.upvote }} 次
|
||||
</view>
|
||||
<view class="shrink-0 text-xs text-gray-600">
|
||||
·
|
||||
</view>
|
||||
<view class="text-[11px] text-gray-600">
|
||||
评论 {{ moment.stats.totalComment }} 次
|
||||
<view class="flex shrink-0 items-center justify-end gap-x-4">
|
||||
<view class="flex items-center gap-x-1 text-sm text-gray-600">
|
||||
<wd-icon name="heart" :size="16" /> {{ moment.stats.upvote }}
|
||||
</view>
|
||||
<view class="flex items-center gap-x-1 text-sm text-gray-600">
|
||||
<wd-icon name="message" :size="16" /> {{ moment.stats.totalComment }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -154,3 +192,12 @@ onPageScroll(() => { })
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.outline-text {
|
||||
color: transparent;
|
||||
/* 填充透明 */
|
||||
-webkit-text-stroke: 2rpx #1a1a1a;
|
||||
/* 描边宽度+颜色 */
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user