1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-09-12 16:40:40 +08:00

refactor: 整体项目样式与组件优化升级

主要变更:
1. 新增uhemoji2图标字体库并全局引入
2. 重构导航组件与页面布局,统一使用自定义导航栏
3. 替换旧图标为emoji图标,统一视觉风格
4. 清理冗余调试代码与注释
5. 优化快捷导航、关于页等页面样式与交互
6. 修复滚动到顶部组件黑名单配置
This commit is contained in:
小莫唐尼
2026-09-04 18:54:34 +08:00
parent 62b604644c
commit 56ce29e69e
15 changed files with 1534 additions and 1440 deletions
+81 -3
View File
@@ -295,7 +295,85 @@ onLoad(() => {
- 页面根节点用 `app-page` 类 + 主题底色:`<view class="app-page min-h-screen w-screen flex flex-col bg-page">` - 页面根节点用 `app-page` 类 + 主题底色:`<view class="app-page min-h-screen w-screen flex flex-col bg-page">`
- 页面标题 `navigationBarTitleText` 写中文;下拉刷新 `enablePullDownRefresh: true` - 页面标题 `navigationBarTitleText` 写中文;下拉刷新 `enablePullDownRefresh: true`
### 5.4 页面数据请求(统一 updateLoadingStatus + uh-data-loading 组件) ### 5.4 页面自定义导航(uh-navbar 组件)
**用途**:子页面(非 tabbar 页,如文章详情、设置、投票详情等)的顶部自定义导航栏。
配合页面 `navigationStyle: 'custom'` 使用,**新页面一律使用它,不要用默认导航栏**。
**前提(definePage 里必须声明)**
```ts
definePage({
style: {
navigationStyle: 'custom', // 关闭系统默认导航栏,让位给 uh-navbar
},
})
```
**Props 一览**
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `use-back` | `boolean` | `true` | 是否显示左侧返回按钮 |
| `use-title` | `boolean` | `true` | 是否显示标题 |
| `default-title` | `string` | - | 默认标题(顶部文案) |
| `title-color` | `string` | - | 标题颜色类名(如 `text-gray-900`),**带此属性时固定颜色、不随滚动变色** |
| `scroll-title` | `string` | - | 滚动后标题(配合 default-title:顶部显示默认标题,滚过 50% 换 scroll-title |
| `need-placeholder` | `boolean` | `true` | 是否生成占位(为页面内容让出导航高度) |
**页面结构模板**(参考 `src/pages-blog/test/test.vue`):
```vue
<script lang="ts" setup>
definePage({
style: {
navigationBarTitleText: '测试页面',
navigationStyle: 'custom', // 使用 uh-navbar 必须声明
},
})
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
</script>
<template>
<!-- 最外层仅作容器(bg-page 底色),不设 padding -->
<view class="w-full min-h-screen bg-page">
<!-- 导航栏:置于页面最顶部,内置占位/安全区处理 -->
<uh-navbar default-title="测试页面" :need-placeholder="true" title-color="text-gray-900" />
<!-- 内容区:从这开始写,因为 uh-navbar 已内置占位(need-placeholder=true) -->
<view class="box-border px-3">
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus" min-height="80vh" />
<view v-else>
请求成功啦
</view>
</view>
</view>
</template>
```
**使用要点**
- **`need-placeholder` 必须按场景传对**
- 页面内容直接从导航下开始(普通子页面)→ `:need-placeholder="true"`(默认即可,如 test.vue / setting.vue
- 页面顶部有全屏封面/背景图,内容要盖到导航下面 → `:need-placeholder="false"`(如 article-detail.vue,封面 `pt-72` 上移)
- **`title-color` 与 `scroll-title` 二选一**
- 页面底色非深色 → 传 `title-color="text-gray-900"`(固定深色,如 test.vue / setting.vue
- 不传时标题会随滚动变白→深灰(顶部透明、滚后加深),适合顶部是深色大图的场景(如 article-detail.vue 只传 `default-title` + `scroll-title`,让滚动变色)
- 中间标题可用**默认插槽覆盖**(不传则显示 `default-title`/`scroll-title`),右侧扩展用 **`#right` 插槽**
```vue
<uh-navbar default-title="偏好设置" :need-placeholder="true">
<template #right>
<view @click="handleSave">保存</view>
</template>
</uh-navbar>
```
- 返回按钮内置(`uni.navigateBack`),无需自写
- easycom 已配置 `uh-` 前缀,直接用 `<uh-navbar />`**无需 import**
### 5.5 页面数据请求(统一 updateLoadingStatus + uh-data-loading 组件)
**数据加载四态**`loading / error / empty / success`,统一用 **数据加载四态**`loading / error / empty / success`,统一用
`useDataLoadingStatus``src/hooks/useDataLoadingStatus.ts`)的 `useDataLoadingStatus``src/hooks/useDataLoadingStatus.ts`)的
@@ -419,7 +497,7 @@ onReachBottom(() => {
列表页/四态展示优先 `updateLoadingStatus` 写法 列表页/四态展示优先 `updateLoadingStatus` 写法
- 简单场景也可用 `useRequest(fn, { immediate })`:返回 `{ loading, error, data, run }` - 简单场景也可用 `useRequest(fn, { immediate })`:返回 `{ loading, error, data, run }`
### 5.5 列表页(分页加载) ### 5.6 列表页(分页加载)
列表页可用 z-pagingeasycom 已配置 `<z-paging>` 直接用)或手写分页。 列表页可用 z-pagingeasycom 已配置 `<z-paging>` 直接用)或手写分页。
手写分页的既有模式(参考 `src/pages/tabbar/home/home.vue`、`pages-blog/votes/votes.vue`): 手写分页的既有模式(参考 `src/pages/tabbar/home/home.vue`、`pages-blog/votes/votes.vue`):
@@ -457,7 +535,7 @@ onReachBottom(() => {
}) })
``` ```
### 5.6 依赖插件的页面(插件可用性 + 维护拦截) ### 5.7 依赖插件的页面(插件可用性 + 维护拦截)
Halo 是插件化 CMS,页面可能依赖插件(投票 plugin-vote、瞬间 PluginMoments 等): Halo 是插件化 CMS,页面可能依赖插件(投票 plugin-vote、瞬间 PluginMoments 等):
@@ -22,8 +22,7 @@ async function fetchNotices() {
/** 点击单条公告标题 → 详情页 */ /** 点击单条公告标题 → 详情页 */
function handleTap(item : INoticeListVo) { function handleTap(item : INoticeListVo) {
if (!item.name) if (!item.name) { return }
return
uni.navigateTo({ url: `/pages-blog/notice/detail?name=${item.name}` }) uni.navigateTo({ url: `/pages-blog/notice/detail?name=${item.name}` })
} }
@@ -38,49 +37,29 @@ onMounted(() => {
</script> </script>
<template> <template>
<view <view v-if="showList.length > 0"
v-if="showList.length > 0" class="uh-global-card-glass box-border mx-3 mt-3 mb-2 flex items-center rounded-xl px-3">
class="uh-global-card-glass box-border mx-3 mt-3 mb-2 flex items-center rounded-xl px-3"
>
<!-- 左侧公告入口 --> <!-- 左侧公告入口 -->
<view class="flex shrink-0 items-center gap-1 py-2 pr-3" @click="handleGoList"> <view class="flex shrink-0 items-center gap-2 py-2 pr-3" @click="handleGoList">
<text class="text-sm"> <wd-icon class-prefix="uhemoji2-icon" name="-happy-" size="36rpx" />
📢 <text class="text-sm font-bold text-red-400">
</text>
<text class="text-xs font-bold text-red-400">
公告 公告
</text> </text>
</view> </view>
<!-- 标题轮播(仅一条时静态展示) --> <!-- 标题轮播(仅一条时静态展示) -->
<view class="h-[60rpx] min-w-0 flex-1 overflow-hidden"> <view class="h-[60rpx] min-w-0 flex-1 overflow-hidden">
<swiper <swiper v-if="showList.length > 1" class="h-full w-full" vertical circular autoplay :interval="3500"
v-if="showList.length > 1" :duration="400">
class="h-full w-full" <swiper-item v-for="(item, index) in showList" :key="item.name || index" class="h-full w-full">
vertical <view class="flex h-full w-full items-center truncate text-xs text-gray-500"
circular @click="handleTap(item)">
autoplay
:interval="3500"
:duration="400"
>
<swiper-item
v-for="(item, index) in showList"
:key="item.name || index"
class="h-full w-full"
>
<view
class="flex h-full w-full items-center truncate text-xs text-gray-500"
@click="handleTap(item)"
>
{{ item.title }} {{ item.title }}
</view> </view>
</swiper-item> </swiper-item>
</swiper> </swiper>
<view <view v-else class="flex h-full w-full items-center truncate text-[24rpx] text-[#555]"
v-else @click="handleTap(showList[0])">
class="flex h-full w-full items-center truncate text-[24rpx] text-[#555]"
@click="handleTap(showList[0])"
>
{{ showList[0]?.title }} {{ showList[0]?.title }}
</view> </view>
</view> </view>
@@ -14,20 +14,27 @@
const navList = computed(() => { const navList = computed(() => {
const loveEnabled = !!(haloConfigs.value.loveConfig as { loveEnabled ?: boolean })?.loveEnabled const loveEnabled = !!(haloConfigs.value.loveConfig as { loveEnabled ?: boolean })?.loveEnabled
const socialEnabled = !!(haloConfigs.value.authorConfig?.social as { enabled ?: boolean } | undefined)?.enabled const socialEnabled = !!(haloConfigs.value.authorConfig?.social as { enabled ?: boolean } | undefined)?.enabled
// <wd-icon class-prefix="uhemoji-icon" name="-smile-" size="32rpx" />
return [ return [
{ {
key: 'archives', key: 'archives',
title: calcAuditModeEnabled.value ? '内容归档' : '文章归档', title: calcAuditModeEnabled.value ? '内容归档' : '文章归档',
bgColor: 'rgba(3, 169, 244, 0.95)', color: '#03A9F4',
icon: 'news', bgGlass: 'rgba(3, 169, 244, 0.14)',
borderColor: 'rgba(3, 169, 244, 0.35)',
iconPrefix: 'uhemoji2-icon',
icon: '-mask',
path: '/pages-blog/archives/archives', path: '/pages-blog/archives/archives',
show: true, show: true,
}, },
{ {
key: 'vote', key: 'vote',
title: '投票中心', title: '投票中心',
bgColor: 'rgba(0, 188, 212, 0.95)', color: '#00BCD4',
icon: 'box', bgGlass: 'rgba(0, 188, 212, 0.14)',
borderColor: 'rgba(0, 188, 212, 0.35)',
iconPrefix: 'uhemoji2-icon',
icon: '-confused',
path: '/pages-blog/votes/votes', path: '/pages-blog/votes/votes',
// show: !calcAuditModeEnabled.value && calcVotePluginEnabled.value, // show: !calcAuditModeEnabled.value && calcVotePluginEnabled.value,
show: true, show: true,
@@ -35,8 +42,11 @@
{ {
key: 'disclaimers', key: 'disclaimers',
title: '友情链接', title: '友情链接',
bgColor: 'rgba(0, 150, 136, 0.95)', color: '#009688',
icon: 'link', bgGlass: 'rgba(0, 150, 136, 0.14)',
borderColor: 'rgba(0, 150, 136, 0.35)',
iconPrefix: 'uhemoji2-icon',
icon: '-wink',
path: '/pages-blog/friend-links/friend-links', path: '/pages-blog/friend-links/friend-links',
// show: calcLinksPluginEnabled.value, // show: calcLinksPluginEnabled.value,
show: true, show: true,
@@ -44,8 +54,11 @@
{ {
key: 'love', key: 'love',
title: '恋爱日记', title: '恋爱日记',
bgColor: 'rgba(255, 76, 103, 0.95)', color: '#FF4C67',
icon: 'heart', bgGlass: 'rgba(255, 76, 103, 0.14)',
borderColor: 'rgba(255, 76, 103, 0.075)',
iconPrefix: 'uhemoji2-icon',
icon: '-in-love',
path: '/pages-blog/love/love', path: '/pages-blog/love/love',
// show: loveEnabled, // show: loveEnabled,
show: true, show: true,
@@ -53,8 +66,11 @@
{ {
key: 'contact-blogger', key: 'contact-blogger',
title: '联系博主', title: '联系博主',
bgColor: 'rgba(255, 152, 0, 0.95)', color: '#FF9800',
icon: 'message', bgGlass: 'rgba(255, 152, 0, 0.14)',
borderColor: 'rgba(255, 152, 0, 0.35)',
iconPrefix: 'uhemoji2-icon',
icon: '-cool',
path: '/pages-blog/contact/contact', path: '/pages-blog/contact/contact',
show: socialEnabled, show: socialEnabled,
}, },
@@ -68,21 +84,37 @@
</script> </script>
<template> <template>
<view v-if="navList.length" class="overflow-hidden rounded-xl p-3 px-4 mb-3"> <view v-if="navList.length" class="box-border overflow-hidden rounded-xl p-3 px-4 mb-3">
<uh-section-title class="mb-4"> <uh-section-title class="mb-4">
快捷导航 快捷导航
</uh-section-title> </uh-section-title>
<view class="grid grid-cols-5 gap-4"> <view class="grid grid-cols-5 gap-4">
<view v-for="item in navList" :key="item.key" class="flex flex-col items-center gap-2" <view v-for="item in navList" :key="item.key" class="flex flex-col items-center gap-2"
@click="handleClickNav(item)"> @click="handleClickNav(item)">
<view class="uh-global-card-glass border h-12 w-12 flex items-center justify-center rounded-2xl" <view
:style="{ backgroundColor: item.bgColor }"> class="uh-global-card-glass uh-shadow-xs h-13 w-13 flex items-center justify-center rounded-2xl border transition-transform active:scale-90"
<wd-icon :name="item.icon" size="24px" color="#fff" /> :style="{
backgroundColor: item.bgGlass
}">
<wd-icon :class-prefix="item.iconPrefix" :name="item.icon" size="64rpx" :color="item.color" />
</view> </view>
<view class="text-xs text-gray-900 font-bold"> <view class="text-xs text-gray-900">
{{ item.title }} {{ item.title }}
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<style scoped lang="scss">
/* 彩色玻璃图标:低透明度同色底 + 同色描边 + 柔和同色投影,图标用实体色保证通透感 */
.quick-nav-icon {
backdrop-filter: blur(16rpx) saturate(160%);
-webkit-backdrop-filter: blur(16rpx) saturate(160%);
/* 低端安卓 WebView 不支持 backdrop-filter 的兜底:提高底色不透明度保证可读性 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
background-color: rgb(255 255 255 / 60%) !important;
}
}
</style>
+12
View File
@@ -6,12 +6,15 @@
useBack : boolean; useBack : boolean;
useTitle : boolean; useTitle : boolean;
defaultTitle ?: string; defaultTitle ?: string;
titleColor ?: string;
scrollTitle ?: string; scrollTitle ?: string;
needPlaceholder ?: boolean;
} }
const props = withDefaults(defineProps<IProps>(), { const props = withDefaults(defineProps<IProps>(), {
useBack: true, useBack: true,
useTitle: true, useTitle: true,
needPlaceholder: true,
}) })
const slots = useSlots() const slots = useSlots()
@@ -31,6 +34,10 @@
const customCalss = computed(() => { const customCalss = computed(() => {
const _class = [] const _class = []
if (props.titleColor) {
_class.push(props.titleColor)
return
}
if (scrollThreshold.value) { if (scrollThreshold.value) {
_class.push('text-white') _class.push('text-white')
} }
@@ -61,6 +68,7 @@
</script> </script>
<template> <template>
<view class="w-full box-border">
<view class="box-border pt-safe w-full fixed left-0 top-0 z-50" :class="customCalss" :style="[customStyle]"> <view class="box-border pt-safe w-full fixed left-0 top-0 z-50" :class="customCalss" :style="[customStyle]">
<view class="w-full h-[46px] flex items-center gap-x-4 box-border px-3 backdrop-blur-[2rpx]"> <view class="w-full h-[46px] flex items-center gap-x-4 box-border px-3 backdrop-blur-[2rpx]">
<!-- 左边 --> <!-- 左边 -->
@@ -82,4 +90,8 @@
</view> </view>
</view> </view>
</view> </view>
<view v-if="props.needPlaceholder" class="box-border w-full pt-safe">
<view class="w-full h-[46px]"></view>
</view>
</view>
</template> </template>
@@ -81,9 +81,7 @@
<!-- 头部:标题 + 关闭 --> <!-- 头部:标题 + 关闭 -->
<view class="flex items-center justify-between"> <view class="flex items-center justify-between">
<view class="flex items-center gap-2"> <view class="flex items-center gap-2">
<text class="text-sm"> <wd-icon class-prefix="uhemoji2-icon" name="-happy-" size="36rpx" />
📢
</text>
<text class="text-md font-bold text-gray-900"> <text class="text-md font-bold text-gray-900">
最新公告 最新公告
</text> </text>
@@ -101,7 +99,8 @@
<!-- 内容 --> <!-- 内容 -->
<view class="mt-4"> <view class="mt-4">
<image v-if="notice.cover" :src="checkImageUrl(notice.cover)" class="w-full h-34 rounded-lg mb-2"></image> <image v-if="notice.cover" :src="checkImageUrl(notice.cover)" class="w-full h-34 rounded-lg mb-2">
</image>
<view class="text-md font-bold leading-snug text-gray-900"> <view class="text-md font-bold leading-snug text-gray-900">
{{ notice.title }} {{ notice.title }}
</view> </view>
@@ -1,8 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
/**
* 插件不可用提示(源自旧项目 components/plugin-unavailable,新建复刻)
* 当依赖的 Halo 插件未安装/未启用时展示:插件 logo、名称、错误标签、描述、插件地址、复制/反馈按钮
*/
import { computed } from 'vue' import { computed } from 'vue'
import { NeedPlugins } from '@/hooks/usePluginAvailable' import { NeedPlugins } from '@/hooks/usePluginAvailable'
@@ -46,17 +42,6 @@
...props.customStyle, ...props.customStyle,
})) }))
function copy() {
if (!pluginInfo.value.url)
return
uni.setClipboardData({
data: pluginInfo.value.url,
showToast: false,
success: () => {
uni.showToast({ icon: 'none', title: '插件地址已复制' })
},
})
}
</script> </script>
<template> <template>
@@ -13,7 +13,7 @@
} }
// 获取当前页面,并且设置黑名单模式,因为有的页面可能不需要滚动到顶部 // 获取当前页面,并且设置黑名单模式,因为有的页面可能不需要滚动到顶部
const balckList = ['pages/maintenance/maintenance'] const balckList = ['pages/maintenance/maintenance','pages-blog/setting/setting']
const pages = getCurrentPages() const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] const currentPage = pages[pages.length - 1]
const visible = computed(() => { const visible = computed(() => {
@@ -461,12 +461,10 @@ const globalAppSettings = computed(() => settingStore.settings)
<template> <template>
<view class="box-border min-h-screen w-screen flex flex-col bg-page pb-safe"> <view class="box-border min-h-screen w-screen flex flex-col bg-page pb-safe">
<!-- 顶部导航 --> <!-- 顶部导航 -->
<uh-navbar default-title="内容详情" :scroll-title="result?.spec?.title" /> <uh-navbar default-title="内容详情" :need-placeholder="false" :scroll-title="result?.spec?.title" />
<!-- 骨架屏 --> <uh-data-loading v-if="loadingStatus !== 'success'" :loading-status="loadingStatus"
<view v-if="loadingStatus !== 'success'" class="box-border p-4"> @refresh="handleGetData()" />
<uh-data-loading :loading-status="loadingStatus" @refresh="handleGetData()" />
</view>
<view v-else class="box-border pt-72"> <view v-else class="box-border pt-72">
<!-- 顶部背景封面区域 --> <!-- 顶部背景封面区域 -->
@@ -479,31 +477,26 @@ const globalAppSettings = computed(() => settingStore.settings)
class="uh-global-card-glass box-border overflow-hidden border rounded-lt-3xl rounded-rt-3xl border-b-none -translate-y-12" class="uh-global-card-glass box-border overflow-hidden border rounded-lt-3xl rounded-rt-3xl border-b-none -translate-y-12"
:style="{ :style="{
boxShadow: '0 -16rpx 12rpx rgba(0, 0, 0, 0.035)', boxShadow: '0 -16rpx 12rpx rgba(0, 0, 0, 0.035)',
}" }">
>
<!-- 顶部信息 --> <!-- 顶部信息 -->
<view class="box-border flex flex-col gap-3 p-3 pb-2"> <view class="box-border flex flex-col gap-3 p-3 pb-2">
<view class="flex items-center gap-x-2"> <view class="flex items-center gap-x-2">
<image <image :src="result.owner.avatar" class="uh-global-card-glass block h-6 w-6 rounded-full"
:src="result.owner.avatar" class="uh-global-card-glass block h-6 w-6 rounded-full" mode="aspectFill" />
mode="aspectFill"
/>
<text class="text-sm font-semibold">{{ result?.owner?.displayName }}</text> <text class="text-sm font-semibold">{{ result?.owner?.displayName }}</text>
</view> </view>
<view class="font-semibold"> <view class="font-semibold">
{{ result?.spec.title }} {{ result?.spec.title }}
</view> </view>
<view class="flex flex-wrap items-center gap-2 text-xs"> <view class="flex flex-wrap items-center gap-2 text-xs">
<text <text v-for="(item, index) in result?.categories" :key="index"
v-for="(item, index) in result?.categories" :key="index" class="uh-global-card-glass uh-shadow-xs border rounded-full px-2 py-1"
class="uh-global-card-glass uh-shadow-xs border rounded-full px-2 py-1" @click="handleToCate(item)" @click="handleToCate(item)">
>
{{ item.spec.displayName }} {{ item.spec.displayName }}
</text> </text>
<text <text v-for="(item, index) in result?.tags" :key="index"
v-for="(item, index) in result?.tags" :key="index" class="uh-global-card-glass uh-shadow-xs border rounded-full px-2 py-1"
class="uh-global-card-glass uh-shadow-xs border rounded-full px-2 py-1" @click="handleToTag(item)" @click="handleToTag(item)">
>
#{{ item.spec.displayName }} #{{ item.spec.displayName }}
</text> </text>
</view> </view>
@@ -545,39 +538,31 @@ const globalAppSettings = computed(() => settingStore.settings)
<!-- 受限阅读 --> <!-- 受限阅读 -->
<template v-if="checkPostRestrictRead(result!)"> <template v-if="checkPostRestrictRead(result!)">
<view v-if="showContentArr.length === 0"> <view v-if="showContentArr.length === 0">
<uh-restrict-read-skeleton <uh-restrict-read-skeleton :loading="true" :lines="3"
:loading="true" :lines="3"
:tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result!)}可见」`" :tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result!)}可见」`"
:button-text="getRestrictReadTypeName(result!)" button-color="#1890ff" :button-text="getRestrictReadTypeName(result!)" button-color="#1890ff"
@refresh="readMore" @refresh="readMore" />
/>
</view> </view>
<view v-for="(showContent, showContentIndex) in showContentArr" v-else :key="showContentIndex"> <view v-for="(showContent, showContentIndex) in showContentArr" v-else :key="showContentIndex">
<mp-html <mp-html lazy-load :domain="markdownConfig.domain ?? ''"
lazy-load :domain="markdownConfig.domain ?? ''"
:loading-img="markdownConfig.loadingGif" scroll-table selectable :loading-img="markdownConfig.loadingGif" scroll-table selectable
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle" :tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
:content="showContent" :markdown="true" :show-line-number="true" :content="showContent" :markdown="true" :show-line-number="true"
:show-language-name="true" copy-by-long-press :show-language-name="true" copy-by-long-press />
/> <uh-restrict-read-skeleton :loading="true" :lines="3"
<uh-restrict-read-skeleton
:loading="true" :lines="3"
:tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result!)}可见」`" :tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result!)}可见」`"
:button-text="getRestrictReadTypeName(result!)" button-color="#1890ff" :button-text="getRestrictReadTypeName(result!)" button-color="#1890ff"
@refresh="readMore" @refresh="readMore" />
/>
</view> </view>
</template> </template>
<!-- 正常渲染 --> <!-- 正常渲染 -->
<template v-else> <template v-else>
<mp-html <mp-html lazy-load :domain="markdownConfig.domain ?? ''"
lazy-load :domain="markdownConfig.domain ?? ''"
:loading-img="markdownConfig.loadingGif" scroll-table selectable :loading-img="markdownConfig.loadingGif" scroll-table selectable
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle" :tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
:content="result?.content?.raw || ''" :markdown="true" :show-line-number="true" :content="result?.content?.raw || ''" :markdown="true" :show-line-number="true"
:show-language-name="true" copy-by-long-press :show-language-name="true" copy-by-long-press />
/>
</template> </template>
</view> </view>
@@ -601,11 +586,9 @@ const globalAppSettings = computed(() => settingStore.settings)
</view> </view>
<!-- 评论区域 --> <!-- 评论区域 -->
<uh-comment-list <uh-comment-list v-if="calcIsShowComment && result" :disallow-comment="!result.spec.allowComment"
v-if="calcIsShowComment && result" :disallow-comment="!result.spec.allowComment"
:post-name="result.metadata.name" :post="result" @on-comment="handleOnComment" :post-name="result.metadata.name" :post="result" @on-comment="handleOnComment"
@on-comment-detail="handleOnShowCommentDetail" @on-comment-detail="handleOnShowCommentDetail" />
/>
</view> </view>
</view> </view>
@@ -613,27 +596,22 @@ const globalAppSettings = computed(() => settingStore.settings)
<view class="fixed bottom-8 left-1/2 z-10 flex items-center justify-center pb-safe -translate-x-1/2"> <view class="fixed bottom-8 left-1/2 z-10 flex items-center justify-center pb-safe -translate-x-1/2">
<!-- #7BE200 --> <!-- #7BE200 -->
<view <view
class="uh-global-card-glass box-border flex items-center justify-center gap-2 border rounded-full p-1 text-primary" class="uh-global-card-glass box-border flex items-center justify-center gap-2 border rounded-full p-1 text-primary">
>
<view <view
class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none" class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none"
:class="{ active: hasUpvoted() }" @click="handleDoLikes" :class="{ active: hasUpvoted() }" @click="handleDoLikes">
>
<wd-icon class-prefix="uhemoji-icon" name="-kiss-" size="36rpx" /> <wd-icon class-prefix="uhemoji-icon" name="-kiss-" size="36rpx" />
<text class="shrink-0 text-sm text-gray-900 font-semibold">点赞</text> <text class="shrink-0 text-sm text-gray-900 font-semibold">点赞</text>
</view> </view>
<view <view v-if="calcIsShowComment"
v-if="calcIsShowComment"
class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none" class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none"
@click="handleToComment()" @click="handleToComment()">
>
<wd-icon class-prefix="uhemoji-icon" name="-thinking" size="36rpx" /> <wd-icon class-prefix="uhemoji-icon" name="-thinking" size="36rpx" />
<text class="shrink-0 text-sm text-gray-900 font-semibold">评论</text> <text class="shrink-0 text-sm text-gray-900 font-semibold">评论</text>
</view> </view>
<view <view
class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none" class="uh-global-card-glass box-border h-[72rpx] flex flex-1 items-center justify-center gap-x-1 border rounded-full px-4 shadow-none"
@click="handleTogglePostFavorite" @click="handleTogglePostFavorite">
>
<wd-icon class-prefix="uhemoji-icon" name="-smile-" size="36rpx" /> <wd-icon class-prefix="uhemoji-icon" name="-smile-" size="36rpx" />
<text class="shrink-0 text-sm text-gray-900 font-semibold" <text class="shrink-0 text-sm text-gray-900 font-semibold"
:style="hasFavorited() ? { color: '#ffb300' } : ''">{{ hasFavorited() ? '已收藏' : '收藏' }}</text> :style="hasFavorited() ? { color: '#ffb300' } : ''">{{ hasFavorited() ? '已收藏' : '收藏' }}</text>
@@ -643,33 +621,25 @@ const globalAppSettings = computed(() => settingStore.settings)
</view> </view>
<!-- 密码弹窗 --> <!-- 密码弹窗 -->
<wd-dialog <wd-dialog v-model="passwordModal.show" title="验证提示" :show-cancel="true" show-confirm-button confirm-text="确定"
v-model="passwordModal.show" title="验证提示" :show-cancel="true" show-confirm-button confirm-text="确定" @confirm="restrictReadCheck">
@confirm="restrictReadCheck"
>
<view class="modal-body py-4"> <view class="modal-body py-4">
<wd-input v-model="restrictReadInputCode" placeholder="请输入密码" /> <wd-input v-model="restrictReadInputCode" placeholder="请输入密码" />
</view> </view>
</wd-dialog> </wd-dialog>
<!-- 验证码弹窗 --> <!-- 验证码弹窗 -->
<wd-dialog <wd-dialog v-model="verificationCodeModal.show" title="验证提示" :show-cancel="true" confirm-text="确定"
v-model="verificationCodeModal.show" title="验证提示" :show-cancel="true" confirm-text="确定" @confirm="restrictReadCheck">
@confirm="restrictReadCheck"
>
<view class="modal-body py-4"> <view class="modal-body py-4">
<image <image v-if="verificationCodeModal.imgUrl" :src="verificationCodeModal.imgUrl"
v-if="verificationCodeModal.imgUrl" :src="verificationCodeModal.imgUrl" class="modal-code-img mb-4 h-[200rpx] w-full" mode="aspectFit" />
class="modal-code-img mb-4 h-[200rpx] w-full" mode="aspectFit"
/>
<wd-input v-model="restrictReadInputCode" placeholder="请输入验证码" class="mt-2" /> <wd-input v-model="restrictReadInputCode" placeholder="请输入验证码" class="mt-2" />
</view> </view>
</wd-dialog> </wd-dialog>
<!-- 评论弹窗 --> <!-- 评论弹窗 -->
<uh-comment-modal <uh-comment-modal v-if="commentModal.show" :show="commentModal.show" :is-comment="commentModal.isComment"
v-if="commentModal.show" :show="commentModal.show" :is-comment="commentModal.isComment" :title="commentModal.title" :post-name="commentModal.postName" @on-close="handleOnCommentModalClose" />
:title="commentModal.title" :post-name="commentModal.postName" @on-close="handleOnCommentModalClose"
/>
</view> </view>
</template> </template>
+4 -7
View File
@@ -1,10 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
/**
* 收藏页(纯本地,文章 × 瞬间)
* 功能:双 Tab(仿图库顶部吸顶胶囊 chip)本地收藏列表 + 跳详情 + 删除 + 状态舞台
* 数据:useFavoritesStore(persist),快照自包含,无需网络加载
* 状态:useDataLoadingStatus + uh-data-loading(与 tabbar 页同构);本地同步数据,状态直接推导
*/
import { computed, ref, watchEffect } from 'vue' import { computed, ref, watchEffect } from 'vue'
import { formatTime } from '@/utils/formatTime' import { formatTime } from '@/utils/formatTime'
import { useFavoritesStore } from '@/store/favorites' import { useFavoritesStore } from '@/store/favorites'
@@ -14,7 +8,7 @@ import type { FavoriteKind, IFavoriteItem } from '@/utils/favorite'
definePage({ definePage({
style: { style: {
navigationBarTitleText: '我的收藏', navigationBarTitleText: '我的收藏',
backgroundColor: '#f6f3ee', navigationStyle: 'custom',
}, },
}) })
@@ -80,6 +74,9 @@ const emptyText = computed(() => (activeKind.value === 'post' ? '还没有收藏
<template> <template>
<view class="box-border min-h-screen w-screen bg-page pb-10"> <view class="box-border min-h-screen w-screen bg-page pb-10">
<!-- 自定义导航 -->
<uh-navbar default-title="我的收藏" title-color="text-gray-900"></uh-navbar>
<!-- 顶部类型 Tab(与图库页同款:吸顶玻璃胶囊 chip) --> <!-- 顶部类型 Tab(与图库页同款:吸顶玻璃胶囊 chip) -->
<wd-sticky> <wd-sticky>
<scroll-view scroll-x class="w-full whitespace-nowrap"> <scroll-view scroll-x class="w-full whitespace-nowrap">
+47 -87
View File
@@ -1,10 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
/**
* 偏好设置页(两层:站点默认 L0 + 本地差异 L1-L,设计见 .docs/config-system-v2-redesign §3-4)
* - 每项可「跟随站点默认」(差异为空)或覆盖为具体值;改动即写本地差异(uh_pref_local_v1)即时生效;
* - 底部「重置为站点默认」= 清空全部本地差异,回退站长在插件后台配置的默认(未配置则为内置默认)。
* - 风格:对齐全站设计语言(bg-page + uh-global-card-glass + uh-section-title)
*/
import { ref } from 'vue' import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { DefaultAppSettings } from '@/config/appSettings' import { DefaultAppSettings } from '@/config/appSettings'
@@ -16,6 +10,7 @@ import type { LocalPrefs } from '@/utils/preference'
definePage({ definePage({
style: { style: {
navigationBarTitleText: '偏好设置', navigationBarTitleText: '偏好设置',
navigationStyle: 'custom'
}, },
}) })
@@ -88,24 +83,7 @@ const layoutPrefs: PrefDef[] = [
] ]
const featurePrefs : PrefDef[] = [ const featurePrefs : PrefDef[] = [
{ key: 'useWaterfull', label: '图库瀑布流模式', kind: 'bool', path: ['gallery', 'useWaterfull'] },
{ key: 'useSimple', label: '友链简洁模式', kind: 'bool', path: ['links', 'useSimple'] },
{ key: 'isAvatarRadius', label: '是否圆形头像', kind: 'bool', path: ['isAvatarRadius'] }, { key: 'isAvatarRadius', label: '是否圆形头像', kind: 'bool', path: ['isAvatarRadius'] },
{ key: 'useDot', label: '轮播图指示器', kind: 'bool', path: ['banner', 'useDot'] },
{
key: 'dotPosition',
label: '指示器位置',
kind: 'enum',
path: ['banner', 'dotPosition'],
options: [
{ label: '右边', value: 'right' },
{ label: '下边', value: 'bottom' },
],
siteLabelOf: (value: string) => {
const map: Record<string, string> = { right: '右边', bottom: '下边', left: '左边', top: '上边' }
return map[value] || value
},
},
] ]
/* ---------------- 状态读取 ---------------- */ /* ---------------- 状态读取 ---------------- */
@@ -205,143 +183,125 @@ function handleResetAll() {
</script> </script>
<template> <template>
<view class="app-page box-border min-h-screen bg-page pb-[200rpx]"> <view class="box-border min-h-screen bg-page">
<!-- 说明 --> <!-- 自定义标题 -->
<view class="pref-tip uh-global-card-glass mx-4 mt-4 flex items-start gap-2 rounded-xl px-4 py-3"> <uh-navbar default-title="偏好设置" title-color="text-gray-900" :need-placeholder="true"></uh-navbar>
<wd-icon name="info" size="28rpx" color="#a8a294" class="mt-0.5 shrink-0" />
<text class="tip-text flex-1 text-2xs text-gray-400 leading-[1.6]">
你的偏好仅保存在本机未自定义的项自动跟随站长在插件后台配置的站点默认点击底部重置为站点默认可清空全部本地偏好
</text>
</view>
<!-- 内容区域 -->
<view class="box-border p-3 flex flex-col gap-y-6">
<!-- 布局设置 --> <!-- 布局设置 -->
<uh-section-title class="mx-4 mb-3 mt-6 text-[30rpx]"> <view class="flex flex-col gap-y-3">
<uh-section-title>
布局 布局
<template #right> <template #right>
<text class="text-2xs text-gray-400">应用以及文章列表布局设置</text> <text class="text-2xs text-gray-400">应用以及文章列表布局设置</text>
</template> </template>
</uh-section-title> </uh-section-title>
<view class="setting-sheet uh-global-card-glass mx-4 overflow-hidden rounded-2xl"> <view class="uh-global-card-glass overflow-hidden rounded-2xl">
<view <view v-for="(def, index) in layoutPrefs" :key="def.key"
v-for="(def, index) in layoutPrefs"
:key="def.key"
class="pick-row flex items-center justify-between px-4 py-4" class="pick-row flex items-center justify-between px-4 py-4"
:class="index < layoutPrefs.length - 1 ? 'border-b border-black/5' : ''" :class="index < layoutPrefs.length - 1 ? 'border-b border-black/5' : ''"
@click="handleOpenEnum(def)" @click="handleOpenEnum(def)">
>
<view class="row-left flex flex-col gap-1"> <view class="row-left flex flex-col gap-1">
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text> <text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
<view class="flex items-center gap-2"> <view class="flex items-center gap-2">
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text> <text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
<view v-else class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none"> <view v-else
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
已自定义 已自定义
</view> </view>
</view> </view>
</view> </view>
<view class="row-value flex items-center gap-2"> <view class="row-value flex items-center gap-2">
<text class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text> <text
class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text>
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" /> <wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
</view> </view>
</view> </view>
</view> </view>
</view>
<!-- 功能设置 --> <!-- 功能设置 -->
<uh-section-title class="mx-4 mb-3 mt-6 text-[30rpx]"> <view class="flex flex-col gap-y-3">
<uh-section-title>
功能 功能
<template #right> <template #right>
<text class="text-2xs text-gray-400">一些常用的功能性设置</text> <text class="text-2xs text-gray-400">一些常用的功能性设置</text>
</template> </template>
</uh-section-title> </uh-section-title>
<view class="setting-sheet uh-global-card-glass mx-4 overflow-hidden rounded-2xl"> <view class="setting-sheet uh-global-card-glass overflow-hidden rounded-2xl">
<template v-for="(def, index) in featurePrefs" :key="def.key"> <template v-for="(def, index) in featurePrefs" :key="def.key">
<!-- 布尔开关 --> <!-- 布尔开关 -->
<view <view v-if="def.kind === 'bool'" class="switch-row flex items-center justify-between px-4 py-4"
v-if="def.kind === 'bool'" :class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''">
class="switch-row flex items-center justify-between px-4 py-4"
:class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''"
>
<view class="row-left flex flex-col gap-1"> <view class="row-left flex flex-col gap-1">
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text> <text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
<view class="flex items-center gap-2"> <view class="flex items-center gap-2">
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text> <text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
<template v-else> <template v-else>
<view class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none"> <view
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
已自定义 已自定义
</view> </view>
<text class="revert-text text-2xs text-gray-400 underline" @click.stop="handleRevert(def.path)">恢复默认</text> <text class="revert-text text-2xs text-gray-400 underline"
@click.stop="handleRevert(def.path)">恢复默认</text>
</template> </template>
</view> </view>
</view> </view>
<wd-switch :model-value="!!valueOf(def.path)" @change="handleSwitchChange(def, $event)" /> <wd-switch :model-value="def.path" @change="handleSwitchChange(def, $event)" />
</view> </view>
<!-- 枚举选择(指示器位置) --> <!-- 枚举选择(指示器位置) -->
<view <view v-else class="pick-row flex items-center justify-between px-4 py-4"
v-else
class="pick-row flex items-center justify-between px-4 py-4"
:class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''" :class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''"
@click="handleOpenEnum(def)" @click="handleOpenEnum(def)">
>
<view class="row-left flex flex-col gap-1"> <view class="row-left flex flex-col gap-1">
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text> <text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
<view class="flex items-center gap-2"> <view class="flex items-center gap-2">
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text> <text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
<template v-else> <template v-else>
<view class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none"> <view
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
已自定义 已自定义
</view> </view>
<text class="revert-text text-2xs text-gray-400 underline" @click.stop="handleRevert(def.path)">恢复默认</text> <text class="revert-text text-2xs text-gray-400 underline"
@click.stop="handleRevert(def.path)">恢复默认</text>
</template> </template>
</view> </view>
</view> </view>
<view class="row-value flex items-center gap-2"> <view class="row-value flex items-center gap-2">
<text class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text> <text
class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text>
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" /> <wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
</view> </view>
</view> </view>
</template> </template>
</view> </view>
</view>
<!-- 底部操作栏(玻璃悬浮 + 底部安全区适配) --> <!-- 底部操作栏(玻璃悬浮-->
<view class="btn-bar uh-global-card-glass fixed bottom-0 left-0 box-border w-screen px-4 pt-3 pb-safe"> <view class="box-border w-full px-2">
<view class="reset-btn h-[84rpx] w-full flex items-center justify-center rounded-full bg-gray-900 active:opacity-80" @click="handleResetAll"> <uh-button custom-class="uh-global-card-glass py-2 !rounded-full"
<text class="text-[28rpx] text-white font-bold">重置为站点默认</text> @click="handleResetAll">恢复默认</uh-button>
</view> </view>
</view> </view>
<!-- 枚举选择底部弹层 --> <!-- 枚举选择底部弹层 -->
<wd-popup <wd-popup v-model="enumSheet.show" position="bottom" closable custom-style="border-radius: 24rpx 24rpx 0 0;"
v-model="enumSheet.show" @close="handleCloseEnum">
position="bottom"
closable
custom-style="border-radius: 24rpx 24rpx 0 0;"
@close="handleCloseEnum"
>
<view v-if="enumSheet.def" class="enum-sheet box-border w-full pb-[env(safe-area-inset-bottom)]"> <view v-if="enumSheet.def" class="enum-sheet box-border w-full pb-[env(safe-area-inset-bottom)]">
<view class="enum-title py-6 text-center text-[30rpx] text-gray-900 font-bold"> <view class="enum-title py-6 text-center text-[30rpx] text-gray-900 font-bold">
{{ enumSheet.def.label }} {{ enumSheet.def.label }}
</view> </view>
<view <view class="enum-item flex items-center justify-between px-6 py-5"
class="enum-item flex items-center justify-between px-6 py-5"
:class="isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'" :class="isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
@click="handleChooseEnum(null)" @click="handleChooseEnum(null)">
>
<text class="text-[28rpx]">跟随站点默认</text> <text class="text-[28rpx]">跟随站点默认</text>
<wd-icon v-if="isFollowing(enumSheet.def)" name="check" size="16px" color="#4d7c0f" /> <wd-icon v-if="isFollowing(enumSheet.def)" name="check" size="16px" color="#4d7c0f" />
</view> </view>
<view <view v-for="opt in enumSheet.def.options" :key="opt.value"
v-for="opt in enumSheet.def.options"
:key="opt.value"
class="enum-item flex items-center justify-between border-t border-[#f0ece2] px-6 py-5" class="enum-item flex items-center justify-between border-t border-[#f0ece2] px-6 py-5"
:class="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'" :class="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
@click="handleChooseEnum(opt.value)" @click="handleChooseEnum(opt.value)">
>
<text class="text-[28rpx]">{{ opt.label }}</text> <text class="text-[28rpx]">{{ opt.label }}</text>
<wd-icon <wd-icon v-if="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def)"
v-if="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def)" name="check" size="16px" color="#4d7c0f" />
name="check"
size="16px"
color="#4d7c0f"
/>
</view> </view>
</view> </view>
</wd-popup> </wd-popup>
+22 -3
View File
@@ -1,14 +1,33 @@
<script setup lang="ts"> <script setup lang="ts">
import { DataLoadingStatusEnum } from '@/hooks/useDataLoadingStatus' import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
definePage({ definePage({
style: { style: {
navigationBarTitleText: '测试页面', navigationBarTitleText: '测试页面',
navigationStyle: 'custom',
}, },
}) })
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
setTimeout(() => {
updateLoadingStatus(DataLoadingStatusEnum.Success)
}, 3000)
</script> </script>
<template> <template>
<view class="w-full h-screen"> <view class="w-full min-h-screen bg-page">
<uh-data-loading :loading-status="DataLoadingStatusEnum.Loading"></uh-data-loading> <uh-navbar default-title="测试页面" :need-placeholder="true" title-color="text-gray-900"></uh-navbar>
<!-- 内容区由于 uh-navbar 内置有占位所以我们的页面的主要内容应该从这里开始比如这里就可以设置内边距或者其他样式最外层的 <view class="w-full min-h-screen bg-page"> 仅作为容器-->
<view class="box-border px-3">
<!-- 加载状态 -->
<uh-data-loading v-if="loadingStatus!==DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
min-height="80vh"></uh-data-loading>
<!-- 实际内容 -->
<view v-else>
请求成功啦
</view>
</view>
</view> </view>
</template> </template>
+38 -32
View File
@@ -82,6 +82,7 @@ const allStats = computed(() => [
interface INavItem { interface INavItem {
key : string key : string
title : string title : string
iconPrefix ?: string
icon : string icon : string
/** 图标块背景色(与首页快捷导航同色板,同一功能同色) */ /** 图标块背景色(与首页快捷导航同色板,同一功能同色) */
bgColor : string bgColor : string
@@ -134,7 +135,8 @@ async function handleGetNavList() {
{ {
key: 'favorites', key: 'favorites',
title: '我的收藏', title: '我的收藏',
icon: 'star', iconPrefix: 'uhemoji2-icon',
icon: '-smiling',
bgColor: 'rgba(255, 179, 0, 0.95)', bgColor: 'rgba(255, 179, 0, 0.95)',
rightText: '', rightText: '',
path: '/pages-blog/favorites/favorites', path: '/pages-blog/favorites/favorites',
@@ -144,7 +146,8 @@ async function handleGetNavList() {
{ {
key: 'data-visual', key: 'data-visual',
title: '数据看板', title: '数据看板',
icon: 'chart', iconPrefix: 'uhemoji2-icon',
icon: '-surprised',
bgColor: 'rgba(102, 60, 201, 0.95)', bgColor: 'rgba(102, 60, 201, 0.95)',
rightText: '站点数据可视化', rightText: '站点数据可视化',
path: '/pages-blog/data-visual/data-visual', path: '/pages-blog/data-visual/data-visual',
@@ -154,7 +157,8 @@ async function handleGetNavList() {
{ {
key: 'archives', key: 'archives',
title: calcAuditModeEnabled.value ? '内容归档' : '文章归档', title: calcAuditModeEnabled.value ? '内容归档' : '文章归档',
icon: 'folder', iconPrefix: 'uhemoji2-icon',
icon: '-mask',
bgColor: 'rgba(3, 169, 244, 0.95)', bgColor: 'rgba(3, 169, 244, 0.95)',
rightText: calcAuditModeEnabled.value ? '全部已归档内容' : '全部已归档文章', rightText: calcAuditModeEnabled.value ? '全部已归档内容' : '全部已归档文章',
path: '/pages-blog/archives/archives', path: '/pages-blog/archives/archives',
@@ -164,7 +168,8 @@ async function handleGetNavList() {
{ {
key: 'love', key: 'love',
title: '恋爱日记', title: '恋爱日记',
icon: 'heart', iconPrefix: 'uhemoji2-icon',
icon: '-in-love',
bgColor: 'rgba(255, 76, 103, 0.95)', bgColor: 'rgba(255, 76, 103, 0.95)',
rightText: '博主的恋爱日记', rightText: '博主的恋爱日记',
path: '/pages-blog/love/love', path: '/pages-blog/love/love',
@@ -175,7 +180,8 @@ async function handleGetNavList() {
{ {
key: 'vote', key: 'vote',
title: '投票中心', title: '投票中心',
icon: 'box', iconPrefix: 'uhemoji2-icon',
icon: '-confused',
bgColor: 'rgba(0, 188, 212, 0.95)', bgColor: 'rgba(0, 188, 212, 0.95)',
rightText: '查看和进行投票', rightText: '查看和进行投票',
path: '/pages-blog/votes/votes', path: '/pages-blog/votes/votes',
@@ -186,7 +192,8 @@ async function handleGetNavList() {
{ {
key: 'friend-links', key: 'friend-links',
title: '友情链接', title: '友情链接',
icon: 'link', iconPrefix: 'uhemoji2-icon',
icon: '-cool',
bgColor: 'rgba(0, 150, 136, 0.95)', bgColor: 'rgba(0, 150, 136, 0.95)',
rightText: '看看博主朋友们吧', rightText: '看看博主朋友们吧',
path: '/pages-blog/friend-links/friend-links', path: '/pages-blog/friend-links/friend-links',
@@ -197,7 +204,8 @@ async function handleGetNavList() {
{ {
key: 'disclaimers', key: 'disclaimers',
title: '免责声明', title: '免责声明',
icon: 'map', iconPrefix: 'uhemoji2-icon',
icon: '-smirking',
bgColor: 'rgba(121, 85, 72, 0.95)', bgColor: 'rgba(121, 85, 72, 0.95)',
rightText: '博客内容免责声明', rightText: '博客内容免责声明',
path: '/pages-blog/disclaimers/disclaimers', path: '/pages-blog/disclaimers/disclaimers',
@@ -208,7 +216,8 @@ async function handleGetNavList() {
{ {
key: 'contact-blogger', key: 'contact-blogger',
title: '联系博主', title: '联系博主',
icon: 'message', iconPrefix: 'uhemoji2-icon',
icon: '-wink',
bgColor: 'rgba(255, 152, 0, 0.95)', bgColor: 'rgba(255, 152, 0, 0.95)',
rightText: '博主常用联系方式', rightText: '博主常用联系方式',
path: '/pages-blog/contact/contact', path: '/pages-blog/contact/contact',
@@ -219,7 +228,8 @@ async function handleGetNavList() {
{ {
key: 'about', key: 'about',
title: '关于项目', title: '关于项目',
icon: 'info', iconPrefix: 'uhemoji2-icon',
icon: '-happy-',
bgColor: 'rgba(96, 125, 139, 0.95)', bgColor: 'rgba(96, 125, 139, 0.95)',
rightText: '小莫唐尼开源项目', rightText: '小莫唐尼开源项目',
path: '/pages-blog/about/about', path: '/pages-blog/about/about',
@@ -230,7 +240,8 @@ async function handleGetNavList() {
{ {
key: 'setting', key: 'setting',
title: '偏好设置', title: '偏好设置',
icon: 'settings', iconPrefix: 'uhemoji2-icon',
icon: '-tired',
bgColor: 'rgba(121, 134, 203, 0.95)', bgColor: 'rgba(121, 134, 203, 0.95)',
rightText: '首页布局、卡片样式等本地偏好', rightText: '首页布局、卡片样式等本地偏好',
path: '/pages-blog/setting/setting', path: '/pages-blog/setting/setting',
@@ -308,31 +319,27 @@ onPullDownRefresh(() => {
<!-- 背景遮罩 --> <!-- 背景遮罩 -->
<view class="absolute left-0 top-0 z-0 h-full w-full bg-black/30 backdrop-blur-[2rpx]" /> <view class="absolute left-0 top-0 z-0 h-full w-full bg-black/30 backdrop-blur-[2rpx]" />
<view class="relative z-6 h-full flex flex-col items-center justify-center pb-[140rpx] pt-safe"> <view class="relative z-6 h-full flex flex-col items-center justify-center pb-[140rpx] pt-safe">
<image <image class="uh-global-card-glass h-20 w-20 rounded-full" :src="bloggerInfo.avatar"
class="uh-global-card-glass h-20 w-20 rounded-full" :src="bloggerInfo.avatar" mode="aspectFill" />
mode="aspectFill"
/>
<view class="mt-4 text-lg text-white font-bold text-shadow-[0_2rpx_8rpx_rgba(0,0,0,0.4)]"> <view class="mt-4 text-lg text-white font-bold text-shadow-[0_2rpx_8rpx_rgba(0,0,0,0.4)]">
{{ bloggerInfo.nickname }} {{ bloggerInfo.nickname }}
</view> </view>
<view <view
class="desc mt-2 px-10 text-center text-[26rpx] text-white/90 leading-relaxed text-shadow-[0_2rpx_8rpx_rgba(0,0,0,0.4)]" class="desc mt-2 px-10 text-center text-[26rpx] text-white/90 leading-relaxed text-shadow-[0_2rpx_8rpx_rgba(0,0,0,0.4)]">
>
{{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }} {{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }}
</view> </view>
</view> </view>
<image <image v-if="calcWaveUrl" :src="calcWaveUrl" mode="scaleToFill"
v-if="calcWaveUrl" :src="calcWaveUrl" mode="scaleToFill" class="gif-wave absolute bottom-0 left-0 z-99 h-[100rpx] w-full" style="mix-blend-mode: screen;" />
class="gif-wave absolute bottom-0 left-0 z-99 h-[100rpx] w-full" style="mix-blend-mode: screen;"
/>
</view> </view>
<!-- 站点统计(上浮玻璃卡,与头部衔接) --> <!-- 站点统计(上浮玻璃卡,与头部衔接) -->
<view class="uh-global-card-glass relative z-100 mx-4 flex border rounded-2xl -mt-12"> <view class="uh-global-card-glass relative z-100 mx-4 flex border rounded-2xl -mt-12">
<view v-for="item in allStats" :key="item.key" class="flex-1 py-6 text-center"> <view v-for="item in allStats" :key="item.key" class="flex-1 py-6 text-center">
<view class="text-lg text-gray-900 font-bold"> <wd-count-to
{{ item.value }} :key="`${item.key}-${item.value}`" :start-val="0" :end-val="item.value"
</view> :duration="900" separator="" color="#111827" custom-class="text-lg font-bold"
/>
<view class="mt-1 text-xs text-gray-500"> <view class="mt-1 text-xs text-gray-500">
{{ item.label }} {{ item.label }}
</view> </view>
@@ -344,18 +351,17 @@ onPullDownRefresh(() => {
<uh-section-title class="mx-4 mb-3 mt-8"> <uh-section-title class="mx-4 mb-3 mt-8">
{{ group.title }} {{ group.title }}
</uh-section-title> </uh-section-title>
<view class="nav-wrap uh-global-card-glass mx-4 overflow-hidden rounded-2xl"> <view class="uh-global-card-glass mx-4 overflow-hidden rounded-2xl">
<view <view v-for="(nav, index) in group.items" :key="nav.key"
v-for="(nav, index) in group.items" :key="nav.key"
class="nav-item flex items-center justify-between px-4" class="nav-item flex items-center justify-between px-4"
:class="index < group.items.length - 1 ? 'border-b border-b-solid border-black/5' : ''" @click="handleOnNav(nav)" :class="index < group.items.length - 1 ? 'border-b border-b-solid border-black/5' : ''"
> @click="handleOnNav(nav)">
<view class="nav-left flex items-center gap-3 py-3"> <view class="nav-left flex items-center gap-3 py-3">
<view <view
class="h-9 w-9 flex items-center justify-center border border-black/5 rounded-xl" class="uh-global-card-glass border uh-shadow-xs h-8 w-8 flex items-center justify-center rounded-xl"
:style="{ backgroundColor: toLightBg(nav.bgColor) }" :style="{ backgroundColor: toLightBg(nav.bgColor) }">
> <wd-icon :class-prefix="nav.iconPrefix" :name="nav.icon" size="36rpx"
<wd-icon :name="nav.icon" size="20px" :color="toSolidColor(nav.bgColor)" /> :color="toSolidColor(nav.bgColor)" />
</view> </view>
<text class="nav-title text-sm text-gray-900 font-bold">{{ nav.title }}</text> <text class="nav-title text-sm text-gray-900 font-bold">{{ nav.title }}</text>
</view> </view>
-2
View File
@@ -148,13 +148,11 @@
onLoad(async () => { onLoad(async () => {
// 检查插件可用性 // 检查插件可用性
await checkPluginAvailable() await checkPluginAvailable()
console.log('uniHaloPluginAvailable',uniHaloPluginAvailable.value)
if (!uniHaloPluginAvailable.value) { if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh() uni.stopPullDownRefresh()
return return
} }
// 开始正常数据请求 // 开始正常数据请求
handleGetCategory() handleGetCategory()
}) })
+1
View File
@@ -1,6 +1,7 @@
// 测试用的 iconfont,可生效 // 测试用的 iconfont,可生效
@import './iconfont.css'; @import './iconfont.css';
@import './uhemoji-iconfont.css'; @import './uhemoji-iconfont.css';
@import './uhemoji2-iconfont.css';
:root, :root,
page { page {
File diff suppressed because one or more lines are too long