mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-07-27 04:20:43 +08:00
refactor: 重构项目并新增多项功能
1. 新增全局配置管理store,拉取uni-halo插件配置 2. 新增post-card文章卡片组件,简化首页文章列表代码 3. 优化tabbar样式与切换逻辑,调整图标库类型 4. 新增随机图片工具函数,重构首页横幅与分类模块 5. 调整openapi生成配置,新增工具类与样式类 6. 移除废弃的tabbar关于页面配置项
This commit is contained in:
@@ -2,7 +2,7 @@ import { defineConfig } from 'openapi-ts-request'
|
|||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
{
|
{
|
||||||
describe: 'unibest-openapi-test',
|
describe: 'uni-openapi-test',
|
||||||
schemaPath: 'https://ukw0y1.laf.run/unibest-opapi-test.json',
|
schemaPath: 'https://ukw0y1.laf.run/unibest-opapi-test.json',
|
||||||
serversPath: './src/service',
|
serversPath: './src/service',
|
||||||
requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions_ } from '@/http/types';`,
|
requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions_ } from '@/http/types';`,
|
||||||
|
|||||||
+13
-5
@@ -4,14 +4,26 @@ import { getCurrentInstance, onMounted, onUnmounted } from 'vue'
|
|||||||
import { navigateToInterceptor } from '@/router/interceptor'
|
import { navigateToInterceptor } from '@/router/interceptor'
|
||||||
import { tabbarStore } from '@/tabbar/store'
|
import { tabbarStore } from '@/tabbar/store'
|
||||||
import { permission } from '@/router/permission'
|
import { permission } from '@/router/permission'
|
||||||
|
import { useUniHaloConfigStore } from './store/config'
|
||||||
|
|
||||||
const { proxy } = (getCurrentInstance() || {}) as any
|
const { proxy } = (getCurrentInstance() || {}) as any
|
||||||
const router = proxy?.$router
|
const router = proxy?.$router
|
||||||
|
|
||||||
router && permission.install(router)
|
router && permission.install(router)
|
||||||
|
|
||||||
onLaunch((options) => {
|
async function init() {
|
||||||
|
try {
|
||||||
|
const uniHaloConfigStore = useUniHaloConfigStore()
|
||||||
|
await uniHaloConfigStore.init()
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLaunch(async (options) => {
|
||||||
console.log('App.vue onLaunch', options)
|
console.log('App.vue onLaunch', options)
|
||||||
|
await init()
|
||||||
})
|
})
|
||||||
onShow((options) => {
|
onShow((options) => {
|
||||||
console.log('App.vue onShow', options)
|
console.log('App.vue onShow', options)
|
||||||
@@ -46,7 +58,3 @@ onUnmounted(() => {
|
|||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { http } from '@/http/alova'
|
import { http } from '@/http/alova'
|
||||||
|
|
||||||
|
export interface IUniHaloConfig {
|
||||||
|
baseURL: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 uni-halo 插件配置
|
* 获取 uni-halo 插件配置
|
||||||
* @returns uni-halo 插件配置响应参数
|
* @returns uni-halo 插件配置响应参数
|
||||||
*/
|
*/
|
||||||
export function queryUniHaloPluginConfigs() {
|
export function queryUniHaloPluginConfigs() {
|
||||||
return http.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getConfigs`, {
|
return http.Get<IUniHaloConfig>(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getConfigs`, {
|
||||||
meta: {
|
meta: {
|
||||||
isHalo: true,
|
isHalo: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { completeUrl } from '@/utils/url'
|
||||||
|
import { timeFrom } from '@/utils/base/timeFrom'
|
||||||
|
import type { ListedPostVo } from '@halo-dev/api-client'
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
post?: ListedPostVo
|
||||||
|
type?: 'default' | 'topCover' | 'rightCover'
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
|
type: 'default',
|
||||||
|
post: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrapClass = computed(() => {
|
||||||
|
if (props.type === 'topCover') {
|
||||||
|
return 'flex-col gap-y-4'
|
||||||
|
}
|
||||||
|
if (props.type === 'rightCover') {
|
||||||
|
return 'relative h-28 p-0! border-2 border-solid border-white'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const coverClass = computed(() => {
|
||||||
|
if (props.type === 'topCover') {
|
||||||
|
return 'h-32 w-full'
|
||||||
|
}
|
||||||
|
if (props.type === 'rightCover') {
|
||||||
|
return 'absolute right-0 top-0 bottom-0 w-32 rounded-lt-0 rounded-lb-0'
|
||||||
|
}
|
||||||
|
return 'h-22 w-22'
|
||||||
|
})
|
||||||
|
|
||||||
|
const textWrapClass = computed(() => {
|
||||||
|
if (props.type === 'topCover') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (props.type === 'rightCover') {
|
||||||
|
return 'p-4 pr-32 relative z-10'
|
||||||
|
}
|
||||||
|
return 'pl-4'
|
||||||
|
})
|
||||||
|
|
||||||
|
const textContainerClass = computed(() => {
|
||||||
|
if (props.type === 'topCover') {
|
||||||
|
return 'gap-y-2'
|
||||||
|
}
|
||||||
|
if (props.type === 'rightCover') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const textFooterClass = computed(() => {
|
||||||
|
if (props.type === 'topCover') {
|
||||||
|
return 'justify-between gap-x-2'
|
||||||
|
}
|
||||||
|
if (props.type === 'rightCover') {
|
||||||
|
return 'gap-x-2'
|
||||||
|
}
|
||||||
|
return 'gap-x-2'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view v-if="post" class="uh-shadow-xs flex justify-center overflow-hidden rounded-2xl bg-white p-4" :class="wrapClass">
|
||||||
|
<view class="shrink-0 overflow-hidden rounded-lg" :class="coverClass">
|
||||||
|
<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>
|
||||||
|
<view class="box-border flex-1" :class="textWrapClass">
|
||||||
|
<view class="h-full flex flex-1 flex-col justify-between" :class="textContainerClass">
|
||||||
|
<view class="line-clamp-1 text-sm text-gray-900 font-bold">
|
||||||
|
{{ post.spec.title }}
|
||||||
|
</view>
|
||||||
|
<view class="line-clamp-2 text-xs text-gray-500">
|
||||||
|
{{ post.status.excerpt }}
|
||||||
|
</view>
|
||||||
|
<view class="w-full flex items-center" :class="textFooterClass">
|
||||||
|
<view class="text-[11px] text-gray-600">
|
||||||
|
{{ timeFrom(post.spec.publishTime) }}
|
||||||
|
</view>
|
||||||
|
<view class="shrink-0 text-xs text-gray-600">
|
||||||
|
·
|
||||||
|
</view>
|
||||||
|
<view class="line-clamp-1 text-[11px] text-gray-600">
|
||||||
|
{{ post.categories.map(c => c.spec.displayName).join('·') }}
|
||||||
|
</view>
|
||||||
|
<template v-if="props.type === 'topCover'">
|
||||||
|
<view class="shrink-0 text-xs text-gray-600">
|
||||||
|
·
|
||||||
|
</view>
|
||||||
|
<view class="text-[11px] text-gray-600">
|
||||||
|
点赞 {{ post.stats.upvote }} 次
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<view class="shrink-0 text-xs text-gray-600">
|
||||||
|
·
|
||||||
|
</view>
|
||||||
|
<view class="text-[11px] text-gray-600">
|
||||||
|
浏览 {{ post.stats.visit }} 次
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.uh-shadow-xs {
|
||||||
|
box-shadow: 0 0 24rpx rgba(0, 0, 0, 0.035);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export function useAppConfig() {
|
||||||
|
return {
|
||||||
|
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
+81
-49
@@ -4,10 +4,11 @@ import { queryPostByName, queryPosts } from '@/api/halo-base/post'
|
|||||||
import { completeUrl } from '@/utils/url'
|
import { completeUrl } from '@/utils/url'
|
||||||
import { timeFrom } from '@/utils/base/timeFrom'
|
import { timeFrom } from '@/utils/base/timeFrom'
|
||||||
import { useListData } from '@/hooks/useListData'
|
import { useListData } from '@/hooks/useListData'
|
||||||
import { randomImageUrl } from '@/utils/randomResources'
|
import { getRandomUrl, randomImageUrl } from '@/utils/randomResources'
|
||||||
|
|
||||||
import type { ListedPostVo, ListedPostVoList, PostV1alpha1PublicApiQueryPostByNameRequest, PostV1alpha1PublicApiQueryPostsRequest, PostVo } from '@halo-dev/api-client'
|
import type { ListedPostVo, ListedPostVoList, PostV1alpha1PublicApiQueryPostByNameRequest, PostV1alpha1PublicApiQueryPostsRequest, PostVo } from '@halo-dev/api-client'
|
||||||
|
|
||||||
|
import PostCard from '@/components/post-card/post-card.vue'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
})
|
})
|
||||||
@@ -50,6 +51,24 @@ const banner = reactive({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const categories = reactive([
|
||||||
|
{
|
||||||
|
name: '全栈开发',
|
||||||
|
count: 120,
|
||||||
|
cover: getRandomUrl(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Docker',
|
||||||
|
count: 0,
|
||||||
|
cover: getRandomUrl(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Vue技术',
|
||||||
|
count: 99,
|
||||||
|
cover: getRandomUrl(),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const quickList = reactive([
|
const quickList = reactive([
|
||||||
{
|
{
|
||||||
pagePath: 'pages/moments/moments',
|
pagePath: 'pages/moments/moments',
|
||||||
@@ -115,6 +134,10 @@ onLoad(() => {
|
|||||||
refresh()
|
refresh()
|
||||||
runSingle({ name: '019eb1ca-e37c-7729-97af-e0b658aeef0f' })
|
runSingle({ name: '019eb1ca-e37c-7729-97af-e0b658aeef0f' })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onPageScroll(({ scrollTop }) => {
|
||||||
|
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -132,22 +155,26 @@ onLoad(() => {
|
|||||||
<view>
|
<view>
|
||||||
<wd-swiper
|
<wd-swiper
|
||||||
v-model:current="banner.current" :list="banner.items" indicator-position="right" autoplay
|
v-model:current="banner.current" :list="banner.items" indicator-position="right" autoplay
|
||||||
:height="290"
|
:height="340"
|
||||||
>
|
>
|
||||||
<template #default="{ index }">
|
<template #default="{ index }">
|
||||||
<view class="relative h-full w-full">
|
<view class="relative h-full w-full">
|
||||||
<image class="h-full w-full object-cover" :src="banner.list[index].value" />
|
<image class="h-full w-full" mode="aspectFill" :src="banner.list[index].value" />
|
||||||
<view class="absolute bottom-0 left-0 right-0 from-black/0 via-black/50 to-black/90 bg-gradient-to-b p-4">
|
<!-- 渐变遮罩 from-black/0 via-black/50 to-black/90 bg-gradient-to-b -->
|
||||||
<view class="w-5/6 flex flex-col gap-y-2">
|
<view class="absolute bottom-0 left-0 right-0 p-4 pb-16">
|
||||||
|
<view class="relative z-20 w-5/6 flex flex-col gap-y-2">
|
||||||
<text class="text-xs text-white/90">{{ banner.list[index].time }}</text>
|
<text class="text-xs text-white/90">{{ banner.list[index].time }}</text>
|
||||||
<text class="line-clamp-1 text-xl text-white font-bold">{{ banner.list[index].title }}</text>
|
<text class="line-clamp-1 text-xl text-white font-bold">{{ banner.list[index].title }}</text>
|
||||||
<text class="line-clamp-1 text-xs text-white/95">{{ banner.list[index].desc }}</text>
|
<text class="line-clamp-1 text-xs text-white/95">{{ banner.list[index].desc }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view
|
||||||
|
class="absolute bottom-0 left-0 right-0 z-10 h-3/5 from-black/0 via-page/50 to-page bg-gradient-to-b"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<template #indicator="{ current, total }">
|
<template #indicator="{ current, total }">
|
||||||
<view class="absolute bottom-4 right-4 text-xs text-white">
|
<view class="absolute bottom-16 right-4 text-xs text-white">
|
||||||
{{ current + 1 }}/{{ total }}
|
{{ current + 1 }}/{{ total }}
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -155,8 +182,8 @@ onLoad(() => {
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 作者卡片 -->
|
<!-- 作者卡片 -->
|
||||||
<view class="mt-4 box-border px-4">
|
<view class="relative z-10 box-border px-4 -mt-12">
|
||||||
<view class="box-border flex flex-col items-center gap-y-2 rounded-2xl bg-white p-4 shadow-sm">
|
<view class="uh-shadow-xs box-border flex flex-col items-center gap-y-2 rounded-2xl bg-white p-4">
|
||||||
<view class="w-full flex items-center justify-between gap-x-2">
|
<view class="w-full flex items-center justify-between gap-x-2">
|
||||||
<image
|
<image
|
||||||
class="uh-shadow-xs h-11 w-11 shrink-0 border-2 border-white rounded-full border-solid"
|
class="uh-shadow-xs h-11 w-11 shrink-0 border-2 border-white rounded-full border-solid"
|
||||||
@@ -174,11 +201,11 @@ onLoad(() => {
|
|||||||
联系我
|
联系我
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="my-1 h-1px w-full bg-gray-100" />
|
<view class="my-1 h-1px w-full bg-gray-50" />
|
||||||
<view class="grid grid-cols-4 w-full gap-x-4">
|
<view class="grid grid-cols-5 w-full gap-x-4">
|
||||||
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
||||||
<text class="text-sm text-gray-900 font-bold">128</text>
|
<text class="text-sm text-gray-900 font-bold">128</text>
|
||||||
<text class="text-xs text-gray-600">文章</text>
|
<text class="text-xs text-gray-600">内容</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
||||||
<text class="text-sm text-gray-900 font-bold">999</text>
|
<text class="text-sm text-gray-900 font-bold">999</text>
|
||||||
@@ -186,24 +213,28 @@ onLoad(() => {
|
|||||||
</view>
|
</view>
|
||||||
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
||||||
<text class="text-sm text-gray-900 font-bold">1.2k</text>
|
<text class="text-sm text-gray-900 font-bold">1.2k</text>
|
||||||
<text class="text-xs text-gray-600">获赞</text>
|
<text class="text-xs text-gray-600">分类</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
||||||
<text class="text-sm text-gray-900 font-bold">128k</text>
|
<text class="text-sm text-gray-900 font-bold">128k</text>
|
||||||
<text class="text-xs text-gray-600">收藏</text>
|
<text class="text-xs text-gray-600">评论</text>
|
||||||
|
</view>
|
||||||
|
<view class="flex flex-col items-center justify-center gap-y-0.5">
|
||||||
|
<text class="text-sm text-gray-900 font-bold">10.5k</text>
|
||||||
|
<text class="text-xs text-gray-600">点赞</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 金刚区 -->
|
<!-- 金刚区 -->
|
||||||
<view class="mt-4 px-4">
|
<view class="mt-6 px-4">
|
||||||
<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="w-1/5 flex flex-col items-center justify-center gap-y-1"
|
||||||
>
|
>
|
||||||
<view class="flex items-center justify-center rounded-3xl bg-white p-3.5 shadow-sm">
|
<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" />
|
||||||
</view>
|
</view>
|
||||||
<text class="text-xs text-gray-800">{{ item.text }}</text>
|
<text class="text-xs text-gray-800">{{ item.text }}</text>
|
||||||
@@ -211,12 +242,39 @@ onLoad(() => {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 分类推荐 -->
|
<!-- 分类推荐(磁贴) -->
|
||||||
|
<view class="mt-6 box-border px-4">
|
||||||
|
<view class="mb-4 box-border flex items-center justify-between">
|
||||||
|
<text class="text-sm text-gray-900 font-bold">热门分类</text>
|
||||||
|
</view>
|
||||||
|
<view class="box-border h-36 flex items-center gap-2 overflow-hidden">
|
||||||
|
<view class="relative box-border h-full flex-1 overflow-hidden border-2 border-white rounded-xl border-solid bg-white">
|
||||||
|
<image :src="completeUrl(categories[0].cover)" mode="heightFix" class="h-full w-full rounded-xl" />
|
||||||
|
<view class="absolute bottom-0 left-0 right-0 z-10 box-border box-border w-full flex flex-col gap-y-1 from-black/0 via-black/50 to-black/90 bg-gradient-to-b p-2">
|
||||||
|
<text class="line-clamp-1 text-xs text-white font-bold">{{ categories[0].name }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="absolute right-2 top-2 z-10 box-border rounded-md bg-black/20 px-1.5 py-0.5">
|
||||||
|
<text class="line-clamp-1 text-[12px] text-white">{{ categories[0].count }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="h-full flex flex-1 flex-col gap-2">
|
||||||
|
<view v-for="cate in categories.slice(1)" :key="cate.name" class="relative box-border flex-1 overflow-hidden border-2 border-white rounded-xl border-solid bg-white">
|
||||||
|
<image :src="completeUrl(cate.cover)" mode="aspectFill" class="w-full rounded-xl" />
|
||||||
|
<view class="absolute bottom-0 left-0 right-0 z-10 box-border box-border w-full flex flex-col gap-y-1 from-black/0 via-black/50 to-black/90 bg-gradient-to-b p-2">
|
||||||
|
<text class="line-clamp-1 text-xs text-white font-bold">{{ cate.name }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="absolute right-2 top-2 z-10 box-border rounded-md bg-black/20 px-1.5 py-0.5">
|
||||||
|
<text class="line-clamp-1 text-[12px] text-white">{{ cate.count }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 瞬间推荐 -->
|
<!-- 瞬间推荐 -->
|
||||||
|
|
||||||
<!-- 置顶文章 -->
|
<!-- 置顶文章 -->
|
||||||
<view v-if="dataSingle" class="mt-4 box-border px-4">
|
<view v-if="dataSingle && false" class="mt-4 box-border px-4">
|
||||||
<view class="mb-2 box-border flex items-center justify-between">
|
<view class="mb-2 box-border flex items-center justify-between">
|
||||||
<text class="text-lg text-gray-900 font-bold">置顶文章</text>
|
<text class="text-lg text-gray-900 font-bold">置顶文章</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -230,42 +288,16 @@ onLoad(() => {
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 最新文章 -->
|
<!-- 最新文章 -->
|
||||||
<view class="mt-4">
|
<view class="mt-4 mt-4 box-border px-4">
|
||||||
<view class="mb-2 box-border flex items-center justify-between px-4">
|
<view class="mb-4 box-border flex items-center justify-between">
|
||||||
<text class="text-lg text-gray-900 font-bold">最新文章</text>
|
<text class="text-sm text-gray-900 font-bold">最新文章</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="box-border w-full flex flex-col items-center justify-center gap-y-4 overflow-hidden px-4">
|
<view class="box-border w-full flex flex-col items-center justify-center gap-y-4 overflow-hidden">
|
||||||
<view v-if="error && postList.length > 0" class="un-shadow-xs w-full rounded-2xl bg-white py-12">
|
<view v-if="error && postList.length > 0" class="un-shadow-xs w-full rounded-2xl bg-white py-12">
|
||||||
<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>
|
||||||
<view
|
<post-card v-for="post in postList" :key="post.metadata.name" :post="post" type="topCover" />
|
||||||
v-for="post in postList" :key="post.metadata.name"
|
|
||||||
class="uh-shadow-xs flex justify-center overflow-hidden rounded-2xl bg-white p-4"
|
|
||||||
>
|
|
||||||
<image :src="completeUrl(post.spec.cover)" mode="aspectFill" class="h-22 w-22 shrink-0 rounded-lg" />
|
|
||||||
<view class="box-border flex-1 pl-4">
|
|
||||||
<view class="h-full flex flex-1 flex-col justify-between">
|
|
||||||
<view class="line-clamp-1 text-sm text-gray-900 font-bold">
|
|
||||||
{{ post.spec.title }}
|
|
||||||
</view>
|
|
||||||
<view class="line-clamp-2 text-xs text-gray-500">
|
|
||||||
{{ post.status.excerpt }}
|
|
||||||
</view>
|
|
||||||
<view class="w-full flex items-center gap-x-4">
|
|
||||||
<view class="text-xs text-gray-600">
|
|
||||||
{{ timeFrom(post.spec.publishTime) }}
|
|
||||||
</view>
|
|
||||||
<view class="text-xs text-gray-600">
|
|
||||||
·
|
|
||||||
</view>
|
|
||||||
<view class="text-xs text-gray-600">
|
|
||||||
{{ post.categories.map(c => c.spec.displayName).join('·') }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { queryUniHaloPluginConfigs } from '@/api/halo-plugin/uni-halo'
|
||||||
|
import type { IUniHaloConfig } from '@/api/halo-plugin/uni-halo'
|
||||||
|
import { deepMerge } from '@/utils/base/deepMerge'
|
||||||
|
|
||||||
|
const defaultConfig: IUniHaloConfig = {
|
||||||
|
baseURL: import.meta.env.VITE_SERVER_BASEURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigsStoreKey = 'UNI_HALO_CONFIG'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uni-halo 配置状态管理
|
||||||
|
*/
|
||||||
|
export const useUniHaloConfigStore = defineStore('uniHaloConfig', () => {
|
||||||
|
/**
|
||||||
|
* 插件配置
|
||||||
|
*/
|
||||||
|
const config = ref<IUniHaloConfig | undefined>(deepMerge({
|
||||||
|
...(uni.getStorageSync(ConfigsStoreKey) ?? {} as IUniHaloConfig),
|
||||||
|
}, defaultConfig))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件配置
|
||||||
|
*/
|
||||||
|
async function init() {
|
||||||
|
try {
|
||||||
|
const res: IUniHaloConfig = await queryUniHaloPluginConfigs()
|
||||||
|
config.value = deepMerge(config.value, res ?? {})
|
||||||
|
uni.setStorageSync(ConfigsStoreKey, config.value)
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
init,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
persist: true,
|
||||||
|
})
|
||||||
@@ -32,20 +32,19 @@ function isActiveByIndex(index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null
|
let timer: ReturnType<typeof setTimeout> | null = null
|
||||||
const isActive = ref(true)
|
const isActive = ref(false)
|
||||||
|
|
||||||
function activeBgColor() {
|
function activeBgColor() {
|
||||||
clearTimeout(timer)
|
|
||||||
isActive.value = false
|
isActive.value = false
|
||||||
|
clearTimeout(timer)
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
timer = null
|
|
||||||
isActive.value = true
|
isActive.value = true
|
||||||
}, 0)
|
}, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => tabbarStore.curIdx, () => {
|
watch(() => tabbarStore.curIdx, () => {
|
||||||
activeBgColor()
|
activeBgColor()
|
||||||
})
|
}, { immediate: true })
|
||||||
|
|
||||||
function handleClick(index: number) {
|
function handleClick(index: number) {
|
||||||
// 点击原来的不做操作
|
// 点击原来的不做操作
|
||||||
@@ -59,6 +58,7 @@ function handleClick(index: number) {
|
|||||||
|
|
||||||
const url = list[index].pagePath
|
const url = list[index].pagePath
|
||||||
tabbarStore.setCurIdx(index)
|
tabbarStore.setCurIdx(index)
|
||||||
|
console.log('handleClick tabbarCacheEnable', tabbarCacheEnable)
|
||||||
if (tabbarCacheEnable) {
|
if (tabbarCacheEnable) {
|
||||||
uni.switchTab({ url })
|
uni.switchTab({ url })
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-12
@@ -43,9 +43,8 @@ export const customTabbarList: CustomTabBarItem[] = [
|
|||||||
{
|
{
|
||||||
text: '%tabbar.home%',
|
text: '%tabbar.home%',
|
||||||
pagePath: 'pages/index/index',
|
pagePath: 'pages/index/index',
|
||||||
iconType: 'unocss',
|
iconType: 'uiLib',
|
||||||
icon: 'i-carbon-home',
|
icon: 'home',
|
||||||
// badge: 'dot',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pagePath: 'pages/gallery/gallery',
|
pagePath: 'pages/gallery/gallery',
|
||||||
@@ -57,16 +56,8 @@ export const customTabbarList: CustomTabBarItem[] = [
|
|||||||
pagePath: 'pages/moments/moments',
|
pagePath: 'pages/moments/moments',
|
||||||
text: '%tabbar.moments%',
|
text: '%tabbar.moments%',
|
||||||
iconType: 'uiLib',
|
iconType: 'uiLib',
|
||||||
icon: 'gift',
|
icon: 'send',
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// pagePath: 'pages-blog/about/about',
|
|
||||||
// text: '%tabbar.about%',
|
|
||||||
// iconType: 'unocss',
|
|
||||||
// icon: 'i-carbon-menu',
|
|
||||||
// badge: 10,
|
|
||||||
// roles: ['admin'],
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
pagePath: 'pages/me/me',
|
pagePath: 'pages/me/me',
|
||||||
text: '%tabbar.me%',
|
text: '%tabbar.me%',
|
||||||
|
|||||||
+10
-5
@@ -58,6 +58,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
const backTopActive = reactive({
|
const backTopActive = reactive({
|
||||||
show: false,
|
show: false,
|
||||||
|
thresholdValue: 300,
|
||||||
beforeScrollTop: 0,
|
beforeScrollTop: 0,
|
||||||
currentScrollTop: 0,
|
currentScrollTop: 0,
|
||||||
})
|
})
|
||||||
@@ -117,9 +118,9 @@ const triggerPageScrollDebounceFn = debounce((scrollTop: number) => {
|
|||||||
backTopActive.beforeScrollTop = backTopActive.currentScrollTop
|
backTopActive.beforeScrollTop = backTopActive.currentScrollTop
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
backTopActive.currentScrollTop = scrollTop
|
backTopActive.currentScrollTop = scrollTop
|
||||||
backTopActive.show = backTopActive.currentScrollTop > backTopActive.beforeScrollTop
|
backTopActive.show = scrollTop > backTopActive.thresholdValue && backTopActive.currentScrollTop > backTopActive.beforeScrollTop
|
||||||
}, 60)
|
}, 30)
|
||||||
}, 60)
|
}, 30)
|
||||||
|
|
||||||
onPageScroll(({ scrollTop }) => {
|
onPageScroll(({ scrollTop }) => {
|
||||||
triggerPageScrollDebounceFn(scrollTop)
|
triggerPageScrollDebounceFn(scrollTop)
|
||||||
@@ -137,7 +138,7 @@ function handleScrollToTop() {
|
|||||||
<view v-if="customTabbarEnable" :class="{ 'h-54px pb-safe': !tabBarConfig.useFloat }">
|
<view v-if="customTabbarEnable" :class="{ 'h-54px pb-safe': !tabBarConfig.useFloat }">
|
||||||
<view class="fixed bottom-6 left-4 right-4 z-100 flex items-center justify-between gap-x-4" @touchmove.stop.prevent>
|
<view class="fixed bottom-6 left-4 right-4 z-100 flex items-center justify-between gap-x-4" @touchmove.stop.prevent>
|
||||||
<view
|
<view
|
||||||
class="uh-shadow-sm relative box-border h-54px flex flex-1 items-center overflow-hidden border border-white rounded-full border-solid bg-white/95 p-1 backdrop-blur-[3px]"
|
class="uh-shadow-sm uh-backdrop-blur relative box-border h-54px flex flex-1 items-center overflow-hidden border border-white rounded-full border-solid bg-white/95 p-1"
|
||||||
>
|
>
|
||||||
<TabbarItem
|
<TabbarItem
|
||||||
v-for="(item, index) in tabbarList" :key="index" :item="item" :count="tabbarList.length"
|
v-for="(item, index) in tabbarList" :key="index" :item="item" :count="tabbarList.length"
|
||||||
@@ -155,7 +156,7 @@ function handleScrollToTop() {
|
|||||||
</view>
|
</view>
|
||||||
<!-- 右侧搜索框或者菜单 -->
|
<!-- 右侧搜索框或者菜单 -->
|
||||||
<view
|
<view
|
||||||
class="uh-shadow-sm box-border h-54px w-54px flex items-center justify-center border border-white rounded-full border-solid bg-white/95 text-2xl backdrop-blur-[3px]"
|
class="uh-shadow-sm uh-backdrop-blur box-border h-54px w-54px flex items-center justify-center border border-white rounded-full border-solid bg-white/95 text-2xl"
|
||||||
>
|
>
|
||||||
<wd-icon v-if="backTopActive.show" name="arrow-up" :size="22" @click="handleScrollToTop" />
|
<wd-icon v-if="backTopActive.show" name="arrow-up" :size="22" @click="handleScrollToTop" />
|
||||||
<wd-icon v-else name="search-line" :size="22" />
|
<wd-icon v-else name="search-line" :size="22" />
|
||||||
@@ -170,4 +171,8 @@ function handleScrollToTop() {
|
|||||||
.uh-shadow-sm {
|
.uh-shadow-sm {
|
||||||
box-shadow: 0 0 24rpx rgba(0, 0, 0, 0.075);
|
box-shadow: 0 0 24rpx rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uh-backdrop-blur {
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export const randomImageUrl = {
|
|||||||
xsot: 'https://api.xsot.cn/bing?jump=true',
|
xsot: 'https://api.xsot.cn/bing?jump=true',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type randomUrlKeys = keyof typeof randomImageUrl
|
||||||
|
|
||||||
export const randomVideosUrl = {
|
export const randomVideosUrl = {
|
||||||
acg: 'https://t.alcy.cc/acg',
|
acg: 'https://t.alcy.cc/acg',
|
||||||
}
|
}
|
||||||
@@ -17,3 +19,12 @@ export function getPicsum(width: number = 800, height?: number) {
|
|||||||
}
|
}
|
||||||
return `https://picsum.photos/${width}/${height}`
|
return `https://picsum.photos/${width}/${height}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取随机图片URL
|
||||||
|
* @param type 图片类型
|
||||||
|
* @returns 随机图片URL
|
||||||
|
*/
|
||||||
|
export function getRandomUrl(type?: randomUrlKeys) {
|
||||||
|
return `${randomImageUrl[type || 'pc' as randomUrlKeys]}?t=${Date.now()}+${Math.random()}`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user