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:
@@ -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-paging(easycom 已配置 `<z-paging>` 直接用)或手写分页。
|
列表页可用 z-paging(easycom 已配置 `<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 等):
|
||||||
|
|
||||||
|
|||||||
@@ -1,100 +1,79 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { getNotices } from '@/api/uni-halo'
|
import { getNotices } from '@/api/uni-halo'
|
||||||
import type { INoticeListVo } from '@/api/types/uni-halo'
|
import type { INoticeListVo } from '@/api/types/uni-halo'
|
||||||
|
|
||||||
/** 轮播展示条数上限(垂直循环) */
|
/** 轮播展示条数上限(垂直循环) */
|
||||||
const MAX_SHOW = 6
|
const MAX_SHOW = 6
|
||||||
|
|
||||||
const list = ref<INoticeListVo[]>([])
|
const list = ref<INoticeListVo[]>([])
|
||||||
const showList = computed(() => list.value.slice(0, MAX_SHOW))
|
const showList = computed(() => list.value.slice(0, MAX_SHOW))
|
||||||
|
|
||||||
async function fetchNotices() {
|
async function fetchNotices() {
|
||||||
try {
|
try {
|
||||||
const res = await getNotices({ page: 1, size: MAX_SHOW })
|
const res = await getNotices({ page: 1, size: MAX_SHOW })
|
||||||
list.value = res.data?.items || []
|
list.value = res.data?.items || []
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error('首页公告获取失败', err)
|
console.error('首页公告获取失败', err)
|
||||||
list.value = []
|
list.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击单条公告标题 → 详情页 */
|
/** 点击单条公告标题 → 详情页 */
|
||||||
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}` })
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** 点击「公告/更多」→ 公告列表页 */
|
/** 点击「公告/更多」→ 公告列表页 */
|
||||||
function handleGoList() {
|
function handleGoList() {
|
||||||
uni.navigateTo({ url: '/pages-blog/notice/notice' })
|
uni.navigateTo({ url: '/pages-blog/notice/notice' })
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchNotices()
|
fetchNotices()
|
||||||
})
|
})
|
||||||
</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-2 py-2 pr-3" @click="handleGoList">
|
||||||
<!-- 左侧公告入口 -->
|
<wd-icon class-prefix="uhemoji2-icon" name="-happy-" size="36rpx" />
|
||||||
<view class="flex shrink-0 items-center gap-1 py-2 pr-3" @click="handleGoList">
|
<text class="text-sm font-bold text-red-400">
|
||||||
<text class="text-sm">
|
公告
|
||||||
📢
|
</text>
|
||||||
</text>
|
</view>
|
||||||
<text class="text-xs font-bold text-red-400">
|
|
||||||
公告
|
|
||||||
</text>
|
|
||||||
</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
|
{{ item.title }}
|
||||||
:interval="3500"
|
</view>
|
||||||
:duration="400"
|
</swiper-item>
|
||||||
>
|
</swiper>
|
||||||
<swiper-item
|
<view v-else class="flex h-full w-full items-center truncate text-[24rpx] text-[#555]"
|
||||||
v-for="(item, index) in showList"
|
@click="handleTap(showList[0])">
|
||||||
:key="item.name || index"
|
{{ showList[0]?.title }}
|
||||||
class="h-full w-full"
|
</view>
|
||||||
>
|
</view>
|
||||||
<view
|
|
||||||
class="flex h-full w-full items-center truncate text-xs text-gray-500"
|
|
||||||
@click="handleTap(item)"
|
|
||||||
>
|
|
||||||
{{ item.title }}
|
|
||||||
</view>
|
|
||||||
</swiper-item>
|
|
||||||
</swiper>
|
|
||||||
<view
|
|
||||||
v-else
|
|
||||||
class="flex h-full w-full items-center truncate text-[24rpx] text-[#555]"
|
|
||||||
@click="handleTap(showList[0])"
|
|
||||||
>
|
|
||||||
{{ showList[0]?.title }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 右侧更多入口 -->
|
<!-- 右侧更多入口 -->
|
||||||
<view class="flex shrink-0 items-center gap-0.5 py-2 pl-2" @click="handleGoList">
|
<view class="flex shrink-0 items-center gap-0.5 py-2 pl-2" @click="handleGoList">
|
||||||
<text class="text-[22rpx] text-[#bbb]">
|
<text class="text-[22rpx] text-[#bbb]">
|
||||||
更多
|
更多
|
||||||
</text>
|
</text>
|
||||||
<wd-icon name="arrow-right" size="12px" color="#bbb" />
|
<wd-icon name="arrow-right" size="12px" color="#bbb" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
/* 布局由 UnoCSS 原子类实现 */
|
/* 布局由 UnoCSS 原子类实现 */
|
||||||
</style>
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -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')
|
||||||
}
|
}
|
||||||
@@ -49,9 +56,9 @@
|
|||||||
}
|
}
|
||||||
return props.scrollTitle;
|
return props.scrollTitle;
|
||||||
})
|
})
|
||||||
|
|
||||||
// todo:注意:如果是从分享进来的,我们需要处理为返回 home页面
|
// todo:注意:如果是从分享进来的,我们需要处理为返回 home页面
|
||||||
function handleBack(){
|
function handleBack() {
|
||||||
uni.navigateBack({ delta: 1 })
|
uni.navigateBack({ delta: 1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,25 +68,30 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="box-border pt-safe w-full fixed left-0 top-0 z-50" :class="customCalss" :style="[customStyle]">
|
<view class="w-full box-border">
|
||||||
<view class="w-full h-[46px] flex items-center gap-x-4 box-border px-3 backdrop-blur-[2rpx]">
|
<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="shrink-0" @click="handleBack()">
|
<!-- 左边 -->
|
||||||
<view
|
<view class="shrink-0" @click="handleBack()">
|
||||||
class="uh-global-card-glass h-7 px-3 rounded-full border flex items-center gap-x-2 text-gray-900 text-sm">
|
<view
|
||||||
<wd-icon name="arrow-left" size="32rpx"></wd-icon>
|
class="uh-global-card-glass h-7 px-3 rounded-full border flex items-center gap-x-2 text-gray-900 text-sm">
|
||||||
<view class="w-[1px] h-4 bg-white/60" />
|
<wd-icon name="arrow-left" size="32rpx"></wd-icon>
|
||||||
<text class="text-xs font-bold">返回</text>
|
<view class="w-[1px] h-4 bg-white/60" />
|
||||||
|
<text class="text-xs font-bold">返回</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 中间 -->
|
||||||
|
<view class="flex-1 truncate text-center font-bold transition-colors duration-300">
|
||||||
|
<slot> {{visibleTitle}} </slot>
|
||||||
|
</view>
|
||||||
|
<!-- 右边 -->
|
||||||
|
<view class="shrink-0 min-w-18">
|
||||||
|
<slot name="right"></slot>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 中间 -->
|
</view>
|
||||||
<view class="flex-1 truncate text-center font-bold transition-colors duration-300">
|
<view v-if="props.needPlaceholder" class="box-border w-full pt-safe">
|
||||||
<slot> {{visibleTitle}} </slot>
|
<view class="w-full h-[46px]"></view>
|
||||||
</view>
|
|
||||||
<!-- 右边 -->
|
|
||||||
<view class="shrink-0 min-w-18">
|
|
||||||
<slot name="right"></slot>
|
|
||||||
</view>
|
|
||||||
</view>
|
</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'
|
||||||
|
|
||||||
@@ -45,18 +41,7 @@
|
|||||||
...defaultStyle,
|
...defaultStyle,
|
||||||
...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(() => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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">
|
||||||
|
|||||||
+283
-323
@@ -1,349 +1,309 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/**
|
import { ref } from 'vue'
|
||||||
* 偏好设置页(两层:站点默认 L0 + 本地差异 L1-L,设计见 .docs/config-system-v2-redesign §3-4)
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
* - 每项可「跟随站点默认」(差异为空)或覆盖为具体值;改动即写本地差异(uh_pref_local_v1)即时生效;
|
import { DefaultAppSettings } from '@/config/appSettings'
|
||||||
* - 底部「重置为站点默认」= 清空全部本地差异,回退站长在插件后台配置的默认(未配置则为内置默认)。
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
* - 风格:对齐全站设计语言(bg-page + uh-global-card-glass + uh-section-title)
|
import { useSettingStore } from '@/store/setting'
|
||||||
*/
|
import { collectSiteDefaults, isLocalOverride, readLocalPrefs } from '@/utils/preference'
|
||||||
import { ref } from 'vue'
|
import type { LocalPrefs } from '@/utils/preference'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
|
||||||
import { DefaultAppSettings } from '@/config/appSettings'
|
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
|
||||||
import { useSettingStore } from '@/store/setting'
|
|
||||||
import { collectSiteDefaults, isLocalOverride, readLocalPrefs } from '@/utils/preference'
|
|
||||||
import type { LocalPrefs } from '@/utils/preference'
|
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
style: {
|
style: {
|
||||||
navigationBarTitleText: '偏好设置',
|
navigationBarTitleText: '偏好设置',
|
||||||
},
|
navigationStyle: 'custom'
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
const appConfigStore = useAppConfigStore()
|
const appConfigStore = useAppConfigStore()
|
||||||
|
|
||||||
/** 确保启动合并已执行(入口页未跑或 H5 直达时兜底) */
|
/** 确保启动合并已执行(入口页未跑或 H5 直达时兜底) */
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
if (!settingStore.siteDefaults) {
|
if (!settingStore.siteDefaults) {
|
||||||
settingStore.applySiteDefaults(collectSiteDefaults(appConfigStore.configs))
|
settingStore.applySiteDefaults(collectSiteDefaults(appConfigStore.configs))
|
||||||
}
|
}
|
||||||
uni.setNavigationBarTitle({ title: '偏好设置' })
|
uni.setNavigationBarTitle({ title: '偏好设置' })
|
||||||
})
|
})
|
||||||
|
|
||||||
/* ---------------- 路径取值工具 ---------------- */
|
/* ---------------- 路径取值工具 ---------------- */
|
||||||
type Path = string[]
|
type Path = string[]
|
||||||
|
|
||||||
function getByPath(obj: unknown, path: Path): unknown {
|
function getByPath(obj : unknown, path : Path) : unknown {
|
||||||
let cursor: unknown = obj
|
let cursor : unknown = obj
|
||||||
for (const key of path) {
|
for (const key of path) {
|
||||||
if (cursor === null || cursor === undefined)
|
if (cursor === null || cursor === undefined)
|
||||||
return undefined
|
return undefined
|
||||||
cursor = (cursor as Record<string, unknown>)[key]
|
cursor = (cursor as Record<string, unknown>)[key]
|
||||||
}
|
}
|
||||||
return cursor
|
return cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按路径构造差异 patch(null 表示删除该键=跟随站点默认) */
|
/** 按路径构造差异 patch(null 表示删除该键=跟随站点默认) */
|
||||||
function buildPatch(path: Path, value: unknown): LocalPrefs {
|
function buildPatch(path : Path, value : unknown) : LocalPrefs {
|
||||||
const [head, ...rest] = path
|
const [head, ...rest] = path
|
||||||
if (rest.length === 0)
|
if (rest.length === 0)
|
||||||
return { [head]: value } as LocalPrefs
|
return { [head]: value } as LocalPrefs
|
||||||
return { [head]: buildPatch(rest, value) } as LocalPrefs
|
return { [head]: buildPatch(rest, value) } as LocalPrefs
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 偏好字段定义(现页已有项;弹幕已下线、友链分组二期再开) */
|
/** 偏好字段定义(现页已有项;弹幕已下线、友链分组二期再开) */
|
||||||
interface PrefDef {
|
interface PrefDef {
|
||||||
key: string
|
key : string
|
||||||
label: string
|
label : string
|
||||||
kind: 'bool' | 'enum'
|
kind : 'bool' | 'enum'
|
||||||
path: Path
|
path : Path
|
||||||
options?: { label: string, value: string }[]
|
options ?: { label : string, value : string }[]
|
||||||
siteLabelOf?: (value: string) => string
|
siteLabelOf ?: (value : string) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutPrefs: PrefDef[] = [
|
const layoutPrefs : PrefDef[] = [
|
||||||
{
|
{
|
||||||
key: 'home',
|
key: 'home',
|
||||||
label: '首页文章布局',
|
label: '首页文章布局',
|
||||||
kind: 'enum',
|
kind: 'enum',
|
||||||
path: ['layout', 'home'],
|
path: ['layout', 'home'],
|
||||||
options: [
|
options: [
|
||||||
{ label: '一行一列', value: 'h_row_col1' },
|
{ label: '一行一列', value: 'h_row_col1' },
|
||||||
{ label: '一行两列', value: 'h_row_col2' },
|
{ label: '一行两列', value: 'h_row_col2' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'cardType',
|
key: 'cardType',
|
||||||
label: '文章卡片样式',
|
label: '文章卡片样式',
|
||||||
kind: 'enum',
|
kind: 'enum',
|
||||||
path: ['layout', 'cardType'],
|
path: ['layout', 'cardType'],
|
||||||
options: [
|
options: [
|
||||||
{ label: '左图右文', value: 'lr_image_text' },
|
{ label: '左图右文', value: 'lr_image_text' },
|
||||||
{ label: '左文右图', value: 'lr_text_image' },
|
{ label: '左文右图', value: 'lr_text_image' },
|
||||||
{ label: '上图下文', value: 'tb_image_text' },
|
{ label: '上图下文', value: 'tb_image_text' },
|
||||||
{ label: '上文下图', value: 'tb_text_image' },
|
{ label: '上文下图', value: 'tb_text_image' },
|
||||||
{ label: '只有文字', value: 'only_text' },
|
{ label: '只有文字', value: 'only_text' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const featurePrefs: PrefDef[] = [
|
const featurePrefs : PrefDef[] = [
|
||||||
{ key: 'useWaterfull', label: '图库瀑布流模式', kind: 'bool', path: ['gallery', 'useWaterfull'] },
|
{ key: 'isAvatarRadius', label: '是否圆形头像', kind: 'bool', path: ['isAvatarRadius'] },
|
||||||
{ key: 'useSimple', label: '友链简洁模式', kind: 'bool', path: ['links', 'useSimple'] },
|
]
|
||||||
{ 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
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
/* ---------------- 状态读取 ---------------- */
|
/* ---------------- 状态读取 ---------------- */
|
||||||
function valueOf(path: Path): unknown {
|
function valueOf(path : Path) : unknown {
|
||||||
return getByPath(settingStore.settings, path)
|
return getByPath(settingStore.settings, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 站点默认值(未配置时回退内置默认) */
|
/** 站点默认值(未配置时回退内置默认) */
|
||||||
function siteDefaultOf(path: Path): unknown {
|
function siteDefaultOf(path : Path) : unknown {
|
||||||
const site = getByPath(settingStore.siteDefaults, path)
|
const site = getByPath(settingStore.siteDefaults, path)
|
||||||
if (site !== undefined && site !== null)
|
if (site !== undefined && site !== null)
|
||||||
return site
|
return site
|
||||||
return getByPath(DefaultAppSettings, path)
|
return getByPath(DefaultAppSettings, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOverridden(path: Path): boolean {
|
function isOverridden(path : Path) : boolean {
|
||||||
return isLocalOverride(readLocalPrefs(), path)
|
return isLocalOverride(readLocalPrefs(), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
function enumLabelOf(def: PrefDef, value: unknown): string {
|
function enumLabelOf(def : PrefDef, value : unknown) : string {
|
||||||
const hit = def.options?.find(opt => opt.value === value)
|
const hit = def.options?.find(opt => opt.value === value)
|
||||||
if (hit)
|
if (hit)
|
||||||
return hit.label
|
return hit.label
|
||||||
if (def.siteLabelOf && typeof value === 'string')
|
if (def.siteLabelOf && typeof value === 'string')
|
||||||
return def.siteLabelOf(value)
|
return def.siteLabelOf(value)
|
||||||
return value === undefined || value === null ? '—' : String(value)
|
return value === undefined || value === null ? '—' : String(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 交互 ---------------- */
|
/* ---------------- 交互 ---------------- */
|
||||||
/** 开关事件(模板透传 $event) */
|
/** 开关事件(模板透传 $event) */
|
||||||
function handleSwitchChange(def: PrefDef, detail: { value?: unknown }) {
|
function handleSwitchChange(def : PrefDef, detail : { value ?: unknown }) {
|
||||||
handleBoolChange(def.path, detail.value === true)
|
handleBoolChange(def.path, detail.value === true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 开关类:选值等于站点默认则还原为跟随(只存差异) */
|
/** 开关类:选值等于站点默认则还原为跟随(只存差异) */
|
||||||
function handleBoolChange(path: Path, next: boolean) {
|
function handleBoolChange(path : Path, next : boolean) {
|
||||||
if (next === siteDefaultOf(path)) {
|
if (next === siteDefaultOf(path)) {
|
||||||
settingStore.savePreference(buildPatch(path, null))
|
settingStore.savePreference(buildPatch(path, null))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
settingStore.savePreference(buildPatch(path, next))
|
settingStore.savePreference(buildPatch(path, next))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 单项还原为跟随站点默认 */
|
/** 单项还原为跟随站点默认 */
|
||||||
function handleRevert(path: Path) {
|
function handleRevert(path : Path) {
|
||||||
settingStore.savePreference(buildPatch(path, null))
|
settingStore.savePreference(buildPatch(path, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 枚举底部弹层 ---------------- */
|
/* ---------------- 枚举底部弹层 ---------------- */
|
||||||
const enumSheet = ref<{ show: boolean, def: PrefDef | null }>({ show: false, def: null })
|
const enumSheet = ref<{ show : boolean, def : PrefDef | null }>({ show: false, def: null })
|
||||||
|
|
||||||
function handleOpenEnum(def: PrefDef) {
|
function handleOpenEnum(def : PrefDef) {
|
||||||
enumSheet.value = { show: true, def }
|
enumSheet.value = { show: true, def }
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseEnum() {
|
function handleCloseEnum() {
|
||||||
enumSheet.value.show = false
|
enumSheet.value.show = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChooseEnum(value: string | null) {
|
function handleChooseEnum(value : string | null) {
|
||||||
const def = enumSheet.value.def
|
const def = enumSheet.value.def
|
||||||
if (def) {
|
if (def) {
|
||||||
if (value === null || value === siteDefaultOf(def.path)) {
|
if (value === null || value === siteDefaultOf(def.path)) {
|
||||||
handleRevert(def.path)
|
handleRevert(def.path)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
settingStore.savePreference(buildPatch(def.path, value))
|
settingStore.savePreference(buildPatch(def.path, value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleCloseEnum()
|
handleCloseEnum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 当前枚举项是否处于「跟随站点默认」 */
|
/** 当前枚举项是否处于「跟随站点默认」 */
|
||||||
function isFollowing(def: PrefDef): boolean {
|
function isFollowing(def : PrefDef) : boolean {
|
||||||
return !isOverridden(def.path)
|
return !isOverridden(def.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 重置全部 ---------------- */
|
/* ---------------- 重置全部 ---------------- */
|
||||||
function handleResetAll() {
|
function handleResetAll() {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定将所有偏好恢复为站点默认吗?本地自定义的偏好将被清除,未配置站点默认的项将恢复为内置默认。',
|
content: '确定将所有偏好恢复为站点默认吗?本地自定义的偏好将被清除,未配置站点默认的项将恢复为内置默认。',
|
||||||
showCancel: true,
|
showCancel: true,
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
confirmText: '确定',
|
confirmText: '确定',
|
||||||
confirmColor: '#03a9f4',
|
confirmColor: '#03a9f4',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
settingStore.resetPreferences()
|
settingStore.resetPreferences()
|
||||||
enumSheet.value.show = false
|
enumSheet.value.show = false
|
||||||
uni.showToast({ icon: 'none', title: '已恢复为站点默认' })
|
uni.showToast({ icon: 'none', title: '已恢复为站点默认' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</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]">
|
<!-- 内容区域 -->
|
||||||
你的偏好仅保存在本机。未自定义的项自动跟随站长在插件后台配置的「站点默认」;点击底部「重置为站点默认」可清空全部本地偏好。
|
<view class="box-border p-3 flex flex-col gap-y-6">
|
||||||
</text>
|
<!-- 布局设置 -->
|
||||||
</view>
|
<view class="flex flex-col gap-y-3">
|
||||||
|
<uh-section-title>
|
||||||
<!-- 布局设置 -->
|
布局
|
||||||
<uh-section-title class="mx-4 mb-3 mt-6 text-[30rpx]">
|
<template #right>
|
||||||
布局
|
<text class="text-2xs text-gray-400">应用以及文章列表布局设置</text>
|
||||||
<template #right>
|
</template>
|
||||||
<text class="text-2xs text-gray-400">应用以及文章列表布局设置</text>
|
</uh-section-title>
|
||||||
</template>
|
<view class="uh-global-card-glass overflow-hidden rounded-2xl">
|
||||||
</uh-section-title>
|
<view v-for="(def, index) in layoutPrefs" :key="def.key"
|
||||||
<view class="setting-sheet uh-global-card-glass mx-4 overflow-hidden rounded-2xl">
|
class="pick-row flex items-center justify-between px-4 py-4"
|
||||||
<view
|
:class="index < layoutPrefs.length - 1 ? 'border-b border-black/5' : ''"
|
||||||
v-for="(def, index) in layoutPrefs"
|
@click="handleOpenEnum(def)">
|
||||||
:key="def.key"
|
<view class="row-left flex flex-col gap-1">
|
||||||
class="pick-row flex items-center justify-between px-4 py-4"
|
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
|
||||||
:class="index < layoutPrefs.length - 1 ? 'border-b border-black/5' : ''"
|
<view class="flex items-center gap-2">
|
||||||
@click="handleOpenEnum(def)"
|
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
>
|
<view v-else
|
||||||
<view class="row-left flex flex-col gap-1">
|
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
||||||
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
|
已自定义
|
||||||
<view class="flex items-center gap-2">
|
</view>
|
||||||
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
</view>
|
||||||
<view v-else class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
</view>
|
||||||
已自定义
|
<view class="row-value flex items-center gap-2">
|
||||||
</view>
|
<text
|
||||||
</view>
|
class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text>
|
||||||
</view>
|
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
|
||||||
<view class="row-value flex items-center gap-2">
|
</view>
|
||||||
<text class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text>
|
</view>
|
||||||
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<!-- 功能设置 -->
|
||||||
</view>
|
<view class="flex flex-col gap-y-3">
|
||||||
|
<uh-section-title>
|
||||||
<!-- 功能设置 -->
|
功能
|
||||||
<uh-section-title class="mx-4 mb-3 mt-6 text-[30rpx]">
|
<template #right>
|
||||||
功能
|
<text class="text-2xs text-gray-400">一些常用的功能性设置</text>
|
||||||
<template #right>
|
</template>
|
||||||
<text class="text-2xs text-gray-400">一些常用的功能性设置</text>
|
</uh-section-title>
|
||||||
</template>
|
<view class="setting-sheet uh-global-card-glass overflow-hidden rounded-2xl">
|
||||||
</uh-section-title>
|
<template v-for="(def, index) in featurePrefs" :key="def.key">
|
||||||
<view class="setting-sheet uh-global-card-glass mx-4 overflow-hidden rounded-2xl">
|
<!-- 布尔开关 -->
|
||||||
<template v-for="(def, index) in featurePrefs" :key="def.key">
|
<view v-if="def.kind === 'bool'" class="switch-row flex items-center justify-between px-4 py-4"
|
||||||
<!-- 布尔开关 -->
|
:class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''">
|
||||||
<view
|
<view class="row-left flex flex-col gap-1">
|
||||||
v-if="def.kind === 'bool'"
|
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
|
||||||
class="switch-row flex items-center justify-between px-4 py-4"
|
<view class="flex items-center gap-2">
|
||||||
:class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''"
|
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
>
|
<template v-else>
|
||||||
<view class="row-left flex flex-col gap-1">
|
<view
|
||||||
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
|
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
||||||
<view class="flex items-center gap-2">
|
已自定义
|
||||||
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
</view>
|
||||||
<template v-else>
|
<text class="revert-text text-2xs text-gray-400 underline"
|
||||||
<view class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
@click.stop="handleRevert(def.path)">恢复默认</text>
|
||||||
已自定义
|
</template>
|
||||||
</view>
|
</view>
|
||||||
<text class="revert-text text-2xs text-gray-400 underline" @click.stop="handleRevert(def.path)">恢复默认</text>
|
</view>
|
||||||
</template>
|
<wd-switch :model-value="def.path" @change="handleSwitchChange(def, $event)" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<!-- 枚举选择(指示器位置) -->
|
||||||
<wd-switch :model-value="!!valueOf(def.path)" @change="handleSwitchChange(def, $event)" />
|
<view v-else class="pick-row flex items-center justify-between px-4 py-4"
|
||||||
</view>
|
:class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''"
|
||||||
<!-- 枚举选择(指示器位置) -->
|
@click="handleOpenEnum(def)">
|
||||||
<view
|
<view class="row-left flex flex-col gap-1">
|
||||||
v-else
|
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
|
||||||
class="pick-row flex items-center justify-between px-4 py-4"
|
<view class="flex items-center gap-2">
|
||||||
:class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''"
|
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
@click="handleOpenEnum(def)"
|
<template v-else>
|
||||||
>
|
<view
|
||||||
<view class="row-left flex flex-col gap-1">
|
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
||||||
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ def.label }}</text>
|
已自定义
|
||||||
<view class="flex items-center gap-2">
|
</view>
|
||||||
<text v-if="isFollowing(def)" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
<text class="revert-text text-2xs text-gray-400 underline"
|
||||||
<template v-else>
|
@click.stop="handleRevert(def.path)">恢复默认</text>
|
||||||
<view class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
</template>
|
||||||
已自定义
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="revert-text text-2xs text-gray-400 underline" @click.stop="handleRevert(def.path)">恢复默认</text>
|
<view class="row-value flex items-center gap-2">
|
||||||
</template>
|
<text
|
||||||
</view>
|
class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text>
|
||||||
</view>
|
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
|
||||||
<view class="row-value flex items-center gap-2">
|
</view>
|
||||||
<text class="value-text text-[26rpx] text-gray-400">{{ enumLabelOf(def, valueOf(def.path)) }}</text>
|
</view>
|
||||||
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
<!-- 底部操作栏(玻璃悬浮-->
|
||||||
</view>
|
<view class="box-border w-full px-2">
|
||||||
|
<uh-button custom-class="uh-global-card-glass py-2 !rounded-full"
|
||||||
<!-- 底部操作栏(玻璃悬浮 + 底部安全区适配) -->
|
@click="handleResetAll">恢复默认</uh-button>
|
||||||
<view class="btn-bar uh-global-card-glass fixed bottom-0 left-0 box-border w-screen px-4 pt-3 pb-safe">
|
</view>
|
||||||
<view class="reset-btn h-[84rpx] w-full flex items-center justify-center rounded-full bg-gray-900 active:opacity-80" @click="handleResetAll">
|
</view>
|
||||||
<text class="text-[28rpx] text-white font-bold">重置为站点默认</text>
|
<!-- 枚举选择底部弹层 -->
|
||||||
</view>
|
<wd-popup v-model="enumSheet.show" position="bottom" closable custom-style="border-radius: 24rpx 24rpx 0 0;"
|
||||||
</view>
|
@close="handleCloseEnum">
|
||||||
|
<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">
|
||||||
<wd-popup
|
{{ enumSheet.def.label }}
|
||||||
v-model="enumSheet.show"
|
</view>
|
||||||
position="bottom"
|
<view class="enum-item flex items-center justify-between px-6 py-5"
|
||||||
closable
|
:class="isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
|
||||||
custom-style="border-radius: 24rpx 24rpx 0 0;"
|
@click="handleChooseEnum(null)">
|
||||||
@close="handleCloseEnum"
|
<text class="text-[28rpx]">跟随站点默认</text>
|
||||||
>
|
<wd-icon v-if="isFollowing(enumSheet.def)" name="check" size="16px" color="#4d7c0f" />
|
||||||
<view v-if="enumSheet.def" class="enum-sheet box-border w-full pb-[env(safe-area-inset-bottom)]">
|
</view>
|
||||||
<view class="enum-title py-6 text-center text-[30rpx] text-gray-900 font-bold">
|
<view v-for="opt in enumSheet.def.options" :key="opt.value"
|
||||||
{{ enumSheet.def.label }}
|
class="enum-item flex items-center justify-between border-t border-[#f0ece2] px-6 py-5"
|
||||||
</view>
|
:class="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
|
||||||
<view
|
@click="handleChooseEnum(opt.value)">
|
||||||
class="enum-item flex items-center justify-between px-6 py-5"
|
<text class="text-[28rpx]">{{ opt.label }}</text>
|
||||||
:class="isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
|
<wd-icon v-if="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def)"
|
||||||
@click="handleChooseEnum(null)"
|
name="check" size="16px" color="#4d7c0f" />
|
||||||
>
|
</view>
|
||||||
<text class="text-[28rpx]">跟随站点默认</text>
|
</view>
|
||||||
<wd-icon v-if="isFollowing(enumSheet.def)" name="check" size="16px" color="#4d7c0f" />
|
</wd-popup>
|
||||||
</view>
|
</view>
|
||||||
<view
|
</template>
|
||||||
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="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
|
|
||||||
@click="handleChooseEnum(opt.value)"
|
|
||||||
>
|
|
||||||
<text class="text-[28rpx]">{{ opt.label }}</text>
|
|
||||||
<wd-icon
|
|
||||||
v-if="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def)"
|
|
||||||
name="check"
|
|
||||||
size="16px"
|
|
||||||
color="#4d7c0f"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</wd-popup>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
@@ -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>
|
||||||
+343
-337
@@ -1,375 +1,381 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/**
|
/**
|
||||||
* 关于页(源自旧项目 pages/tabbar/about/about.vue,新建复刻)
|
* 关于页(源自旧项目 pages/tabbar/about/about.vue,新建复刻)
|
||||||
* 功能:博主信息 + 站点统计 + 功能导航 + 版权
|
* 功能:博主信息 + 站点统计 + 功能导航 + 版权
|
||||||
* 风格:对齐全站设计语言(bg-page + uh-global-card-glass + uh-section-title + 彩色图标块)
|
* 风格:对齐全站设计语言(bg-page + uh-global-card-glass + uh-section-title + 彩色图标块)
|
||||||
*/
|
*/
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { onPullDownRefresh, onShow } from '@dcloudio/uni-app'
|
import { onPullDownRefresh, onShow } from '@dcloudio/uni-app'
|
||||||
import { getBlogStatistics } from '@/api/halo'
|
import { getBlogStatistics } from '@/api/halo'
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { useFavoritesStore } from '@/store/favorites'
|
import { useFavoritesStore } from '@/store/favorites'
|
||||||
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
||||||
import { checkHasAdminLogin } from '@/utils/auth'
|
import { checkHasAdminLogin } from '@/utils/auth'
|
||||||
import { t } from '@/locale'
|
import { t } from '@/locale'
|
||||||
import type { IBlogStats } from '@/api/types/halo'
|
import type { IBlogStats } from '@/api/types/halo'
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
style: {
|
style: {
|
||||||
navigationBarTitleText: '关于',
|
navigationBarTitleText: '关于',
|
||||||
enablePullDownRefresh: true,
|
enablePullDownRefresh: true,
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const appConfigStore = useAppConfigStore()
|
const appConfigStore = useAppConfigStore()
|
||||||
const favoritesStore = useFavoritesStore()
|
const favoritesStore = useFavoritesStore()
|
||||||
const haloConfigs = computed(() => appConfigStore.configs)
|
const haloConfigs = computed(() => appConfigStore.configs)
|
||||||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||||
const calcVotePluginEnabled = computed(() => !!haloConfigs.value.pluginConfig?.votePlugin?.enabled)
|
const calcVotePluginEnabled = computed(() => !!haloConfigs.value.pluginConfig?.votePlugin?.enabled)
|
||||||
const calcLinksPluginEnabled = computed(() => !!haloConfigs.value.pluginConfig?.linksPlugin?.enabled)
|
const calcLinksPluginEnabled = computed(() => !!haloConfigs.value.pluginConfig?.linksPlugin?.enabled)
|
||||||
/** 数据看板插件可用性(供导航项显隐判断) */
|
/** 数据看板插件可用性(供导航项显隐判断) */
|
||||||
const { check: checkDataVisualPlugin } = usePluginAvailable('plugin-data-statistics')
|
const { check: checkDataVisualPlugin } = usePluginAvailable('plugin-data-statistics')
|
||||||
|
|
||||||
/* ---------------- 计算属性 ---------------- */
|
/* ---------------- 计算属性 ---------------- */
|
||||||
const bloggerInfo = computed(() => {
|
const bloggerInfo = computed(() => {
|
||||||
const blogger = haloConfigs.value.authorConfig?.blogger as
|
const blogger = haloConfigs.value.authorConfig?.blogger as
|
||||||
| { nickname?: string, avatar?: string, description?: string }
|
| { nickname ?: string, avatar ?: string, description ?: string }
|
||||||
| undefined
|
| undefined
|
||||||
return {
|
return {
|
||||||
nickname: blogger?.nickname || '',
|
nickname: blogger?.nickname || '',
|
||||||
avatar: checkAvatarUrl(blogger?.avatar),
|
avatar: checkAvatarUrl(blogger?.avatar),
|
||||||
description: blogger?.description || '',
|
description: blogger?.description || '',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const pageConfig = computed(() => haloConfigs.value.pageConfig?.aboutConfig as
|
const pageConfig = computed(() => haloConfigs.value.pageConfig?.aboutConfig as
|
||||||
| { bgImageUrl?: string, waveImageUrl?: string }
|
| { bgImageUrl ?: string, waveImageUrl ?: string }
|
||||||
| undefined)
|
| undefined)
|
||||||
|
|
||||||
const calcProfileStyle = computed(() => ({
|
const calcProfileStyle = computed(() => ({
|
||||||
backgroundImage: `url(${checkImageUrl(pageConfig.value?.bgImageUrl)})`,
|
backgroundImage: `url(${checkImageUrl(pageConfig.value?.bgImageUrl)})`,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const calcWaveUrl = computed(() => checkImageUrl(pageConfig.value?.waveImageUrl))
|
const calcWaveUrl = computed(() => checkImageUrl(pageConfig.value?.waveImageUrl))
|
||||||
|
|
||||||
const basicConfig = computed(() => haloConfigs.value.basicConfig as
|
const basicConfig = computed(() => haloConfigs.value.basicConfig as
|
||||||
| {
|
| {
|
||||||
copyrightConfig?: { enabled?: boolean, content?: string }
|
copyrightConfig ?: { enabled ?: boolean, content ?: string }
|
||||||
disclaimers?: { enabled?: boolean }
|
disclaimers ?: { enabled ?: boolean }
|
||||||
showAboutSystem?: boolean
|
showAboutSystem ?: boolean
|
||||||
}
|
}
|
||||||
| undefined)
|
| undefined)
|
||||||
|
|
||||||
const copyrightConfig = computed(() => basicConfig.value?.copyrightConfig)
|
const copyrightConfig = computed(() => basicConfig.value?.copyrightConfig)
|
||||||
|
|
||||||
const loveEnabled = computed(() => !!(haloConfigs.value.loveConfig as { loveEnabled?: boolean } | undefined)?.loveEnabled)
|
const loveEnabled = computed(() => !!(haloConfigs.value.loveConfig as { loveEnabled ?: boolean } | undefined)?.loveEnabled)
|
||||||
const socialEnabled = computed(() => !!(haloConfigs.value.authorConfig?.social as { enabled?: boolean } | undefined)?.enabled)
|
const socialEnabled = computed(() => !!(haloConfigs.value.authorConfig?.social as { enabled ?: boolean } | undefined)?.enabled)
|
||||||
|
|
||||||
/* ---------------- 状态 ---------------- */
|
/* ---------------- 状态 ---------------- */
|
||||||
const statisticsShowMore = ref(false)
|
const statisticsShowMore = ref(false)
|
||||||
const statistics = ref<IBlogStats>({ post: 0, comment: 0, category: 0, visit: 0, upvote: 0 })
|
const statistics = ref<IBlogStats>({ post: 0, comment: 0, category: 0, visit: 0, upvote: 0 })
|
||||||
|
|
||||||
/** 主行统计(常驻展示) */
|
/** 主行统计(常驻展示) */
|
||||||
const allStats = computed(() => [
|
const allStats = computed(() => [
|
||||||
{ key: 'post', label: '内容', value: statistics.value.post },
|
{ key: 'post', label: '内容', value: statistics.value.post },
|
||||||
{ key: 'visit', label: '访客', value: statistics.value.visit },
|
{ key: 'visit', label: '访客', value: statistics.value.visit },
|
||||||
{ key: 'category', label: '分类', value: statistics.value.category },
|
{ key: 'category', label: '分类', value: statistics.value.category },
|
||||||
{ key: 'comment', label: '评论', value: statistics.value.comment },
|
{ key: 'comment', label: '评论', value: statistics.value.comment },
|
||||||
{ key: 'upvote', label: '点赞', value: statistics.value.upvote },
|
{ key: 'upvote', label: '点赞', value: statistics.value.upvote },
|
||||||
])
|
])
|
||||||
|
|
||||||
interface INavItem {
|
interface INavItem {
|
||||||
key: string
|
key : string
|
||||||
title: string
|
title : string
|
||||||
icon: string
|
iconPrefix ?: string
|
||||||
/** 图标块背景色(与首页快捷导航同色板,同一功能同色) */
|
icon : string
|
||||||
bgColor: string
|
/** 图标块背景色(与首页快捷导航同色板,同一功能同色) */
|
||||||
rightText: string
|
bgColor : string
|
||||||
path: string | null
|
rightText : string
|
||||||
isAdmin?: boolean
|
path : string | null
|
||||||
openType?: string
|
isAdmin ?: boolean
|
||||||
show: boolean
|
openType ?: string
|
||||||
/** 分组:blog=博客功能 more=更多信息 */
|
show : boolean
|
||||||
group: 'blog' | 'more'
|
/** 分组:blog=博客功能 more=更多信息 */
|
||||||
}
|
group : 'blog' | 'more'
|
||||||
|
}
|
||||||
|
|
||||||
const navList = ref<INavItem[]>([])
|
const navList = ref<INavItem[]>([])
|
||||||
|
|
||||||
/** 分组渲染(过滤后空组整组隐藏) */
|
/** 分组渲染(过滤后空组整组隐藏) */
|
||||||
const calcNavGroups = computed(() => {
|
const calcNavGroups = computed(() => {
|
||||||
const visible = navList.value.filter(n => n.show)
|
const visible = navList.value.filter(n => n.show)
|
||||||
const groupDefs: { key: 'blog' | 'more', title: string }[] = [
|
const groupDefs : { key : 'blog' | 'more', title : string }[] = [
|
||||||
{ key: 'blog', title: '博客功能' },
|
{ key: 'blog', title: '博客功能' },
|
||||||
{ key: 'more', title: '其他功能' },
|
{ key: 'more', title: '其他功能' },
|
||||||
]
|
]
|
||||||
return groupDefs
|
return groupDefs
|
||||||
.map(def => ({ ...def, items: visible.filter(n => n.group === def.key) }))
|
.map(def => ({ ...def, items: visible.filter(n => n.group === def.key) }))
|
||||||
.filter(group => group.items.length > 0)
|
.filter(group => group.items.length > 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
/* ---------------- 功能导航 ---------------- */
|
/* ---------------- 功能导航 ---------------- */
|
||||||
/** 图标块浅色背景:品牌深色 rgba 降透明度 → 轻量底色 */
|
/** 图标块浅色背景:品牌深色 rgba 降透明度 → 轻量底色 */
|
||||||
function toLightBg(rgba: string) {
|
function toLightBg(rgba : string) {
|
||||||
return rgba.replace('0.95)', '0.15)')
|
return rgba.replace('0.95)', '0.15)')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 图标颜色:品牌深色实色 */
|
/** 图标颜色:品牌深色实色 */
|
||||||
function toSolidColor(rgba: string) {
|
function toSolidColor(rgba : string) {
|
||||||
return rgba.replace('0.95)', '1)')
|
return rgba.replace('0.95)', '1)')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 收藏导航项右侧文案跟随收藏总数(收藏页返回/切回时刷新) */
|
/** 收藏导航项右侧文案跟随收藏总数(收藏页返回/切回时刷新) */
|
||||||
function syncFavoritesNavText() {
|
function syncFavoritesNavText() {
|
||||||
const nav = navList.value.find(n => n.key === 'favorites')
|
const nav = navList.value.find(n => n.key === 'favorites')
|
||||||
if (nav) {
|
if (nav) {
|
||||||
nav.rightText = `共 ${favoritesStore.counts.total} 条收藏`
|
nav.rightText = `共 ${favoritesStore.counts.total} 条收藏`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleGetNavList() {
|
async function handleGetNavList() {
|
||||||
const dataVisualAvailable = await checkDataVisualPlugin()
|
const dataVisualAvailable = await checkDataVisualPlugin()
|
||||||
|
|
||||||
navList.value = [
|
navList.value = [
|
||||||
{
|
{
|
||||||
key: 'favorites',
|
key: 'favorites',
|
||||||
title: '我的收藏',
|
title: '我的收藏',
|
||||||
icon: 'star',
|
iconPrefix: 'uhemoji2-icon',
|
||||||
bgColor: 'rgba(255, 179, 0, 0.95)',
|
icon: '-smiling',
|
||||||
rightText: '',
|
bgColor: 'rgba(255, 179, 0, 0.95)',
|
||||||
path: '/pages-blog/favorites/favorites',
|
rightText: '',
|
||||||
show: true,
|
path: '/pages-blog/favorites/favorites',
|
||||||
group: 'blog',
|
show: true,
|
||||||
},
|
group: 'blog',
|
||||||
{
|
},
|
||||||
key: 'data-visual',
|
{
|
||||||
title: '数据看板',
|
key: 'data-visual',
|
||||||
icon: 'chart',
|
title: '数据看板',
|
||||||
bgColor: 'rgba(102, 60, 201, 0.95)',
|
iconPrefix: 'uhemoji2-icon',
|
||||||
rightText: '站点数据可视化',
|
icon: '-surprised',
|
||||||
path: '/pages-blog/data-visual/data-visual',
|
bgColor: 'rgba(102, 60, 201, 0.95)',
|
||||||
show: dataVisualAvailable,
|
rightText: '站点数据可视化',
|
||||||
group: 'blog',
|
path: '/pages-blog/data-visual/data-visual',
|
||||||
},
|
show: dataVisualAvailable,
|
||||||
{
|
group: 'blog',
|
||||||
key: 'archives',
|
},
|
||||||
title: calcAuditModeEnabled.value ? '内容归档' : '文章归档',
|
{
|
||||||
icon: 'folder',
|
key: 'archives',
|
||||||
bgColor: 'rgba(3, 169, 244, 0.95)',
|
title: calcAuditModeEnabled.value ? '内容归档' : '文章归档',
|
||||||
rightText: calcAuditModeEnabled.value ? '全部已归档内容' : '全部已归档文章',
|
iconPrefix: 'uhemoji2-icon',
|
||||||
path: '/pages-blog/archives/archives',
|
icon: '-mask',
|
||||||
show: true,
|
bgColor: 'rgba(3, 169, 244, 0.95)',
|
||||||
group: 'blog',
|
rightText: calcAuditModeEnabled.value ? '全部已归档内容' : '全部已归档文章',
|
||||||
},
|
path: '/pages-blog/archives/archives',
|
||||||
{
|
show: true,
|
||||||
key: 'love',
|
group: 'blog',
|
||||||
title: '恋爱日记',
|
},
|
||||||
icon: 'heart',
|
{
|
||||||
bgColor: 'rgba(255, 76, 103, 0.95)',
|
key: 'love',
|
||||||
rightText: '博主的恋爱日记',
|
title: '恋爱日记',
|
||||||
path: '/pages-blog/love/love',
|
iconPrefix: 'uhemoji2-icon',
|
||||||
show: loveEnabled.value,
|
icon: '-in-love',
|
||||||
// show: true,
|
bgColor: 'rgba(255, 76, 103, 0.95)',
|
||||||
group: 'blog',
|
rightText: '博主的恋爱日记',
|
||||||
},
|
path: '/pages-blog/love/love',
|
||||||
{
|
show: loveEnabled.value,
|
||||||
key: 'vote',
|
// show: true,
|
||||||
title: '投票中心',
|
group: 'blog',
|
||||||
icon: 'box',
|
},
|
||||||
bgColor: 'rgba(0, 188, 212, 0.95)',
|
{
|
||||||
rightText: '查看和进行投票',
|
key: 'vote',
|
||||||
path: '/pages-blog/votes/votes',
|
title: '投票中心',
|
||||||
show: !calcAuditModeEnabled.value && calcVotePluginEnabled.value,
|
iconPrefix: 'uhemoji2-icon',
|
||||||
// show: true,
|
icon: '-confused',
|
||||||
group: 'blog',
|
bgColor: 'rgba(0, 188, 212, 0.95)',
|
||||||
},
|
rightText: '查看和进行投票',
|
||||||
{
|
path: '/pages-blog/votes/votes',
|
||||||
key: 'friend-links',
|
show: !calcAuditModeEnabled.value && calcVotePluginEnabled.value,
|
||||||
title: '友情链接',
|
// show: true,
|
||||||
icon: 'link',
|
group: 'blog',
|
||||||
bgColor: 'rgba(0, 150, 136, 0.95)',
|
},
|
||||||
rightText: '看看博主朋友们吧',
|
{
|
||||||
path: '/pages-blog/friend-links/friend-links',
|
key: 'friend-links',
|
||||||
show: calcLinksPluginEnabled.value,
|
title: '友情链接',
|
||||||
// show: true,
|
iconPrefix: 'uhemoji2-icon',
|
||||||
group: 'blog',
|
icon: '-cool',
|
||||||
},
|
bgColor: 'rgba(0, 150, 136, 0.95)',
|
||||||
{
|
rightText: '看看博主朋友们吧',
|
||||||
key: 'disclaimers',
|
path: '/pages-blog/friend-links/friend-links',
|
||||||
title: '免责声明',
|
show: calcLinksPluginEnabled.value,
|
||||||
icon: 'map',
|
// show: true,
|
||||||
bgColor: 'rgba(121, 85, 72, 0.95)',
|
group: 'blog',
|
||||||
rightText: '博客内容免责声明',
|
},
|
||||||
path: '/pages-blog/disclaimers/disclaimers',
|
{
|
||||||
show: !!basicConfig.value?.disclaimers?.enabled,
|
key: 'disclaimers',
|
||||||
// show: true,
|
title: '免责声明',
|
||||||
group: 'more',
|
iconPrefix: 'uhemoji2-icon',
|
||||||
},
|
icon: '-smirking',
|
||||||
{
|
bgColor: 'rgba(121, 85, 72, 0.95)',
|
||||||
key: 'contact-blogger',
|
rightText: '博客内容免责声明',
|
||||||
title: '联系博主',
|
path: '/pages-blog/disclaimers/disclaimers',
|
||||||
icon: 'message',
|
show: !!basicConfig.value?.disclaimers?.enabled,
|
||||||
bgColor: 'rgba(255, 152, 0, 0.95)',
|
// show: true,
|
||||||
rightText: '博主常用联系方式',
|
group: 'more',
|
||||||
path: '/pages-blog/contact/contact',
|
},
|
||||||
show: socialEnabled.value,
|
{
|
||||||
// show: true,
|
key: 'contact-blogger',
|
||||||
group: 'more',
|
title: '联系博主',
|
||||||
},
|
iconPrefix: 'uhemoji2-icon',
|
||||||
{
|
icon: '-wink',
|
||||||
key: 'about',
|
bgColor: 'rgba(255, 152, 0, 0.95)',
|
||||||
title: '关于项目',
|
rightText: '博主常用联系方式',
|
||||||
icon: 'info',
|
path: '/pages-blog/contact/contact',
|
||||||
bgColor: 'rgba(96, 125, 139, 0.95)',
|
show: socialEnabled.value,
|
||||||
rightText: '小莫唐尼开源项目',
|
// show: true,
|
||||||
path: '/pages-blog/about/about',
|
group: 'more',
|
||||||
show: !!basicConfig.value?.showAboutSystem,
|
},
|
||||||
// show: true,
|
{
|
||||||
group: 'more',
|
key: 'about',
|
||||||
},
|
title: '关于项目',
|
||||||
{
|
iconPrefix: 'uhemoji2-icon',
|
||||||
key: 'setting',
|
icon: '-happy-',
|
||||||
title: '偏好设置',
|
bgColor: 'rgba(96, 125, 139, 0.95)',
|
||||||
icon: 'settings',
|
rightText: '小莫唐尼开源项目',
|
||||||
bgColor: 'rgba(121, 134, 203, 0.95)',
|
path: '/pages-blog/about/about',
|
||||||
rightText: '首页布局、卡片样式等本地偏好',
|
show: !!basicConfig.value?.showAboutSystem,
|
||||||
path: '/pages-blog/setting/setting',
|
// show: true,
|
||||||
show: true,
|
group: 'more',
|
||||||
group: 'more',
|
},
|
||||||
},
|
{
|
||||||
]
|
key: 'setting',
|
||||||
syncFavoritesNavText()
|
title: '偏好设置',
|
||||||
}
|
iconPrefix: 'uhemoji2-icon',
|
||||||
|
icon: '-tired',
|
||||||
|
bgColor: 'rgba(121, 134, 203, 0.95)',
|
||||||
|
rightText: '首页布局、卡片样式等本地偏好',
|
||||||
|
path: '/pages-blog/setting/setting',
|
||||||
|
show: true,
|
||||||
|
group: 'more',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
syncFavoritesNavText()
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- 数据加载 ---------------- */
|
/* ---------------- 数据加载 ---------------- */
|
||||||
async function handleGetData() {
|
async function handleGetData() {
|
||||||
try {
|
try {
|
||||||
const res = await getBlogStatistics()
|
const res = await getBlogStatistics()
|
||||||
statistics.value = res.data
|
statistics.value = res.data
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error('获取统计失败', err)
|
console.error('获取统计失败', err)
|
||||||
uni.showToast({ icon: 'none', title: t('common.loadFailedRetry') })
|
uni.showToast({ icon: 'none', title: t('common.loadFailedRetry') })
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 交互 ---------------- */
|
/* ---------------- 交互 ---------------- */
|
||||||
function handleOnNav(data: { path: string | null, isAdmin?: boolean }) {
|
function handleOnNav(data : { path : string | null, isAdmin ?: boolean }) {
|
||||||
const { path, isAdmin } = data
|
const { path, isAdmin } = data
|
||||||
if (!path)
|
if (!path)
|
||||||
return
|
return
|
||||||
|
|
||||||
// 拦截后台管理页面(需超管登录)
|
// 拦截后台管理页面(需超管登录)
|
||||||
if (isAdmin && !checkHasAdminLogin()) {
|
if (isAdmin && !checkHasAdminLogin()) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '未登录超管账号或登录状态已过期,是否立即登录?',
|
content: '未登录超管账号或登录状态已过期,是否立即登录?',
|
||||||
showCancel: true,
|
showCancel: true,
|
||||||
cancelText: '否',
|
cancelText: '否',
|
||||||
cancelColor: '#999999',
|
cancelColor: '#999999',
|
||||||
confirmText: '是',
|
confirmText: '是',
|
||||||
confirmColor: '#03a9f4',
|
confirmColor: '#03a9f4',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.navigateTo({ url: '/pages/auth/login' })
|
uni.navigateTo({ url: '/pages/auth/login' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.navigateTo({ url: path })
|
uni.navigateTo({ url: path })
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 生命周期 ---------------- */
|
/* ---------------- 生命周期 ---------------- */
|
||||||
watch(haloConfigs, () => {
|
watch(haloConfigs, () => {
|
||||||
handleGetNavList()
|
handleGetNavList()
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
handleGetData()
|
handleGetData()
|
||||||
|
|
||||||
// 从收藏页返回/切回时刷新收藏数文案
|
// 从收藏页返回/切回时刷新收藏数文案
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
syncFavoritesNavText()
|
syncFavoritesNavText()
|
||||||
})
|
})
|
||||||
|
|
||||||
onPullDownRefresh(() => {
|
onPullDownRefresh(() => {
|
||||||
handleGetData()
|
handleGetData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="box-border min-h-screen w-screen bg-page pb-8">
|
<view class="box-border min-h-screen w-screen bg-page pb-8">
|
||||||
<!-- 头部:博主信息(背景图 + 遮罩 + wave,内容区做状态栏适配) -->
|
<!-- 头部:博主信息(背景图 + 遮罩 + wave,内容区做状态栏适配) -->
|
||||||
<view class="blogger-info relative h-76 w-full bg-cover bg-no-repeat" :style="[calcProfileStyle]">
|
<view class="blogger-info relative h-76 w-full bg-cover bg-no-repeat" :style="[calcProfileStyle]">
|
||||||
<!-- 背景遮罩 -->
|
<!-- 背景遮罩 -->
|
||||||
<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)]">
|
||||||
/>
|
{{ bloggerInfo.nickname }}
|
||||||
<view class="mt-4 text-lg text-white font-bold text-shadow-[0_2rpx_8rpx_rgba(0,0,0,0.4)]">
|
</view>
|
||||||
{{ bloggerInfo.nickname }}
|
<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)]">
|
||||||
<view
|
{{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }}
|
||||||
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)]"
|
</view>
|
||||||
>
|
</view>
|
||||||
{{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }}
|
<image v-if="calcWaveUrl" :src="calcWaveUrl" mode="scaleToFill"
|
||||||
</view>
|
class="gif-wave absolute bottom-0 left-0 z-99 h-[100rpx] w-full" style="mix-blend-mode: screen;" />
|
||||||
</view>
|
</view>
|
||||||
<image
|
|
||||||
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;"
|
|
||||||
/>
|
|
||||||
</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">
|
/>
|
||||||
{{ item.label }}
|
<view class="mt-1 text-xs text-gray-500">
|
||||||
</view>
|
{{ item.label }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 功能导航(分组玻璃卡) -->
|
<!-- 功能导航(分组玻璃卡) -->
|
||||||
<template v-for="group in calcNavGroups" :key="group.key">
|
<template v-for="group in calcNavGroups" :key="group.key">
|
||||||
<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' : ''"
|
||||||
:class="index < group.items.length - 1 ? 'border-b border-b-solid border-black/5' : ''" @click="handleOnNav(nav)"
|
@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="uh-global-card-glass border uh-shadow-xs h-8 w-8 flex items-center justify-center rounded-xl"
|
||||||
class="h-9 w-9 flex items-center justify-center border border-black/5 rounded-xl"
|
:style="{ backgroundColor: toLightBg(nav.bgColor) }">
|
||||||
:style="{ backgroundColor: toLightBg(nav.bgColor) }"
|
<wd-icon :class-prefix="nav.iconPrefix" :name="nav.icon" size="36rpx"
|
||||||
>
|
:color="toSolidColor(nav.bgColor)" />
|
||||||
<wd-icon :name="nav.icon" size="20px" :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>
|
<view class="nav-right flex items-center gap-2">
|
||||||
<view class="nav-right flex items-center gap-2">
|
<text class="nav-right-text text-xs text-gray-400">{{ nav.rightText }}</text>
|
||||||
<text class="nav-right-text text-xs text-gray-400">{{ nav.rightText }}</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>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 版权 -->
|
<!-- 版权 -->
|
||||||
<view v-if="copyrightConfig?.enabled" class="mt-6 px-6 text-center text-xs text-gray-400">
|
<view v-if="copyrightConfig?.enabled" class="mt-6 px-6 text-center text-xs text-gray-400">
|
||||||
<view>{{ copyrightConfig.content }}</view>
|
<view>{{ copyrightConfig.content }}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -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,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
Reference in New Issue
Block a user