mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-07-27 04:20:43 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21eeb912fd | |||
| a60def91a0 |
Vendored
+2
-1
@@ -7,4 +7,5 @@ VITE_SHOW_SOURCEMAP=false
|
|||||||
|
|
||||||
# development mode 后台请求地址
|
# development mode 后台请求地址
|
||||||
# VITE_UNI_HALO_BASEURL='https://www.yijunzhao.cn'
|
# 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'
|
||||||
|
|||||||
@@ -1,13 +1,52 @@
|
|||||||
import { http } from '@/http/alova'
|
import { http } from '@/http/alova'
|
||||||
|
import type { IHaloListPaginationRequestBase, IHaloListResponseBase } from '@/api/types/halo'
|
||||||
|
|
||||||
|
const EndpointBase = '/apis/api.link.halo.run/v1alpha1'
|
||||||
|
|
||||||
|
export type LinkTypes = 'website' | 'mpweixin' | 'android' | 'ios'
|
||||||
|
|
||||||
|
export interface ILink {
|
||||||
|
metadata: {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
creationTimestamp: string
|
||||||
|
}
|
||||||
|
spec: {
|
||||||
|
displayName: string
|
||||||
|
url: string
|
||||||
|
logo: string
|
||||||
|
groupName: string
|
||||||
|
priority: number
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
// uni halo 独有配置
|
||||||
|
uh: {
|
||||||
|
// 站点类型
|
||||||
|
type: LinkTypes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILinkGroup {
|
||||||
|
metadata: {
|
||||||
|
name: string
|
||||||
|
creationTimestamp: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
spec: {
|
||||||
|
name?: string
|
||||||
|
displayName: string
|
||||||
|
priority: number
|
||||||
|
links: ILink[]
|
||||||
|
}
|
||||||
|
links: ILink[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取链接组分组列表
|
* 获取链接组分组列表
|
||||||
* @param params 获取链接分分组列表请求参数
|
|
||||||
* @returns 获取链接分分组组列表响应参数
|
* @returns 获取链接分分组组列表响应参数
|
||||||
*/
|
*/
|
||||||
export function queryLinkGroups(params: any) {
|
export function queryLinkGroups() {
|
||||||
return http.Get<any>(`/apis/api.link.halo.run/v1alpha1/linkgroups`, {
|
return http.Get<ILinkGroup[]>(`${EndpointBase}/linkgroups`, {
|
||||||
params,
|
|
||||||
meta: { isHalo: true },
|
meta: { isHalo: true },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -17,9 +56,32 @@ export function queryLinkGroups(params: any) {
|
|||||||
* @param params 获取链接列表请求参数
|
* @param params 获取链接列表请求参数
|
||||||
* @returns 获取链接列表响应参数
|
* @returns 获取链接列表响应参数
|
||||||
*/
|
*/
|
||||||
export function queryLinks(params: any) {
|
export function queryLinks(params: IHaloListPaginationRequestBase) {
|
||||||
return http.Get<any>(`/apis/api.link.halo.run/v1alpha1/links`, {
|
return http.Get<IHaloListResponseBase<ILink>>(`${EndpointBase}/links`, {
|
||||||
params,
|
params,
|
||||||
meta: { isHalo: true },
|
meta: { isHalo: true },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机返回一组链接
|
||||||
|
* @param params 随机返回一组链接请求参数
|
||||||
|
* @param params.maxSize 随机返回的链接数量 取值范围为 1 到 100
|
||||||
|
* @returns 获取随机一组链接响应参数
|
||||||
|
*/
|
||||||
|
export function queryRandomLinks(params: { maxSize: number }) {
|
||||||
|
return http.Get<ILink[]>(`${EndpointBase}/-/random`, {
|
||||||
|
params,
|
||||||
|
meta: { isHalo: true },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取链接总数
|
||||||
|
* @returns 获取链接总数响应参数
|
||||||
|
*/
|
||||||
|
export function queryTotalLinks() {
|
||||||
|
return http.Get<number>(`${EndpointBase}/-/total`, {
|
||||||
|
meta: { isHalo: true },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,64 @@
|
|||||||
import { http } from '@/http/alova'
|
import { http } from '@/http/alova'
|
||||||
|
import type { TimeParseResult } from '@/utils/base/time'
|
||||||
|
import type { IHaloListResponseBase } from '@/api/types/halo'
|
||||||
|
|
||||||
const EndpointBase = '/apis/api.moment.halo.run/v1alpha1/moments'
|
const EndpointBase = '/apis/api.moment.halo.run/v1alpha1/moments'
|
||||||
|
|
||||||
|
export interface IMomentListRequest {
|
||||||
|
page: number // 分页页码,从 1 开始
|
||||||
|
size: number // 分页条数
|
||||||
|
tag?: string // 标签
|
||||||
|
ownerName?: string // 创建者用户名 name
|
||||||
|
startDate?: string // 开始时间 通过时间区间筛选发布时间
|
||||||
|
endDate?: string // 结束时间
|
||||||
|
sort?: string[] // 排序字段,格式为 字段名,排序方式,排序方式可选值为 asc 或 desc,如 spec.releaseTime,desc,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MomentMediaType = 'PHOTO' | 'VIDEO' | 'POST' | 'AUDIO'
|
||||||
|
export interface IMoment {
|
||||||
|
metadata: {
|
||||||
|
name: string // 唯一标识
|
||||||
|
creationTimestamp: string // 创建时间
|
||||||
|
}
|
||||||
|
spec: {
|
||||||
|
content: {
|
||||||
|
raw: string // 原始内容,一般用于编辑器使用
|
||||||
|
html: string // HTML 内容,用于主题端进行最终渲染的内容
|
||||||
|
medium: [
|
||||||
|
// 媒体内容
|
||||||
|
{
|
||||||
|
type: MomentMediaType // 类型
|
||||||
|
url: string // 链接
|
||||||
|
originType: string // 原始类型,例如:image/jpeg
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_parseReleaseTime: TimeParseResult // 用于后续存储发布时间的结构化时间对象
|
||||||
|
releaseTime: string // 发布时间
|
||||||
|
visible: 'PUBLIC' | 'PRIVATE' // 可见性
|
||||||
|
owner: string // 所属用户
|
||||||
|
tags: string[] // 所拥有的标签
|
||||||
|
}
|
||||||
|
owner: {
|
||||||
|
name: string // 用户名
|
||||||
|
avatar: string // 头像
|
||||||
|
bio: string // 描述
|
||||||
|
displayName: string // 显示名称
|
||||||
|
}
|
||||||
|
stats: {
|
||||||
|
upvote: number // 点赞数
|
||||||
|
totalComment: number // 评论数
|
||||||
|
approvedComment: number // 审核通过的评论数
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取瞬间列表
|
* 获取瞬间列表
|
||||||
* @param params 获取瞬间列表请求参数
|
* @param params 获取瞬间列表请求参数
|
||||||
* @returns 获取瞬间列表响应参数
|
* @returns 获取瞬间列表响应参数
|
||||||
*/
|
*/
|
||||||
export function queryMoments(params: any) {
|
export function queryMoments(params: IMomentListRequest) {
|
||||||
return http.Get<any>(`${EndpointBase}`, {
|
return http.Get<IHaloListResponseBase<IMoment>>(`${EndpointBase}`, {
|
||||||
params,
|
params,
|
||||||
meta: {
|
meta: {
|
||||||
isHalo: true,
|
isHalo: true,
|
||||||
@@ -22,7 +72,7 @@ export function queryMoments(params: any) {
|
|||||||
* @returns 获取瞬间详情响应参数
|
* @returns 获取瞬间详情响应参数
|
||||||
*/
|
*/
|
||||||
export function queryMomentByName(name: string) {
|
export function queryMomentByName(name: string) {
|
||||||
return http.Get<any>(`${EndpointBase}/${name}`, {
|
return http.Get<IMoment>(`${EndpointBase}/${name}`, {
|
||||||
meta: {
|
meta: {
|
||||||
isHalo: true,
|
isHalo: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,27 @@ export interface IQueryPhotosRequest {
|
|||||||
size?: number
|
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 {
|
export interface IPhoto {
|
||||||
metadata: {
|
metadata: {
|
||||||
creationTimestamp: string
|
creationTimestamp: string
|
||||||
@@ -36,12 +57,10 @@ export interface IPhoto {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取图库分组列表
|
* 获取图库分组列表
|
||||||
* @param params 获取图库分组列表请求参数
|
|
||||||
* @returns 获取图库分组列表响应参数
|
* @returns 获取图库分组列表响应参数
|
||||||
*/
|
*/
|
||||||
export function queryPhotoGroups(params: IQueryPhotoGroupsRequest) {
|
export function queryPhotoGroups() {
|
||||||
return http.Get<any>(`/apis/api.photo.halo.run/v1alpha1/photogroups`, {
|
return http.Get<Array<IPhotoGroup>>(`/apis/api.photo.halo.run/v1alpha1/photogroups`, {
|
||||||
params,
|
|
||||||
meta: {
|
meta: {
|
||||||
isHalo: true,
|
isHalo: true,
|
||||||
},
|
},
|
||||||
@@ -51,10 +70,11 @@ export function queryPhotoGroups(params: IQueryPhotoGroupsRequest) {
|
|||||||
/**
|
/**
|
||||||
* 获取图库标签列表 列出所有不重复的标签名称及对应图片数量,支持可选的 name 参数进行大小写不敏感模糊过滤
|
* 获取图库标签列表 列出所有不重复的标签名称及对应图片数量,支持可选的 name 参数进行大小写不敏感模糊过滤
|
||||||
* @param params 获取图库标签列表请求参数
|
* @param params 获取图库标签列表请求参数
|
||||||
|
* @param params.name 标签名称
|
||||||
* @returns 获取图库标签列表响应参数
|
* @returns 获取图库标签列表响应参数
|
||||||
*/
|
*/
|
||||||
export function queryPhotoTags(params: any) {
|
export function queryPhotoTags(params: { name?: string }) {
|
||||||
return http.Get<any>(`/apis/api.photo.halo.run/v1alpha1/tags`, {
|
return http.Get<IHaloListResponseBase<IPhotoTag>>(`/apis/api.photo.halo.run/v1alpha1/tags`, {
|
||||||
params,
|
params,
|
||||||
meta: {
|
meta: {
|
||||||
isHalo: true,
|
isHalo: true,
|
||||||
@@ -70,6 +90,7 @@ export function queryPhotoTags(params: any) {
|
|||||||
export function queryPhotos(params: IQueryPhotosRequest) {
|
export function queryPhotos(params: IQueryPhotosRequest) {
|
||||||
return http.Get<IHaloListResponseBase<IPhoto>>(`/apis/api.photo.halo.run/v1alpha1/photos`, {
|
return http.Get<IHaloListResponseBase<IPhoto>>(`/apis/api.photo.halo.run/v1alpha1/photos`, {
|
||||||
params,
|
params,
|
||||||
|
cacheFor: null,
|
||||||
meta: {
|
meta: {
|
||||||
isHalo: true,
|
isHalo: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,25 @@
|
|||||||
import { http } from '@/http/alova'
|
import { http } from '@/http/alova'
|
||||||
|
|
||||||
export interface IUniHaloConfig {
|
export interface IUniHaloConfig {
|
||||||
baseURL: string
|
base: {
|
||||||
|
// 博客域名
|
||||||
|
domain: string
|
||||||
|
// 是否为个人资质的小程序
|
||||||
|
isIndividual: boolean
|
||||||
|
}
|
||||||
|
home: {
|
||||||
|
// 是否显示金刚区
|
||||||
|
isShowShortcutDock: boolean
|
||||||
|
}
|
||||||
|
moment: {
|
||||||
|
|
||||||
|
}
|
||||||
|
link: {
|
||||||
|
|
||||||
|
}
|
||||||
|
gallery: {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,3 +21,13 @@ export interface IHaloListRequestBase {
|
|||||||
page?: number
|
page?: number
|
||||||
size?: number
|
size?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Halo 列表分页请求基础结构
|
||||||
|
*/
|
||||||
|
export interface IHaloListPaginationRequestBase extends IHaloListRequestBase {
|
||||||
|
keyword?: string
|
||||||
|
sort?: string
|
||||||
|
labelSelector?: string
|
||||||
|
fieldSelector?: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { queryPhotos } from '@/api/halo-plugin/photo'
|
|||||||
import { completeUrl } from '@/utils/url'
|
import { completeUrl } from '@/utils/url'
|
||||||
import { useHaloScroll } from '@/hooks/useHaloScroll'
|
import { useHaloScroll } from '@/hooks/useHaloScroll'
|
||||||
import { random } from '@/utils/base/random'
|
import { random } from '@/utils/base/random'
|
||||||
|
import { preview } from '@/utils/imageHelper'
|
||||||
import type { IPhoto, IQueryPhotosRequest } from '@/api/halo-plugin/photo'
|
import type { IPhoto, IQueryPhotosRequest } from '@/api/halo-plugin/photo'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
groupName: string
|
groupMetadataName: string
|
||||||
|
groupDisplayName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<IProps>()
|
const props = defineProps<IProps>()
|
||||||
@@ -16,6 +18,7 @@ const emits = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
response, // 响应式的原始数据响应
|
||||||
list, // 响应式的数据列表
|
list, // 响应式的数据列表
|
||||||
loading, // 是否加载中
|
loading, // 是否加载中
|
||||||
finished, // 是否已全部加载
|
finished, // 是否已全部加载
|
||||||
@@ -27,6 +30,7 @@ const {
|
|||||||
params: {
|
params: {
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 12,
|
size: 12,
|
||||||
|
group: props.groupMetadataName.trim() ?? undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -36,61 +40,62 @@ function getImageItemClass() {
|
|||||||
const randomIndex = random(0, imageItemClass.length - 1)
|
const randomIndex = random(0, imageItemClass.length - 1)
|
||||||
return imageItemClass[randomIndex]
|
return imageItemClass[randomIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePreview(item: IPhoto) {
|
||||||
|
preview(completeUrl(item.spec.url), list.value.map(item => completeUrl(item.spec.url)))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<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="box-border w-full flex shrink-0 items-center justify-between gap-x-4">
|
||||||
<view class="text-gray-900 font-bold">
|
<view class="text-gray-900 font-bold">
|
||||||
{{ props.groupName }}
|
{{ props.groupDisplayName }}
|
||||||
</view>
|
</view>
|
||||||
<view class="shrink-0">
|
<view class="shrink-0">
|
||||||
<wd-icon name="close-circle" size="24px" color="#1A1A1A" @click="emits('close')" />
|
<wd-icon name="close-circle" size="24px" color="#1A1A1A" @click="emits('close')" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="mt-4 w-full">
|
<view class="mt-2 w-full">
|
||||||
<scroll-view
|
<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"
|
:refresher-triggered="loading" @scrolltolower="loadMore" @refresherrefresh="refresh"
|
||||||
>
|
>
|
||||||
<view class="w-full flex flex-wrap gap-y-4">
|
<view class="grid grid-cols-2 box-border w-full gap-x-3 gap-y-2 overflow-hidden px-1">
|
||||||
<view
|
<view v-for="(item) in list" :key="item.metadata.name" class="box-border w-full">
|
||||||
v-for="(item) in list" :key="item.metadata.name"
|
|
||||||
class="box-border w-1/2 overflow-hidden px-2"
|
|
||||||
:class="[getImageItemClass()]"
|
|
||||||
>
|
|
||||||
<image
|
<image
|
||||||
class="uh-shadow-xs w-full border-2 border-white rounded-xl border-solid bg-white/80"
|
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)"
|
:src="completeUrl(item.spec.url)" mode="aspectFill" @click="handlePreview(item)"
|
||||||
mode="widthFix"
|
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-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="暂无数据">
|
<wd-empty tip="暂无数据">
|
||||||
<template #image>
|
<template #image>
|
||||||
<wd-icon name="empty" color="#1A1A1A" :size="52" />
|
<wd-icon name="empty" color="#1A1A1A" :size="52" />
|
||||||
</template>
|
</template>
|
||||||
</wd-empty>
|
</wd-empty>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view class="mt-4 w-full flex items-center justify-center gap-x-2 text-xs text-gray-900">
|
||||||
v-if="loading || error || finished"
|
<view class="text-xs text-gray-900">
|
||||||
class="mt-4 w-full flex items-center justify-center text-xs text-gray-900"
|
共 {{ response?.total || 0 }} 张 已加载 {{ list.length }} 张
|
||||||
>
|
|
||||||
<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"
|
|
||||||
>
|
|
||||||
<wd-icon name="refresh" :size="14" /> 加载失败,点击刷新试试
|
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="finished" class="text-black/60">
|
<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">
|
||||||
|
<wd-icon name="refresh" :size="14" /> 加载失败,点击刷新试试
|
||||||
|
</view>
|
||||||
|
<view v-else-if="finished" class="text-xs text-gray-900">
|
||||||
|
数据已全部加载
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import type { ListedPostVo } from '@halo-dev/api-client'
|
|||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
post?: ListedPostVo
|
post?: ListedPostVo
|
||||||
type?: 'default' | 'topCover' | 'rightCover'
|
type?: 'noCover' | 'leftCover' | 'bottomCover' | 'rightCover' | 'topCover'
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<IProps>(), {
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
type: 'default',
|
type: 'leftCover',
|
||||||
post: undefined,
|
post: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -20,6 +20,15 @@ const wrapClass = computed(() => {
|
|||||||
if (props.type === 'rightCover') {
|
if (props.type === 'rightCover') {
|
||||||
return 'relative h-28 p-0! border-2 border-solid border-white'
|
return 'relative h-28 p-0! border-2 border-solid border-white'
|
||||||
}
|
}
|
||||||
|
if (props.type === 'noCover') {
|
||||||
|
return 'pl-0'
|
||||||
|
}
|
||||||
|
if (props.type === 'leftCover') {
|
||||||
|
return ' '
|
||||||
|
}
|
||||||
|
if (props.type === 'bottomCover') {
|
||||||
|
return 'flex-col gap-y-2'
|
||||||
|
}
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -30,7 +39,16 @@ const coverClass = computed(() => {
|
|||||||
if (props.type === 'rightCover') {
|
if (props.type === 'rightCover') {
|
||||||
return 'absolute right-0 top-0 bottom-0 w-32 rounded-lt-0 rounded-lb-0'
|
return 'absolute right-0 top-0 bottom-0 w-32 rounded-lt-0 rounded-lb-0'
|
||||||
}
|
}
|
||||||
return 'h-22 w-22'
|
if (props.type === 'noCover') {
|
||||||
|
return 'hidden!'
|
||||||
|
}
|
||||||
|
if (props.type === 'leftCover') {
|
||||||
|
return 'h-22 w-22'
|
||||||
|
}
|
||||||
|
if (props.type === 'bottomCover') {
|
||||||
|
return 'w-full h-32 order-2'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const textWrapClass = computed(() => {
|
const textWrapClass = computed(() => {
|
||||||
@@ -40,6 +58,10 @@ const textWrapClass = computed(() => {
|
|||||||
if (props.type === 'rightCover') {
|
if (props.type === 'rightCover') {
|
||||||
return 'p-4 pr-32 relative z-10'
|
return 'p-4 pr-32 relative z-10'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.type === 'bottomCover') {
|
||||||
|
return 'order-1'
|
||||||
|
}
|
||||||
return 'pl-4'
|
return 'pl-4'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -47,9 +69,15 @@ const textContainerClass = computed(() => {
|
|||||||
if (props.type === 'topCover') {
|
if (props.type === 'topCover') {
|
||||||
return 'gap-y-2'
|
return 'gap-y-2'
|
||||||
}
|
}
|
||||||
|
if (props.type === 'noCover') {
|
||||||
|
return 'gap-y-2'
|
||||||
|
}
|
||||||
if (props.type === 'rightCover') {
|
if (props.type === 'rightCover') {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
if (props.type === 'bottomCover') {
|
||||||
|
return 'gap-y-2'
|
||||||
|
}
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -60,12 +88,15 @@ const textFooterClass = computed(() => {
|
|||||||
if (props.type === 'rightCover') {
|
if (props.type === 'rightCover') {
|
||||||
return 'gap-x-2'
|
return 'gap-x-2'
|
||||||
}
|
}
|
||||||
|
if (props.type === 'bottomCover') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
return 'gap-x-2'
|
return 'gap-x-2'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view v-if="post" class="uh-shadow-xs flex justify-center overflow-hidden rounded-2xl bg-white p-4" :class="wrapClass">
|
<view v-if="post" class="uh-shadow-xs box-border w-full flex justify-center overflow-hidden rounded-2xl bg-white p-4" :class="wrapClass">
|
||||||
<view class="shrink-0 overflow-hidden rounded-lg" :class="coverClass">
|
<view class="shrink-0 overflow-hidden rounded-lg" :class="coverClass">
|
||||||
<image :src="completeUrl(post.spec.cover)" mode="aspectFill" class="h-full w-full" />
|
<image :src="completeUrl(post.spec.cover)" mode="aspectFill" class="h-full w-full" />
|
||||||
<view v-if="props.type === 'rightCover'" class="absolute bottom-0 left-0 top-0 z-10 h-full w-full from-white/0 via-white/10 to-white bg-gradient-to-l" />
|
<view v-if="props.type === 'rightCover'" class="absolute bottom-0 left-0 top-0 z-10 h-full w-full from-white/0 via-white/10 to-white bg-gradient-to-l" />
|
||||||
@@ -79,7 +110,7 @@ const textFooterClass = computed(() => {
|
|||||||
{{ post.status.excerpt }}
|
{{ post.status.excerpt }}
|
||||||
</view>
|
</view>
|
||||||
<view class="w-full flex items-center" :class="textFooterClass">
|
<view class="w-full flex items-center" :class="textFooterClass">
|
||||||
<view class="text-[11px] text-gray-600">
|
<view class="line-clamp-1 text-[11px] text-gray-600">
|
||||||
{{ timeFrom(post.spec.publishTime) }}
|
{{ timeFrom(post.spec.publishTime) }}
|
||||||
</view>
|
</view>
|
||||||
<view class="shrink-0 text-xs text-gray-600">
|
<view class="shrink-0 text-xs text-gray-600">
|
||||||
@@ -99,7 +130,7 @@ const textFooterClass = computed(() => {
|
|||||||
<view class="shrink-0 text-xs text-gray-600">
|
<view class="shrink-0 text-xs text-gray-600">
|
||||||
·
|
·
|
||||||
</view>
|
</view>
|
||||||
<view class="text-[11px] text-gray-600">
|
<view class="line-clamp-1 text-[11px] text-gray-600">
|
||||||
浏览 {{ post.stats.visit }} 次
|
浏览 {{ post.stats.visit }} 次
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface UseScrollOptions<T, P extends IHaloListRequestBase = { page: number, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface UseScrollReturn<T> {
|
interface UseScrollReturn<T> {
|
||||||
|
response: Ref<IHaloListResponseBase<T>>
|
||||||
list: Ref<T[]>
|
list: Ref<T[]>
|
||||||
loading: Ref<boolean>
|
loading: Ref<boolean>
|
||||||
finished: Ref<boolean>
|
finished: Ref<boolean>
|
||||||
@@ -25,6 +26,7 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const finished = ref(false)
|
const finished = ref(false)
|
||||||
const error = ref<any>(null)
|
const error = ref<any>(null)
|
||||||
|
const isLoadMore = ref(false)
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (loading.value || finished.value) {
|
if (loading.value || finished.value) {
|
||||||
@@ -38,17 +40,23 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
|||||||
response.value = await fetchData(params)
|
response.value = await fetchData(params)
|
||||||
finished.value = !response.value?.hasNext
|
finished.value = !response.value?.hasNext
|
||||||
list.value.push(...response.value?.items || [])
|
list.value.push(...response.value?.items || [])
|
||||||
params.page++
|
if (!finished.value) {
|
||||||
|
params.page += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
error.value = err
|
error.value = err
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
isLoadMore.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
|
if (isLoadMore.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
params.page = 1
|
params.page = 1
|
||||||
finished.value = false
|
finished.value = false
|
||||||
list.value = []
|
list.value = []
|
||||||
@@ -56,6 +64,7 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
|
isLoadMore.value = true
|
||||||
await loadData()
|
await loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +73,7 @@ export function useHaloScroll<T, P extends IHaloListRequestBase = { page: number
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
response,
|
||||||
list,
|
list,
|
||||||
loading,
|
loading,
|
||||||
finished,
|
finished,
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export function useListData<T>(options: IUseListDataOptions<T>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
console.log('刷新数据', options)
|
|
||||||
if (options.loading.value) {
|
if (options.loading.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,7 +67,6 @@ export function useListData<T>(options: IUseListDataOptions<T>) {
|
|||||||
|
|
||||||
/** 下拉刷新 */
|
/** 下拉刷新 */
|
||||||
onPullDownRefresh(() => {
|
onPullDownRefresh(() => {
|
||||||
console.log('下拉刷新', options)
|
|
||||||
refresh()
|
refresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -87,8 +85,6 @@ export function useListData<T>(options: IUseListDataOptions<T>) {
|
|||||||
|
|
||||||
/** 监听items变化 */
|
/** 监听items变化 */
|
||||||
watch(() => _items.value, (newVal) => {
|
watch(() => _items.value, (newVal) => {
|
||||||
console.log('监听数据变化', newVal)
|
|
||||||
console.log('监听数据变化', options)
|
|
||||||
if (_error.value) {
|
if (_error.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ interface IUseRequestReturn<T, P = undefined> {
|
|||||||
* @param options.loadingToastMask 加载提示是否显示mask,默认为false。
|
* @param options.loadingToastMask 加载提示是否显示mask,默认为false。
|
||||||
* @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。
|
* @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。
|
||||||
*/
|
*/
|
||||||
export default function useRequest<T, P = undefined>(
|
export function useRequest<T, P = undefined>(
|
||||||
func: (args?: P) => Promise<T>,
|
func: (args?: P) => Promise<T>,
|
||||||
options: IUseRequestOptions<T> = { immediate: false, loadingToast: false, loadingToastContent: '加载中', loadingToastMask: false },
|
options: IUseRequestOptions<T> = { immediate: false, loadingToast: false, loadingToastContent: '加载中', loadingToastMask: false },
|
||||||
): IUseRequestReturn<T, P> {
|
): IUseRequestReturn<T, P> {
|
||||||
@@ -70,3 +70,5 @@ export default function useRequest<T, P = undefined>(
|
|||||||
}
|
}
|
||||||
return { loading, error, data, run }
|
return { loading, error, data, run }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default useRequest
|
||||||
|
|||||||
@@ -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>
|
||||||
+283
-219
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getPicSumImages } from '@/utils/randomResources'
|
import useRequest from '@/hooks/useRequest'
|
||||||
import { queryLinks } from '@/api/halo-plugin/link'
|
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({
|
defineOptions({
|
||||||
name: 'Gallery',
|
name: 'Gallery',
|
||||||
@@ -14,145 +17,29 @@ definePage({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
interface GalleryItem {
|
interface IGalleryGroup {
|
||||||
metadataName: string
|
metadataName: string
|
||||||
displayName: string
|
displayName: string
|
||||||
url: string
|
creationTimestamp: string
|
||||||
}
|
photoCount: number
|
||||||
|
priority: number
|
||||||
interface GalleryGroup {
|
photos: IPhoto[]
|
||||||
metadataName: string
|
|
||||||
displayName: string
|
|
||||||
images: GalleryItem[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupBanner = reactive({
|
const groupBanner = reactive({
|
||||||
current: 0,
|
current: 0,
|
||||||
})
|
})
|
||||||
const galleryStyle = ref('list')
|
const galleryStyle = ref('mix')
|
||||||
const showListPanel = reactive({
|
const showListPanel = reactive({
|
||||||
show: false,
|
show: false,
|
||||||
metadataName: '',
|
metadataName: '',
|
||||||
|
displayName: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 相册分组预览
|
// 相册分组预览
|
||||||
const galleryGroups = ref<GalleryGroup[]>([
|
const galleryGroups = ref<IGalleryGroup[]>([])
|
||||||
{
|
|
||||||
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 { loading, error, data, run } = useRequest<Array<IPhotoGroup>>(queryPhotoGroups, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
||||||
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) {
|
function onGroupBannerChange(e: any) {
|
||||||
groupBanner.current = e.detail.current
|
groupBanner.current = e.detail.current
|
||||||
@@ -160,7 +47,7 @@ function onGroupBannerChange(e: any) {
|
|||||||
|
|
||||||
function getGroupImageRotateClass(index: number) {
|
function getGroupImageRotateClass(index: number) {
|
||||||
if (index === 0) {
|
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) {
|
if (index === 1) {
|
||||||
return 'z-20 -rotate-6 group-hover:-rotate-22 border-2 border-solid border-white uh-shadow-card'
|
return 'z-20 -rotate-6 group-hover:-rotate-22 border-2 border-solid border-white uh-shadow-card'
|
||||||
@@ -208,137 +95,314 @@ function getGalleryListItemClass(index: number, total: number) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleShowListPanel(metadataName: string) {
|
function handleShowListPanel(group: IGalleryGroup) {
|
||||||
showListPanel.metadataName = metadataName
|
showListPanel.metadataName = group.metadataName
|
||||||
|
showListPanel.displayName = group.displayName
|
||||||
showListPanel.show = true
|
showListPanel.show = true
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(() => {
|
// 查询分组的数据
|
||||||
refresh()
|
function getGalleryPhotosByGroups() {
|
||||||
|
if (galleryGroups.value.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 执行对每个分组查询图片
|
||||||
|
galleryGroups.value.forEach(async (group) => {
|
||||||
|
const randomSize = random(1, 4)
|
||||||
|
const queryParams = {
|
||||||
|
group: undefined,
|
||||||
|
fieldSelector: undefined,
|
||||||
|
page: 1,
|
||||||
|
size: randomSize,
|
||||||
|
}
|
||||||
|
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(() => { })
|
onPageScroll(() => { })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 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
|
<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"
|
@change="onGroupBannerChange"
|
||||||
>
|
>
|
||||||
<swiper-item v-for="img in galleryGroups[0].images" :key="img.metadataName">
|
<swiper-item v-for="photo in galleryGroups[0].photos.slice(0, 5)" :key="photo.metadata.name">
|
||||||
<image :src="img.url" class="h-full w-full object-cover" />
|
<image :src="completeUrl(photo.spec.url)" class="h-full w-full object-cover" />
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
|
<!-- 渐变底部遮罩 -->
|
||||||
<view
|
<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>
|
<view
|
||||||
<view class="absolute bottom-4 left-4 flex flex-col gap-y-1">
|
v-if="galleryGroups.length > 1 && galleryGroups[0].photos.length >= 4"
|
||||||
<view class="text-sm text-white font-bold">
|
class="relative z-10 box-border w-full flex items-center justify-center gap-x-2 overflow-hidden p-4 -mt-12"
|
||||||
显示图片的名称
|
>
|
||||||
</view>
|
<view class="uh-shadow-card box-border w-full flex items-center justify-center gap-x-4 rounded-xl bg-white p-4">
|
||||||
<view class="text-xs text-white/50">
|
<image
|
||||||
显示描述/日期
|
v-for="(photo, index) in galleryGroups[0].photos.slice(0, 5)" :key="photo.metadata.name"
|
||||||
</view>
|
:src="completeUrl(photo.spec.url)" mode="aspectFill"
|
||||||
<view class="w-full flex items-center justify-center gap-x-2">
|
class="box-border h-12 w-12 border-2 border-white rounded-lg border-solid transition-all duration-300"
|
||||||
<image
|
:class="[
|
||||||
v-for="(item, index) in galleryGroups[0].images" :key="item.metadataName" :src="item.url"
|
{
|
||||||
mode="scaleToFill"
|
'shadow-md rotate-4': groupBanner.current === index,
|
||||||
class="box-border h-8 w-8 border-2 border-white rounded-lg border-solid transition-all duration-300"
|
},
|
||||||
:class="[
|
]" @click="groupBanner.current = index"
|
||||||
{
|
/>
|
||||||
'scale-110': groupBanner.current === index,
|
|
||||||
},
|
|
||||||
index % 2 === 0 ? '-rotate-4' : 'rotate-4',
|
|
||||||
]" @click="groupBanner.current = index"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="mt-4 box-border px-4">
|
||||||
|
<!-- 分组列表区域 -->
|
||||||
|
<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>
|
||||||
|
|
||||||
<!-- 分组列表区域 -->
|
<!-- 卡片模式 -->
|
||||||
<view class="mt-6 w-full flex items-center justify-between">
|
<view v-if="galleryStyle === 'card'" class="grid grid-cols-2 mt-4 w-full gap-4">
|
||||||
<text class="text-sm text-gray-900 font-bold">我的相册</text>
|
<view
|
||||||
<text class="text-[10px] text-gray-600">共 30 组记录</text>
|
v-for="group in galleryGroups" :key="group.metadataName"
|
||||||
</view>
|
class="group relative box-border h-36 w-full flex items-center justify-center p-1"
|
||||||
<!-- 混合模式 -->
|
@click="handleShowListPanel(group)"
|
||||||
<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"
|
<view class="relative h-full w-full">
|
||||||
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
|
<view
|
||||||
v-if="index === 0"
|
v-for="(photo, index) in group.photos.slice(0, 1)" :key="photo.metadata.name"
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
<text class="text-xs text-white font-bold">{{ group.displayName }}</text>
|
<image :src="completeUrl(photo.spec.url)" mode="scaleToFill" class="h-full w-full transition-all duration-300 group-hover:scale-110" />
|
||||||
<text class="text-[8px] text-white/80">{{ group.images.length }} 张图片</text>
|
<!-- 文字区域 -->
|
||||||
|
<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>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 列表卡片模式 -->
|
<!-- 混合模式 -->
|
||||||
<view v-else-if="galleryStyle === 'list'" class="mt-2 w-full flex flex-col gap-y-4">
|
<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="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
|
<view
|
||||||
class="grid gap-1 overflow-hidden rounded-lg"
|
v-for="group in galleryGroups" :key="group.metadataName"
|
||||||
:class="[getGalleryListContainerClass(group.images.slice(0, 4).length)]"
|
class="group relative box-border h-36 w-full flex items-center justify-center p-1"
|
||||||
|
@click="handleShowListPanel(group)"
|
||||||
>
|
>
|
||||||
<view
|
<!-- 预览图区域 -->
|
||||||
v-for="(img, index) in group.images.slice(0, 4)" :key="img.metadataName"
|
<view class="relative h-full w-full">
|
||||||
class="overflow-hidden"
|
<view
|
||||||
:class="getGalleryListItemClass(index, group.images.slice(0, 4).length)"
|
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"
|
||||||
<image
|
:class="[getGroupImageRotateClass(index)]"
|
||||||
:src="img.url" mode="scaleToFill"
|
>
|
||||||
class="h-full w-full object-cover transition-transform duration-500 hover:scale-105"
|
<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-xs text-white/80">{{ group.photoCount }} 张图片</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 列表卡片模式 -->
|
||||||
|
<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-if="group.photoCount > 0"
|
||||||
|
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)"
|
||||||
|
>
|
||||||
|
<!-- 顶部区域 -->
|
||||||
|
<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-xs text-gray-400">{{ group.photoCount }} 张</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 图片列表 -->
|
||||||
|
<view
|
||||||
|
class="grid gap-1 overflow-hidden rounded-lg"
|
||||||
|
: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" :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>
|
||||||
|
</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>
|
||||||
</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>
|
</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">
|
<view class="w-full shrink-0">
|
||||||
|
|||||||
+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({
|
const exampleImageUrls = reactive({
|
||||||
banner: 'https://fastly.picsum.photos/id/353/800/800.jpg?hmac=RaDuQ92sSXj4q5vOYna9G00J7KrBUC3eBS0slCWYZXA',
|
banner: 'https://fastly.picsum.photos/id/353/800/800.jpg?hmac=RaDuQ92sSXj4q5vOYna9G00J7KrBUC3eBS0slCWYZXA',
|
||||||
avatar: 'https://www.xiaoxiaomo.cn/logo.jpg',
|
avatar: 'https://www.xiaoxiaomo.cn/logo.jpg',
|
||||||
@@ -122,7 +127,7 @@ onPageScroll(() => { })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="min-h-screen bg-page2">
|
<view class="bg-page3 min-h-screen">
|
||||||
<!-- 顶部banner -->
|
<!-- 顶部banner -->
|
||||||
<view v-if="false" class="relative h-72 w-full">
|
<view v-if="false" class="relative h-72 w-full">
|
||||||
<image class="h-72 w-full object-cover" :src="exampleImageUrls.banner" />
|
<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">
|
<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>
|
</view>
|
||||||
|
|
||||||
<!-- 金刚区 -->
|
<!-- 金刚区 -->
|
||||||
@@ -172,7 +198,7 @@ onPageScroll(() => { })
|
|||||||
<view class="flex items-center justify-around gap-x-4">
|
<view class="flex items-center justify-around gap-x-4">
|
||||||
<view
|
<view
|
||||||
v-for="item in quickList" :key="item.pagePath"
|
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">
|
<view class="uh-shadow-xs flex items-center justify-center rounded-2xl bg-white p-3.5">
|
||||||
<wd-icon :name="item.icon" :size="24" />
|
<wd-icon :name="item.icon" :size="24" />
|
||||||
@@ -211,7 +237,7 @@ onPageScroll(() => { })
|
|||||||
<wd-empty :icon-size="60" icon="no-result" tip="暂无数据" />
|
<wd-empty :icon-size="60" icon="no-result" tip="暂无数据" />
|
||||||
</view>
|
</view>
|
||||||
<template v-else>
|
<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>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ definePage({
|
|||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
console.log('index onLoad')
|
console.log('index onLoad')
|
||||||
// 默认跳转首页
|
// 默认跳转首页
|
||||||
// uni.switchTab({ url: '/pages/home/home' })
|
uni.switchTab({ url: '/pages/home/home' })
|
||||||
uni.switchTab({ url: '/pages/gallery/gallery' })
|
// uni.switchTab({ url: '/pages/links/links' })
|
||||||
|
// uni.switchTab({ url: '/pages/moments/moments' })
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+97
-26
@@ -1,13 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getCurrentInstance } from 'vue'
|
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 { queryLinkGroups, queryLinks } from '@/api/halo-plugin/link'
|
||||||
import { cover } from '@/utils/imageHelper'
|
import { cover } from '@/utils/imageHelper'
|
||||||
import { copy } from '@/utils/base/clipboard'
|
import { copy } from '@/utils/base/clipboard'
|
||||||
import { getRect } from '@/utils/base/getRect'
|
import { getRect } from '@/utils/base/getRect'
|
||||||
import { useListData } from '@/hooks/useListData'
|
import { useListData } from '@/hooks/useListData'
|
||||||
import type { IPaginationParams } from '@/hooks/useListData'
|
|
||||||
import { debounce } from '@/utils/base/debounce'
|
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({
|
defineOptions({
|
||||||
name: 'Links',
|
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 { config } = storeToRefs(useUniHaloConfigStore())
|
||||||
const { loading, error, data, run } = useRequest<any, IPaginationParams>(queryLinks, { loadingToast: false, loadingToastContent: '加载中...', loadingToastMask: true })
|
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: {
|
params: {
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 0,
|
size: 0,
|
||||||
@@ -53,12 +61,20 @@ interface ILinkGroupData {
|
|||||||
name: string
|
name: string
|
||||||
index: string
|
index: string
|
||||||
priority: number
|
priority: number
|
||||||
links: any[]
|
links: ILink[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const linkTypes: LinkTypes[] = ['mpweixin', 'website', 'android', 'ios']
|
||||||
|
|
||||||
const linkGroups = computed(() => {
|
const linkGroups = computed(() => {
|
||||||
const groupedLinks = links.value.reduce((acc, link) => {
|
const groupedLinks = links.value.reduce((acc, link) => {
|
||||||
const groupName = link.spec.groupName
|
const groupName = link.spec.groupName
|
||||||
|
|
||||||
|
// 处理站点类型
|
||||||
|
link.uh = {
|
||||||
|
type: linkTypes[random(0, linkTypes.length - 1)],
|
||||||
|
}
|
||||||
|
|
||||||
const group = dataGroups.value?.find(item => item.metadata.name === groupName)
|
const group = dataGroups.value?.find(item => item.metadata.name === groupName)
|
||||||
let _groupName = '未分组'
|
let _groupName = '未分组'
|
||||||
if (group) {
|
if (group) {
|
||||||
@@ -74,7 +90,7 @@ const linkGroups = computed(() => {
|
|||||||
}
|
}
|
||||||
acc[_groupName].links.push(toRaw(link))
|
acc[_groupName].links.push(toRaw(link))
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, any[]>)
|
}, {} as Record<string, ILinkGroupData>)
|
||||||
// // 分组还需要根据 分组中的 spec.priority 字段进行排序
|
// // 分组还需要根据 分组中的 spec.priority 字段进行排序
|
||||||
return Object.values(groupedLinks)
|
return Object.values(groupedLinks)
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
@@ -149,39 +165,54 @@ onLoad(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
|
||||||
<view>分组数据</view>
|
|
||||||
<view>{{ dataGroups }}</view>
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<!-- 分组列表 -->
|
<!-- 分组列表 -->
|
||||||
<view v-if="linkGroups.length !== 0" class="box-border w-full flex flex-col gap-y-4 overflow-hidden p-4 pb-0">
|
<view v-if="linkGroups.length !== 0" class="box-border w-full flex flex-col gap-y-6 overflow-hidden p-4 pb-0">
|
||||||
<template v-for="(item, index) in linkGroups" :key="item.name">
|
<view v-for="(item, index) in linkGroups" :key="item.name" class="flex flex-col gap-y-4">
|
||||||
<view
|
<view
|
||||||
v-if="linkGroups.length !== 1" class="flex items-center justify-between text-sm"
|
v-if="linkGroups.length !== 1" class="flex items-center justify-between text-sm"
|
||||||
:class="[`group-start-${index}`]"
|
: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>
|
<text class="text-xs text-gray-600"> {{ item.links.length }} 条记录 </text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 分组卡片 -->
|
<!-- 分组卡片 -->
|
||||||
<view
|
<view
|
||||||
v-for="(link, linkIndex) in item.links" :key="link.metadata.name"
|
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="[
|
:class="[
|
||||||
linkIndex === item.links.length - 1 ? `group-end-${index}` : '',
|
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="box-border w-full flex items-center justify-between gap-x-3">
|
||||||
<view
|
<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" />
|
<image :src="cover(link.spec.logo)" mode="aspectFill" class="h-full w-full" />
|
||||||
</view>
|
</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">
|
<view class="line-clamp-1 text-sm text-gray-900 font-bold">
|
||||||
{{ link.spec.displayName }}
|
{{ link.spec.displayName }}
|
||||||
</view>
|
</view>
|
||||||
@@ -189,22 +220,58 @@ onLoad(async () => {
|
|||||||
{{ link.spec.url }}
|
{{ link.spec.url }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="shrink-0">
|
<view v-if="false" class="shrink-0">
|
||||||
<view
|
<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)"
|
@click="copy(link.spec.url, true)"
|
||||||
>
|
>
|
||||||
复制
|
复制
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</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 }}
|
{{ link.spec.description }}
|
||||||
<!-- {{ '你好,我是 UNI HALO v3.0 新版本,开源免费 Halo 博客小程序,支持多端编译。' }} -->
|
<!-- {{ '你好,我是 UNI HALO v3.0 新版本,开源免费 Halo 博客小程序,支持多端编译。' }} -->
|
||||||
</view>
|
</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>
|
</view>
|
||||||
</template>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="w-full flex flex-1 items-center justify-center">
|
<view v-else class="w-full flex flex-1 items-center justify-center">
|
||||||
<wd-empty icon="no-content" tip="暂无友链">
|
<wd-empty icon="no-content" tip="暂无友链">
|
||||||
@@ -216,7 +283,7 @@ onLoad(async () => {
|
|||||||
|
|
||||||
<!-- 导航 -->
|
<!-- 导航 -->
|
||||||
<view
|
<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%)',
|
transform: 'translateY(-50%)',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
@@ -278,4 +345,8 @@ onLoad(async () => {
|
|||||||
.uh-shadow-card {
|
.uh-shadow-card {
|
||||||
box-shadow: 0 16rpx 60rpx rgba(0, 0, 0, 0.04);
|
box-shadow: 0 16rpx 60rpx rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uh-blur-3xl {
|
||||||
|
filter: blur(64px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function radioChange(evt) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<view class="mt-3 break-all px-3 text-center text-green-500">
|
||||||
{{ userInfo.username ? '已登录' : '未登录' }}
|
{{ userInfo.username ? '已登录' : '未登录' }}
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
+106
-59
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { cover } from '@/utils/imageHelper'
|
import { cover } from '@/utils/imageHelper'
|
||||||
import { copy } from '@/utils/base/clipboard'
|
|
||||||
import { queryMoments } from '@/api/halo-plugin/moment'
|
import { queryMoments } from '@/api/halo-plugin/moment'
|
||||||
import { timeFrom } from '@/utils/base/timeFrom'
|
import { parseDateTime } from '@/utils/base/time'
|
||||||
import { timeFormat } from '@/utils/base/timeFormat'
|
import type { IMoment, IMomentListRequest } from '@/api/halo-plugin/moment'
|
||||||
|
import type { IHaloListResponseBase } from '@/api/types/halo'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Moments',
|
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: {
|
params: {
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
@@ -30,7 +39,7 @@ const { list: moments, refresh } = useListData<any>({
|
|||||||
updateData({
|
updateData({
|
||||||
error: error.value,
|
error: error.value,
|
||||||
hasNext: data.value?.hasNext,
|
hasNext: data.value?.hasNext,
|
||||||
items: data.value?.items ?? [],
|
items: formatDataItems(data.value?.items ?? []),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onReachBottom: async (params, updateData) => {
|
onReachBottom: async (params, updateData) => {
|
||||||
@@ -38,7 +47,7 @@ const { list: moments, refresh } = useListData<any>({
|
|||||||
updateData({
|
updateData({
|
||||||
error: error.value,
|
error: error.value,
|
||||||
hasNext: data.value?.hasNext,
|
hasNext: data.value?.hasNext,
|
||||||
items: data.value?.items ?? [],
|
items: formatDataItems(data.value?.items ?? []),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -54,86 +63,115 @@ onPageScroll(() => { })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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-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 v-if="useTimeline" 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
|
<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="{
|
:style="{
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<!-- 瞬间卡片 -->
|
<!-- 瞬间卡片 -->
|
||||||
<view class="box-border flex-1 pl-2">
|
<view
|
||||||
<view class="mb-2 text-sm text-gray-900 font-semibold">
|
class="box-border flex-1"
|
||||||
{{ timeFormat(moment.spec.releaseTime) }}
|
:class="{ 'pl-2': useTimeline }"
|
||||||
</view>
|
>
|
||||||
<view
|
<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="w-full flex items-center justify-between">
|
||||||
<view class="box-border w-full flex items-center justify-between gap-x-4">
|
<!-- 时间信息 -->
|
||||||
|
<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
|
<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>
|
</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" />
|
<rich-text :nodes="moment.spec.content.raw" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 瞬间附件 -->
|
<!-- 瞬间附件 -->
|
||||||
<view class="w-full">
|
<view class="mt-2 hidden w-full">
|
||||||
<!-- 如果有附件 -->
|
<!-- 如果有附件 -->
|
||||||
</view>
|
</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">
|
<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 class="box-border w-full flex items-center justify-between gap-x-2">
|
||||||
<view
|
<view
|
||||||
v-for="tag in moment.spec.tags" :key="tag"
|
class="uh-shadow-md box-border h-6 w-6 shrink-0 overflow-hidden border-2 border-white rounded-lg border-solid bg-white"
|
||||||
class="text-xs text-gray-600"
|
>
|
||||||
>
|
<image :src="cover(moment.owner.avatar)" mode="aspectFill" class="h-full w-full" />
|
||||||
{{ tag }}
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="shrink-0 text-xs text-gray-600">
|
<view class="flex-1 text-sm text-gray-600">
|
||||||
·
|
{{ moment.owner.displayName }}
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</view>
|
||||||
|
|
||||||
<view class="text-[11px] text-gray-600">
|
<view class="flex shrink-0 items-center justify-end gap-x-4">
|
||||||
点赞 {{ moment.stats.upvote }} 次
|
<view class="flex items-center gap-x-1 text-sm text-gray-600">
|
||||||
</view>
|
<wd-icon name="heart" :size="16" /> {{ moment.stats.upvote }}
|
||||||
<view class="shrink-0 text-xs text-gray-600">
|
</view>
|
||||||
·
|
<view class="flex items-center gap-x-1 text-sm text-gray-600">
|
||||||
</view>
|
<wd-icon name="message" :size="16" /> {{ moment.stats.totalComment }}
|
||||||
<view class="text-[11px] text-gray-600">
|
</view>
|
||||||
评论 {{ moment.stats.totalComment }} 次
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -154,3 +192,12 @@ onPageScroll(() => { })
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.outline-text {
|
||||||
|
color: transparent;
|
||||||
|
/* 填充透明 */
|
||||||
|
-webkit-text-stroke: 2rpx #1a1a1a;
|
||||||
|
/* 描边宽度+颜色 */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
+25
-2
@@ -4,7 +4,22 @@ import type { IUniHaloConfig } from '@/api/halo-plugin/uni-halo'
|
|||||||
import { deepMerge } from '@/utils/base/deepMerge'
|
import { deepMerge } from '@/utils/base/deepMerge'
|
||||||
|
|
||||||
const defaultConfig: IUniHaloConfig = {
|
const defaultConfig: IUniHaloConfig = {
|
||||||
baseURL: import.meta.env.VITE_UNI_HALO_BASEURL,
|
base: {
|
||||||
|
domain: import.meta.env.VITE_UNI_HALO_DOMAIN,
|
||||||
|
isIndividual: false,
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
isShowShortcutDock: true,
|
||||||
|
},
|
||||||
|
moment: {
|
||||||
|
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
|
||||||
|
},
|
||||||
|
gallery: {
|
||||||
|
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfigsStoreKey = 'UNI_HALO_CONFIG'
|
const ConfigsStoreKey = 'UNI_HALO_CONFIG'
|
||||||
@@ -21,7 +36,8 @@ export const useUniHaloConfigStore = defineStore('uniHaloConfig', () => {
|
|||||||
}, defaultConfig))
|
}, defaultConfig))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取插件配置
|
* 初始化配置,从服务器获取配置并合并到默认配置中
|
||||||
|
* 一般只需要在全局调用一次即可
|
||||||
*/
|
*/
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
@@ -35,9 +51,16 @@ export const useUniHaloConfigStore = defineStore('uniHaloConfig', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
config.value = defaultConfig
|
||||||
|
console.log('reset', config.value)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
defaultConfig,
|
||||||
config,
|
config,
|
||||||
init,
|
init,
|
||||||
|
reset,
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
persist: true,
|
persist: true,
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
|
||||||
|
* 所以这里做一个兼容polyfill的兼容处理
|
||||||
|
*/
|
||||||
|
export function padStartPolyfill() {
|
||||||
|
if (!String.prototype.padStart) {
|
||||||
|
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
|
||||||
|
String.prototype.padStart = function (this: string, maxLength: number, fillString: string = ' '): string {
|
||||||
|
if (Object.prototype.toString.call(fillString) !== '[object String]')
|
||||||
|
throw new TypeError('fillString must be String')
|
||||||
|
if (this.length >= maxLength) {
|
||||||
|
return String(this)
|
||||||
|
}
|
||||||
|
const fillLength = maxLength - this.length
|
||||||
|
let times = Math.ceil(fillLength / fillString.length)
|
||||||
|
while (times) {
|
||||||
|
times >>= 1
|
||||||
|
fillString += fillString
|
||||||
|
if (times === 1) {
|
||||||
|
fillString += fillString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fillString.slice(0, fillLength) + this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
padStartPolyfill()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间解析结果类型
|
||||||
|
*/
|
||||||
|
export interface TimeParseResult {
|
||||||
|
year: number
|
||||||
|
month: string
|
||||||
|
day: string
|
||||||
|
hour: string
|
||||||
|
minute: string
|
||||||
|
second: string
|
||||||
|
quarter: {
|
||||||
|
en: string
|
||||||
|
zh: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
week: {
|
||||||
|
en: string
|
||||||
|
zh: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析时间字符串,返回年月日时分秒、季度、周信息
|
||||||
|
* @param timeStr 时间字符串(标准时间格式 / 时间戳字符串 / 时间戳)
|
||||||
|
* @returns 结构化时间对象
|
||||||
|
*/
|
||||||
|
export function parseDateTime(timeStr: string | number): TimeParseResult {
|
||||||
|
const date = new Date(timeStr)
|
||||||
|
|
||||||
|
// 校验时间合法性
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
throw new TypeError('传入的时间字符串格式不合法')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础时间
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
const second = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
|
||||||
|
// ========== 季度处理 ==========
|
||||||
|
const monthNum = date.getMonth() + 1
|
||||||
|
let quarterValue: number
|
||||||
|
let quarterEn: string
|
||||||
|
let quarterZh: string
|
||||||
|
|
||||||
|
if (monthNum >= 1 && monthNum <= 3) {
|
||||||
|
quarterValue = 1
|
||||||
|
quarterEn = 'Q1'
|
||||||
|
quarterZh = '第一季度'
|
||||||
|
}
|
||||||
|
else if (monthNum >= 4 && monthNum <= 6) {
|
||||||
|
quarterValue = 2
|
||||||
|
quarterEn = 'Q2'
|
||||||
|
quarterZh = '第二季度'
|
||||||
|
}
|
||||||
|
else if (monthNum >= 7 && monthNum <= 9) {
|
||||||
|
quarterValue = 3
|
||||||
|
quarterEn = 'Q3'
|
||||||
|
quarterZh = '第三季度'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
quarterValue = 4
|
||||||
|
quarterEn = 'Q4'
|
||||||
|
quarterZh = '第四季度'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 周处理(国内规则:周一=1,周日=7) ==========
|
||||||
|
// getDay(): 0=周日,1=周一...6=周六
|
||||||
|
const originWeek = date.getDay()
|
||||||
|
let weekValue: number
|
||||||
|
if (originWeek === 0) {
|
||||||
|
weekValue = 7
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
weekValue = originWeek
|
||||||
|
}
|
||||||
|
|
||||||
|
const weekMap = [
|
||||||
|
{ en: 'Sunday', zh: '星期日' },
|
||||||
|
{ en: 'Monday', zh: '星期一' },
|
||||||
|
{ en: 'Tuesday', zh: '星期二' },
|
||||||
|
{ en: 'Wednesday', zh: '星期三' },
|
||||||
|
{ en: 'Thursday', zh: '星期四' },
|
||||||
|
{ en: 'Friday', zh: '星期五' },
|
||||||
|
{ en: 'Saturday', zh: '星期六' },
|
||||||
|
]
|
||||||
|
const weekItem = weekMap[originWeek]
|
||||||
|
|
||||||
|
return {
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
day,
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
second,
|
||||||
|
quarter: {
|
||||||
|
en: quarterEn,
|
||||||
|
zh: quarterZh,
|
||||||
|
value: quarterValue,
|
||||||
|
},
|
||||||
|
week: {
|
||||||
|
en: weekItem.en,
|
||||||
|
zh: weekItem.zh,
|
||||||
|
value: weekValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseDateTime
|
||||||
@@ -1,25 +1,6 @@
|
|||||||
// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
|
import { padStartPolyfill } from './time'
|
||||||
// 所以这里做一个兼容polyfill的兼容处理
|
|
||||||
if (!String.prototype.padStart) {
|
padStartPolyfill()
|
||||||
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
|
|
||||||
String.prototype.padStart = function (this: string, maxLength: number, fillString: string = ' '): string {
|
|
||||||
if (Object.prototype.toString.call(fillString) !== '[object String]')
|
|
||||||
throw new TypeError('fillString must be String')
|
|
||||||
if (this.length >= maxLength) {
|
|
||||||
return String(this)
|
|
||||||
}
|
|
||||||
const fillLength = maxLength - this.length
|
|
||||||
let times = Math.ceil(fillLength / fillString.length)
|
|
||||||
while (times) {
|
|
||||||
times >>= 1
|
|
||||||
fillString += fillString
|
|
||||||
if (times === 1) {
|
|
||||||
fillString += fillString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fillString.slice(0, fillLength) + this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 时间格式化
|
* 时间格式化
|
||||||
|
|||||||
@@ -12,3 +12,21 @@ export function cover(cover: string) {
|
|||||||
}
|
}
|
||||||
return getRandomUrl()
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export default defineConfig({
|
|||||||
primary: 'var(--wot-color-theme,#0957DE)',
|
primary: 'var(--wot-color-theme,#0957DE)',
|
||||||
page: '#F8F8F8',
|
page: '#F8F8F8',
|
||||||
page2: '#F9FAFB',
|
page2: '#F9FAFB',
|
||||||
|
page3: '#F8FAFC',
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
/** 提供更小号的字体,用法如:text-2xs */
|
/** 提供更小号的字体,用法如:text-2xs */
|
||||||
|
|||||||
Reference in New Issue
Block a user