hookehuyr

feat: 新增消息和我的页面并重构首页布局

新增消息页 (message) 和我的页 (mine),包含基础布局和授权状态展示。
新增 AppTabbar 组件实现底部导航栏,支持首页、消息、我的三栏切换。
重构首页布局,将原有测试功能移入测试中心并更新页面标题。
更新 app.config.js 和组件类型声明以支持新页面和组件。
......@@ -7,7 +7,10 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AppTabbar: typeof import('./src/components/AppTabbar.vue')['default']
IndexNav: typeof import('./src/components/indexNav.vue')['default']
NutTabbar: typeof import('@nutui/nutui-taro')['Tabbar']
NutTabbarItem: typeof import('@nutui/nutui-taro')['TabbarItem']
Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
QrCode: typeof import('./src/components/qrCode.vue')['default']
......
export default {
pages: [
'pages/index/index',
'pages/message/index',
'pages/mine/index',
'pages/pay-test/index',
'pages/pay-bridge/index',
'pages/webview-preview/index',
......
<template>
<view class="app-tabbar">
<nut-tabbar
:model-value="activeTab"
bottom
placeholder
safe-area-inset-bottom
:active-color="activeColor"
:unactive-color="inactiveColor"
@tab-switch="handleTabSwitch"
>
<nut-tabbar-item
v-for="item in tabItems"
:key="item.name"
:name="item.name"
:tab-title="item.title"
>
<template #icon="{ active }">
<component
:is="item.icon"
size="18"
:color="active ? activeColor : inactiveColor"
/>
</template>
</nut-tabbar-item>
</nut-tabbar>
</view>
</template>
<script setup>
import { computed } from 'vue'
import Taro from '@tarojs/taro'
import { Home, Message, My } from '@nutui/icons-vue-taro'
const props = defineProps({
current: {
type: String,
required: true,
},
})
const activeColor = '#a67939'
const inactiveColor = '#8b95a7'
const tabItems = [
{
name: 'home',
title: '首页',
icon: Home,
url: '/pages/index/index',
},
{
name: 'message',
title: '消息',
icon: Message,
url: '/pages/message/index',
},
{
name: 'mine',
title: '我的',
icon: My,
url: '/pages/mine/index',
},
]
const activeTab = computed(() => props.current)
const handleTabSwitch = (...args) => {
const nextTab = args[1]
const target = tabItems.find((item) => item.name === nextTab)
if (!target || target.name === props.current) {
return
}
Taro.redirectTo({
url: target.url,
})
}
</script>
<style lang="less">
.app-tabbar {
:deep(.nut-tabbar) {
left: 24rpx;
right: 24rpx;
bottom: 24rpx;
width: auto;
border: 2rpx solid rgba(166, 121, 57, 0.12);
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 24rpx 60rpx rgba(15, 23, 42, 0.12);
overflow: hidden;
}
:deep(.nut-tabbar-item_icon-box) {
padding-top: 8rpx;
}
:deep(.nut-tabbar-item_icon-box_nav-word) {
margin-top: 8rpx;
font-size: 22rpx;
font-weight: 600;
}
}
</style>
......@@ -6,5 +6,5 @@
* @Description: 首页配置
*/
export default {
navigationBarTitleText: '觉林寺测试'
navigationBarTitleText: '首页'
}
......
<template>
<view class="index-page">
<view class="index-header">
<text class="title">觉林寺</text>
<view class="page-content">
<view class="hero-card">
<text class="hero-eyebrow">觉林寺小程序</text>
<text class="hero-title">首页</text>
<text class="hero-desc">
当前先完成首页、消息、我的三栏结构,测试能力统一收口到测试中心,避免首页继续堆放调试按钮。
</text>
</view>
<view class="index-body">
<text class="tip">授权和支付最小测试入口</text>
<view class="status-card">
<text class="status-label">当前授权状态</text>
<text class="status-value" :class="{ authed: is_authed }">
{{ is_authed ? '已授权' : '未授权' }}
<view>
<text class="section-label">当前授权状态</text>
<text class="status-text">应用启动时会优先尝试静默授权</text>
</view>
<text class="status-tag" :class="{ authed: isAuthed }">
{{ isAuthed ? '已授权' : '未授权' }}
</text>
</view>
<view class="overview-grid">
<view class="overview-card">
<text class="card-title">首页</text>
<text class="card-desc">展示当前项目概览与测试入口。</text>
</view>
<view class="overview-card">
<text class="card-title">消息</text>
<text class="card-desc">后续承接通知、订单提醒与系统消息。</text>
</view>
<view class="overview-card">
<text class="card-title">我的</text>
<text class="card-desc">后续承接个人信息、授权状态与常用功能。</text>
</view>
</view>
<view class="test-entry-card">
<text class="section-label">测试入口</text>
<text class="test-entry-desc">
支付测试与 WebView 预览已移入测试中心,首页只保留统一入口。
</text>
<button class="primary-btn" @tap="goToTestCenter">进入测试中心</button>
</view>
<button class="primary-btn" @tap="goToPayTest">进入支付测试页</button>
<button class="secondary-btn" @tap="goToWebviewPreview">打开 WebView 预览页</button>
<button class="secondary-btn" @tap="refreshAuthStatus">刷新授权状态</button>
</view>
<AppTabbar current="home" />
</view>
</template>
<script setup>
import { ref } from 'vue'
import Taro, { useDidShow } from '@tarojs/taro'
import AppTabbar from '@/components/AppTabbar.vue'
import { hasAuth } from '@/utils/authRedirect'
Taro.setNavigationBarTitle({ title: '首页' })
const is_authed = ref(false)
const isAuthed = ref(false)
const refreshAuthStatus = () => {
is_authed.value = hasAuth()
isAuthed.value = hasAuth()
}
const goToPayTest = () => {
const goToTestCenter = () => {
Taro.navigateTo({
url: '/pages/pay-test/index',
})
}
const goToWebviewPreview = () => {
Taro.navigateTo({
url: '/pages/webview-preview/index',
})
}
useDidShow(() => {
refreshAuthStatus()
})
......@@ -51,85 +73,128 @@ useDidShow(() => {
<style lang="less">
.index-page {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
background-color: #f5f5f5;
background:
radial-gradient(circle at top right, rgba(166, 121, 57, 0.18), transparent 32%),
linear-gradient(180deg, #fffaf3 0%, #f6f7fb 100%);
.index-header {
width: 100%;
padding: 80rpx 0 40rpx;
display: flex;
justify-content: center;
.title {
font-size: 48rpx;
font-weight: bold;
color: #333;
.page-content {
padding: 32rpx 24rpx 0;
box-sizing: border-box;
}
.hero-card,
.status-card,
.overview-card,
.test-entry-card {
background: rgba(255, 255, 255, 0.94);
border: 2rpx solid rgba(166, 121, 57, 0.08);
border-radius: 28rpx;
box-shadow: 0 20rpx 60rpx rgba(15, 23, 42, 0.06);
box-sizing: border-box;
}
.index-body {
flex: 1;
.hero-card {
display: flex;
width: 100%;
padding: 0 48rpx 80rpx;
box-sizing: border-box;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 36rpx 32rpx;
}
.tip {
font-size: 28rpx;
color: #999;
.hero-eyebrow {
font-size: 24rpx;
font-weight: 600;
letter-spacing: 4rpx;
color: #a67939;
}
.hero-title {
margin-top: 12rpx;
font-size: 52rpx;
font-weight: 700;
color: #1f2937;
}
.hero-desc {
margin-top: 18rpx;
font-size: 26rpx;
line-height: 1.8;
color: #6b7280;
}
.status-card {
width: 100%;
padding: 32rpx;
border-radius: 24rpx;
background: #fff;
margin-top: 24rpx;
padding: 28rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.04);
.status-label {
font-size: 28rpx;
color: #666;
gap: 24rpx;
}
.status-value {
.section-label {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #d9485f;
color: #111827;
}
.authed {
color: #2d8c4d;
}
.status-text,
.card-desc,
.test-entry-desc {
display: block;
margin-top: 12rpx;
font-size: 24rpx;
line-height: 1.7;
color: #6b7280;
}
.primary-btn,
.secondary-btn {
width: 100%;
.status-tag {
flex-shrink: 0;
padding: 12rpx 22rpx;
border-radius: 999rpx;
font-size: 30rpx;
line-height: 88rpx;
font-size: 24rpx;
font-weight: 600;
color: #b45309;
background: #fef3c7;
}
.primary-btn {
color: #fff;
background: linear-gradient(135deg, #111827, #374151);
.status-tag.authed {
color: #166534;
background: #dcfce7;
}
.overview-grid {
margin-top: 24rpx;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20rpx;
}
.secondary-btn {
color: #374151;
background: #fff;
border: 2rpx solid #d1d5db;
.overview-card {
padding: 28rpx;
}
.overview-card:last-child {
grid-column: 1 / span 2;
}
.card-title {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #111827;
}
.test-entry-card {
margin-top: 24rpx;
padding: 32rpx;
}
.primary-btn {
margin-top: 24rpx;
border-radius: 999rpx;
font-size: 30rpx;
line-height: 88rpx;
color: #fff;
background: linear-gradient(135deg, #a67939, #8f5e20);
}
}
</style>
......
export default {
navigationBarTitleText: '消息',
}
<template>
<view class="message-page">
<view class="page-content">
<view class="hero-card">
<text class="hero-title">消息</text>
<text class="hero-desc">
这里预留给系统通知、预约提醒和支付结果消息。当前先完成 Tab 栏结构,后续再接真实消息数据。
</text>
</view>
<view class="placeholder-card">
<text class="section-title">当前状态</text>
<text class="section-desc">
暂无消息内容,后续可在这里接接口列表、未读计数和消息详情跳转。
</text>
</view>
</view>
<AppTabbar current="message" />
</view>
</template>
<script setup>
import AppTabbar from '@/components/AppTabbar.vue'
</script>
<style lang="less">
.message-page {
min-height: 100vh;
background:
radial-gradient(circle at top left, rgba(166, 121, 57, 0.12), transparent 28%),
linear-gradient(180deg, #fffaf4 0%, #f4f6fb 100%);
.page-content {
padding: 32rpx 24rpx 0;
box-sizing: border-box;
}
.hero-card,
.placeholder-card {
padding: 32rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.94);
border: 2rpx solid rgba(166, 121, 57, 0.08);
box-shadow: 0 20rpx 60rpx rgba(15, 23, 42, 0.06);
box-sizing: border-box;
}
.hero-title,
.section-title {
display: block;
font-size: 40rpx;
font-weight: 700;
color: #111827;
}
.hero-desc,
.section-desc {
display: block;
margin-top: 16rpx;
font-size: 26rpx;
line-height: 1.8;
color: #6b7280;
}
.placeholder-card {
margin-top: 24rpx;
}
.section-title {
font-size: 30rpx;
}
}
</style>
export default {
navigationBarTitleText: '我的',
}
<template>
<view class="mine-page">
<view class="page-content">
<view class="hero-card">
<text class="hero-title">我的</text>
<text class="hero-desc">
这里预留给个人资料、授权信息和常用入口。当前阶段先提供基础占位和授权状态展示。
</text>
</view>
<view class="status-card">
<text class="section-title">授权状态</text>
<view class="status-row">
<text class="section-desc">当前小程序登录态</text>
<text class="status-tag" :class="{ authed: isAuthed }">
{{ isAuthed ? '已授权' : '未授权' }}
</text>
</view>
</view>
</view>
<AppTabbar current="mine" />
</view>
</template>
<script setup>
import { ref } from 'vue'
import { useDidShow } from '@tarojs/taro'
import AppTabbar from '@/components/AppTabbar.vue'
import { hasAuth } from '@/utils/authRedirect'
const isAuthed = ref(false)
useDidShow(() => {
isAuthed.value = hasAuth()
})
</script>
<style lang="less">
.mine-page {
min-height: 100vh;
background:
radial-gradient(circle at top right, rgba(166, 121, 57, 0.14), transparent 30%),
linear-gradient(180deg, #fffaf4 0%, #f3f5f9 100%);
.page-content {
padding: 32rpx 24rpx 0;
box-sizing: border-box;
}
.hero-card,
.status-card {
padding: 32rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.94);
border: 2rpx solid rgba(166, 121, 57, 0.08);
box-shadow: 0 20rpx 60rpx rgba(15, 23, 42, 0.06);
box-sizing: border-box;
}
.hero-title,
.section-title {
display: block;
font-size: 40rpx;
font-weight: 700;
color: #111827;
}
.hero-desc,
.section-desc {
display: block;
margin-top: 16rpx;
font-size: 26rpx;
line-height: 1.8;
color: #6b7280;
}
.status-card {
margin-top: 24rpx;
}
.section-title {
font-size: 30rpx;
}
.status-row {
margin-top: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
}
.status-tag {
flex-shrink: 0;
padding: 12rpx 22rpx;
border-radius: 999rpx;
font-size: 24rpx;
font-weight: 600;
color: #b45309;
background: #fef3c7;
}
.status-tag.authed {
color: #166534;
background: #dcfce7;
}
}
</style>
export default {
navigationBarTitleText: '支付测试',
navigationBarTitleText: '测试中心',
}
......
<template>
<view class="pay-test-page">
<view class="hero-card">
<text class="hero-title">微信支付最小测试页</text>
<text class="hero-title">测试中心</text>
<text class="hero-desc">
这里仅验证授权是否可用,以及点击按钮后能否成功拉起微信支付弹框
这里统一承接当前首页里的测试能力,包括 WebView 预览和微信支付拉起测试
</text>
</view>
<view class="panel">
<view class="panel-head">
<text class="panel-title">调试入口</text>
</view>
<text class="panel-tip">
如需验证 H5 通过小程序桥页发起支付,可先从这里进入 WebView 预览页。
</text>
<button class="outline-btn" @tap="goToWebviewPreview">
打开 WebView 预览页
</button>
</view>
<view class="panel">
<view class="panel-head">
<text class="panel-title">授权状态</text>
<text class="auth-tag" :class="{ authed: is_authed }">
{{ is_authed ? '已授权' : '未授权' }}
......@@ -53,7 +65,7 @@
<script setup>
import { ref, watch } from 'vue'
import { useDidShow, useLoad } from '@tarojs/taro'
import Taro, { useDidShow, useLoad } from '@tarojs/taro'
import { useWechatMiniPay } from '@/composables/useWechatMiniPay'
const order_id = ref('')
......@@ -91,6 +103,12 @@ const handleRefreshAuth = async () => {
})
}
const goToWebviewPreview = () => {
Taro.navigateTo({
url: '/pages/webview-preview/index',
})
}
const handlePay = async () => {
await pay_by_order_id(order_id.value, {
auto_auth: false,
......