435 行
13 KiB
Vue
435 行
13 KiB
Vue
<template>
|
||
<view class="custom-tabbar-root" :class="{ 'theme-warm': currentThemeKey === 'warm' }">
|
||
<!-- 底部占位块,确保内容不被导航栏遮挡 -->
|
||
<view class="tabbar-placeholder" style="width: 100%; pointer-events: none;"></view>
|
||
|
||
<!-- 固定在屏幕底部的导航栏实体 -->
|
||
<view class="tabbar-container" style="position: fixed; bottom: 0; left: 0; right: 0; margin: 0 auto; max-width: 450px; display: flex; align-items: flex-start; justify-content: space-around; z-index: 999;">
|
||
<!-- 发单人端 (5 项:首页 / 附近活动 / 我要发单+ / 友圈 / 个人中心) -->
|
||
<template v-if="currentRole === 'PLAYER'">
|
||
<!-- 1. 首页 -->
|
||
<view
|
||
class="tab-item btn-active"
|
||
:class="{ active: current === 'home' }"
|
||
style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 118rpx; position: relative;"
|
||
@click="switchTab('home', '/pages/index/index')"
|
||
>
|
||
<image class="tab-icon-svg" :src="getTabIcon('home')" mode="aspectFit" style="width: 50rpx; height: 50rpx; flex-shrink: 0;" />
|
||
<text class="tab-label">首页</text>
|
||
</view>
|
||
|
||
<!-- 2. 附近活动 (原本的活动项目) -->
|
||
<view
|
||
class="tab-item btn-active"
|
||
:class="{ active: current === 'activity' }"
|
||
style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 118rpx; position: relative;"
|
||
@click="switchTab('activity', '/pages/activity/list')"
|
||
>
|
||
<image class="tab-icon-svg" :src="getTabIcon('activity')" mode="aspectFit" style="width: 50rpx; height: 50rpx; flex-shrink: 0;" />
|
||
<text class="tab-label">附近活动</text>
|
||
</view>
|
||
|
||
<!-- 3. 我要发单 (核心大圆圈 + 突出悬浮按键,居中对称) -->
|
||
<view class="tab-item special-item-wrap btn-active" style="flex: 1; height: 118rpx; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; position: relative;" @click="handlePublish">
|
||
<view class="plus-circle-btn" style="width: 96rpx; height: 96rpx; min-width: 96rpx; min-height: 96rpx; max-width: 96rpx; max-height: 96rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
|
||
<image class="plus-svg" :src="ICONS.tabPlus" mode="aspectFit" style="width: 48rpx; height: 48rpx; flex-shrink: 0;" />
|
||
</view>
|
||
<text class="tab-label special-label">我要发单</text>
|
||
</view>
|
||
|
||
<!-- 4. 友圈 -->
|
||
<view
|
||
class="tab-item btn-active"
|
||
:class="{ active: current === 'community' }"
|
||
style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 118rpx; position: relative;"
|
||
@click="switchTab('community', '/pages/community/index')"
|
||
>
|
||
<image class="tab-icon-svg" :src="getTabIcon('community')" mode="aspectFit" style="width: 50rpx; height: 50rpx; flex-shrink: 0;" />
|
||
<text class="tab-label">友圈</text>
|
||
</view>
|
||
|
||
<!-- 5. 个人中心 -->
|
||
<view
|
||
class="tab-item btn-active"
|
||
:class="{ active: current === 'user' }"
|
||
style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 118rpx; position: relative;"
|
||
@click="switchTab('user', '/pages/user/index')"
|
||
>
|
||
<image class="tab-icon-svg" :src="getTabIcon('user')" mode="aspectFit" style="width: 50rpx; height: 50rpx; flex-shrink: 0;" />
|
||
<text class="tab-label">个人中心</text>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 接单人端 (3 项:接单大厅 / 我要接单[5km雷达] / 个人中心) -->
|
||
<template v-else>
|
||
<!-- 1. 接单大厅 -->
|
||
<view
|
||
class="tab-item btn-active"
|
||
:class="{ active: current === 'hall' }"
|
||
style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 118rpx; position: relative;"
|
||
@click="switchTab('hall', '/pages/expert/hall')"
|
||
>
|
||
<image class="tab-icon-svg" :src="getTabIcon('hall')" mode="aspectFit" style="width: 50rpx; height: 50rpx; flex-shrink: 0;" />
|
||
<text class="tab-label">接单大厅</text>
|
||
</view>
|
||
|
||
<!-- 2. 我要接单 (核心特色:地图智能匹配附近5km定位) -->
|
||
<view
|
||
class="tab-item radar-item-wrap btn-active"
|
||
:class="{ active: current === 'radar' }"
|
||
style="flex: 1; height: 118rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative;"
|
||
@click="switchTab('radar', '/pages/expert/radar')"
|
||
>
|
||
<view class="radar-badge-box" style="position: relative; display: flex; align-items: center; justify-content: center;">
|
||
<view class="radar-icon-box" :class="{ 'radar-anim': current === 'radar' }" style="width: 52rpx; height: 52rpx; display: flex; align-items: center; justify-content: center;">
|
||
<image class="radar-svg" :src="getTabIcon('radar')" mode="aspectFit" style="width: 52rpx; height: 52rpx; flex-shrink: 0;" />
|
||
</view>
|
||
<text class="radar-pill">附近5km</text>
|
||
</view>
|
||
<text class="tab-label radar-label">我要接单</text>
|
||
</view>
|
||
|
||
<!-- 3. 个人中心 -->
|
||
<view
|
||
class="tab-item btn-active"
|
||
:class="{ active: current === 'user' }"
|
||
style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 118rpx; position: relative;"
|
||
@click="switchTab('user', '/pages/user/index')"
|
||
>
|
||
<image class="tab-icon-svg" :src="getTabIcon('user')" mode="aspectFit" style="width: 50rpx; height: 50rpx; flex-shrink: 0;" />
|
||
<text class="tab-label">个人中心</text>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
options: {
|
||
styleIsolation: 'apply-shared',
|
||
virtualHost: true
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted } from 'vue';
|
||
import { ICONS, getTabIconUri } from '@/common/icons.js';
|
||
import { getCurrentThemeKey } from '@/common/theme.js';
|
||
import { getCurrentRole, setCurrentRole, ROLE_TYPES, isExpertCertified, ensureLoginOrRedirect } from '@/common/role.js';
|
||
|
||
const props = defineProps({
|
||
current: {
|
||
type: String,
|
||
default: 'activity'
|
||
}
|
||
});
|
||
|
||
const currentRole = ref(getCurrentRole());
|
||
const currentThemeKey = ref(getCurrentThemeKey());
|
||
|
||
const getTabIcon = (key) => {
|
||
const isActive = props.current === key;
|
||
const isWarm = currentThemeKey.value === 'warm';
|
||
let color;
|
||
if (isActive) {
|
||
if (key === 'radar' || (currentRole.value === 'EXPERT' && key === 'hall')) {
|
||
color = '#10B981';
|
||
} else {
|
||
color = isWarm ? '#EA580C' : '#6366F1';
|
||
}
|
||
} else {
|
||
color = isWarm ? '#A8A29E' : '#64748B';
|
||
}
|
||
return getTabIconUri(key, color);
|
||
};
|
||
|
||
const onRoleChanged = (newRole) => {
|
||
if (newRole === 'EXPERT' && !isExpertCertified()) {
|
||
currentRole.value = 'PLAYER';
|
||
return;
|
||
}
|
||
currentRole.value = newRole;
|
||
};
|
||
|
||
const onThemeChanged = (newTheme) => {
|
||
currentThemeKey.value = newTheme;
|
||
};
|
||
|
||
onMounted(() => {
|
||
currentRole.value = getCurrentRole();
|
||
if (currentRole.value === 'EXPERT' && !isExpertCertified()) {
|
||
currentRole.value = 'PLAYER';
|
||
}
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
try {
|
||
uni.hideTabBar({ animation: false, fail: () => {} });
|
||
} catch (e) {}
|
||
uni.$on('role-change', onRoleChanged);
|
||
uni.$on('theme-change', onThemeChanged);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('role-change', onRoleChanged);
|
||
uni.$off('theme-change', onThemeChanged);
|
||
});
|
||
|
||
const switchTab = (tabKey, targetPath) => {
|
||
if (props.current === tabKey) return;
|
||
if ((tabKey === 'hall' || tabKey === 'radar') && !isExpertCertified()) {
|
||
uni.showToast({
|
||
title: '尚未通过大神认证,无法访问接单端',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
uni.reLaunch({
|
||
url: targetPath
|
||
});
|
||
};
|
||
|
||
const handlePublish = () => {
|
||
if (!ensureLoginOrRedirect('/pages/activity/create')) {
|
||
return;
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/activity/create'
|
||
});
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.custom-tabbar-root {
|
||
width: 100%;
|
||
|
||
/* 底部占位高度,确保页面内容滚动到底部不被遮挡 */
|
||
.tabbar-placeholder {
|
||
width: 100%;
|
||
height: calc(126rpx + env(safe-area-inset-bottom));
|
||
pointer-events: none;
|
||
}
|
||
|
||
.tabbar-container {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
margin: 0 auto;
|
||
max-width: 450px;
|
||
box-sizing: border-box;
|
||
height: calc(118rpx + env(safe-area-inset-bottom));
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-around;
|
||
z-index: 999;
|
||
|
||
/* 冷光暗夜默认主题 */
|
||
background: rgba(21, 29, 48, 0.95);
|
||
backdrop-filter: blur(16px);
|
||
border-top: 1rpx solid rgba(255, 255, 255, 0.08);
|
||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.35);
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 118rpx;
|
||
position: relative;
|
||
|
||
.tab-icon-svg {
|
||
width: 50rpx;
|
||
height: 50rpx;
|
||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.tab-label {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
margin-top: 4rpx;
|
||
font-weight: 500;
|
||
transition: all 0.25s ease;
|
||
}
|
||
|
||
&.active {
|
||
.tab-icon-svg {
|
||
transform: scale(1.06);
|
||
}
|
||
.tab-label {
|
||
color: #6366F1;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 核心:发单人端中间“我要发单”突出大圆圈 + */
|
||
.special-item-wrap {
|
||
position: relative;
|
||
top: -24rpx;
|
||
height: 118rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
padding-top: 2rpx;
|
||
flex-shrink: 0;
|
||
|
||
.plus-circle-btn {
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
min-width: 96rpx;
|
||
min-height: 96rpx;
|
||
max-width: 96rpx;
|
||
max-height: 96rpx;
|
||
aspect-ratio: 1 / 1;
|
||
border-radius: 50%;
|
||
box-sizing: border-box;
|
||
flex-shrink: 0;
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 4rpx solid #FFFFFF;
|
||
box-shadow: 0 8rpx 20rpx rgba(99, 102, 241, 0.45);
|
||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||
|
||
.plus-svg {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
|
||
.special-label {
|
||
margin-top: 4rpx;
|
||
color: #A5B4FC;
|
||
font-weight: 600;
|
||
font-size: 22rpx;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
&:active .plus-circle-btn {
|
||
transform: scale(0.92);
|
||
box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.3);
|
||
}
|
||
}
|
||
|
||
/* 接单人端:“我要接单”雷达微标与动画 */
|
||
.radar-item-wrap {
|
||
height: 118rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
.radar-badge-box {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.radar-icon-box {
|
||
width: 52rpx;
|
||
height: 52rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.radar-svg {
|
||
width: 52rpx;
|
||
height: 52rpx;
|
||
}
|
||
}
|
||
|
||
.radar-pill {
|
||
position: absolute;
|
||
top: -12rpx;
|
||
right: -40rpx;
|
||
background: linear-gradient(135deg, #10B981, #059669);
|
||
color: #FFFFFF;
|
||
font-size: 18rpx;
|
||
line-height: 1.2;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 12rpx;
|
||
font-weight: 700;
|
||
box-shadow: 0 2rpx 8rpx rgba(16, 185, 129, 0.4);
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.radar-label {
|
||
font-size: 23rpx;
|
||
color: #94A3B8;
|
||
margin-top: 4rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&.active {
|
||
.radar-label {
|
||
color: #10B981;
|
||
font-weight: 600;
|
||
}
|
||
.radar-anim {
|
||
animation: pulseRadar 1.8s infinite;
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes pulseRadar {
|
||
0% {
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
transform: scale(1.15);
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
/* 浅色暖阳 (Warm Theme) 适配 */
|
||
&.theme-warm {
|
||
.tabbar-container {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
border-top: 1rpx solid #F1EFE9;
|
||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.tab-item {
|
||
.tab-label {
|
||
color: #78716C;
|
||
}
|
||
|
||
&.active {
|
||
.tab-label {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.special-item-wrap {
|
||
.plus-circle-btn {
|
||
background: linear-gradient(135deg, #EA580C 0%, #F59E0B 100%);
|
||
border: 4rpx solid #FFFFFF;
|
||
box-shadow: 0 8rpx 20rpx rgba(234, 88, 12, 0.4);
|
||
}
|
||
.special-label {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
|
||
.radar-item-wrap {
|
||
&.active {
|
||
.radar-label {
|
||
color: #10B981;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|