mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor: 重构偏好设置模块,新增全局设置弹窗与通用hook
1. 抽离usePreferenceRows通用hook,统一管理偏好设置的字段定义、状态读写与交互逻辑 2. 新增全局操作组件与全局设置弹窗,替换原有的滚动到顶部组件 3. 重构页面端偏好设置页面,复用通用hook减少重复代码 4. 修复并优化了原有组件的页面适配逻辑
This commit is contained in:
+25
-29
@@ -1,37 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import FgTabbar from '@/tabbar/index.vue'
|
import { isPageTabbar } from './tabbar/store'
|
||||||
import { isPageTabbar } from './tabbar/store'
|
import { currRoute } from './utils'
|
||||||
import { currRoute } from './utils'
|
import CustomTabbar from '@/tabbar/index.vue'
|
||||||
|
|
||||||
const isCurrentPageTabbar = ref(true)
|
const isCurrentPageTabbar = ref(true)
|
||||||
onShow(() => {
|
const globalSettingsVisible = ref(false)
|
||||||
const { path } = currRoute()
|
onShow(() => {
|
||||||
// “蜡笔小开心”提到本地是 '/pages/index/index',线上是 '/' 导致线上 tabbar 不见了
|
const { path } = currRoute()
|
||||||
// 所以这里需要判断一下,如果是 '/' 就当做首页,也要显示 tabbar
|
if (path === '/') {
|
||||||
if (path === '/') {
|
isCurrentPageTabbar.value = true
|
||||||
isCurrentPageTabbar.value = true
|
}
|
||||||
}
|
else {
|
||||||
else {
|
isCurrentPageTabbar.value = isPageTabbar(path)
|
||||||
isCurrentPageTabbar.value = isPageTabbar(path)
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const helloKuRoot = ref('Hello AppKuVue')
|
|
||||||
|
|
||||||
const exposeRef = ref('this is form app.Ku.vue')
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
exposeRef,
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view>
|
<view>
|
||||||
<uh-scroll-top />
|
<uh-global-actions>
|
||||||
|
<uh-scrolltop-button :fixed="false"/>
|
||||||
|
<uh-settings-button :fixed="false" @click="globalSettingsVisible = !globalSettingsVisible"/>
|
||||||
|
</uh-global-actions>
|
||||||
|
|
||||||
<KuRootView />
|
<uh-settings-popup v-model="globalSettingsVisible" @close="globalSettingsVisible = false" />
|
||||||
|
|
||||||
<FgTabbar v-if="isCurrentPageTabbar" />
|
<KuRootView />
|
||||||
</view>
|
|
||||||
|
<CustomTabbar v-if="isCurrentPageTabbar" />
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
||||||
import { getCurrentInstance, onMounted, onUnmounted } from 'vue'
|
import { getCurrentInstance, onMounted, onUnmounted } from 'vue'
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
interface IEmits {
|
interface IEmits {
|
||||||
(e : 'update:modelValue', value : boolean) : void
|
(e : 'update:modelValue', value : boolean) : void
|
||||||
(e : 'close') : void
|
(e : 'close') : void
|
||||||
|
(e : 'open') : void
|
||||||
(e : 'click-modal') : void
|
(e : 'click-modal') : void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +79,7 @@
|
|||||||
:lazy-render="lazyRender"
|
: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')"
|
||||||
|
@enter="emit('open')"
|
||||||
@click-modal="emit('click-modal')"
|
@click-modal="emit('click-modal')"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="fixed right-3 bottom-22 z-50 flex flex-col gap-y-3 pb-safe">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
+13
-6
@@ -1,18 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
interface IProps {
|
interface IProps {
|
||||||
customClass?: string;
|
customClass ?: string;
|
||||||
|
fixed ?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<IProps>(), {
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
customClass: ''
|
customClass: '',
|
||||||
|
fixed: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleScrollTop() {
|
function handleScrollTop() {
|
||||||
uni.pageScrollTo({ scrollTop: 0, duration: 500 })
|
uni.pageScrollTo({ scrollTop: 0, duration: 500 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前页面,并且设置黑名单模式,因为有的页面可能不需要滚动到顶部
|
// 黑名单模式
|
||||||
const balckList = ['pages/maintenance/maintenance', 'pages-blog/setting/setting', 'pages-blog/love/love']
|
const balckList = [
|
||||||
|
'pages/maintenance/maintenance',
|
||||||
|
'pages-blog/setting/setting',
|
||||||
|
'pages-blog/love/love',
|
||||||
|
'pages/tabbar/about/about'
|
||||||
|
]
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
const currentPage = pages[pages.length - 1]
|
const currentPage = pages[pages.length - 1]
|
||||||
const visible = computed(() => {
|
const visible = computed(() => {
|
||||||
@@ -20,13 +27,13 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
const _customClass = computed(() => {
|
const _customClass = computed(() => {
|
||||||
const colorClass = currentPage.route.includes('/love/')?'text-love':'text-primary'
|
const colorClass = currentPage.route.includes('/love/') ? 'text-love' : 'text-primary'
|
||||||
return `${props.customClass} ${colorClass}`
|
return `${props.customClass} ${colorClass}`
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view v-if="visible" class="fixed bottom-22 right-3 z-50 pb-safe">
|
<view v-if="visible" :class="[props.fixed?'fixed bottom-22 right-3 z-50 pb-safe':'']">
|
||||||
<view class="uh-global-card-glass border h-11 w-11 flex items-center justify-center rounded-full"
|
<view class="uh-global-card-glass border h-11 w-11 flex items-center justify-center rounded-full"
|
||||||
:class="_customClass" @click="handleScrollTop">
|
:class="_customClass" @click="handleScrollTop">
|
||||||
<wd-icon name="arrow-up" size="42rpx" />
|
<wd-icon name="arrow-up" size="42rpx" />
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface IProps {
|
||||||
|
containerClass ?: string;
|
||||||
|
customClass ?: string;
|
||||||
|
fixed ?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
|
customClass: '',
|
||||||
|
fixed: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 白名单模式
|
||||||
|
const whiteList = ['pages/tabbar/home/home', 'pages-blog/articles/articles', 'pages-blog/archives/archives']
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
const currentPage = pages[pages.length - 1]
|
||||||
|
const visible = computed(() => {
|
||||||
|
return whiteList.includes(currentPage.route)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view v-if="visible" :class="[props.fixed?'fixed bottom-22 right-3 z-50 pb-safe':'',props.containerClass]">
|
||||||
|
<view class="uh-global-card-glass border h-11 w-11 flex items-center justify-center rounded-full text-primary"
|
||||||
|
:class="props.customClass">
|
||||||
|
<wd-icon name="settings" size="42rpx" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useSettingStore } from '@/store/setting'
|
||||||
|
import { usePreferenceRows } from '@/hooks/usePreferenceRows'
|
||||||
|
import { useSettingsPopup } from '@/hooks/useSettingsPopup'
|
||||||
|
|
||||||
|
const settingStore = useSettingStore()
|
||||||
|
|
||||||
|
const {
|
||||||
|
SETTING_TABS,
|
||||||
|
layoutGroups,
|
||||||
|
featureRows,
|
||||||
|
prefValueOf,
|
||||||
|
isFollowing,
|
||||||
|
handleRevert,
|
||||||
|
handleBoolChange,
|
||||||
|
handleChoose,
|
||||||
|
} = usePreferenceRows()
|
||||||
|
|
||||||
|
const { settingsPopupVisible } = useSettingsPopup()
|
||||||
|
|
||||||
|
const emits = defineEmits(['close', 'open'])
|
||||||
|
|
||||||
|
const activeTab = ref<'layout' | 'feature'>('layout')
|
||||||
|
|
||||||
|
// 进行过滤,只保留当前页面的布局和功能
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
const currentPage = pages[pages.length - 1]
|
||||||
|
const filterLayoutGroups = computed(() => {
|
||||||
|
return layoutGroups.value.filter((group) => {
|
||||||
|
return currentPage.route.split('/').pop() === group.key
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/* ---------------- 交互 ---------------- */
|
||||||
|
function handleSwitchChange(def : { path : string[] }, detail : { value ?: unknown }) {
|
||||||
|
handleBoolChange(def.path, detail.value === true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentValueOf(path : string[]) : string | null {
|
||||||
|
return isFollowing(path) ? null : String(prefValueOf(path) ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInlineChoose(path : string[], value : string | null) : void {
|
||||||
|
handleChoose(path, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- 重置全部 ---------------- */
|
||||||
|
function handleResetAll() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定将所有偏好恢复为站点默认吗?本地自定义的偏好将被清除,未配置站点默认的项将恢复为内置默认。',
|
||||||
|
showCancel: true,
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
confirmColor: '#03a9f4',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
settingStore.resetPreferences()
|
||||||
|
uni.showToast({ icon: 'none', title: '已恢复为站点默认' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
emits('close')
|
||||||
|
}
|
||||||
|
function onOpen() {
|
||||||
|
emits('open')
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<uh-glass-popup v-model="settingsPopupVisible" position="bottom" custom-class="rounded-xl !border"
|
||||||
|
safe-area-inset-bottom :z-index="99999" hide-when-close @close="onClose" @open="onOpen">
|
||||||
|
<view class="box-border px-3 pt-3">
|
||||||
|
<view class="mb-3 flex items-center justify-between">
|
||||||
|
<text class="text-md font-bold">偏好设置</text>
|
||||||
|
<view
|
||||||
|
class="uh-global-card-glass shadow-none !bg-white/5 border flex h-6 w-6 items-center justify-center rounded-lg text-gray-500"
|
||||||
|
@click="onClose()">
|
||||||
|
<wd-icon name="close" size="16px" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分段器:布局 / 功能 -->
|
||||||
|
<view class="uh-global-card-glass shadow-none flex rounded-xl p-1">
|
||||||
|
<view v-for="tab in SETTING_TABS" :key="tab.key"
|
||||||
|
class="box-border flex-1 rounded-lg py-1.5 text-center text-sm"
|
||||||
|
:class="activeTab === tab.key ? 'bg-primary font-bold' : 'text-gray-500'"
|
||||||
|
@click="activeTab = tab.key">
|
||||||
|
{{ tab.label }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 内容区(弹层内滚动) -->
|
||||||
|
<scroll-view scroll-y :show-scrollbar="false" class="mt-4 max-h-[60vh]">
|
||||||
|
<view class="box-border flex flex-col gap-y-6 pb-1">
|
||||||
|
<!-- 布局:按页面分组(枚举项内联分段器) -->
|
||||||
|
<template v-if="activeTab === 'layout'">
|
||||||
|
<view v-for="group in filterLayoutGroups" :key="group.key" class="flex flex-col gap-y-3">
|
||||||
|
<uh-section-title>{{ group.label }}</uh-section-title>
|
||||||
|
<view class="uh-global-card-glass shadow-none overflow-hidden rounded-xl">
|
||||||
|
<view v-for="(row) in group.rows" :key="row.key" class="box-border p-3">
|
||||||
|
<!-- 左 label / 右 说明 -->
|
||||||
|
<view class="flex items-center justify-between">
|
||||||
|
<text class="row-label text-sm text-gray-900 font-bold">{{ row.label }}</text>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text v-if="row.following" class="row-sub text-2xs text-gray-400">默认</text>
|
||||||
|
<view v-else
|
||||||
|
class="rounded-full bg-secondary px-2 py-1 text-xs text-gray-900 leading-none">
|
||||||
|
已自定义
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 下方横向选项(分段器风格:默认 + options) -->
|
||||||
|
<view class="mt-3 flex flex-wrap gap-2">
|
||||||
|
<view
|
||||||
|
class="rounded-full px-3 py-1 text-xs uh-global-card-glass shadow-none border"
|
||||||
|
:class="currentValueOf(row.path) === null ? 'bg-secondary font-bold' : 'text-gray-500'"
|
||||||
|
@click="handleInlineChoose(row.path, null)">
|
||||||
|
默认
|
||||||
|
</view>
|
||||||
|
<view v-for="opt in row.options" :key="opt.value"
|
||||||
|
class="rounded-full px-3 py-1 text-xs uh-global-card-glass shadow-none border"
|
||||||
|
:class="currentValueOf(row.path) === opt.value ? 'bg-secondary font-bold' : ' border-gray-100 text-gray-500'"
|
||||||
|
@click="handleInlineChoose(row.path, opt.value)">
|
||||||
|
{{ opt.label }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 功能 -->
|
||||||
|
<template v-else>
|
||||||
|
<view class="flex flex-col gap-y-3">
|
||||||
|
<uh-section-title>
|
||||||
|
功能
|
||||||
|
<template #right>
|
||||||
|
<text class="text-2xs text-gray-400">一些常用的功能性设置</text>
|
||||||
|
</template>
|
||||||
|
</uh-section-title>
|
||||||
|
<view class="setting-sheet uh-global-card-glass overflow-hidden rounded-2xl">
|
||||||
|
<template v-for="(row, index) in featureRows" :key="row.key">
|
||||||
|
<!-- 布尔开关 -->
|
||||||
|
<view v-if="row.kind === 'bool'"
|
||||||
|
class="switch-row flex items-center justify-between px-4 py-4"
|
||||||
|
:class="index < featureRows.length - 1 ? 'border-b border-black/5' : ''">
|
||||||
|
<view class="row-left flex flex-col gap-1">
|
||||||
|
<text
|
||||||
|
class="row-label text-[28rpx] text-gray-900 font-bold">{{ row.label }}</text>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text v-if="row.following"
|
||||||
|
class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
|
<template v-else>
|
||||||
|
<view
|
||||||
|
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
||||||
|
已自定义
|
||||||
|
</view>
|
||||||
|
<text class="revert-text text-2xs text-gray-400 underline"
|
||||||
|
@click.stop="handleRevert(row.path)">
|
||||||
|
恢复默认
|
||||||
|
</text>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<wd-switch :model-value="row.boolValue"
|
||||||
|
@change="handleSwitchChange(row, $event)" />
|
||||||
|
</view>
|
||||||
|
<!-- 枚举项:内联分段器(与布局项一致) -->
|
||||||
|
<view v-else class="px-4 py-4"
|
||||||
|
:class="index < featureRows.length - 1 ? 'border-b border-black/5' : ''">
|
||||||
|
<view class="flex items-center justify-between">
|
||||||
|
<text
|
||||||
|
class="row-label text-[28rpx] text-gray-900 font-bold">{{ row.label }}</text>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text v-if="row.following"
|
||||||
|
class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
|
<view v-else
|
||||||
|
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
||||||
|
已自定义
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-3 flex flex-wrap gap-2">
|
||||||
|
<view class="rounded-full px-3 py-1 text-xs"
|
||||||
|
:class="currentValueOf(row.path) === null ? 'bg-primary font-bold' : 'uh-global-card-glass !bg-white/60 text-gray-500'"
|
||||||
|
@click="handleInlineChoose(row.path, null)">
|
||||||
|
默认
|
||||||
|
</view>
|
||||||
|
<view v-for="opt in row.options" :key="opt.value"
|
||||||
|
class="rounded-full px-3 py-1 text-xs"
|
||||||
|
:class="currentValueOf(row.path) === opt.value ? 'bg-primary font-bold' : 'uh-global-card-glass !bg-white/60 text-gray-500'"
|
||||||
|
@click="handleInlineChoose(row.path, opt.value)">
|
||||||
|
{{ opt.label }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<view class="box-border w-full pb-4 pt-3 flex items-center gap-x-2">
|
||||||
|
<uh-button custom-class="flex-1 uh-global-card-glass bg-white/90 border py-2 !rounded-xl"
|
||||||
|
@click="onClose()">
|
||||||
|
关闭
|
||||||
|
</uh-button>
|
||||||
|
<uh-button custom-class="flex-1 uh-global-card-glass border py-2 !rounded-xl" @click="handleResetAll">
|
||||||
|
恢复默认
|
||||||
|
</uh-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uh-glass-popup>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* 偏好设置展示行与交互(页面 setting.vue 与全局弹窗 uh-settings-popup 共用)
|
||||||
|
* 收敛:字段定义(PrefDef/页面分组/布局/功能)、三态(跟随站点默认/已自定义/恢复默认)、
|
||||||
|
* 行构建、选值交互。两个消费方不再各自重复定义。
|
||||||
|
*/
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { DefaultAppSettings } from '@/config/appSettings';
|
||||||
|
import { useSettingStore } from '@/store/setting';
|
||||||
|
import { isLocalOverride, readLocalPrefs } from '@/utils/preference';
|
||||||
|
import type { LocalPrefs } from '@/utils/preference';
|
||||||
|
|
||||||
|
/** 偏好字段定义 */
|
||||||
|
export interface PrefDef {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
kind: 'bool' | 'enum';
|
||||||
|
path: string[];
|
||||||
|
options?: { label: string; value: string }[];
|
||||||
|
siteLabelOf?: (value: string) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 布局设置按页面分组(每组:列表布局 + 卡片样式) */
|
||||||
|
export const PAGE_GROUPS = [
|
||||||
|
{ key: 'home', label: '首页' },
|
||||||
|
{ key: 'articles', label: '文章页面' },
|
||||||
|
{ key: 'archives', label: '归档页面' }
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 布局偏好字段(页面分组 × 列表布局/卡片样式) */
|
||||||
|
export const LAYOUT_PREFS: PrefDef[] = PAGE_GROUPS.flatMap((group) => [
|
||||||
|
{
|
||||||
|
key: `${group.key}ListLayout`,
|
||||||
|
label: '列表布局',
|
||||||
|
kind: 'enum',
|
||||||
|
path: ['layout', group.key, 'listLayout'],
|
||||||
|
options: [
|
||||||
|
{ label: '单列', value: 'single' },
|
||||||
|
{ label: '双列', value: 'double' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${group.key}CardType`,
|
||||||
|
label: '卡片样式',
|
||||||
|
kind: 'enum',
|
||||||
|
path: ['layout', group.key, 'cardType'],
|
||||||
|
options: [
|
||||||
|
{ label: '上图下文', value: 'image_top' },
|
||||||
|
{ label: '左文右图', value: 'image_right' },
|
||||||
|
{ label: '上文下图', value: 'image_bottom' },
|
||||||
|
{ label: '左图右文', value: 'image_left' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** 功能偏好字段 */
|
||||||
|
export const FEATURE_PREFS: PrefDef[] = [{ key: 'isAvatarRadius', label: '是否圆形头像', kind: 'bool', path: ['isAvatarRadius'] }];
|
||||||
|
|
||||||
|
/** 顶部分段器(布局 / 功能) */
|
||||||
|
export const SETTING_TABS: { key: 'layout' | 'feature'; label: string }[] = [
|
||||||
|
{ key: 'layout', label: '布局' },
|
||||||
|
{ key: 'feature', label: '功能' }
|
||||||
|
];
|
||||||
|
|
||||||
|
export function usePreferenceRows() {
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
|
/* ---------------- 路径取值工具 ---------------- */
|
||||||
|
function getByPath(obj: unknown, path: string[]): unknown {
|
||||||
|
let cursor: unknown = obj;
|
||||||
|
for (const key of path) {
|
||||||
|
if (cursor === null || cursor === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
cursor = (cursor as Record<string, unknown>)[key];
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按路径构造差异 patch(null 表示删除该键=跟随站点默认) */
|
||||||
|
function buildPatch(path: string[], value: unknown): LocalPrefs {
|
||||||
|
const [head, ...rest] = path;
|
||||||
|
if (rest.length === 0) return { [head]: value } as LocalPrefs;
|
||||||
|
return { [head]: buildPatch(rest, value) } as LocalPrefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- 状态读取 ---------------- */
|
||||||
|
function prefValueOf(path: string[]): unknown {
|
||||||
|
return getByPath(settingStore.settings, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 站点默认值(未配置时回退内置默认) */
|
||||||
|
function siteDefaultOf(path: string[]): unknown {
|
||||||
|
const site = getByPath(settingStore.siteDefaults, path);
|
||||||
|
if (site !== undefined && site !== null) return site;
|
||||||
|
return getByPath(DefaultAppSettings, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOverridden(path: string[]): boolean {
|
||||||
|
return isLocalOverride(readLocalPrefs(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumLabelOf(def: PrefDef, value: unknown): string {
|
||||||
|
const hit = def.options?.find((opt) => opt.value === value);
|
||||||
|
if (hit) return hit.label;
|
||||||
|
if (def.siteLabelOf && typeof value === 'string') return def.siteLabelOf(value);
|
||||||
|
return value === undefined || value === null ? '—' : String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- 展示行(预计算,避免模板渲染期函数调用) ---------------- */
|
||||||
|
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.path),
|
||||||
|
boolValue: def.kind === 'bool' ? prefValueOf(def.path) === true : false
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutRows = computed(() => buildRows(LAYOUT_PREFS));
|
||||||
|
const featureRows = computed(() => buildRows(FEATURE_PREFS));
|
||||||
|
|
||||||
|
/** 布局设置按页面分组的展示行 */
|
||||||
|
const layoutGroups = computed(() =>
|
||||||
|
PAGE_GROUPS.map((group) => ({
|
||||||
|
key: group.key,
|
||||||
|
label: group.label,
|
||||||
|
rows: layoutRows.value.filter((row) => row.path[1] === group.key)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 当前项是否处于「跟随站点默认」(本地未覆盖) */
|
||||||
|
function isFollowing(path: string[]): boolean {
|
||||||
|
return !isOverridden(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单项还原为跟随站点默认 */
|
||||||
|
function handleRevert(path: string[]): void {
|
||||||
|
settingStore.savePreference(buildPatch(path, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 开关类:选值等于站点默认则还原为跟随(只存差异) */
|
||||||
|
function handleBoolChange(path: string[], next: boolean): void {
|
||||||
|
if (next === siteDefaultOf(path)) {
|
||||||
|
settingStore.savePreference(buildPatch(path, null));
|
||||||
|
} else {
|
||||||
|
settingStore.savePreference(buildPatch(path, next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 枚举选值:null 或等于站点默认则还原为跟随,否则写入差异(内联分段器与弹层共用) */
|
||||||
|
function handleChoose(path: string[], value: string | null): void {
|
||||||
|
if (value === null || value === siteDefaultOf(path)) {
|
||||||
|
handleRevert(path);
|
||||||
|
} else {
|
||||||
|
settingStore.savePreference(buildPatch(path, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
settingStore,
|
||||||
|
PAGE_GROUPS,
|
||||||
|
SETTING_TABS,
|
||||||
|
getByPath,
|
||||||
|
buildPatch,
|
||||||
|
prefValueOf,
|
||||||
|
siteDefaultOf,
|
||||||
|
isOverridden,
|
||||||
|
enumLabelOf,
|
||||||
|
buildRows,
|
||||||
|
layoutRows,
|
||||||
|
featureRows,
|
||||||
|
layoutGroups,
|
||||||
|
isFollowing,
|
||||||
|
handleRevert,
|
||||||
|
handleBoolChange,
|
||||||
|
handleChoose
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export function useSettingsPopup() {
|
||||||
|
const settingsPopupVisible = ref(false);
|
||||||
|
|
||||||
|
function openSettingsPopup() {
|
||||||
|
settingsPopupVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSettingsPopup() {
|
||||||
|
settingsPopupVisible.value = false;
|
||||||
|
console.log('Settings popup closed', settingsPopupVisible.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { settingsPopupVisible, openSettingsPopup, closeSettingsPopup };
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { DefaultAppSettings } from '@/config/appSettings'
|
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { useSettingStore } from '@/store/setting'
|
import { useSettingStore } from '@/store/setting'
|
||||||
import { collectSiteDefaults, isLocalOverride, readLocalPrefs } from '@/utils/preference'
|
import { collectSiteDefaults } from '@/utils/preference'
|
||||||
import type { LocalPrefs } from '@/utils/preference'
|
import { usePreferenceRows } from '@/hooks/usePreferenceRows'
|
||||||
|
import type { PrefDef } from '@/hooks/usePreferenceRows'
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
style: {
|
style: {
|
||||||
@@ -17,6 +17,18 @@
|
|||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
const appConfigStore = useAppConfigStore()
|
const appConfigStore = useAppConfigStore()
|
||||||
|
|
||||||
|
/** 公共:字段定义/三态/行构建/选值(与全局弹窗 uh-settings-popup 共用 usePreferenceRows) */
|
||||||
|
const {
|
||||||
|
SETTING_TABS,
|
||||||
|
layoutGroups,
|
||||||
|
featureRows,
|
||||||
|
prefValueOf,
|
||||||
|
isFollowing,
|
||||||
|
handleRevert,
|
||||||
|
handleBoolChange,
|
||||||
|
handleChoose,
|
||||||
|
} = usePreferenceRows()
|
||||||
|
|
||||||
/** 确保启动合并已执行(入口页未跑或 H5 直达时兜底) */
|
/** 确保启动合并已执行(入口页未跑或 H5 直达时兜底) */
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
if (!settingStore.siteDefaults) {
|
if (!settingStore.siteDefaults) {
|
||||||
@@ -25,138 +37,24 @@
|
|||||||
uni.setNavigationBarTitle({ title: '偏好设置' })
|
uni.setNavigationBarTitle({ title: '偏好设置' })
|
||||||
})
|
})
|
||||||
|
|
||||||
/* ---------------- 路径取值工具 ---------------- */
|
|
||||||
type Path = string[]
|
|
||||||
|
|
||||||
function getByPath(obj : unknown, path : Path) : unknown {
|
|
||||||
let cursor : unknown = obj
|
|
||||||
for (const key of path) {
|
|
||||||
if (cursor === null || cursor === undefined)
|
|
||||||
return undefined
|
|
||||||
cursor = (cursor as Record<string, unknown>)[key]
|
|
||||||
}
|
|
||||||
return cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 按路径构造差异 patch(null 表示删除该键=跟随站点默认) */
|
|
||||||
function buildPatch(path : Path, value : unknown) : LocalPrefs {
|
|
||||||
const [head, ...rest] = path
|
|
||||||
if (rest.length === 0)
|
|
||||||
return { [head]: value } as LocalPrefs
|
|
||||||
return { [head]: buildPatch(rest, value) } as LocalPrefs
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 偏好字段定义(现页已有项;弹幕已下线、友链分组二期再开) */
|
|
||||||
interface PrefDef {
|
|
||||||
key : string
|
|
||||||
label : string
|
|
||||||
kind : 'bool' | 'enum'
|
|
||||||
path : Path
|
|
||||||
options ?: { label : string, value : string }[]
|
|
||||||
siteLabelOf ?: (value : string) => string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 布局设置按页面分组(每组:列表布局 + 卡片样式) */
|
|
||||||
const PAGE_GROUPS = [
|
|
||||||
{ key: 'home', label: '首页' },
|
|
||||||
{ key: 'articles', label: '文章列表' },
|
|
||||||
{ key: 'archives', label: '文章归档' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const layoutPrefs : PrefDef[] = PAGE_GROUPS.flatMap(group => [
|
|
||||||
{
|
|
||||||
key: `${group.key}ListLayout`,
|
|
||||||
label: '列表布局',
|
|
||||||
kind: 'enum',
|
|
||||||
path: ['layout', group.key, 'listLayout'],
|
|
||||||
options: [
|
|
||||||
{ label: '单列', value: 'single' },
|
|
||||||
{ label: '双列', value: 'double' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: `${group.key}CardType`,
|
|
||||||
label: '卡片样式',
|
|
||||||
kind: 'enum',
|
|
||||||
path: ['layout', group.key, 'cardType'],
|
|
||||||
options: [
|
|
||||||
{ label: '上图下文', value: 'image_top' },
|
|
||||||
{ label: '左文右图', value: 'image_right' },
|
|
||||||
{ label: '上文下图', value: 'image_bottom' },
|
|
||||||
{ label: '左图右文', value: 'image_left' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const featurePrefs : PrefDef[] = [
|
|
||||||
{ key: 'isAvatarRadius', label: '是否圆形头像', kind: 'bool', path: ['isAvatarRadius'] },
|
|
||||||
]
|
|
||||||
|
|
||||||
/* ---------------- 顶部分段器(布局 / 功能) ---------------- */
|
/* ---------------- 顶部分段器(布局 / 功能) ---------------- */
|
||||||
const settingTabs : { key : 'layout' | 'feature', label : string }[] = [
|
|
||||||
{ key: 'layout', label: '布局' },
|
|
||||||
{ key: 'feature', label: '功能' },
|
|
||||||
]
|
|
||||||
const activeTab = ref<'layout' | 'feature'>('layout')
|
const activeTab = ref<'layout' | 'feature'>('layout')
|
||||||
|
|
||||||
/* ---------------- 状态读取 ---------------- */
|
|
||||||
function prefValueOf(path : Path) : unknown {
|
|
||||||
return getByPath(settingStore.settings, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 站点默认值(未配置时回退内置默认) */
|
|
||||||
function siteDefaultOf(path : Path) : unknown {
|
|
||||||
const site = getByPath(settingStore.siteDefaults, path)
|
|
||||||
if (site !== undefined && site !== null)
|
|
||||||
return site
|
|
||||||
return getByPath(DefaultAppSettings, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOverridden(path : Path) : boolean {
|
|
||||||
return isLocalOverride(readLocalPrefs(), path)
|
|
||||||
}
|
|
||||||
|
|
||||||
function enumLabelOf(def : PrefDef, value : unknown) : string {
|
|
||||||
const hit = def.options?.find(opt => opt.value === value)
|
|
||||||
if (hit)
|
|
||||||
return hit.label
|
|
||||||
if (def.siteLabelOf && typeof value === 'string')
|
|
||||||
return def.siteLabelOf(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) {
|
|
||||||
if (next === siteDefaultOf(path)) {
|
|
||||||
settingStore.savePreference(buildPatch(path, null))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
settingStore.savePreference(buildPatch(path, next))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 单项还原为跟随站点默认 */
|
|
||||||
function handleRevert(path : Path) {
|
|
||||||
settingStore.savePreference(buildPatch(path, null))
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- 枚举底部弹层(uh-glass-popup + wd-picker-view) ---------------- */
|
/* ---------------- 枚举底部弹层(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)[]>([''])
|
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) ?? '')]
|
pickerValue.value = [isFollowing(def.path) ? '' : String(prefValueOf(def.path) ?? '')]
|
||||||
console.log('pickerValue', pickerValue.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseEnum() {
|
function handleCloseEnum() {
|
||||||
@@ -166,12 +64,7 @@
|
|||||||
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)) {
|
handleChoose(def.path, value)
|
||||||
handleRevert(def.path)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
settingStore.savePreference(buildPatch(def.path, value))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
handleCloseEnum()
|
handleCloseEnum()
|
||||||
}
|
}
|
||||||
@@ -203,39 +96,6 @@
|
|||||||
handleCloseEnum()
|
handleCloseEnum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 当前枚举项是否处于「跟随站点默认」 */
|
|
||||||
function isFollowing(def : PrefDef) : boolean {
|
|
||||||
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))
|
|
||||||
|
|
||||||
/** 布局设置按页面分组的展示行 */
|
|
||||||
const layoutGroups = computed(() => PAGE_GROUPS.map(group => ({
|
|
||||||
key: group.key,
|
|
||||||
label: group.label,
|
|
||||||
rows: layoutRows.value.filter(row => row.path[1] === group.key),
|
|
||||||
})))
|
|
||||||
|
|
||||||
/* ---------------- 重置全部 ---------------- */
|
/* ---------------- 重置全部 ---------------- */
|
||||||
function handleResetAll() {
|
function handleResetAll() {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
@@ -265,7 +125,7 @@
|
|||||||
<view class="box-border flex flex-col gap-y-6 p-3">
|
<view class="box-border flex flex-col gap-y-6 p-3">
|
||||||
<!-- 顶部分段器:布局 / 功能 -->
|
<!-- 顶部分段器:布局 / 功能 -->
|
||||||
<view class="uh-global-card-glass flex rounded-full p-1">
|
<view class="uh-global-card-glass flex rounded-full p-1">
|
||||||
<view v-for="tab in settingTabs" :key="tab.key"
|
<view v-for="tab in SETTING_TABS" :key="tab.key"
|
||||||
class="flex-1 rounded-full py-1.5 text-center text-sm"
|
class="flex-1 rounded-full py-1.5 text-center text-sm"
|
||||||
:class="activeTab === tab.key ? 'bg-primary font-bold' : 'text-gray-500'"
|
:class="activeTab === tab.key ? 'bg-primary font-bold' : 'text-gray-500'"
|
||||||
@click="activeTab = tab.key">
|
@click="activeTab = tab.key">
|
||||||
@@ -311,58 +171,58 @@
|
|||||||
</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="(row, index) in featureRows" :key="row.key">
|
<template v-for="(row, index) in featureRows" :key="row.key">
|
||||||
<!-- 布尔开关 -->
|
<!-- 布尔开关 -->
|
||||||
<view v-if="row.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 < featureRows.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">{{ row.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="row.following" 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(row.path)">
|
@click.stop="handleRevert(row.path)">
|
||||||
恢复默认
|
恢复默认
|
||||||
</text>
|
</text>
|
||||||
</template>
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<wd-switch :model-value="row.boolValue" @change="handleSwitchChange(row, $event)" />
|
||||||
|
</view>
|
||||||
|
<!-- 枚举选择 -->
|
||||||
|
<view v-else class="pick-row flex items-center justify-between px-4 py-4"
|
||||||
|
:class="index < featureRows.length - 1 ? 'border-b border-black/5' : ''"
|
||||||
|
@click="handleOpenEnum(row)">
|
||||||
|
<view class="row-left flex flex-col gap-1">
|
||||||
|
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ row.label }}</text>
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<text v-if="row.following" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
||||||
|
<template v-else>
|
||||||
|
<view
|
||||||
|
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
||||||
|
已自定义
|
||||||
|
</view>
|
||||||
|
<text class="revert-text text-2xs text-gray-400 underline"
|
||||||
|
@click.stop="handleRevert(row.path)">
|
||||||
|
恢复默认
|
||||||
|
</text>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="row-value flex items-center gap-2">
|
||||||
|
<text class="value-text text-[26rpx] text-gray-400">{{ row.displayValue }}</text>
|
||||||
|
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<wd-switch :model-value="row.boolValue" @change="handleSwitchChange(row, $event)" />
|
</template>
|
||||||
</view>
|
</view>
|
||||||
<!-- 枚举选择(指示器位置) -->
|
|
||||||
<view v-else class="pick-row flex items-center justify-between px-4 py-4"
|
|
||||||
:class="index < featureRows.length - 1 ? 'border-b border-black/5' : ''"
|
|
||||||
@click="handleOpenEnum(row)">
|
|
||||||
<view class="row-left flex flex-col gap-1">
|
|
||||||
<text class="row-label text-[28rpx] text-gray-900 font-bold">{{ row.label }}</text>
|
|
||||||
<view class="flex items-center gap-2">
|
|
||||||
<text v-if="row.following" class="row-sub text-2xs text-gray-400">跟随站点默认</text>
|
|
||||||
<template v-else>
|
|
||||||
<view
|
|
||||||
class="rounded-full bg-secondary px-2 py-0.5 text-[20rpx] text-[#4d7c0f] leading-none">
|
|
||||||
已自定义
|
|
||||||
</view>
|
|
||||||
<text class="revert-text text-2xs text-gray-400 underline"
|
|
||||||
@click.stop="handleRevert(row.path)">
|
|
||||||
恢复默认
|
|
||||||
</text>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="row-value flex items-center gap-2">
|
|
||||||
<text class="value-text text-[26rpx] text-gray-400">{{ row.displayValue }}</text>
|
|
||||||
<wd-icon name="arrow-right" size="12px" color="#c8c2b4" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</template>
|
||||||
</template>
|
<!-- 底部操作栏-->
|
||||||
<!-- 底部操作栏-->
|
|
||||||
<view class="box-border w-full">
|
<view class="box-border w-full">
|
||||||
<uh-button custom-class="uh-global-card-glass py-2 !rounded-xl" @click="handleResetAll">
|
<uh-button custom-class="uh-global-card-glass py-2 !rounded-xl" @click="handleResetAll">
|
||||||
恢复默认
|
恢复默认
|
||||||
|
|||||||
Reference in New Issue
Block a user