mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-07-27 04:20:43 +08:00
feat(gallery): 实现相册功能对接后端API,替换静态模拟数据
1. 重构相册页面,使用真实的Halo图片插件API获取分组与图片数据 2. 新增图片预览工具函数,支持大图预览 3. 更新滚动加载hook,补充响应式原始数据与加载状态控制 4. 调整相册面板组件参数与样式,适配真实数据展示 5. 修复开发环境接口地址配置
This commit is contained in:
Vendored
+2
-1
@@ -7,4 +7,5 @@ VITE_SHOW_SOURCEMAP=false
|
||||
|
||||
# development mode 后台请求地址
|
||||
# VITE_UNI_HALO_BASEURL='https://www.yijunzhao.cn'
|
||||
VITE_UNI_HALO_BASEURL='https://www.xhhao.com'
|
||||
# VITE_UNI_HALO_BASEURL='https://www.xhhao.com'
|
||||
VITE_UNI_HALO_BASEURL='https://ryanc.cc'
|
||||
|
||||
@@ -18,6 +18,27 @@ export interface IQueryPhotosRequest {
|
||||
size?: number
|
||||
}
|
||||
|
||||
export interface IPhotoGroup {
|
||||
metadata: {
|
||||
creationTimestamp: string
|
||||
generateName: string
|
||||
name: string
|
||||
version: string
|
||||
}
|
||||
spec: {
|
||||
displayName: string
|
||||
priority: number
|
||||
}
|
||||
status: {
|
||||
photoCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPhotoTag {
|
||||
name: string
|
||||
photoCount: number
|
||||
}
|
||||
|
||||
export interface IPhoto {
|
||||
metadata: {
|
||||
creationTimestamp: string
|
||||
@@ -36,12 +57,10 @@ export interface IPhoto {
|
||||
|
||||
/**
|
||||
* 获取图库分组列表
|
||||
* @param params 获取图库分组列表请求参数
|
||||
* @returns 获取图库分组列表响应参数
|
||||
*/
|
||||
export function queryPhotoGroups(params: IQueryPhotoGroupsRequest) {
|
||||
return http.Get<any>(`/apis/api.photo.halo.run/v1alpha1/photogroups`, {
|
||||
params,
|
||||
export function queryPhotoGroups() {
|
||||
return http.Get<Array<IPhotoGroup>>(`/apis/api.photo.halo.run/v1alpha1/photogroups`, {
|
||||
meta: {
|
||||
isHalo: true,
|
||||
},
|
||||
@@ -51,10 +70,11 @@ export function queryPhotoGroups(params: IQueryPhotoGroupsRequest) {
|
||||
/**
|
||||
* 获取图库标签列表 列出所有不重复的标签名称及对应图片数量,支持可选的 name 参数进行大小写不敏感模糊过滤
|
||||
* @param params 获取图库标签列表请求参数
|
||||
* @param params.name 标签名称
|
||||
* @returns 获取图库标签列表响应参数
|
||||
*/
|
||||
export function queryPhotoTags(params: any) {
|
||||
return http.Get<any>(`/apis/api.photo.halo.run/v1alpha1/tags`, {
|
||||
export function queryPhotoTags(params: { name?: string }) {
|
||||
return http.Get<IHaloListResponseBase<IPhotoTag>>(`/apis/api.photo.halo.run/v1alpha1/tags`, {
|
||||
params,
|
||||
meta: {
|
||||
isHalo: true,
|
||||
@@ -70,6 +90,7 @@ export function queryPhotoTags(params: any) {
|
||||
export function queryPhotos(params: IQueryPhotosRequest) {
|
||||
return http.Get<IHaloListResponseBase<IPhoto>>(`/apis/api.photo.halo.run/v1alpha1/photos`, {
|
||||
params,
|
||||
cacheFor: null,
|
||||
meta: {
|
||||
isHalo: true,
|
||||
},
|
||||
|
||||
@@ -3,10 +3,12 @@ import { queryPhotos } from '@/api/halo-plugin/photo'
|
||||
import { completeUrl } from '@/utils/url'
|
||||
import { useHaloScroll } from '@/hooks/useHaloScroll'
|
||||
import { random } from '@/utils/base/random'
|
||||
import { preview } from '@/utils/imageHelper'
|
||||
import type { IPhoto, IQueryPhotosRequest } from '@/api/halo-plugin/photo'
|
||||
|
||||
interface IProps {
|
||||
groupName: string
|
||||
groupMetadataName: string
|
||||
groupDisplayName: string
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
@@ -16,6 +18,7 @@ const emits = defineEmits<{
|
||||
}>()
|
||||
|
||||
const {
|
||||
response, // 响应式的原始数据响应
|
||||
list, // 响应式的数据列表
|
||||
loading, // 是否加载中
|
||||
finished, // 是否已全部加载
|
||||
@@ -27,6 +30,7 @@ const {
|
||||
params: {
|
||||
page: 1,
|
||||
size: 12,
|
||||
group: props.groupMetadataName.trim() ?? undefined,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -36,61 +40,62 @@ function getImageItemClass() {
|
||||
const randomIndex = random(0, imageItemClass.length - 1)
|
||||
return imageItemClass[randomIndex]
|
||||
}
|
||||
|
||||
function handlePreview(item: IPhoto) {
|
||||
preview(completeUrl(item.spec.url), list.value.map(item => completeUrl(item.spec.url)))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 相册列表:根据分组显示 -->
|
||||
<view class="uh-backdrop-blur-xs fixed inset-0 z-110 flex items-center justify-center bg-black/20">
|
||||
<view
|
||||
class="uh-backdrop-blur-xs fixed inset-0 z-110 flex items-center justify-center bg-black/20"
|
||||
@click.stop="emits('close')"
|
||||
>
|
||||
<wd-transition :show="true" :lazy-render="true" :duration="300" name="slide-left">
|
||||
<view class="box-border w-[88vw] rounded-2xl bg-page2 p-4">
|
||||
<view class="box-border w-[88vw] rounded-2xl bg-white p-4" @click.stop>
|
||||
<view class="box-border w-full flex shrink-0 items-center justify-between gap-x-4">
|
||||
<view class="text-gray-900 font-bold">
|
||||
{{ props.groupName }}
|
||||
{{ props.groupDisplayName }}
|
||||
</view>
|
||||
<view class="shrink-0">
|
||||
<wd-icon name="close-circle" size="24px" color="#1A1A1A" @click="emits('close')" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-4 w-full">
|
||||
<view class="mt-2 w-full">
|
||||
<scroll-view
|
||||
v-if="list" class="h-[56vh] w-full" scroll-y :refresher-enabled="true"
|
||||
v-if="list" class="h-[60vh] w-full" scroll-y :refresher-enabled="true"
|
||||
:refresher-triggered="loading" @scrolltolower="loadMore" @refresherrefresh="refresh"
|
||||
>
|
||||
<view class="w-full flex flex-wrap gap-y-4">
|
||||
<view
|
||||
v-for="(item) in list" :key="item.metadata.name"
|
||||
class="box-border w-1/2 overflow-hidden px-2"
|
||||
:class="[getImageItemClass()]"
|
||||
>
|
||||
<view class="grid grid-cols-2 box-border w-full gap-x-3 gap-y-2 overflow-hidden px-1">
|
||||
<view v-for="(item) in list" :key="item.metadata.name" class="box-border w-full">
|
||||
<image
|
||||
class="uh-shadow-xs w-full border-2 border-white rounded-xl border-solid bg-white/80"
|
||||
:src="completeUrl(item.spec.url)"
|
||||
mode="widthFix"
|
||||
class="uh-shadow-xs box-border h-32 w-full border-2 border-white rounded-xl border-solid bg-white/80"
|
||||
:src="completeUrl(item.spec.url)" mode="aspectFill" @click="handlePreview(item)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-else class="h-[56vh] flex items-center justify-center">
|
||||
<view v-else class="h-[60vh] flex items-center justify-center">
|
||||
<wd-empty tip="暂无数据">
|
||||
<template #image>
|
||||
<wd-icon name="empty" color="#1A1A1A" :size="52" />
|
||||
</template>
|
||||
</wd-empty>
|
||||
</view>
|
||||
<view
|
||||
v-if="loading || error || finished"
|
||||
class="mt-4 w-full flex items-center justify-center text-xs text-gray-900"
|
||||
>
|
||||
<view class="mt-4 w-full flex items-center justify-center gap-x-2 text-xs text-gray-900">
|
||||
<view class="text-xs text-gray-900">
|
||||
共 {{ response?.total || 0 }} 张 已加载 {{ list.length }} 张
|
||||
</view>
|
||||
<view v-if="loading || error || finished">
|
||||
<wd-loading v-if="loading" text="加载中..." direction="horizontal" color="#1A1A1A" :size="16" />
|
||||
<view
|
||||
v-else-if="error"
|
||||
class="w-full rounded-lg bg-gray-900 py-1.5 text-center text-xs text-white"
|
||||
>
|
||||
<view v-else-if="error" class="w-full rounded-lg bg-gray-900 py-1.5 text-center text-xs text-white">
|
||||
<wd-icon name="refresh" :size="14" /> 加载失败,点击刷新试试
|
||||
</view>
|
||||
<view v-else-if="finished" class="text-black/60">
|
||||
数据已全部加载,没有更多了
|
||||
<view v-else-if="finished" class="text-xs text-gray-900">
|
||||
数据已全部加载
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -8,6 +8,7 @@ interface UseScrollOptions<T, P extends IHaloListRequestBase = { page: number, s
|
||||
}
|
||||
|
||||
interface UseScrollReturn<T> {
|
||||
response: Ref<IHaloListResponseBase<T>>
|
||||
list: Ref<T[]>
|
||||
loading: Ref<boolean>
|
||||
finished: Ref<boolean>
|
||||
@@ -25,6 +26,7 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const error = ref<any>(null)
|
||||
const isLoadMore = ref(false)
|
||||
|
||||
const loadData = async () => {
|
||||
if (loading.value || finished.value) {
|
||||
@@ -38,17 +40,23 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
||||
response.value = await fetchData(params)
|
||||
finished.value = !response.value?.hasNext
|
||||
list.value.push(...response.value?.items || [])
|
||||
params.page++
|
||||
if (!finished.value) {
|
||||
params.page += 1
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
error.value = err
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
isLoadMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
if (isLoadMore.value) {
|
||||
return
|
||||
}
|
||||
params.page = 1
|
||||
finished.value = false
|
||||
list.value = []
|
||||
@@ -56,6 +64,7 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
||||
}
|
||||
|
||||
const loadMore = async () => {
|
||||
isLoadMore.value = true
|
||||
await loadData()
|
||||
}
|
||||
|
||||
@@ -64,6 +73,7 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
||||
})
|
||||
|
||||
return {
|
||||
response,
|
||||
list,
|
||||
loading,
|
||||
finished,
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
<script lang="ts" setup>
|
||||
import { getPicSumImages } from '@/utils/randomResources'
|
||||
import { queryLinks } from '@/api/halo-plugin/link'
|
||||
|
||||
defineOptions({
|
||||
name: 'Gallery',
|
||||
})
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '相册',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
enablePullDownRefresh: true,
|
||||
},
|
||||
})
|
||||
|
||||
interface GalleryItem {
|
||||
metadataName: string
|
||||
displayName: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface GalleryGroup {
|
||||
metadataName: string
|
||||
displayName: string
|
||||
images: GalleryItem[]
|
||||
}
|
||||
|
||||
const groupBanner = reactive({
|
||||
current: 0,
|
||||
})
|
||||
const galleryStyle = ref('list')
|
||||
const showListPanel = reactive({
|
||||
show: false,
|
||||
metadataName: '',
|
||||
})
|
||||
|
||||
// 相册分组预览
|
||||
const galleryGroups = ref<GalleryGroup[]>([
|
||||
{
|
||||
metadataName: 'photo-group-HGaGs',
|
||||
displayName: '我们的故事',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称3',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称4',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
{
|
||||
metadataName: '履行记录',
|
||||
displayName: '履行记录',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称3',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
{
|
||||
metadataName: '家庭时光',
|
||||
displayName: '家庭时光',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
{
|
||||
metadataName: '其他',
|
||||
displayName: '其他',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
])
|
||||
|
||||
// 相册图片
|
||||
const galleryItems = ref<GalleryItem[]>([
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(400, 400),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称3',
|
||||
url: getPicSumImages(400, 800),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称4',
|
||||
url: getPicSumImages(1920, 1080),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称5',
|
||||
url: getPicSumImages(800, 1920),
|
||||
},
|
||||
])
|
||||
|
||||
const { loading, error, data, run } = useRequest<any, IPaginationParams>(queryLinks, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
|
||||
const { list: links, refresh } = useListData<any>({
|
||||
params: {
|
||||
page: 1,
|
||||
size: 0,
|
||||
},
|
||||
loading: toRef(loading),
|
||||
onPullDownRefresh: async (params, updateData) => {
|
||||
await run({ ...params })
|
||||
updateData({
|
||||
error: error.value,
|
||||
hasNext: data.value?.hasNext,
|
||||
items: data.value?.items ?? [],
|
||||
})
|
||||
},
|
||||
onReachBottom: async (params, updateData) => {
|
||||
await run({ ...params })
|
||||
updateData({
|
||||
error: error.value,
|
||||
hasNext: data.value?.hasNext,
|
||||
items: data.value?.items ?? [],
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function onGroupBannerChange(e: any) {
|
||||
groupBanner.current = e.detail.current
|
||||
}
|
||||
|
||||
function getGroupImageRotateClass(index: number) {
|
||||
if (index === 0) {
|
||||
return 'z-30 border-2 border-solid border-white uh-shadow-card 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'
|
||||
}
|
||||
if (index === 2) {
|
||||
return 'z-10 rotate-6 group-hover:rotate-22 border-2 border-solid border-white uh-shadow-card'
|
||||
}
|
||||
}
|
||||
|
||||
// -- 图片列表模式
|
||||
// 图片列表容器样式
|
||||
function getGalleryListContainerClass(total: number) {
|
||||
if (total === 1) {
|
||||
return 'h-36'
|
||||
}
|
||||
if (total === 2) {
|
||||
return 'grid-cols-2 h-32'
|
||||
}
|
||||
if (total === 3) {
|
||||
return 'grid-cols-2 grid-rows-2 h-36'
|
||||
}
|
||||
if (total === 4) {
|
||||
return 'grid-cols-2 grid-rows-2 h-42'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 图片项样式
|
||||
function getGalleryListItemClass(index: number, total: number) {
|
||||
if (total === 1) {
|
||||
return ''
|
||||
}
|
||||
if (total === 2) {
|
||||
return ''
|
||||
}
|
||||
if (total === 3) {
|
||||
if (index === 0) {
|
||||
return 'row-span-2'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
if (total === 4) {
|
||||
return ''
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function handleShowListPanel(metadataName: string) {
|
||||
showListPanel.metadataName = metadataName
|
||||
showListPanel.show = true
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
refresh()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
onPageScroll(() => { })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen w-full flex flex-col bg-page">
|
||||
<!-- 分组列表:常态显示 -->
|
||||
<view class="box-border w-full px-4 py-4">
|
||||
<!-- 顶部区域固定区域 -->
|
||||
<view class="relative w-full">
|
||||
<swiper
|
||||
autoplay circular class="h-46 w-full overflow-hidden rounded-xl" :current="groupBanner.current"
|
||||
@change="onGroupBannerChange"
|
||||
>
|
||||
<swiper-item v-for="img in galleryGroups[0].images" :key="img.metadataName">
|
||||
<image :src="img.url" class="h-full w-full object-cover" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view
|
||||
class="absolute left-4 top-4 flex flex-col items-center justify-center rounded-2xl bg-orange-500 px-2 py-1.5 text-xs text-white font-bold"
|
||||
>
|
||||
最新精选
|
||||
</view>
|
||||
<view class="absolute bottom-4 left-4 flex flex-col gap-y-1">
|
||||
<view class="text-sm text-white font-bold">
|
||||
显示图片的名称
|
||||
</view>
|
||||
<view class="text-xs text-white/50">
|
||||
显示描述/日期
|
||||
</view>
|
||||
<view class="w-full flex items-center justify-center gap-x-2">
|
||||
<image
|
||||
v-for="(item, index) in galleryGroups[0].images" :key="item.metadataName" :src="item.url"
|
||||
mode="scaleToFill"
|
||||
class="box-border h-8 w-8 border-2 border-white rounded-lg border-solid transition-all duration-300"
|
||||
:class="[
|
||||
{
|
||||
'scale-110': groupBanner.current === index,
|
||||
},
|
||||
index % 2 === 0 ? '-rotate-4' : 'rotate-4',
|
||||
]" @click="groupBanner.current = index"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分组列表区域 -->
|
||||
<view class="mt-6 w-full flex items-center justify-between">
|
||||
<text class="text-sm text-gray-900 font-bold">我的相册</text>
|
||||
<text class="text-[10px] text-gray-600">共 30 组记录</text>
|
||||
</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"
|
||||
>
|
||||
<!-- 预览图区域 -->
|
||||
<view class="relative h-full w-full">
|
||||
<view
|
||||
v-for="(img, index) in group.images.slice(0, 3)" :key="img.metadataName"
|
||||
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="img.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.images.length }} 张图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表卡片模式 -->
|
||||
<view v-else-if="galleryStyle === 'list'" class="mt-2 w-full flex flex-col gap-y-4">
|
||||
<view
|
||||
v-for="group in galleryGroups" :key="group.metadataName"
|
||||
class="box-border flex flex-col gap-y-2 overflow-hidden rounded-xl bg-white p-4 pt-3" @click="handleShowListPanel(group.metadataName)"
|
||||
>
|
||||
<!-- 顶部区域 -->
|
||||
<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.images.length }} 张</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图片列表 -->
|
||||
<view
|
||||
class="grid gap-1 overflow-hidden rounded-lg"
|
||||
:class="[getGalleryListContainerClass(group.images.slice(0, 4).length)]"
|
||||
>
|
||||
<view
|
||||
v-for="(img, index) in group.images.slice(0, 4)" :key="img.metadataName"
|
||||
class="overflow-hidden"
|
||||
:class="getGalleryListItemClass(index, group.images.slice(0, 4).length)"
|
||||
>
|
||||
<image
|
||||
:src="img.url" mode="scaleToFill"
|
||||
class="h-full w-full object-cover transition-transform duration-500 hover:scale-105"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 分组列表:无数据显示 -->
|
||||
<view class="hidden">
|
||||
无数据
|
||||
</view>
|
||||
|
||||
<!-- 相册列表:根据分组显示 -->
|
||||
<uh-gallery-panel v-if="showListPanel.show" :group-name="showListPanel.metadataName" @close="showListPanel.show = false" />
|
||||
|
||||
<!-- 底部导航占位 -->
|
||||
<view class="w-full shrink-0">
|
||||
<uh-tabbar-page-placeholder />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
+127
-169
@@ -1,6 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { getPicSumImages } from '@/utils/randomResources'
|
||||
import { queryLinks } from '@/api/halo-plugin/link'
|
||||
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'
|
||||
|
||||
defineOptions({
|
||||
name: 'Gallery',
|
||||
@@ -14,16 +16,13 @@ definePage({
|
||||
},
|
||||
})
|
||||
|
||||
interface GalleryItem {
|
||||
interface IGalleryGroup {
|
||||
metadataName: string
|
||||
displayName: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface GalleryGroup {
|
||||
metadataName: string
|
||||
displayName: string
|
||||
images: GalleryItem[]
|
||||
creationTimestamp: string
|
||||
photoCount: number
|
||||
priority: number
|
||||
photos: IPhoto[]
|
||||
}
|
||||
|
||||
const groupBanner = reactive({
|
||||
@@ -33,126 +32,13 @@ const galleryStyle = ref('list')
|
||||
const showListPanel = reactive({
|
||||
show: false,
|
||||
metadataName: '',
|
||||
displayName: '',
|
||||
})
|
||||
|
||||
// 相册分组预览
|
||||
const galleryGroups = ref<GalleryGroup[]>([
|
||||
{
|
||||
metadataName: 'photo-group-HGaGs',
|
||||
displayName: '我们的故事',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称3',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称4',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
{
|
||||
metadataName: '履行记录',
|
||||
displayName: '履行记录',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称3',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
{
|
||||
metadataName: '家庭时光',
|
||||
displayName: '家庭时光',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}, {
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
{
|
||||
metadataName: '其他',
|
||||
displayName: '其他',
|
||||
images: [{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(800, 600),
|
||||
}],
|
||||
},
|
||||
])
|
||||
const galleryGroups = ref<IGalleryGroup[]>([])
|
||||
|
||||
// 相册图片
|
||||
const galleryItems = ref<GalleryItem[]>([
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称1',
|
||||
url: getPicSumImages(400, 400),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称2',
|
||||
url: getPicSumImages(800, 600),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称3',
|
||||
url: getPicSumImages(400, 800),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称4',
|
||||
url: getPicSumImages(1920, 1080),
|
||||
},
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '图片名称5',
|
||||
url: getPicSumImages(800, 1920),
|
||||
},
|
||||
])
|
||||
|
||||
const { loading, error, data, run } = useRequest<any, IPaginationParams>(queryLinks, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
|
||||
const { list: links, refresh } = useListData<any>({
|
||||
params: {
|
||||
page: 1,
|
||||
size: 0,
|
||||
},
|
||||
loading: toRef(loading),
|
||||
onPullDownRefresh: async (params, updateData) => {
|
||||
await run({ ...params })
|
||||
updateData({
|
||||
error: error.value,
|
||||
hasNext: data.value?.hasNext,
|
||||
items: data.value?.items ?? [],
|
||||
})
|
||||
},
|
||||
onReachBottom: async (params, updateData) => {
|
||||
await run({ ...params })
|
||||
updateData({
|
||||
error: error.value,
|
||||
hasNext: data.value?.hasNext,
|
||||
items: data.value?.items ?? [],
|
||||
})
|
||||
},
|
||||
})
|
||||
const { loading, error, data, run } = useRequest<Array<IPhotoGroup>>(queryPhotoGroups, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||
|
||||
function onGroupBannerChange(e: any) {
|
||||
groupBanner.current = e.detail.current
|
||||
@@ -208,16 +94,84 @@ function getGalleryListItemClass(index: number, total: number) {
|
||||
return ''
|
||||
}
|
||||
|
||||
function handleShowListPanel(metadataName: string) {
|
||||
showListPanel.metadataName = metadataName
|
||||
function handleShowListPanel(group: IGalleryGroup) {
|
||||
showListPanel.metadataName = group.metadataName
|
||||
showListPanel.displayName = group.displayName
|
||||
showListPanel.show = true
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
refresh()
|
||||
// 查询分组的数据
|
||||
function getGalleryPhotosByGroups() {
|
||||
if (galleryGroups.value.length === 0) {
|
||||
return
|
||||
}
|
||||
// 执行对每个分组查询图片
|
||||
galleryGroups.value.forEach(async (group) => {
|
||||
const queryParams = {
|
||||
group: undefined,
|
||||
fieldSelector: undefined,
|
||||
page: 1,
|
||||
size: 4,
|
||||
}
|
||||
if (group.metadataName.trim()) {
|
||||
queryParams.group = group.metadataName
|
||||
}
|
||||
if (!group.metadataName.trim()) {
|
||||
queryParams.fieldSelector = 'spec.groupName!=""'
|
||||
}
|
||||
const res = await queryPhotos(queryParams)
|
||||
if (res?.items) {
|
||||
group.photos = res.items
|
||||
}
|
||||
if (!group.metadataName.trim()) {
|
||||
group.photoCount = res.total
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => loading.value, () => {
|
||||
if (loading.value || error.value) {
|
||||
return
|
||||
}
|
||||
|
||||
galleryGroups.value = [
|
||||
{
|
||||
metadataName: '',
|
||||
displayName: '未分组',
|
||||
creationTimestamp: '',
|
||||
photoCount: 0,
|
||||
priority: 0,
|
||||
photos: [],
|
||||
},
|
||||
]
|
||||
|
||||
data.value.forEach((item) => {
|
||||
galleryGroups.value.push({
|
||||
metadataName: item.metadata.name,
|
||||
displayName: item.spec.displayName,
|
||||
creationTimestamp: item.metadata.creationTimestamp,
|
||||
photoCount: item.status.photoCount,
|
||||
priority: item.spec.priority,
|
||||
photos: [],
|
||||
})
|
||||
})
|
||||
getGalleryPhotosByGroups()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
async function init() {
|
||||
await run()
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
onPullDownRefresh(async () => {
|
||||
await init()
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
|
||||
onPageScroll(() => { })
|
||||
@@ -226,47 +180,39 @@ onPageScroll(() => { })
|
||||
<template>
|
||||
<view class="min-h-screen w-full flex flex-col bg-page">
|
||||
<!-- 分组列表:常态显示 -->
|
||||
<view class="box-border w-full px-4 py-4">
|
||||
<view v-if="galleryGroups.length !== 0" class="box-border w-full">
|
||||
<!-- 顶部区域固定区域 -->
|
||||
<view class="relative w-full">
|
||||
<view v-if="galleryGroups.length > 1 && galleryGroups[0].photos.length >= 4" class="relative w-full">
|
||||
<swiper
|
||||
autoplay circular class="h-46 w-full overflow-hidden rounded-xl" :current="groupBanner.current"
|
||||
autoplay circular class="h-56 w-full overflow-hidden" :current="groupBanner.current"
|
||||
@change="onGroupBannerChange"
|
||||
>
|
||||
<swiper-item v-for="img in galleryGroups[0].images" :key="img.metadataName">
|
||||
<image :src="img.url" class="h-full w-full object-cover" />
|
||||
<swiper-item v-for="photo in galleryGroups[0].photos.slice(0, 5)" :key="photo.metadata.name">
|
||||
<image :src="completeUrl(photo.spec.url)" class="h-full w-full object-cover" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<!-- 渐变底部遮罩 -->
|
||||
<view
|
||||
class="absolute left-4 top-4 flex flex-col items-center justify-center rounded-2xl bg-orange-500 px-2 py-1.5 text-xs text-white font-bold"
|
||||
>
|
||||
最新精选
|
||||
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 class="absolute bottom-4 left-4 flex flex-col gap-y-1">
|
||||
<view class="text-sm text-white font-bold">
|
||||
显示图片的名称
|
||||
</view>
|
||||
<view class="text-xs text-white/50">
|
||||
显示描述/日期
|
||||
</view>
|
||||
<view class="w-full flex items-center justify-center gap-x-2">
|
||||
<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="(item, index) in galleryGroups[0].images" :key="item.metadataName" :src="item.url"
|
||||
mode="scaleToFill"
|
||||
class="box-border h-8 w-8 border-2 border-white rounded-lg border-solid transition-all duration-300"
|
||||
v-for="(photo, index) in galleryGroups[0].photos.slice(0, 5)" :key="photo.metadata.name"
|
||||
:src="completeUrl(photo.spec.url)" mode="aspectFill"
|
||||
class="box-border h-12 w-12 border-2 border-white rounded-lg border-solid transition-all duration-300"
|
||||
:class="[
|
||||
{
|
||||
'scale-110': groupBanner.current === index,
|
||||
'shadow-md rotate-4': groupBanner.current === index,
|
||||
},
|
||||
index % 2 === 0 ? '-rotate-4' : 'rotate-4',
|
||||
]" @click="groupBanner.current = index"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-4 box-border px-4">
|
||||
<!-- 分组列表区域 -->
|
||||
<view class="mt-6 w-full flex items-center justify-between">
|
||||
<view v-if="false" class="w-full flex items-center justify-between">
|
||||
<text class="text-sm text-gray-900 font-bold">我的相册</text>
|
||||
<text class="text-[10px] text-gray-600">共 30 组记录</text>
|
||||
</view>
|
||||
@@ -279,18 +225,18 @@ onPageScroll(() => { })
|
||||
<!-- 预览图区域 -->
|
||||
<view class="relative h-full w-full">
|
||||
<view
|
||||
v-for="(img, index) in group.images.slice(0, 3)" :key="img.metadataName"
|
||||
v-for="(photo, index) in group.photos.slice(0, 3)" :key="photo.metadata.name"
|
||||
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="img.url" mode="scaleToFill" class="h-full w-full" />
|
||||
<image :src="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.images.length }} 张图片</text>
|
||||
<text class="text-[8px] text-white/80">{{ group.photoCount }} 张图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -299,9 +245,11 @@ onPageScroll(() => { })
|
||||
|
||||
<!-- 列表卡片模式 -->
|
||||
<view v-else-if="galleryStyle === 'list'" class="mt-2 w-full flex flex-col gap-y-4">
|
||||
<template v-for="group in galleryGroups" :key="group.metadataName">
|
||||
<view
|
||||
v-for="group in galleryGroups" :key="group.metadataName"
|
||||
class="box-border flex flex-col gap-y-2 overflow-hidden rounded-xl bg-white p-4 pt-3" @click="handleShowListPanel(group.metadataName)"
|
||||
v-if="group.photoCount > 0"
|
||||
class="box-border flex flex-col gap-y-2 overflow-hidden rounded-xl bg-white p-4 pt-3"
|
||||
@click="handleShowListPanel(group)"
|
||||
>
|
||||
<!-- 顶部区域 -->
|
||||
<view class="flex items-center justify-between">
|
||||
@@ -309,36 +257,46 @@ 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.images.length }} 张</text>
|
||||
<text class="text-[10px] text-gray-400">{{ group.photoCount }} 张</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图片列表 -->
|
||||
<view
|
||||
class="grid gap-1 overflow-hidden rounded-lg"
|
||||
:class="[getGalleryListContainerClass(group.images.slice(0, 4).length)]"
|
||||
:class="[getGalleryListContainerClass(group.photos.slice(0, 4).length)]"
|
||||
>
|
||||
<view
|
||||
v-for="(img, index) in group.images.slice(0, 4)" :key="img.metadataName"
|
||||
class="overflow-hidden"
|
||||
:class="getGalleryListItemClass(index, group.images.slice(0, 4).length)"
|
||||
v-for="(photo, index) in group.photos.slice(0, 4)" :key="photo.metadata.name"
|
||||
class="overflow-hidden" :class="getGalleryListItemClass(index, group.photos.slice(0, 4).length)"
|
||||
>
|
||||
<image
|
||||
:src="img.url" mode="scaleToFill"
|
||||
:src="completeUrl(photo.spec.url)" mode="aspectFill"
|
||||
class="h-full w-full object-cover transition-transform duration-500 hover:scale-105"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 分组列表:无数据显示 -->
|
||||
<view class="hidden">
|
||||
无数据
|
||||
<view v-else class="w-full flex flex-1 items-center justify-center">
|
||||
<wd-empty icon="no-content" tip="暂无数据">
|
||||
<template #image>
|
||||
<wd-icon name="empty" color="#1A1A1A" :size="52" />
|
||||
</template>
|
||||
</wd-empty>
|
||||
</view>
|
||||
|
||||
<!-- 相册列表:根据分组显示 -->
|
||||
<uh-gallery-panel v-if="showListPanel.show" :group-name="showListPanel.metadataName" @close="showListPanel.show = false" />
|
||||
<uh-gallery-panel
|
||||
v-if="showListPanel.show"
|
||||
:group-metadata-name="showListPanel.metadataName"
|
||||
:group-display-name="showListPanel.displayName"
|
||||
@close="showListPanel.show = false"
|
||||
/>
|
||||
|
||||
<!-- 底部导航占位 -->
|
||||
<view class="w-full shrink-0">
|
||||
|
||||
@@ -12,3 +12,21 @@ export function cover(cover: string) {
|
||||
}
|
||||
return getRandomUrl()
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片预览
|
||||
* @param url 图片URL
|
||||
* @param urls 图片URL列表
|
||||
*/
|
||||
export function preview(url: string, urls: string[]) {
|
||||
if (urls.length === 0) {
|
||||
return
|
||||
}
|
||||
uni.previewImage({
|
||||
current: url,
|
||||
urls,
|
||||
showmenu: true,
|
||||
indicator: 'number',
|
||||
loop: true,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user