feat(tabbar): 底部菜单增加框架自带原生tabBar,首屏对齐微官网门户并适配安全区间距

这个提交包含在:
wikiboss
2026-09-21 10:26:38 +08:00
父节点 d7c0050258
当前提交 4843c6d713
共有 5 个文件被更改,包括 211 次插入 和 4663 次删除

查看文件

@@ -0,0 +1,45 @@
import * as fs from 'fs';
import * as path from 'path';
describe('uni-app 框架自带原生 tabBar 规范测试 (TDD Red -> Green)', () => {
const rootDir = path.resolve(__dirname, '../../../mpuniapp');
const pagesJsonPath = path.join(rootDir, 'pages.json');
it('1. pages.json 中必须配置合法且符合 uni-app 原生规范的 tabBar', () => {
expect(fs.existsSync(pagesJsonPath)).toBe(true);
const content = fs.readFileSync(pagesJsonPath, 'utf-8');
// 去除 json 中的注释
const cleanJson = content.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '$1');
const config = JSON.parse(cleanJson);
expect(config.tabBar).toBeDefined();
expect(Array.isArray(config.tabBar.list)).toBe(true);
expect(config.tabBar.list.length).toBeGreaterThanOrEqual(2);
expect(config.tabBar.list.length).toBeLessThanOrEqual(5);
// 关键规范:tabBar.list[0].pagePath 必须与 pages[0].path 一致,原生 tabBar 才能在首屏正常加载
expect(config.tabBar.list[0].pagePath).toBe(config.pages[0].path);
expect(config.tabBar.list[0].text).toBe('官网');
// 校验颜色 Token
expect(config.tabBar.color.toLowerCase()).toBe('#667085');
expect(config.tabBar.selectedColor.toLowerCase()).toBe('#0b5cff');
});
it('2. tabBar 中声明的所有图标资源文件必须真实存在', () => {
const content = fs.readFileSync(pagesJsonPath, 'utf-8');
const cleanJson = content.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '$1');
const config = JSON.parse(cleanJson);
for (const item of config.tabBar.list) {
if (item.iconPath) {
const iconFile = path.join(rootDir, item.iconPath);
expect(fs.existsSync(iconFile)).toBe(true);
}
if (item.selectedIconPath) {
const selIconFile = path.join(rootDir, item.selectedIconPath);
expect(fs.existsSync(selIconFile)).toBe(true);
}
}
});
});

文件差异内容过多而无法显示 加载差异

查看文件

@@ -1,5 +1,5 @@
<template>
<view class="portal-page" :class="pageRtlClass">
<template>
<view class="portal-page" :class="pageRtlClass" :style="pageTheme">
<view class="locale-bar">
<view class="locale-selector-container">
<i18n-selector :model-value="currentLang" @change="switchLang" />
@@ -196,6 +196,25 @@ import {
import I18nSelector from '../../components/i18n-selector/i18n-selector.uvue';
import AudioPronounce from '../../components/audio-pronounce/audio-pronounce.uvue';
import { releaseAudioPlayer } from './useAudioPlayer.uts';
import { utsLightColors, utsRadius, utsSpacing } from '../../common/tokens.uts';
const pageTheme: Record<string, string> = {
'--oc-primary': utsLightColors.primary,
'--oc-primary-soft': utsLightColors.primarySoft,
'--oc-ai': utsLightColors.ai,
'--oc-bg': utsLightColors.bg,
'--oc-surface': utsLightColors.surface,
'--oc-border': utsLightColors.border,
'--oc-text': utsLightColors.text,
'--oc-text-2': utsLightColors.text2,
'--oc-radius-btn': utsRadius.button,
'--oc-radius-card': utsRadius.card,
'--oc-space-4': utsSpacing.space4,
'--oc-space-8': utsSpacing.space8,
'--oc-space-12': utsSpacing.space12,
'--oc-space-16': utsSpacing.space16,
'--oc-space-24': utsSpacing.space24,
};
const defaultAvatar = 'https://cdn.omnicard.ai/assets/default-avatar.png';
@@ -639,60 +658,68 @@ onUnload(() => {
padding-top: 12px;
}
/* 2. 快捷动作条 */
/* 2. 快捷动作条 (4列规整网格,触控区 >= 44x44) */
.quick-action-bar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 16px;
gap: 8px;
margin-bottom: var(--oc-space-16, 16px);
}
.action-item {
flex: 1 1 20%;
min-width: 64px;
min-height: 52px;
background-color: #FFFFFF;
border: 1px solid #E3E7ED;
border-radius: 12px;
margin: 0 4px;
width: calc(25% - 6px);
min-height: 56px;
background-color: var(--oc-bg, #FFFFFF);
border: 1px solid var(--oc-border, #E3E7ED);
border-radius: var(--oc-radius-btn, 12px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px 4px;
box-sizing: border-box;
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.action-item:active {
opacity: 0.7;
}
.action-icon-box {
margin-bottom: 2px;
}
.action-icon-text {
font-size: 12px;
font-size: 11px;
font-weight: 700;
color: #0B5CFF;
color: var(--oc-primary, #0B5CFF);
letter-spacing: 0.5px;
}
.action-item-label {
font-size: 11px;
color: #101828;
color: var(--oc-text, #101828);
}
/* 3. Bento Grid 网格 */
/* 3. Bento Grid 网格 (便当盒美学) */
.bento-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
gap: 12px;
}
.bento-card {
background-color: #FFFFFF;
border: 1px solid #E3E7ED;
border-radius: 16px;
padding: 16px;
margin-bottom: 12px;
background-color: var(--oc-bg, #FFFFFF);
border: 1px solid var(--oc-border, #E3E7ED);
border-radius: var(--oc-radius-card, 16px);
padding: var(--oc-space-16, 16px);
display: flex;
flex-direction: column;
box-sizing: border-box;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
transition: transform 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.bento-card:active {
transform: scale(0.99);
}
.bento-col-1 {
width: 48.5%;
width: calc(50% - 6px);
}
.bento-col-2 {
width: 100%;
@@ -700,13 +727,13 @@ onUnload(() => {
.bento-tag {
align-self: flex-start;
padding: 2px 8px;
background-color: #F7F8FA;
background-color: var(--oc-surface, #F7F8FA);
border-radius: 8px;
margin-bottom: 8px;
}
.bento-tag-text {
font-size: 11px;
color: #667085;
color: var(--oc-text-2, #667085);
font-weight: 500;
}
.bento-cover {
@@ -714,56 +741,73 @@ onUnload(() => {
height: 120px;
border-radius: 8px;
margin-bottom: 10px;
background-color: #F7F8FA;
background-color: var(--oc-surface, #F7F8FA);
}
.bento-title {
font-size: 15px;
font-weight: 600;
color: #101828;
color: var(--oc-text, #101828);
margin-bottom: 6px;
line-height: 1.3;
line-height: 1.25;
}
.bento-content {
font-size: 13px;
color: #667085;
line-height: 1.4;
color: var(--oc-text-2, #667085);
line-height: 1.6;
}
/* 底部垫高与常驻主 CTA */
/* 底部垫高与常驻主 CTA (视口安全区 safe-area-inset-bottom 适配) */
.portal-bottom-spacer {
height: 80px;
height: calc(140px + var(--window-bottom, 0px) + env(safe-area-inset-bottom, 0px));
}
.ai-entry {
position: fixed;
right: 16px;
bottom: 96px;
bottom: calc(80px + var(--window-bottom, 0px) + env(safe-area-inset-bottom, 0px));
min-height: 44px;
min-width: 44px;
padding: 8px 12px;
color: #7C5CFF;
font-size: 12px;
padding: 8px 14px;
color: var(--oc-ai, #7C5CFF);
background-color: var(--oc-bg, #FFFFFF);
border: 1px solid var(--oc-border, #E3E7ED);
border-radius: 22px;
box-shadow: 0 2px 8px rgba(124, 92, 255, 0.12);
font-size: 13px;
font-weight: 600;
text-align: center;
z-index: 99;
display: flex;
align-items: center;
justify-content: center;
}
.portal-cta-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 12px 16px 24px 16px;
background-color: #FFFFFF;
border-top: 1px solid #E3E7ED;
bottom: var(--window-bottom, 0px);
padding: 12px var(--oc-space-16, 16px) calc(16px + env(safe-area-inset-bottom, 0px)) var(--oc-space-16, 16px);
background-color: var(--oc-bg, #FFFFFF);
border-top: 1px solid var(--oc-border, #E3E7ED);
box-sizing: border-box;
z-index: 100;
}
.primary-cta-btn {
height: 48px;
background-color: #0B5CFF;
border-radius: 12px;
min-height: 44px;
background-color: var(--oc-primary, #0B5CFF);
border-radius: var(--oc-radius-btn, 12px);
display: flex;
align-items: center;
justify-content: center;
width: 100%;
border-width: 0;
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.primary-cta-btn:active {
opacity: 0.85;
}
.primary-cta-btn-text {
color: #FFFFFF;
color: var(--oc-bg, #FFFFFF);
font-size: 15px;
font-weight: 600;
}

二进制
mpuniapp/static/tab-portal-active.png 普通文件

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 987 B

二进制
mpuniapp/static/tab-portal.png 普通文件

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 1.0 KiB