mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor: 批量优化页面组件与弹窗组件
1. 重构多个页面的插件检测逻辑,统一提示文案为维护中样式,新增页面跳转自动滚动到顶部并初始化搜索/数据加载 2. 优化uh-glass-popup组件,新增lazyRender和hideWhenClose属性并设置默认值 3. 重构友链、瞬间等页面的加载逻辑,移除多余的setTimeout,统一使用sleep延迟 4. 重写偏好设置页面的枚举选择弹窗,替换为自定义弹窗组件 5. 优化瞬间页面的UI布局,改为时间轴样式 6. 修复多个组件的样式细节与代码规范问题
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
/** 单列选项(wd-picker PickerOption 的宽松形态,label/value 与 wot 一致) */
|
||||||
|
interface IPickerOption {
|
||||||
|
label?: string | number
|
||||||
|
value?: string | number
|
||||||
|
disabled?: boolean
|
||||||
|
children?: IPickerOption[]
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
/** v-model:visible 是否显示 */
|
||||||
|
visible: boolean
|
||||||
|
/** 弹层标题 */
|
||||||
|
title?: string
|
||||||
|
/** 选择器数据(单列/多列;与 wd-picker columns 一致) */
|
||||||
|
columns?: Array<IPickerOption | IPickerOption[]>
|
||||||
|
/** 选中项(单列如 ['value']) */
|
||||||
|
modelValue?: (string | number)[]
|
||||||
|
/** 确认/取消按钮文案 */
|
||||||
|
confirmButtonText?: string
|
||||||
|
cancelButtonText?: string
|
||||||
|
/** 自定义层级 */
|
||||||
|
zIndex?: number
|
||||||
|
/** 点击遮罩是否关闭 */
|
||||||
|
closeOnClickModal?: boolean
|
||||||
|
/** 底部安全距离适配 */
|
||||||
|
safeAreaInsetBottom?: boolean
|
||||||
|
/** 追加到选择器根元素的自定义类 */
|
||||||
|
customClass?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IEmits {
|
||||||
|
(e: 'update:visible', value: boolean): void
|
||||||
|
(e: 'update:modelValue', value: (string | number)[]): void
|
||||||
|
(e: 'confirm', payload: { value: (string | number)[] }): void
|
||||||
|
(e: 'open'): void
|
||||||
|
(e: 'cancel'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
|
title: '',
|
||||||
|
columns: () => [],
|
||||||
|
modelValue: () => [],
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
zIndex: 15,
|
||||||
|
closeOnClickModal: true,
|
||||||
|
safeAreaInsetBottom: true,
|
||||||
|
customClass: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<IEmits>()
|
||||||
|
|
||||||
|
const pickerClass = computed(() => `uh-picker ${props.customClass}`.trim())
|
||||||
|
|
||||||
|
/* ---------------- wd-picker 事件转发(在 script 处理,模板只做绑定) ---------------- */
|
||||||
|
function handleUpdateVisible(value: boolean) {
|
||||||
|
emit('update:visible', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateModelValue(value: (string | number)[]) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirm(payload: { value: (string | number)[] }) {
|
||||||
|
emit('confirm', payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpen() {
|
||||||
|
emit('open')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-picker
|
||||||
|
:visible="visible" :title="title" :columns="columns" :model-value="modelValue"
|
||||||
|
:confirm-button-text="confirmButtonText" :cancel-button-text="cancelButtonText" :z-index="zIndex"
|
||||||
|
:close-on-click-modal="closeOnClickModal" :safe-area-inset-bottom="safeAreaInsetBottom"
|
||||||
|
:custom-class="pickerClass" @update:visible="handleUpdateVisible" @update:model-value="handleUpdateModelValue"
|
||||||
|
@confirm="handleConfirm" @open="handleOpen" @cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</wd-picker>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 弹层面板玻璃质感(与 uh-glass-popup 同款;wd-picker 内层 popup 挂 .wd-picker__popup) */
|
||||||
|
:deep(.wd-picker__popup .wd-popup) {
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: rgb(255 255 255 / 85%);
|
||||||
|
border: 4rpx solid rgb(255 255 255 / 90%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1rpx 0 rgb(255 255 255 / 75%),
|
||||||
|
0 8rpx 32rpx rgb(90 105 200 / 14%);
|
||||||
|
backdrop-filter: blur(24rpx) saturate(160%);
|
||||||
|
-webkit-backdrop-filter: blur(24rpx) saturate(160%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 低端 WebView 不支持 backdrop-filter 的兜底:提高不透明度保证可读性 */
|
||||||
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
||||||
|
:deep(.wd-picker__popup .wd-popup) {
|
||||||
|
background-color: rgb(255 255 255 / 88%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-popup--bottom) {
|
||||||
|
left: 24rpx;
|
||||||
|
bottom: 24rpx;
|
||||||
|
right: 24rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
radius ?: string
|
radius ?: string
|
||||||
/** 是否开启 wot 位置自适应圆角(bottom→上圆角 / center→四圆角等),与 radius 二选一 */
|
/** 是否开启 wot 位置自适应圆角(bottom→上圆角 / center→四圆角等),与 radius 二选一 */
|
||||||
round ?: boolean
|
round ?: boolean
|
||||||
|
lazyRender ?: boolean
|
||||||
|
hideWhenClose?: boolean
|
||||||
/** 追加到面板的内联样式,如宽度:width:640rpx; */
|
/** 追加到面板的内联样式,如宽度:width:640rpx; */
|
||||||
customStyle ?: string
|
customStyle ?: string
|
||||||
customClass? :string
|
customClass? :string
|
||||||
@@ -43,6 +45,8 @@
|
|||||||
safeAreaInsetBottom: false,
|
safeAreaInsetBottom: false,
|
||||||
radius: '',
|
radius: '',
|
||||||
round: false,
|
round: false,
|
||||||
|
lazyRender: true,
|
||||||
|
hideWhenClose: true,
|
||||||
customStyle: '',
|
customStyle: '',
|
||||||
customClass:''
|
customClass:''
|
||||||
})
|
})
|
||||||
@@ -66,10 +70,12 @@
|
|||||||
:closable="closable"
|
:closable="closable"
|
||||||
:modal="modal"
|
:modal="modal"
|
||||||
:close-on-click-modal="closeOnClickModal"
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
:hide-when-close="hideWhenClose"
|
||||||
:safe-area-inset-bottom="safeAreaInsetBottom"
|
:safe-area-inset-bottom="safeAreaInsetBottom"
|
||||||
:round="round"
|
:round="round"
|
||||||
:custom-style="panelStyle"
|
:custom-style="panelStyle"
|
||||||
:custom-class="panelClass"
|
:custom-class="panelClass"
|
||||||
|
:lazy-render="lazyRender"
|
||||||
@update:model-value="(value: boolean) => emit('update:modelValue', value)"
|
@update:model-value="(value: boolean) => emit('update:modelValue', value)"
|
||||||
@close="emit('close')"
|
@close="emit('close')"
|
||||||
@click-modal="emit('click-modal')"
|
@click-modal="emit('click-modal')"
|
||||||
|
|||||||
@@ -201,7 +201,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<uh-glass-popup v-model="isShow" :z-index="100" position="bottom"
|
<uh-glass-popup v-model="isShow" :z-index="100" position="bottom"
|
||||||
custom-class="!border rounded-lt-2xl rounded-rt-2xl" @close="handleClose(false)">
|
custom-class="!border rounded-xl" @close="handleClose(false)">
|
||||||
<view class="mb-4 relative w-full flex items-center justify-around box-border px-4 pt-4">
|
<view class="mb-4 relative w-full flex items-center justify-around box-border px-4 pt-4">
|
||||||
<view class="w-full flex flex-col gap-y-1">
|
<view class="w-full flex flex-col gap-y-1">
|
||||||
<text class="text-md font-bold">申请收录小程序</text>
|
<text class="text-md font-bold">申请收录小程序</text>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
pluginId : string
|
pluginId : string
|
||||||
errorText ?: string
|
errorText ?: string
|
||||||
checking : boolean
|
checking : boolean
|
||||||
|
customClass ?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
errorText: '',
|
errorText: '',
|
||||||
})
|
})
|
||||||
@@ -26,7 +27,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view v-if="pluginInfo" class="max-w-3/5 mx-auto my-auto box-border flex flex-col items-center justify-center gap-6 text-sm">
|
<view v-if="pluginInfo"
|
||||||
|
class="max-w-3/5 mx-auto my-auto box-border flex flex-col items-center justify-center gap-6 text-sm"
|
||||||
|
:class="props.customClass">
|
||||||
|
|
||||||
<wd-icon class-prefix="uhemoji-icon" name="-cry" size="160rpx"></wd-icon>
|
<wd-icon class-prefix="uhemoji-icon" name="-cry" size="160rpx"></wd-icon>
|
||||||
|
|
||||||
@@ -46,8 +49,7 @@
|
|||||||
<!-- 微信端客服会话只能由原生 button 的 open-type="contact" 唤起,故此处不用 uh-button(view 实现) -->
|
<!-- 微信端客服会话只能由原生 button 的 open-type="contact" 唤起,故此处不用 uh-button(view 实现) -->
|
||||||
<button
|
<button
|
||||||
class="uh-contact-btn bg-white py-2 px-4 !rounded-full text-black text-sm leading-none flex items-center justify-center"
|
class="uh-contact-btn bg-white py-2 px-4 !rounded-full text-black text-sm leading-none flex items-center justify-center"
|
||||||
open-type="contact"
|
open-type="contact" hover-class="none">提交反馈</button>
|
||||||
hover-class="none">提交反馈</button>
|
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -59,5 +61,6 @@
|
|||||||
.uh-contact-btn::after {
|
.uh-contact-btn::after {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
/**
|
|
||||||
* 插件不可用提示(源自旧项目 components/plugin-unavailable,新建复刻)
|
|
||||||
* 当依赖的 Halo 插件未安装/未启用时展示:插件 logo、名称、错误标签、描述、插件地址、复制/反馈按钮
|
|
||||||
*/
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { NeedPlugins } from '@/utils/plugin'
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
|
||||||
/** 插件名称(与 NeedPlugins 中的 id 对应) */
|
|
||||||
pluginId: string
|
|
||||||
errorText?: string
|
|
||||||
useDecoration?: boolean
|
|
||||||
useBorder?: boolean
|
|
||||||
customStyle?: Record<string, string>
|
|
||||||
}>(), {
|
|
||||||
errorText: '',
|
|
||||||
useDecoration: true,
|
|
||||||
useBorder: true,
|
|
||||||
customStyle: () => ({}),
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'on-refresh'): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
/** 插件信息(未在清单中时兜底) */
|
|
||||||
const pluginInfo = computed(() => {
|
|
||||||
const info = NeedPlugins.get(props.pluginId)
|
|
||||||
return info || {
|
|
||||||
id: props.pluginId,
|
|
||||||
name: props.pluginId,
|
|
||||||
desc: '',
|
|
||||||
logo: '',
|
|
||||||
url: '',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const defaultStyle = {
|
|
||||||
width: '80vw',
|
|
||||||
borderRadius: '24rpx',
|
|
||||||
}
|
|
||||||
|
|
||||||
const calcCustomStyle = computed(() => ({
|
|
||||||
...defaultStyle,
|
|
||||||
...props.customStyle,
|
|
||||||
}))
|
|
||||||
|
|
||||||
function copy() {
|
|
||||||
if (!pluginInfo.value.url)
|
|
||||||
return
|
|
||||||
uni.setClipboardData({
|
|
||||||
data: pluginInfo.value.url,
|
|
||||||
showToast: false,
|
|
||||||
success: () => {
|
|
||||||
uni.showToast({ icon: 'none', title: '插件地址已复制' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view
|
|
||||||
v-if="pluginInfo"
|
|
||||||
class="uh-plugin-unavailable mx-auto my-auto box-border flex flex-col gap-6 p-10 text-[28rpx]"
|
|
||||||
:class="{ border: useBorder, decoration: useDecoration }"
|
|
||||||
:style="[calcCustomStyle]"
|
|
||||||
>
|
|
||||||
<!-- 图标 -->
|
|
||||||
<image class="plugin-logo box-border h-[120rpx] w-[120rpx] rounded-3xl" :src="pluginInfo.logo" mode="scaleToFill" />
|
|
||||||
<!-- 名称 -->
|
|
||||||
<view class="plugin-name box-border text-[32rpx] text-[#333] font-bold">
|
|
||||||
{{ pluginInfo.name }}
|
|
||||||
</view>
|
|
||||||
<!-- 错误标签 -->
|
|
||||||
<view class="plugin-error box-border rounded-[36rpx] px-4 py-1.5 text-[24rpx] font-bold" style="background-color: rgb(255 61 49 / 7.5%); color: rgb(255 61 49);">
|
|
||||||
未安装/启用插件
|
|
||||||
</view>
|
|
||||||
<!-- 描述 -->
|
|
||||||
<view class="plugin-desc box-border w-[60vw] text-center text-[24rpx] text-[#64748b]">
|
|
||||||
{{ pluginInfo.desc }}
|
|
||||||
</view>
|
|
||||||
<!-- 自定义错误提示 -->
|
|
||||||
<view v-if="errorText" class="plugin-tip box-border border-2 rounded-xl border-dashed px-5 py-2.5 text-[24rpx]" style="border-color: #f2c97d; color: #f0a020;">
|
|
||||||
{{ errorText }}
|
|
||||||
</view>
|
|
||||||
<!-- 插件地址 -->
|
|
||||||
<view class="plugin-url box-border w-full overflow-hidden text-ellipsis whitespace-nowrap rounded-xl bg-[#f1f5f9] px-6 py-4 text-[24rpx] text-[#666]">
|
|
||||||
插件地址:{{ pluginInfo.url }}
|
|
||||||
</view>
|
|
||||||
<!-- 反馈按钮/复制地址 -->
|
|
||||||
<view class="plugin-btns box-border w-full">
|
|
||||||
<!-- #ifndef MP-WEIXIN -->
|
|
||||||
<wd-button type="primary" block size="medium" @click="copy">
|
|
||||||
复制地址
|
|
||||||
</wd-button>
|
|
||||||
<!-- #endif -->
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
|
||||||
<view class="flex gap-3">
|
|
||||||
<wd-button type="primary" plain block size="medium" @click="copy">
|
|
||||||
复制地址
|
|
||||||
</wd-button>
|
|
||||||
<wd-button type="warning" plain block size="medium" open-type="contact">
|
|
||||||
提交反馈
|
|
||||||
</wd-button>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
<!-- 刷新按钮 -->
|
|
||||||
<view class="flex justify-center">
|
|
||||||
<wd-button size="small" plain type="info" @click="emit('on-refresh')">
|
|
||||||
刷新试试
|
|
||||||
</wd-button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="plugin-copyright text-[20rpx] text-[#999]" style="transform: scale(0.9) translateY(20px);">
|
|
||||||
提示:请确保 Halo 博客已安装相关插件
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.uh-plugin-unavailable {
|
|
||||||
&.border {
|
|
||||||
border: 2rpx solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.decoration {
|
|
||||||
background-color: rgb(255 255 255 / 95%);
|
|
||||||
box-shadow: 0 0 12rpx rgb(226 232 240 / 35%);
|
|
||||||
backdrop-filter: blur(6rpx);
|
|
||||||
border-top: 12rpx solid rgb(3 169 244);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
import { getMiniProgramLinkGroupedList } from '@/api/uni-halo'
|
import { getMiniProgramLinkGroupedList } from '@/api/uni-halo'
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { useSettingStore } from '@/store/setting'
|
import { useSettingStore } from '@/store/setting'
|
||||||
|
import { sleep } from '@/utils/common'
|
||||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||||
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
||||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||||
@@ -29,24 +30,37 @@
|
|||||||
/** 站点 tab:PluginLinks */
|
/** 站点 tab:PluginLinks */
|
||||||
const { pluginId: sitePluginId, checking: siteChecking, tips: siteTips, available: sitePluginAvailable, check: checkSitePluginAvailable } = usePluginAvailable({
|
const { pluginId: sitePluginId, checking: siteChecking, tips: siteTips, available: sitePluginAvailable, check: checkSitePluginAvailable } = usePluginAvailable({
|
||||||
pluginId: NeedPluginIds.PluginLinks,
|
pluginId: NeedPluginIds.PluginLinks,
|
||||||
tips: '检测到当前插件没有安装或者启用,无法使用友情链接功能哦,请联系管理员',
|
tips: '啊偶,功能正在维护中...',
|
||||||
|
callback: (isAvailable) => {
|
||||||
|
if (!isAvailable) { return }
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: 0,
|
||||||
|
})
|
||||||
|
handleGetLinkGroupData()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
/** 小程序 tab:plugin-uni-halo */
|
/** 小程序 tab:plugin-uni-halo */
|
||||||
const { pluginId: miniPluginId, checking: miniChecking, tips: miniTips, available: miniPluginAvailable, check: checkMiniPluginAvailable } = usePluginAvailable({
|
const { pluginId: miniPluginId, checking: miniChecking, tips: miniTips, available: miniPluginAvailable, check: checkMiniPluginAvailable } = usePluginAvailable({
|
||||||
pluginId: NeedPluginIds.PluginUniHalo,
|
pluginId: NeedPluginIds.PluginUniHalo,
|
||||||
tips: '检测到当前插件没有安装或者启用,无法使用小程序链接功能哦,请联系管理员',
|
tips: '啊偶,功能正在维护中...', callback: (isAvailable) => {
|
||||||
|
if (!isAvailable) { return }
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: 0,
|
||||||
|
})
|
||||||
|
handleGetMiniProgramLinks()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 重新检测站点插件:可用则拉取友链数据(供 uh-plugin-unavailable 刷新按钮) */
|
/** 重新检测站点插件:可用则拉取友链数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||||
async function handleSitePluginRefresh() {
|
async function handleSitePluginRefresh() {
|
||||||
if (await checkSitePluginAvailable())
|
if (await checkSitePluginAvailable()) { handleGetLinkGroupData() }
|
||||||
handleGetLinkGroupData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重新检测小程序插件:可用则拉取小程序链接数据(供 uh-plugin-unavailable 刷新按钮) */
|
/** 重新检测小程序插件:可用则拉取小程序链接数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||||
async function handleMiniPluginRefresh() {
|
async function handleMiniPluginRefresh() {
|
||||||
if (await checkMiniPluginAvailable())
|
if (await checkMiniPluginAvailable()) { handleGetMiniProgramLinks() }
|
||||||
handleGetMiniProgramLinks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- tabs ---------------- */
|
/* ---------------- tabs ---------------- */
|
||||||
@@ -64,8 +78,7 @@
|
|||||||
|
|
||||||
// 审核模式下小程序 tab 隐藏,强制停留在站点 tab
|
// 审核模式下小程序 tab 隐藏,强制停留在站点 tab
|
||||||
watch(() => appConfigStore.auditModeEnabled, (enabled) => {
|
watch(() => appConfigStore.auditModeEnabled, (enabled) => {
|
||||||
if (enabled)
|
if (enabled) { activeTabIndex.value = 0 }
|
||||||
activeTabIndex.value = 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/* ==================== 站点 tab(plugin-links) ==================== */
|
/* ==================== 站点 tab(plugin-links) ==================== */
|
||||||
@@ -122,12 +135,11 @@
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
dataList.value = dataList.value.concat(list)
|
dataList.value = dataList.value.concat(list)
|
||||||
setTimeout(() => {
|
await sleep(600)
|
||||||
updateSiteLoadingStatus(
|
updateSiteLoadingStatus(
|
||||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||||
)
|
)
|
||||||
loadMoreText.value = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
loadMoreText.value = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
@@ -135,9 +147,7 @@
|
|||||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
setTimeout(() => {
|
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +183,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcSiteThumbnail(val ?: string) : string {
|
|
||||||
if (!val)
|
|
||||||
return ''
|
|
||||||
const _val = val.endsWith('/') ? val : `${val}/`
|
|
||||||
return `https://image.thum.io/get/width/1000/crop/800/${_val}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== 小程序 tab(plugin-uni-halo) ==================== */
|
/* ==================== 小程序 tab(plugin-uni-halo) ==================== */
|
||||||
/* ---------------- 状态 ---------------- */
|
/* ---------------- 状态 ---------------- */
|
||||||
const { loadingStatus: miniLoadingStatus, updateLoadingStatus: updateMiniLoadingStatus } = useDataLoadingStatus()
|
const { loadingStatus: miniLoadingStatus, updateLoadingStatus: updateMiniLoadingStatus } = useDataLoadingStatus()
|
||||||
@@ -193,20 +196,17 @@
|
|||||||
try {
|
try {
|
||||||
const res = await getMiniProgramLinkGroupedList()
|
const res = await getMiniProgramLinkGroupedList()
|
||||||
miniGroups.value = res.data || []
|
miniGroups.value = res.data || []
|
||||||
setTimeout(() => {
|
await sleep(600)
|
||||||
updateMiniLoadingStatus(
|
updateMiniLoadingStatus(
|
||||||
miniGroups.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
miniGroups.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||||
)
|
)
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
updateMiniLoadingStatus(DataLoadingStatusEnum.Error)
|
updateMiniLoadingStatus(DataLoadingStatusEnum.Error)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
setTimeout(() => {
|
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,16 +393,17 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 悬浮按钮 -->
|
<!-- 悬浮按钮 -->
|
||||||
<view class="flot-buttons fixed bottom-10 right-4 z-999 flex flex-col gap-1.5">
|
<view class="flot-buttons fixed bottom-8 right-3 z-999 flex flex-col gap-1.5">
|
||||||
<view v-if="!haloPluginConfigs?.linksSubmitPlugin?.enabled"
|
<view v-if="!haloPluginConfigs?.linksSubmitPlugin?.enabled"
|
||||||
class="fab-btn uh-global-card-glass h-10 w-10 flex items-center justify-center rounded-full"
|
class="fab-btn uh-global-card-glass h-11 w-11 flex items-center justify-center rounded-full"
|
||||||
@click="toSubmitLinkPage">
|
@click="toSubmitLinkPage">
|
||||||
<wd-icon name="edit" size="20px" color="#6b7280" />
|
<wd-icon name="edit" size="20px" color="#6b7280" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 详情弹窗 -->
|
<!-- 详情弹窗 -->
|
||||||
<uh-glass-popup v-model="detail.show" position="bottom" :z-index="999" custom-class="rounded-xl !border">
|
<uh-glass-popup v-model="detail.show" position="bottom" :z-index="999"
|
||||||
|
custom-class="rounded-xl !border">
|
||||||
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
||||||
<view class="w-full flex flex-col gap-y-1">
|
<view class="w-full flex flex-col gap-y-1">
|
||||||
<text class="text-md font-bold">站点详情</text>
|
<text class="text-md font-bold">站点详情</text>
|
||||||
@@ -418,23 +419,24 @@
|
|||||||
<view class="flex">
|
<view class="flex">
|
||||||
<image class="h-20 w-20 shrink-0 rounded-2xl uh-global-card-glass"
|
<image class="h-20 w-20 shrink-0 rounded-2xl uh-global-card-glass"
|
||||||
:src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill" />
|
:src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill" />
|
||||||
<view class="ml-4 flex flex-1 flex-col gap-y-1 justify-center">
|
<view class="ml-4 flex flex-1 flex-col gap-y-1.5 justify-center">
|
||||||
<view class="text-lg text-gray-900 font-bold">
|
<view class="text-md text-gray-900 font-bold">
|
||||||
{{ detail.data.spec.displayName }}
|
{{ detail.data.spec.displayName }}
|
||||||
</view>
|
</view>
|
||||||
<view class="flex items-center gap-x-2">
|
<view class="flex items-center gap-x-2">
|
||||||
<text class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">{{ detail.data.spec.groupName }}</text>
|
<text
|
||||||
<text class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">
|
class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">{{ detail.data.spec.groupName }}</text>
|
||||||
|
<text
|
||||||
|
class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">
|
||||||
复制地址
|
复制地址
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view @click="handleCopyLink(detail.data)">
|
<view @click="handleCopyLink(detail.data)">
|
||||||
<text
|
<text class="text-xs text-gray-900">{{ detail.data.spec.url }}</text>
|
||||||
class="text-xs text-gray-900">{{ detail.data.spec.url }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="poup-desc mt-4 text-[28rpx] text-gray-600 leading-[1.6]">
|
<view class="poup-desc mt-4 text-xs text-gray-600 leading-5">
|
||||||
{{ detail.data.spec.description || '这个博主很懒,没写简介~' }}
|
{{ detail.data.spec.description || '这个博主很懒,没写简介~' }}
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@@ -457,11 +459,11 @@
|
|||||||
<view v-else class="content flex flex-1 flex-col">
|
<view v-else class="content flex flex-1 flex-col">
|
||||||
<!-- 分组列表 -->
|
<!-- 分组列表 -->
|
||||||
<view class="box-border flex-1 p-3">
|
<view class="box-border flex-1 p-3">
|
||||||
<view v-for="group in miniGroups" :key="group.groupName || 'ungrouped'" class="group-item mb-8">
|
<view v-for="group in miniGroups" :key="group.groupName || 'ungrouped'" class="group-item mb-4">
|
||||||
<view class="mb-4 flex items-center">
|
<view class="mb-3 flex items-center">
|
||||||
<text class="mr-2 inline-block h-[28rpx] w-[8rpx] rounded-full bg-secondary" />
|
<text class="mr-2 inline-block h-4 w-1 rounded-full bg-secondary" />
|
||||||
<text
|
<text
|
||||||
class="text-[30rpx] text-gray-900 font-bold">{{ group.displayName || '未分组' }}</text>
|
class="text-sm text-gray-900 font-bold">{{ group.displayName || '未分组' }}</text>
|
||||||
<text class="ml-3 text-xs text-gray-400">({{ group.links.length }})</text>
|
<text class="ml-3 text-xs text-gray-400">({{ group.links.length }})</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="group-cards flex flex-col gap-4">
|
<view class="group-cards flex flex-col gap-4">
|
||||||
@@ -491,9 +493,9 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 申请收录悬浮按钮 -->
|
<!-- 申请收录悬浮按钮 -->
|
||||||
<view class="fixed bottom-10 right-4 z-50">
|
<view class="fixed bottom-8 right-3 z-50">
|
||||||
<view
|
<view
|
||||||
class="box-border flex flex-col w-10 h-10 items-center justify-center rounded-full bg-gray-900"
|
class="box-border flex flex-col w-11 h-11 items-center justify-center rounded-full bg-gray-900"
|
||||||
@click="handleOpenApply">
|
@click="handleOpenApply">
|
||||||
<text class="text-xs text-white">申请</text>
|
<text class="text-xs text-white">申请</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -501,7 +503,8 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 小程序详情弹窗 -->
|
<!-- 小程序详情弹窗 -->
|
||||||
<uh-glass-popup v-model="miniDetail.show" :z-index="999" position="bottom" custom-class="rounded-xl !border">
|
<uh-glass-popup v-model="miniDetail.show" :z-index="999" position="bottom"
|
||||||
|
custom-class="!rounded-xl !border">
|
||||||
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
||||||
<view class="w-full flex flex-col gap-y-1">
|
<view class="w-full flex flex-col gap-y-1">
|
||||||
<text class="text-md font-bold">小程序详情</text>
|
<text class="text-md font-bold">小程序详情</text>
|
||||||
|
|||||||
@@ -241,10 +241,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="uh-global-love-page box-border min-h-screen w-screen flex flex-col">
|
<view class="uh-global-love-page box-border min-h-screen w-screen flex flex-col">
|
||||||
<!-- 自定义导航 -->
|
|
||||||
<uh-navbar default-title="恋爱清单" title-color="text-love" back-class="text-love"/>
|
<uh-navbar default-title="恋爱清单" title-color="text-love" back-class="text-love"/>
|
||||||
|
|
||||||
<!-- 粘性筛选区:参考投票页顶部胶囊设计,每个维度独立状态 -->
|
|
||||||
<wd-sticky>
|
<wd-sticky>
|
||||||
<view class="box-border px-3 pb-1 pt-2">
|
<view class="box-border px-3 pb-1 pt-2">
|
||||||
<view class="box-border flex items-center justify-between gap-x-2">
|
<view class="box-border flex items-center justify-between gap-x-2">
|
||||||
@@ -263,16 +261,10 @@
|
|||||||
</view>
|
</view>
|
||||||
</wd-sticky>
|
</wd-sticky>
|
||||||
|
|
||||||
<!-- 加载/错误/空占位(状态机) -->
|
|
||||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||||
min-height="60vh" empty-text="暂时还没有恋爱清单,快去制定你们的恋爱清单吧~" @refresh="handleGetList" />
|
min-height="60vh" empty-text="暂时还没有恋爱清单,快去制定你们的恋爱清单吧~" @refresh="handleGetList" />
|
||||||
|
|
||||||
<!-- 清单列表 -->
|
|
||||||
<view v-else class="box-border flex flex-1 flex-col gap-y-3 p-3 pb-safe">
|
<view v-else class="box-border flex flex-1 flex-col gap-y-3 p-3 pb-safe">
|
||||||
<view
|
|
||||||
class="uh-global-card-glass uh-shadow-xs box-border w-full rounded-xl p-3 text-center text-xs text-love">
|
|
||||||
看看我们的恋爱清单都完成了哪些吧
|
|
||||||
</view>
|
|
||||||
<block v-for="(item, index) in showList" :key="item.name">
|
<block v-for="(item, index) in showList" :key="item.name">
|
||||||
<view
|
<view
|
||||||
class="uh-global-card-glass uh-shadow-xs box-border w-full flex flex-col items-center rounded-xl p-3">
|
class="uh-global-card-glass uh-shadow-xs box-border w-full flex flex-col items-center rounded-xl p-3">
|
||||||
@@ -301,7 +293,7 @@
|
|||||||
<view class="desc-label w-16 shrink-0 text-gray-500 ">
|
<view class="desc-label w-16 shrink-0 text-gray-500 ">
|
||||||
计划内容
|
计划内容
|
||||||
</view>
|
</view>
|
||||||
<view class="desc-value w-0 flex-1 text-gray-900 leading-4">
|
<view class="desc-value w-0 flex-1 text-gray-900 leading-5">
|
||||||
{{ item.content || '-' }}
|
{{ item.content || '-' }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -22,7 +22,15 @@
|
|||||||
|
|
||||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||||
pluginId: NeedPluginIds.PluginSearchWidget,
|
pluginId: NeedPluginIds.PluginSearchWidget,
|
||||||
tips: '啊偶,当前功能未开放!',
|
tips: '啊偶,功能正在维护中...',
|
||||||
|
callback: (isAvailable) => {
|
||||||
|
if (!isAvailable) { return }
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: 0,
|
||||||
|
})
|
||||||
|
handleOnSearch()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handlePluginRefresh() {
|
async function handlePluginRefresh() {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
definePage({
|
definePage({
|
||||||
style: {
|
style: {
|
||||||
navigationBarTitleText: '偏好设置',
|
navigationBarTitleText: '偏好设置',
|
||||||
navigationStyle: 'custom'
|
navigationStyle: 'custom',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
/* ---------------- 状态读取 ---------------- */
|
/* ---------------- 状态读取 ---------------- */
|
||||||
function valueOf(path : Path) : unknown {
|
function prefValueOf(path : Path) : unknown {
|
||||||
return getByPath(settingStore.settings, path)
|
return getByPath(settingStore.settings, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +133,17 @@
|
|||||||
settingStore.savePreference(buildPatch(path, null))
|
settingStore.savePreference(buildPatch(path, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 枚举底部弹层 ---------------- */
|
/* ---------------- 枚举底部弹层(uh-glass-popup + wd-picker-view) ---------------- */
|
||||||
const enumSheet = ref<{ show : boolean, def : PrefDef | null }>({ show: false, def: null })
|
const enumSheet = ref<{ show : boolean, def : PrefDef | null }>({ show: false, def: null })
|
||||||
|
/** 弹层内滚动中的临时选中值(单列;确认时才落库,取消不生效) */
|
||||||
|
const pickerValue = ref<(string | number)[]>([''])
|
||||||
|
|
||||||
function handleOpenEnum(def : PrefDef) {
|
function handleOpenEnum(def : PrefDef) {
|
||||||
|
console.log('handleOpenEnum', def)
|
||||||
enumSheet.value = { show: true, def }
|
enumSheet.value = { show: true, def }
|
||||||
|
// 打开时同步当前值(跟随站点默认 → 空串哨兵)
|
||||||
|
pickerValue.value = [isFollowing(def) ? '' : String(prefValueOf(def.path) ?? '')]
|
||||||
|
console.log('pickerValue', pickerValue.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseEnum() {
|
function handleCloseEnum() {
|
||||||
@@ -157,37 +163,59 @@
|
|||||||
handleCloseEnum()
|
handleCloseEnum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- wd-picker 弹层数据 ---------------- */
|
/* ---------------- wd-picker-view 弹层数据 ---------------- */
|
||||||
/** 枚举弹层列(首项「跟随站点默认」,空串哨兵映射 null) */
|
/** 枚举弹层列(首项「跟随站点默认」,空串哨兵映射 null) */
|
||||||
const enumColumns = computed(() => {
|
const enumColumns = computed(() => {
|
||||||
const def = enumSheet.value.def
|
const def = enumSheet.value.def
|
||||||
if (!def)
|
if (!def) { return [] }
|
||||||
return []
|
|
||||||
return [
|
return [
|
||||||
{ label: '跟随站点默认', value: '' },
|
{ label: '跟随站点默认', value: '' },
|
||||||
...(def.options || []).map(opt => ({ label: opt.label, value: opt.value })),
|
...(def.options || []).map(opt => ({ label: opt.label, value: opt.value })),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 当前选中列值(单列;跟随站点默认时为空串) */
|
/** wd-picker-view 滚动变化:更新临时选中值(未确认不落库) */
|
||||||
const enumValue = computed(() => {
|
function handlePickerChange(payload : { selectedValues : (string | number)[] }) {
|
||||||
const def = enumSheet.value.def
|
pickerValue.value = payload.selectedValues
|
||||||
if (!def)
|
}
|
||||||
return ['']
|
|
||||||
return [isFollowing(def) ? '' : String(valueOf(def.path) ?? '')]
|
|
||||||
})
|
|
||||||
|
|
||||||
/** wd-picker 确认:空串哨兵还原为「跟随站点默认」 */
|
/** 确认:空串哨兵还原为「跟随站点默认」 */
|
||||||
function handlePickerConfirm(payload: { value: (string | number)[] }) {
|
function handlePickerConfirm() {
|
||||||
const picked = String(payload.value[0] ?? '')
|
const picked = String(pickerValue.value[0] ?? '')
|
||||||
handleChooseEnum(picked === '' ? null : picked)
|
handleChooseEnum(picked === '' ? null : picked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 取消:不落库,直接关闭 */
|
||||||
|
function handlePickerCancel() {
|
||||||
|
handleCloseEnum()
|
||||||
|
}
|
||||||
|
|
||||||
/** 当前枚举项是否处于「跟随站点默认」 */
|
/** 当前枚举项是否处于「跟随站点默认」 */
|
||||||
function isFollowing(def : PrefDef) : boolean {
|
function isFollowing(def : PrefDef) : boolean {
|
||||||
return !isOverridden(def.path)
|
return !isOverridden(def.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------- 展示行(预计算,避免模板渲染期函数调用) ---------------- */
|
||||||
|
/** 偏好展示行:展示文本/跟随态/开关值在数据层算好,模板只做属性访问 */
|
||||||
|
interface PrefRow extends PrefDef {
|
||||||
|
displayValue : string
|
||||||
|
following : boolean
|
||||||
|
/** 仅 kind==='bool' 使用 */
|
||||||
|
boolValue : boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRows(defs : PrefDef[]) : PrefRow[] {
|
||||||
|
return defs.map(def => ({
|
||||||
|
...def,
|
||||||
|
displayValue: enumLabelOf(def, prefValueOf(def.path)),
|
||||||
|
following: isFollowing(def),
|
||||||
|
boolValue: def.kind === 'bool' ? prefValueOf(def.path) === true : false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutRows = computed(() => buildRows(layoutPrefs))
|
||||||
|
const featureRows = computed(() => buildRows(featurePrefs))
|
||||||
|
|
||||||
/* ---------------- 重置全部 ---------------- */
|
/* ---------------- 重置全部 ---------------- */
|
||||||
function handleResetAll() {
|
function handleResetAll() {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
@@ -211,10 +239,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="box-border min-h-screen bg-page">
|
<view class="box-border min-h-screen bg-page">
|
||||||
<!-- 自定义标题 -->
|
<!-- 自定义标题 -->
|
||||||
<uh-navbar default-title="偏好设置" title-color="text-gray-900" :need-placeholder="true"></uh-navbar>
|
<uh-navbar default-title="偏好设置" title-color="text-gray-900" :need-placeholder="true" />
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<view class="box-border p-3 flex flex-col gap-y-6">
|
<view class="box-border flex flex-col gap-y-6 p-3">
|
||||||
<!-- 布局设置 -->
|
<!-- 布局设置 -->
|
||||||
<view class="flex flex-col gap-y-3">
|
<view class="flex flex-col gap-y-3">
|
||||||
<uh-section-title>
|
<uh-section-title>
|
||||||
@@ -224,14 +252,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</uh-section-title>
|
</uh-section-title>
|
||||||
<view class="uh-global-card-glass overflow-hidden rounded-2xl">
|
<view class="uh-global-card-glass overflow-hidden rounded-2xl">
|
||||||
<view v-for="(def, index) in layoutPrefs" :key="def.key"
|
<view v-for="(row, index) in layoutRows" :key="row.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 < layoutRows.length - 1 ? 'border-b border-black/5' : ''"
|
||||||
@click="handleOpenEnum(def)">
|
@click="handleOpenEnum(row)">
|
||||||
<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">{{ row.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="row.following" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
<view v-else
|
<view v-else
|
||||||
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
||||||
已自定义
|
已自定义
|
||||||
@@ -239,8 +267,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="row-value flex items-center gap-2">
|
<view class="row-value flex items-center gap-2">
|
||||||
<text
|
<text class="value-text text-[26rpx] text-gray-400">{{ row.displayValue }}</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>
|
||||||
@@ -255,64 +282,87 @@
|
|||||||
</template>
|
</template>
|
||||||
</uh-section-title>
|
</uh-section-title>
|
||||||
<view class="setting-sheet uh-global-card-glass 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="(row, index) in featureRows" :key="row.key">
|
||||||
<!-- 布尔开关 -->
|
<!-- 布尔开关 -->
|
||||||
<view v-if="def.kind === 'bool'" class="switch-row flex items-center justify-between px-4 py-4"
|
<view v-if="row.kind === 'bool'" class="switch-row flex items-center justify-between px-4 py-4"
|
||||||
:class="index < featurePrefs.length - 1 ? 'border-b border-black/5' : ''">
|
:class="index < featureRows.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">{{ row.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="row.following" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<view
|
<view
|
||||||
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
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"
|
<text class="revert-text text-2xs text-gray-400 underline"
|
||||||
@click.stop="handleRevert(def.path)">恢复默认</text>
|
@click.stop="handleRevert(row.path)">
|
||||||
|
恢复默认
|
||||||
|
</text>
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<wd-switch :model-value="valueOf(def.path) === true" @change="handleSwitchChange(def, $event)" />
|
<wd-switch :model-value="row.boolValue" @change="handleSwitchChange(row, $event)" />
|
||||||
</view>
|
</view>
|
||||||
<!-- 枚举选择(指示器位置) -->
|
<!-- 枚举选择(指示器位置) -->
|
||||||
<view v-else class="pick-row flex items-center justify-between px-4 py-4"
|
<view 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 < featureRows.length - 1 ? 'border-b border-black/5' : ''"
|
||||||
@click="handleOpenEnum(def)">
|
@click="handleOpenEnum(row)">
|
||||||
<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">{{ row.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="row.following" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<view
|
<view
|
||||||
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
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"
|
<text class="revert-text text-2xs text-gray-400 underline"
|
||||||
@click.stop="handleRevert(def.path)">恢复默认</text>
|
@click.stop="handleRevert(row.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
|
<text class="value-text text-[26rpx] text-gray-400">{{ row.displayValue }}</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>
|
||||||
<!-- 底部操作栏(玻璃悬浮) -->
|
<!-- 底部操作栏-->
|
||||||
<view class="box-border w-full px-2">
|
<view class="box-border w-full">
|
||||||
<uh-button custom-class="uh-global-card-glass py-2 !rounded-full"
|
<uh-button custom-class="uh-global-card-glass py-2 !rounded-xl" @click="handleResetAll">
|
||||||
@click="handleResetAll">恢复默认</uh-button>
|
恢复默认
|
||||||
|
</uh-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 枚举选择弹层(wd-picker 自带底部弹层与工具栏) -->
|
|
||||||
<wd-picker
|
<!-- 枚举选择弹层(uh-glass-popup + wd-picker-view,底部取消/确认,参考 uh-album-photo-viewer 布局) -->
|
||||||
v-model:visible="enumSheet.show" :title="enumSheet.def?.label || ''" :columns="enumColumns"
|
<uh-glass-popup v-model="enumSheet.show" :hide-when-close="false" position="bottom" custom-class="rounded-xl">
|
||||||
:model-value="enumValue" confirm-button-text="确定" cancel-button-text="取消"
|
<view class="box-border px-4 py-4">
|
||||||
@confirm="handlePickerConfirm"
|
<!-- 标题 -->
|
||||||
/>
|
<view class="mb-3 flex items-center justify-between">
|
||||||
|
<text class="text-md font-bold">{{ enumSheet.def?.label || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 选择器 -->
|
||||||
|
<wd-picker-view :columns="enumColumns" v-model="pickerValue"
|
||||||
|
custom-class="!p-0 !bg-transparent !rounded-xl overflow-hidden" @change="handlePickerChange" />
|
||||||
|
<!-- 底部操作:取消 / 确认 -->
|
||||||
|
<view class="mt-4 flex items-center justify-center gap-x-3">
|
||||||
|
<uh-button custom-class="flex-1 py-2 uh-global-card-glass border !rounded-xl bg-white/90"
|
||||||
|
@click="handlePickerCancel">
|
||||||
|
取消
|
||||||
|
</uh-button>
|
||||||
|
<uh-button
|
||||||
|
custom-class="flex-1 py-2 uh-global-card-glass !rounded-xl border bg-primary text-gray-900"
|
||||||
|
@click="handlePickerConfirm">
|
||||||
|
确定
|
||||||
|
</uh-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uh-glass-popup>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
import { getPhotoGroupList, getPhotoListByGroupName } from '@/api/halo'
|
import { getPhotoGroupList, getPhotoListByGroupName } from '@/api/halo'
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { checkImageUrl } from '@/utils/url'
|
import { checkImageUrl } from '@/utils/url'
|
||||||
|
import { usePluginAvailable } from '@/hooks/usePluginAvailable'
|
||||||
|
import { sleep } from '@/utils/common'
|
||||||
import { t } from '@/locale'
|
import { t } from '@/locale'
|
||||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||||
import type { IPhoto, IPhotoGroup } from '@/api/types/halo'
|
import type { IPhoto, IPhotoGroup } from '@/api/types/halo'
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
/** 依赖插件(PluginPhotos) */
|
/** 依赖插件(PluginPhotos) */
|
||||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||||
pluginId: 'PluginPhotos',
|
pluginId: 'PluginPhotos',
|
||||||
tips: '很抱歉,功能正在维护中...',
|
tips: '啊偶,功能正在维护中...',
|
||||||
callback: (isAvailable) => {
|
callback: (isAvailable) => {
|
||||||
if (!isAvailable) { return }
|
if (!isAvailable) { return }
|
||||||
uni.pageScrollTo({
|
uni.pageScrollTo({
|
||||||
@@ -114,6 +116,7 @@
|
|||||||
? dataList.value.concat(list)
|
? dataList.value.concat(list)
|
||||||
: list
|
: list
|
||||||
}
|
}
|
||||||
|
await sleep(600)
|
||||||
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||||
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
|
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
|
||||||
}
|
}
|
||||||
@@ -123,11 +126,7 @@
|
|||||||
loadMoreText.value = t('common.loadFailed')
|
loadMoreText.value = t('common.loadFailed')
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
setTimeout(() => {
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
lock.value = false
|
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,29 +190,30 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="min-h-screen w-screen flex flex-col bg-page pb-6">
|
<view class="box-border min-h-screen w-screen flex flex-col bg-page pb-6">
|
||||||
<uh-navbar :use-back="false" default-title="我的图库" title-color="text-gray-900"></uh-navbar>
|
<uh-navbar :use-back="false" default-title="我的图库" title-color="text-gray-900"></uh-navbar>
|
||||||
|
|
||||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
||||||
:checking="checking" @on-refresh="checkPluginAvailable" />
|
:checking="checking" @on-refresh="checkPluginAvailable" />
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<wd-sticky v-if="category.list.length!==0" class="w-full">
|
<wd-sticky v-if="category.list.length!==0" class="w-full">
|
||||||
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-2">
|
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-2">
|
||||||
<view v-for="(cate, index) in category.list" :key="cate.spec.displayName"
|
<view v-for="(cate, index) in category.list" :key="cate.spec.displayName"
|
||||||
class="uh-global-card-glass uh-shadow-xs mb-1 ml-3 inline-block border rounded-2xl px-4 py-1 text-sm"
|
class="uh-global-card-glass uh-shadow-xs mb-1 ml-3 inline-flex border rounded-2xl px-4 py-1 text-sm"
|
||||||
:class="{ 'bg-primary text-gray-900 font-bold': index === category.activeIndex }"
|
:class="{ 'bg-primary text-gray-900 font-bold': index === category.activeIndex }"
|
||||||
@click="handleGetDataByCategory(index, cate)">
|
@click="handleGetDataByCategory(index, cate)">
|
||||||
{{ cate.spec.displayName }} <text
|
{{ cate.spec.displayName }}
|
||||||
v-if="cate.spec.displayName!=='全部'">({{ cate.status?.photoCount ?? 0 }})</text>
|
<text v-if="cate.spec.displayName!=='全部'">
|
||||||
|
({{ cate.status?.photoCount ?? 0 }})
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</wd-sticky>
|
</wd-sticky>
|
||||||
|
|
||||||
<!-- 加载/错误占位 -->
|
|
||||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||||
@refresh="handleGetCategory" />
|
@refresh="handleGetCategory" />
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<view v-else class="box-border w-full p-3">
|
<view v-else class="box-border w-full p-3">
|
||||||
<view class="grid grid-cols-2 gap-2.5">
|
<view class="grid grid-cols-2 gap-2.5">
|
||||||
<view v-for="(item, index) in dataList" :key="index"
|
<view v-for="(item, index) in dataList" :key="index"
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/**
|
|
||||||
* 瞬间页(源自旧项目 pages/tabbar/moments/moments.vue,新建复刻)
|
|
||||||
* 功能:瞬间卡片列表(头像/内容/图片/音频/视频/标签) + 分页加载
|
|
||||||
* 设计:社交信息流(实心白纸卡 + 着色昵称 + 朋友圈式不缩进正文),区别于工具页的玻璃拟态
|
|
||||||
*/
|
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import { getMomentList } from '@/api/halo'
|
import { getMomentList } from '@/api/halo'
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||||
@@ -13,8 +9,8 @@
|
|||||||
import { checkAvatarUrl, checkThumbnailUrl } from '@/utils/url'
|
import { checkAvatarUrl, checkThumbnailUrl } from '@/utils/url'
|
||||||
import { buildMomentFavoriteItem } from '@/utils/favorite'
|
import { buildMomentFavoriteItem } from '@/utils/favorite'
|
||||||
import { generateUUID } from '@/utils/uuid'
|
import { generateUUID } from '@/utils/uuid'
|
||||||
|
import { sleep } from '@/utils/common'
|
||||||
import { formatTime } from '@/utils/formatTime'
|
import { formatTime } from '@/utils/formatTime'
|
||||||
import { randomTagColor } from '@/utils/random'
|
|
||||||
import { t } from '@/locale'
|
import { t } from '@/locale'
|
||||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||||
import { markdownConfig } from '@/config/markdown'
|
import { markdownConfig } from '@/config/markdown'
|
||||||
@@ -42,22 +38,26 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 站点名称(原 startConfig.title 已随启动页下线,改读 appConfig.appInfo.name) */
|
|
||||||
const siteName = computed(() => {
|
const siteName = computed(() => {
|
||||||
const appInfo = haloConfigs.value.appConfig?.appInfo as { name ?: string } | undefined
|
const appInfo = haloConfigs.value.appConfig?.appInfo as { name ?: string } | undefined
|
||||||
return appInfo?.name || bloggerInfo.value.nickname || 'uni-halo'
|
return appInfo?.name || bloggerInfo.value.nickname || 'uni-halo'
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 依赖插件(plugin-moments,参考 gallery 对象传参模式) */
|
|
||||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||||
pluginId: NeedPluginIds.PluginMoments,
|
pluginId: NeedPluginIds.PluginMoments,
|
||||||
tips: '检测到当前插件没有安装或者启用,无法使用瞬间功能哦,请联系管理员',
|
tips: '啊偶,功能正在维护中...',
|
||||||
|
callback: (isAvailable) => {
|
||||||
|
if (!isAvailable) { return }
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: 0,
|
||||||
|
})
|
||||||
|
handleGetData()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 重新检测插件:可用则拉取数据(供 uh-plugin-unavailable 刷新按钮) */
|
|
||||||
async function handlePluginRefresh() {
|
async function handlePluginRefresh() {
|
||||||
if (await checkPluginAvailable())
|
if (await checkPluginAvailable()) { handleGetData() }
|
||||||
handleGetData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 状态 ---------------- */
|
/* ---------------- 状态 ---------------- */
|
||||||
@@ -70,6 +70,10 @@
|
|||||||
videos ?: { id ?: string, url : string }[]
|
videos ?: { id ?: string, url : string }[]
|
||||||
audios ?: { type ?: string, url : string }[]
|
audios ?: { type ?: string, url : string }[]
|
||||||
spec : IMoment['spec'] & { newHtml ?: string }
|
spec : IMoment['spec'] & { newHtml ?: string }
|
||||||
|
year : string
|
||||||
|
month : string
|
||||||
|
day : string
|
||||||
|
weekend : string
|
||||||
}
|
}
|
||||||
const dataList = ref<MomentCard[]>([])
|
const dataList = ref<MomentCard[]>([])
|
||||||
const isLoadMore = ref(false)
|
const isLoadMore = ref(false)
|
||||||
@@ -77,20 +81,33 @@
|
|||||||
const videoContexts = ref<Record<string, UniApp.VideoContext | undefined>>({})
|
const videoContexts = ref<Record<string, UniApp.VideoContext | undefined>>({})
|
||||||
const currentVideoId = ref<string | null>(null)
|
const currentVideoId = ref<string | null>(null)
|
||||||
|
|
||||||
/** 移除内容中的 tag 链接 */
|
|
||||||
function removeTagLinksCompletely(htmlString : string) : string {
|
function removeTagLinksCompletely(htmlString : string) : string {
|
||||||
const regex = /<a\b[^>]+class=(['"])[^'"]*\btag\b[^'"]*\1[^>]*>[\s\S]*?<\/a>/gi
|
const regex = /<a\b[^>]+class=(['"])[^'"]*\btag\b[^'"]*\1[^>]*>[\s\S]*?<\/a>/gi
|
||||||
return htmlString.replace(regex, '')
|
return htmlString.replace(regex, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 瞬间项映射(spec.content.medium 拆分为 images/videos/audios + 内容 tag 清理 + 作者兜底) */
|
const WEEKDAY_TEXT = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||||
|
|
||||||
|
function splitMomentDate(timeStr ?: string) {
|
||||||
|
const d = timeStr ? dayjs(timeStr) : null
|
||||||
|
if (!d || !d.isValid()) {
|
||||||
|
return { year: '', month: '', day: '', weekend: '' }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
year: `${d.year()}`,
|
||||||
|
month: `${d.month() + 1}`,
|
||||||
|
day: `${d.date()}`,
|
||||||
|
weekend: WEEKDAY_TEXT[d.day()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function mapMomentItem(item : IMoment) : MomentCard {
|
function mapMomentItem(item : IMoment) : MomentCard {
|
||||||
const medium = (item.spec.content?.medium || [])
|
const medium = (item.spec.content?.medium || [])
|
||||||
.map(x => ({ ...x, url: x.url || '' }))
|
.map(x => ({ ...x, url: x.url || '' }))
|
||||||
const owner = item.owner
|
const owner = item.owner
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
// 无顶层 owner(如个别历史接口)时兜底为博主信息
|
...splitMomentDate(item.spec?.releaseTime),
|
||||||
owner: owner?.displayName
|
owner: owner?.displayName
|
||||||
? owner
|
? owner
|
||||||
: { displayName: bloggerInfo.value.nickname || '', name: bloggerInfo.value.nickname || '', avatar: bloggerInfo.value.avatar },
|
: { displayName: bloggerInfo.value.nickname || '', name: bloggerInfo.value.nickname || '', avatar: bloggerInfo.value.avatar },
|
||||||
@@ -107,7 +124,6 @@
|
|||||||
/* ---------------- 数据加载 ---------------- */
|
/* ---------------- 数据加载 ---------------- */
|
||||||
async function handleGetData() {
|
async function handleGetData() {
|
||||||
if (calcAuditModeEnabled.value) {
|
if (calcAuditModeEnabled.value) {
|
||||||
// 审核模式:真实瞬间按 audit-data moments 过滤(数组顺序即展示顺序)
|
|
||||||
const auditMomentNames = appConfigStore.auditData.spec?.moments || []
|
const auditMomentNames = appConfigStore.auditData.spec?.moments || []
|
||||||
try {
|
try {
|
||||||
const res = await getMomentList({ page: 1, size: 0 })
|
const res = await getMomentList({ page: 1, size: 0 })
|
||||||
@@ -117,12 +133,9 @@
|
|||||||
filtered.sort((a, b) => (orderMap.get(a.metadata.name) ?? 999) - (orderMap.get(b.metadata.name) ?? 999))
|
filtered.sort((a, b) => (orderMap.get(a.metadata.name) ?? 999) - (orderMap.get(b.metadata.name) ?? 999))
|
||||||
const tempItems = filtered.map(mapMomentItem)
|
const tempItems = filtered.map(mapMomentItem)
|
||||||
dataList.value = tempItems
|
dataList.value = tempItems
|
||||||
nextTick(() => {
|
await sleep(600)
|
||||||
createVideoContexts(tempItems)
|
|
||||||
})
|
|
||||||
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||||
loadMoreText.value = t('common.noMore')
|
loadMoreText.value = t('common.noMore')
|
||||||
uni.hideLoading()
|
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -133,7 +146,6 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showLoading({ mask: true, title: t('common.loading') })
|
|
||||||
if (!isLoadMore.value) {
|
if (!isLoadMore.value) {
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||||
}
|
}
|
||||||
@@ -151,11 +163,9 @@
|
|||||||
dataList.value = isLoadMore.value
|
dataList.value = isLoadMore.value
|
||||||
? dataList.value.concat(tempItems)
|
? dataList.value.concat(tempItems)
|
||||||
: tempItems
|
: tempItems
|
||||||
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
|
||||||
|
|
||||||
nextTick(() => {
|
await sleep(600)
|
||||||
createVideoContexts(tempItems)
|
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
@@ -163,46 +173,10 @@
|
|||||||
loadMoreText.value = t('common.loadFailed')
|
loadMoreText.value = t('common.loadFailed')
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
setTimeout(() => {
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 视频互斥 ---------------- */
|
|
||||||
function createVideoContexts(list : { videos ?: { id ?: string }[] }[]) {
|
|
||||||
stopAllVideos()
|
|
||||||
list.map(item => item.videos || []).flat().forEach((item) => {
|
|
||||||
if (item.id) {
|
|
||||||
videoContexts.value[item.id] = uni.createVideoContext(`video_${item.id}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopAllVideos(excludesVideoId : string | null = null) {
|
|
||||||
Object.keys(videoContexts.value).forEach((videoId) => {
|
|
||||||
if (!excludesVideoId || excludesVideoId !== videoId) {
|
|
||||||
videoContexts.value[videoId]?.pause()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVideoPlay(videoId : string) {
|
|
||||||
currentVideoId.value = videoId
|
|
||||||
stopAllVideos(videoId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVideoPause(videoId : string) {
|
|
||||||
if (currentVideoId.value === videoId) {
|
|
||||||
currentVideoId.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVideoEnded() {
|
|
||||||
currentVideoId.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- 交互 ---------------- */
|
/* ---------------- 交互 ---------------- */
|
||||||
function handlePreview(index : number, list : { url : string }[]) {
|
function handlePreview(index : number, list : { url : string }[]) {
|
||||||
uni.previewImage({
|
uni.previewImage({
|
||||||
@@ -220,12 +194,10 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 是否已收藏该瞬间(卡片收藏格高亮) */
|
|
||||||
function isMomentFavorite(moment : IMoment) : boolean {
|
function isMomentFavorite(moment : IMoment) : boolean {
|
||||||
return favoritesStore.isFavorite('moment', moment.metadata.name)
|
return favoritesStore.isFavorite('moment', moment.metadata.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 切换收藏(收藏/取消),收藏时按当前卡片内容生成快照入库 */
|
|
||||||
function handleToggleMomentFavorite(moment : MomentCard) {
|
function handleToggleMomentFavorite(moment : MomentCard) {
|
||||||
if (!moment) { return }
|
if (!moment) { return }
|
||||||
const favorited = favoritesStore.toggle(buildMomentFavoriteItem(moment))
|
const favorited = favoritesStore.toggle(buildMomentFavoriteItem(moment))
|
||||||
@@ -271,7 +243,8 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
onReachBottom(() => {
|
onReachBottom(() => {
|
||||||
if (!uniHaloPluginAvailable.value) { return }
|
if (!uniHaloPluginAvailable.value)
|
||||||
|
return
|
||||||
if (calcAuditModeEnabled.value) {
|
if (calcAuditModeEnabled.value) {
|
||||||
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
|
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
|
||||||
return
|
return
|
||||||
@@ -289,91 +262,86 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page">
|
<view class="box-border min-h-screen w-screen flex flex-col bg-page">
|
||||||
<uh-navbar :use-back="false" default-title="我的日常" title-color="text-gray-900"></uh-navbar>
|
<uh-navbar :use-back="false" default-title="我的日常" title-color="text-gray-900" />
|
||||||
|
|
||||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
||||||
:checking="checking" @on-refresh="handlePluginRefresh" />
|
:checking="checking" @on-refresh="handlePluginRefresh" />
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- 加载失败(可重试) -->
|
|
||||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||||
min-height="60vh" @refresh="handleGetData" />
|
min-height="75vh" @refresh="handleGetData" />
|
||||||
|
|
||||||
<view v-else class="box-border flex flex-col gap-3 p-3 pt-0">
|
<view v-else class="box-border flex flex-col gap-3 p-3 pt-0">
|
||||||
|
|
||||||
<!-- 瞬间卡片 -->
|
<!-- 瞬间卡片 -->
|
||||||
<view v-for="moment in dataList" :key="moment.metadata.name"
|
<view v-for="moment in dataList" :key="moment.metadata.name" class="flex gap-x-2">
|
||||||
class="uh-global-card-glass uh-shadow-xs overflow-hidden rounded-xl">
|
<view class="shrink-0 flex flex-col gap-y-2 w-13">
|
||||||
<!-- 作者 -->
|
<view class="shrink-0 flex flex-col items-center font-bold">
|
||||||
|
<text
|
||||||
|
class="date-day text-xl text-primary leading-none">{{ moment.day }}/{{ moment.month }}</text>
|
||||||
|
<text class="date-year-month mt-2 text-sm text-gray-600">{{ moment.year }}</text>
|
||||||
|
<text class="date-weekend mt-1 text-xs text-gray-600">{{ moment.weekend }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="flex-1 w-full flex flex-col items-center">
|
||||||
|
<view class="shrink-0 w-4 h-4 bg-primary rounded-full uh-global-card-glass"></view>
|
||||||
|
<view class="w-1 h-full flex-1 bg-primary uh-global-card-glass rounded-full border"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="uh-global-card-glass uh-shadow-xs flex-1 overflow-hidden rounded-xl">
|
||||||
<view class="box-border flex items-center px-4 pt-4">
|
<view class="box-border flex items-center px-4 pt-4">
|
||||||
<view class="flex-1 flex items-center">
|
<view class="flex flex-1 items-center">
|
||||||
<image class="avatar h-[72rpx] w-[72rpx] shrink-0 rounded-full"
|
<image class="avatar h-9 w-9 shrink-0 rounded-full"
|
||||||
:src="checkAvatarUrl(moment.owner?.avatar || bloggerInfo.avatar)" mode="aspectFill" />
|
:src="checkAvatarUrl(moment.owner?.avatar || bloggerInfo.avatar)"
|
||||||
<view class="ml-3 flex flex-col">
|
mode="aspectFill" />
|
||||||
|
<view class="ml-2 flex flex-col">
|
||||||
<view class="text-sm text-gray-900 font-bold">
|
<view class="text-sm text-gray-900 font-bold">
|
||||||
{{ moment.owner?.displayName || bloggerInfo.nickname }}
|
{{ moment.owner?.displayName || bloggerInfo.nickname }}
|
||||||
</view>
|
</view>
|
||||||
<view class="mt-0.5 text-xs text-gray-400">
|
<view class="text-xs text-gray-400">
|
||||||
{{ formatMomentTime(moment.spec.releaseTime) }}
|
{{ formatMomentTime(moment.spec.releaseTime) }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="shrink-0">
|
<view class="shrink-0">
|
||||||
<uh-button custom-class="!py-1 bg-secondary font-semibold">详情</uh-button>
|
<uh-button custom-class="!py-1 bg-secondary text-xs font-semibold"
|
||||||
|
@click="handleToMomentDetail(moment)">
|
||||||
|
详情
|
||||||
|
</uh-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 正文 -->
|
<!-- 正文 -->
|
||||||
<view class="box-border px-4 pt-3">
|
<view class="box-border px-4 pt-3">
|
||||||
<view class="relative box-border bg-page p-3 rounded-lg">
|
<view class="relative box-border rounded-lg bg-page p-3">
|
||||||
<mp-html lazy-load :domain="markdownConfig.domain ?? ''"
|
<mp-html 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="moment.spec.newHtml || ''" :markdown="true" :show-line-number="true"
|
:content="moment.spec.newHtml || ''" :markdown="true" :show-line-number="true"
|
||||||
:show-language-name="true" copy-by-long-press
|
:show-language-name="true" copy-by-long-press />
|
||||||
@click.stop="handleToMomentDetail(moment)" />
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<view v-if="moment.images && moment.images.length !== 0"
|
<view v-if="moment.images && moment.images.length !== 0"
|
||||||
class="box-border flex flex-wrap items-start px-3 pt-3">
|
class="box-border flex flex-wrap items-start px-3 pt-2">
|
||||||
<view v-for="(image, mediumIndex) in moment.images" :key="mediumIndex"
|
<view v-for="(image, mediumIndex) in moment.images" :key="mediumIndex"
|
||||||
class="image-item box-border p-1"
|
class="image-item box-border p-1"
|
||||||
:class="moment.images && moment.images.length === 1 ? 'h-[350rpx] w-full' : (moment.images && moment.images.length === 2 ? 'h-[250rpx] w-1/2' : 'h-[200rpx] w-1/3')">
|
:class="moment.images && moment.images.length === 1 ? 'h-32 w-full' : (moment.images && moment.images.length === 2 ? 'h-[250rpx] w-1/2' : 'h-20 w-1/3')">
|
||||||
<image mode="aspectFill" class="image-src h-full w-full rounded-lg" :src="image.url"
|
<image mode="aspectFill" class="h-full w-full rounded-lg" :src="image.url"
|
||||||
@click="handlePreview(mediumIndex, moment.images || [])" />
|
@click="handlePreview(mediumIndex, moment.images || [])" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 音频 -->
|
|
||||||
<view v-if="moment.audios && moment.audios.length !== 0"
|
|
||||||
class="box-border flex flex-col gap-3 px-4 pt-3">
|
|
||||||
<uh-audio-player v-for="audio in moment.audios" :key="audio.url" :src="audio.url"
|
|
||||||
:poster="bloggerInfo.avatar" :name="`来自${siteName}的声音`" :author="bloggerInfo.nickname" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 视频 -->
|
|
||||||
<view v-if="moment.videos && moment.videos.length !== 0"
|
|
||||||
class="box-border flex flex-col gap-3 px-4 pt-3">
|
|
||||||
<video v-for="(video, index) in moment.videos" :id="`video_${video.id}`" :key="index"
|
|
||||||
class="video-src h-[400rpx] w-full rounded-xl" :src="video.url" :show-mute-btn="true"
|
|
||||||
:controls="true" :show-center-play-btn="true" :enable-progress-gesture="true"
|
|
||||||
@play="onVideoPlay(video.id || '')" @pause="onVideoPause(video.id || '')"
|
|
||||||
@ended="onVideoEnded" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="moment.spec.tags && moment.spec.tags.length !== 0"
|
<view v-if="moment.spec.tags && moment.spec.tags.length !== 0"
|
||||||
class="box-border px-4 mt-3 flex flex-wrap gap-2">
|
class="mt-3 box-border flex flex-wrap gap-2 px-4">
|
||||||
<text v-for="(tag, tagIndex) in moment.spec.tags" :key="tagIndex"
|
<text v-for="(tag, tagIndex) in moment.spec.tags" :key="tagIndex"
|
||||||
class="py-1 px-2 text-xs rounded-xl bg-secondary">
|
class="rounded-xl bg-secondary px-2 py-1 text-xs">
|
||||||
# {{ tag }}
|
# {{ tag }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- (点赞/评论) -->
|
<!-- (点赞/评论) -->
|
||||||
<view
|
<view
|
||||||
class="mt-2 mb-1 box-border w-full flex items-center justify-between gap-x-12 border-t border-black/5 py-3 px-4 text-xs text-gray-400">
|
class="mb-1 mt-2 box-border w-full flex items-center justify-between border-t border-black/5 px-4 py-3 text-xs text-gray-400">
|
||||||
<view class="flex items-center gap-x-1">
|
<view class="flex items-center gap-x-1">
|
||||||
<wd-icon class-prefix="uhemoji-icon" name="-kiss-" size="32rpx" />
|
<wd-icon class-prefix="uhemoji-icon" name="-kiss-" size="32rpx" />
|
||||||
<text class="text-sm text-gray-600">点赞 {{ moment.stats.upvote || 0 }}</text>
|
<text class="text-sm text-gray-600">点赞 {{ moment.stats.upvote || 0 }}</text>
|
||||||
@@ -385,11 +353,13 @@
|
|||||||
<view class="flex items-center gap-x-1" @click.stop="handleToggleMomentFavorite(moment)">
|
<view class="flex items-center gap-x-1" @click.stop="handleToggleMomentFavorite(moment)">
|
||||||
<wd-icon class-prefix="uhemoji-icon" name="-smile-" size="32rpx" />
|
<wd-icon class-prefix="uhemoji-icon" name="-smile-" size="32rpx" />
|
||||||
<text class="text-sm text-gray-600"
|
<text class="text-sm text-gray-600"
|
||||||
:style="isMomentFavorite(moment) ? { color: '#ffb300' } : ''">{{ isMomentFavorite(moment) ? '已收藏' : '收藏' }}</text>
|
:style="isMomentFavorite(moment) ? { color: '#ffb300' } : ''">
|
||||||
|
{{ isMomentFavorite(moment) ? '已收藏' : '收藏' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="load-text pb-5 pt-1 text-center text-xs text-gray-500">
|
<view class="load-text pb-5 pt-1 text-center text-xs text-gray-500">
|
||||||
{{ loadMoreText }}
|
{{ loadMoreText }}
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
Reference in New Issue
Block a user