更新模板
This commit is contained in:
44
components/custom-back.uvue
Normal file
44
components/custom-back.uvue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<view class="custom-back" :style="{ top: top + 'px' }" @click="back">
|
||||
<view class="back-icon">
|
||||
<cl-icon name="arrow-left-line" :size="40" color="#333" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { router } from '@/cool';
|
||||
|
||||
const top = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
const info = uni.getWindowInfo();
|
||||
// 状态栏高度 + 适当的间距,通常胶囊按钮在该区域垂直居中
|
||||
top.value = info.statusBarHeight + 6;
|
||||
});
|
||||
|
||||
function back() {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-back {
|
||||
position: fixed;
|
||||
left: 20rpx;
|
||||
z-index: 999;
|
||||
|
||||
.back-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
border: 1rpx solid #eee;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,68 +2,192 @@
|
||||
<cl-footer
|
||||
:pt="{
|
||||
content: {
|
||||
className: '!p-0 h-[60px]'
|
||||
className: '!p-0 !overflow-visible'
|
||||
}
|
||||
}"
|
||||
>
|
||||
<view class="custom-tabbar" :class="{ 'is-dark': isDark }">
|
||||
<view
|
||||
class="custom-tabbar-item"
|
||||
v-for="item in list"
|
||||
:key="item.pagePath"
|
||||
@tap="router.to(item.pagePath)"
|
||||
v-for="(item, index) in currentList"
|
||||
:key="index"
|
||||
@tap="onTabClick(item)"
|
||||
>
|
||||
<!-- 普通图标 -->
|
||||
<cl-image
|
||||
:src="path == item.pagePath ? item.icon2 : item.icon"
|
||||
v-if="!item.isCenter"
|
||||
:src="path == item.pagePath ? item.selectedIconPath : item.iconPath"
|
||||
:height="56"
|
||||
:width="56"
|
||||
></cl-image>
|
||||
|
||||
<!-- 文字 -->
|
||||
<cl-text
|
||||
v-if="item.text != null"
|
||||
v-if="!item.isCenter && item.text != ''"
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
'text-xs mt-1',
|
||||
[path == item.pagePath, 'text-primary-500', 'text-surface-400']
|
||||
])
|
||||
}"
|
||||
>{{ t(item.text!) }}</cl-text
|
||||
>{{ item.text }}</cl-text
|
||||
>
|
||||
|
||||
<!-- 中间占位 -->
|
||||
<view v-if="item.isCenter" style="width: 56rpx; height: 56rpx;"></view>
|
||||
</view>
|
||||
|
||||
<!-- 悬浮的大LOGO按钮 -->
|
||||
<view
|
||||
class="center-btn-wrapper"
|
||||
v-if="centerBtnConfig.visible"
|
||||
@tap="onCenterClick"
|
||||
>
|
||||
<image
|
||||
class="center-btn-image"
|
||||
:src="centerBtnConfig.iconPath != '' ? centerBtnConfig.iconPath : '/static/images/logo_center_btn.png'"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</cl-footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ctx, isDark, parseClass, router } from "@/cool";
|
||||
import { ctx, isDark, parseClass, router, useCool, parseObject } from "@/cool";
|
||||
import { t } from "@/locale";
|
||||
import { computed } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "custom-tabbar"
|
||||
});
|
||||
|
||||
type Item = {
|
||||
icon: string;
|
||||
icon2: string;
|
||||
type TabItem = {
|
||||
text: string;
|
||||
pagePath: string;
|
||||
text: string | null;
|
||||
iconPath: string;
|
||||
selectedIconPath: string;
|
||||
isCenter?: boolean;
|
||||
};
|
||||
|
||||
type CenterBtnConfig = {
|
||||
visible: boolean;
|
||||
iconPath: string;
|
||||
pagePath: string;
|
||||
};
|
||||
|
||||
const { service } = useCool();
|
||||
const path = computed(() => router.path());
|
||||
|
||||
// tabbar 列表
|
||||
const list = computed<Item[]>(() => {
|
||||
return (ctx.tabBar.list ?? []).map((e) => {
|
||||
// 默认 TabBar 列表
|
||||
const defaultList = computed<TabItem[]>(() => {
|
||||
return (ctx.tabBar.list ?? []).map((e): TabItem => {
|
||||
return {
|
||||
icon: e.iconPath!,
|
||||
icon2: e.selectedIconPath!,
|
||||
iconPath: e.iconPath!,
|
||||
selectedIconPath: e.selectedIconPath!,
|
||||
pagePath: e.pagePath,
|
||||
text: t(e.text?.replaceAll("%", "")!)
|
||||
} as Item;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// 动态配置
|
||||
const dynamicList = ref<TabItem[]>([]);
|
||||
const centerBtnConfig = ref<CenterBtnConfig>({
|
||||
visible: false,
|
||||
iconPath: "",
|
||||
pagePath: ""
|
||||
});
|
||||
|
||||
// 最终使用的列表
|
||||
const currentList = computed<TabItem[]>(() => {
|
||||
if (dynamicList.value.length > 0) {
|
||||
return dynamicList.value;
|
||||
}
|
||||
// 如果有中间按钮,需要在默认列表中间插入占位
|
||||
if (centerBtnConfig.value.visible) {
|
||||
const list = [...defaultList.value];
|
||||
const centerIndex = Math.floor(list.length / 2);
|
||||
list.splice(centerIndex, 0, {
|
||||
text: "",
|
||||
pagePath: "",
|
||||
iconPath: "",
|
||||
selectedIconPath: "",
|
||||
isCenter: true
|
||||
} as TabItem);
|
||||
return list;
|
||||
}
|
||||
return defaultList.value;
|
||||
});
|
||||
|
||||
// 加载配置
|
||||
/* Loading Config Logic */
|
||||
async function loadConfig() {
|
||||
// 先设置默认的中间按钮配置
|
||||
centerBtnConfig.value = {
|
||||
visible: true,
|
||||
iconPath: "/static/images/logo_center_btn.png",
|
||||
pagePath: "/pages/seed/store"
|
||||
} as CenterBtnConfig;
|
||||
|
||||
try {
|
||||
const res = await service.base.comm.param({ key: "app_tabbar" });
|
||||
if (res != null && res != '') {
|
||||
const config = parseObject<UTSJSONObject>(res);
|
||||
if (config != null) {
|
||||
// 解析列表
|
||||
const listData = config.getArray<UTSJSONObject>("list");
|
||||
if (listData != null && listData.length > 0) {
|
||||
const newList : TabItem[] = [];
|
||||
listData.forEach((e) => {
|
||||
newList.push({
|
||||
text: e.getString("text") ?? "",
|
||||
pagePath: e.getString("pagePath") ?? "",
|
||||
iconPath: e.getString("iconPath") ?? "",
|
||||
selectedIconPath: e.getString("selectedIconPath") ?? "",
|
||||
isCenter: e.getBoolean("isCenter") ?? false
|
||||
} as TabItem);
|
||||
});
|
||||
dynamicList.value = newList;
|
||||
}
|
||||
|
||||
// 解析中间按钮
|
||||
const btn = config.get("centerBtn");
|
||||
if (btn != null) {
|
||||
const btnObj = btn as UTSJSONObject;
|
||||
centerBtnConfig.value = {
|
||||
visible: btnObj.getBoolean("visible") ?? true,
|
||||
iconPath: btnObj.getString("iconPath") ?? "/static/images/logo_center_btn.png",
|
||||
pagePath: btnObj.getString("pagePath") ?? "/pages/seed/store"
|
||||
} as CenterBtnConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("使用默认 TabBar 配置");
|
||||
}
|
||||
}
|
||||
|
||||
function onTabClick(item: TabItem) {
|
||||
if (!item.isCenter && item.pagePath != "") {
|
||||
router.push({
|
||||
path: item.pagePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onCenterClick() {
|
||||
if (centerBtnConfig.value.pagePath != "") {
|
||||
router.push({
|
||||
path: centerBtnConfig.value.pagePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig();
|
||||
});
|
||||
|
||||
// 隐藏原生 tabBar
|
||||
// #ifndef MP
|
||||
if (ctx.tabBar.list != null) {
|
||||
@@ -73,11 +197,63 @@ if (ctx.tabBar.list != null) {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-tabbar {
|
||||
@apply flex flex-row items-center flex-1;
|
||||
|
||||
&-item {
|
||||
@apply flex flex-col items-center justify-center flex-1;
|
||||
.custom-tabbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
min-height: 120rpx;
|
||||
padding-top: 10rpx;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: #ffffff;
|
||||
overflow: visible;
|
||||
box-sizing: border-box;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-tabbar-item {
|
||||
flex: 1; /* 关键:确保所有项(包括占位符)平分宽度 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.center-placeholder {
|
||||
width: 120rpx; /* 与大按钮宽度接近,确保空间对齐 */
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
.center-btn-wrapper {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 50rpx; /* 进一步调高,应对不同机型的安全区 */
|
||||
transform: translateX(-50%);
|
||||
width: 130rpx;
|
||||
height: 130rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 65rpx;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
border: 4rpx solid #ffffff;
|
||||
|
||||
.center-btn-image {
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
border-radius: 55rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.center-btn-wrapper::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -1rpx; left: -1rpx; right: -1rpx; bottom: -1rpx;
|
||||
border-radius: 66rpx;
|
||||
border: 1rpx solid #eee;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user