Allow browsing before mini login

This commit is contained in:
stu2not
2026-06-30 14:11:31 +08:00
parent be036ea7fe
commit ec390113ad
11 changed files with 436 additions and 53 deletions

View File

@@ -1,8 +1,8 @@
export default defineAppConfig({
pages: [
"pages/device/index",
"pages/login/index",
"pages/bind/index",
"pages/device/index",
"pages/notification/index",
"pages/chat/index",
"pages/chat/detail/index",

View File

@@ -25,6 +25,53 @@
overflow: hidden;
}
.guest-card {
margin: 0 24px;
padding: 44px 32px;
border-radius: 20px;
background: #FFFFFF;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
text-align: center;
}
.guest-card-icon {
display: block;
font-size: 56px;
line-height: 1;
}
.guest-card-title {
display: block;
margin-top: 18px;
font-size: 34px;
font-weight: 700;
color: #1A1A1A;
}
.guest-card-desc {
display: block;
margin-top: 14px;
font-size: 26px;
line-height: 1.6;
color: #666666;
}
.guest-card-btn {
display: flex;
align-items: center;
justify-content: center;
height: 84px;
margin-top: 30px;
border-radius: 16px;
background: #07C160;
}
.guest-card-btn-text {
font-size: 30px;
font-weight: 700;
color: #FFFFFF;
}
.conversation-item {
background: #FFFFFF;
border-radius: 20px;

View File

@@ -10,6 +10,7 @@ export default function Chat() {
const systemBanner = useSystemBanner()
const [conversations, setConversations] = useState<ChatConversation[]>([])
const [loading, setLoading] = useState(true)
const [isGuest, setIsGuest] = useState(false)
useDidShow(() => {
void loadConversations()
@@ -17,10 +18,13 @@ export default function Chat() {
const loadConversations = async () => {
if (!getToken()) {
Taro.reLaunch({ url: '/pages/login/index' })
setConversations([])
setIsGuest(true)
setLoading(false)
return
}
setIsGuest(false)
setLoading(true)
try {
const data = await getConversations()
@@ -55,13 +59,27 @@ export default function Chat() {
})
}
const handleGuestLogin = () => {
Taro.setStorageSync('postLoginRedirect', '/pages/chat/index')
Taro.navigateTo({ url: '/pages/login/index' })
}
return (
<View className='chat-page'>
<View className='page-header'>
<Text className='page-title'></Text>
</View>
{loading ? (
{isGuest ? (
<View className='guest-card'>
<Text className='guest-card-icon'>💬</Text>
<Text className='guest-card-title'></Text>
<Text className='guest-card-desc'> AI </Text>
<View className='guest-card-btn' onClick={handleGuestLogin}>
<Text className='guest-card-btn-text'></Text>
</View>
</View>
) : loading ? (
<View className='loading'>
<Text>...</Text>
</View>

View File

@@ -18,6 +18,119 @@
}
}
.guest-hero {
padding: 88px 40px 32px;
background: #FFFFFF;
text-align: center;
}
.guest-device-icon {
display: flex;
align-items: center;
justify-content: center;
width: 132px;
height: 132px;
margin: 0 auto 24px;
border-radius: 32px;
background: #FEF3E8;
.robot-img {
width: 72px;
height: 72px;
}
}
.guest-title {
display: block;
font-size: 44px;
line-height: 1.2;
font-weight: 700;
color: #1A1A1A;
}
.guest-subtitle {
display: block;
margin-top: 18px;
font-size: 28px;
line-height: 1.6;
color: #666666;
}
.guest-feature-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
padding: 24px;
}
.guest-feature-card {
min-height: 204px;
padding: 26px 22px;
border-radius: 20px;
background: #FFFFFF;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.guest-feature-icon {
width: 54px;
height: 54px;
margin-bottom: 18px;
}
.guest-feature-title {
display: block;
font-size: 28px;
font-weight: 700;
color: #1A1A1A;
}
.guest-feature-desc {
display: block;
margin-top: 10px;
font-size: 24px;
line-height: 1.45;
color: #666666;
}
.guest-action-card {
margin: 0 24px 24px;
padding: 30px 26px;
border-radius: 20px;
background: #FFFFFF;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.guest-action-title {
display: block;
font-size: 30px;
font-weight: 700;
color: #1A1A1A;
}
.guest-action-desc {
display: block;
margin-top: 10px;
font-size: 25px;
line-height: 1.5;
color: #666666;
}
.guest-primary-btn {
display: flex;
align-items: center;
justify-content: center;
height: 88px;
margin-top: 24px;
border-radius: 16px;
background: #07C160;
}
.guest-primary-btn-text {
font-size: 30px;
font-weight: 700;
color: #FFFFFF;
}
.status-card {
position: relative;
overflow: hidden;

View File

@@ -62,6 +62,11 @@ function formatSleepRange(status: DeviceStatus | null): string {
return `${start}-${end}`
}
function goLoginWithRedirect(url = '/pages/device/index') {
Taro.setStorageSync('postLoginRedirect', url)
Taro.navigateTo({ url: '/pages/login/index' })
}
function formatAlarmTime(value?: string | null): string {
if (!value) return '--'
const date = new Date(value)
@@ -129,6 +134,7 @@ export default function Device() {
const [isSavingVolume, setIsSavingVolume] = useState(false)
const [sleepWakePending, setSleepWakePending] = useState<'on' | 'off' | null>(null)
const [volumeValue, setVolumeValue] = useState(0)
const [isGuest, setIsGuest] = useState(false)
const volumeCommandInFlightRef = useRef(false)
useDidShow(() => {
@@ -137,11 +143,18 @@ export default function Device() {
const loadDeviceInfo = async () => {
if (!getToken()) {
Taro.reLaunch({ url: '/pages/login/index' })
setBinding(null)
setChild(null)
setDeviceStatus(null)
setDeviceAlarms([])
setVolumeValue(0)
setIsGuest(true)
setIsLoading(false)
return
}
setIsLoading(true)
setIsGuest(false)
try {
const context = await loadCurrentChildBindingContext()
setChild(context.currentChild)
@@ -264,6 +277,52 @@ export default function Device() {
)
}
if (isGuest) {
return (
<View className='device-page'>
<View className='guest-hero'>
<View className='guest-device-icon'>
<Image className='robot-img' src={require('../../assets/tab-icons/robot.png')} mode='aspectFit' />
</View>
<Text className='guest-title'></Text>
<Text className='guest-subtitle'></Text>
</View>
<View className='guest-feature-grid'>
<View className='guest-feature-card'>
<Image className='guest-feature-icon' src={require('../../assets/tab-icons/device-icon.png')} mode='aspectFit' />
<Text className='guest-feature-title'></Text>
<Text className='guest-feature-desc'></Text>
</View>
<View className='guest-feature-card'>
<Image className='guest-feature-icon' src={require('../../assets/tab-icons/location-icon.png')} mode='aspectFit' />
<Text className='guest-feature-title'></Text>
<Text className='guest-feature-desc'></Text>
</View>
<View className='guest-feature-card'>
<Image className='guest-feature-icon' src={require('../../assets/tab-icons/chat-icon.png')} mode='aspectFit' />
<Text className='guest-feature-title'></Text>
<Text className='guest-feature-desc'></Text>
</View>
<View className='guest-feature-card'>
<Image className='guest-feature-icon' src={require('../../assets/tab-icons/manage-icon.png')} mode='aspectFit' />
<Text className='guest-feature-title'></Text>
<Text className='guest-feature-desc'></Text>
</View>
</View>
<View className='guest-action-card'>
<Text className='guest-action-title'></Text>
<Text className='guest-action-desc'></Text>
<View className='guest-primary-btn' onClick={() => goLoginWithRedirect('/pages/device/index')}>
<Text className='guest-primary-btn-text'></Text>
</View>
</View>
{systemBanner}
</View>
)
}
if (!child) {
return (
<View className='device-page'>

View File

@@ -56,6 +56,10 @@
margin-bottom: 24px;
}
.guest-map-section {
height: 42vh;
}
.map {
width: 100%;
height: 100%;

View File

@@ -135,6 +135,7 @@ export default function Location() {
const [emptyState, setEmptyState] = useState<{ title: string; desc: string; actionText: string; actionUrl: string } | null>(
null
)
const [isGuest, setIsGuest] = useState(false)
useDidShow(() => {
void loadLocation(true)
@@ -144,10 +145,18 @@ export default function Location() {
const targetMode = nextMode || trajectoryMode
if (!getToken()) {
Taro.reLaunch({ url: '/pages/login/index' })
setDeviceLocation(null)
setTrajectory([])
setSelectedPoint(null)
setCoordinates(DEFAULT_COORDINATES)
setEmptyState(null)
setLocationNotice(null)
setIsGuest(true)
setLoading(false)
return
}
setIsGuest(false)
if (showToast) {
Taro.showLoading({ title: '刷新中...' })
}
@@ -372,7 +381,36 @@ export default function Location() {
<Text className='page-title'></Text>
</View>
{emptyState ? (
{isGuest ? (
<>
<View className='map-section guest-map-section'>
<Map
className='map'
latitude={DEFAULT_COORDINATES.latitude}
longitude={DEFAULT_COORDINATES.longitude}
scale={11}
onError={(event) => {
console.error('[location] guest map error:', event.detail)
}}
/>
</View>
<View className='location-empty-shell'>
<View className='location-empty-card'>
<Text className='location-empty-title'></Text>
<Text className='location-empty-desc'></Text>
<View
className='location-empty-action'
onClick={() => {
Taro.setStorageSync('postLoginRedirect', '/pages/location/index')
Taro.navigateTo({ url: '/pages/login/index' })
}}
>
<Text className='location-empty-action-text'></Text>
</View>
</View>
</View>
</>
) : emptyState ? (
<View className='location-empty-shell'>
<View className='location-empty-card'>
<Text className='location-empty-title'>{emptyState.title}</Text>

View File

@@ -72,30 +72,26 @@
}
}
.nickname-card {
.login-info-card {
background: #FFFFFF;
border-radius: 20px;
padding: 28px 24px;
margin-bottom: 32px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
.nickname-label {
.login-info-title {
display: block;
font-size: 28px;
color: #1A1A1A;
font-weight: 600;
margin-bottom: 18px;
}
.nickname-input {
width: 100%;
height: 88px;
box-sizing: border-box;
padding: 0 24px;
border-radius: 16px;
background: #F5F7FA;
font-size: 30px;
color: #1A1A1A;
.login-info-desc {
display: block;
margin-top: 12px;
font-size: 25px;
line-height: 1.5;
color: #666666;
}
}

View File

@@ -1,4 +1,4 @@
import { View, Text, Button, Input, Checkbox, CheckboxGroup } from '@tarojs/components'
import { View, Text, Button, Checkbox, CheckboxGroup } from '@tarojs/components'
import { useEffect, useState } from 'react'
import Taro from '@tarojs/taro'
import { getToken, wechatLogin } from '@/services/auth'
@@ -6,7 +6,6 @@ import './index.scss'
export default function Login() {
const [submitting, setSubmitting] = useState(false)
const [nickname, setNickname] = useState('')
const [agreementAccepted, setAgreementAccepted] = useState(false)
useEffect(() => {
@@ -21,12 +20,6 @@ export default function Login() {
return
}
const cachedUserInfo = (Taro.getStorageSync('userInfo') || {}) as {
nickname?: string
}
if (cachedUserInfo.nickname) {
setNickname(cachedUserInfo.nickname)
}
}, [])
const handleLogin = async () => {
@@ -39,12 +32,6 @@ export default function Login() {
return
}
const cachedUserInfo = (Taro.getStorageSync('userInfo') || {}) as {
nickname?: string
avatar_url?: string
}
const normalizedNickname = nickname.trim() || cachedUserInfo.nickname?.trim() || ''
setSubmitting(true)
Taro.showLoading({ title: '登录中...' })
@@ -56,24 +43,18 @@ export default function Login() {
const session = await wechatLogin({
code: loginRes.code,
nickname: normalizedNickname || undefined,
avatar_url: cachedUserInfo.avatar_url,
})
const nextUserInfo = {
nickname: session.nickname || normalizedNickname,
avatar_url: cachedUserInfo.avatar_url,
nickname: session.nickname || '',
}
if (nextUserInfo.nickname || nextUserInfo.avatar_url) {
if (nextUserInfo.nickname) {
Taro.setStorageSync('userInfo', {
nickname: nextUserInfo.nickname,
avatar_url: nextUserInfo.avatar_url,
})
setNickname(nextUserInfo.nickname || '')
} else {
Taro.removeStorageSync('userInfo')
setNickname('')
}
Taro.showToast({ title: '登录成功', icon: 'success' })
@@ -170,22 +151,15 @@ export default function Login() {
</View>
</View>
<View className='nickname-card'>
<Text className='nickname-label'></Text>
<Input
className='nickname-input'
type='nickname'
maxlength={64}
placeholder='请输入家长昵称'
value={nickname}
onInput={(event) => setNickname(event.detail.value)}
/>
<View className='login-info-card'>
<Text className='login-info-title'></Text>
<Text className='login-info-desc'></Text>
</View>
<View className='login-btn-wrapper'>
<Button className='login-btn' loading={submitting} disabled={submitting} onClick={handleLogin}>
<Text className='btn-icon'>🌐</Text>
<Text className='btn-text'>{submitting ? '登录中...' : '微信一键登录'}</Text>
<Text className='btn-text'>{submitting ? '登录中...' : '登录后继续'}</Text>
</Button>
</View>

View File

@@ -102,6 +102,85 @@
}
}
.guest-manage-card {
margin: 0 24px 24px;
padding: 44px 32px;
border-radius: 20px;
background: #FFFFFF;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
text-align: center;
}
.guest-manage-icon {
display: block;
font-size: 58px;
line-height: 1;
}
.guest-manage-title {
display: block;
margin-top: 18px;
font-size: 34px;
font-weight: 700;
color: #1A1A1A;
}
.guest-manage-desc {
display: block;
margin-top: 14px;
font-size: 26px;
line-height: 1.6;
color: #666666;
}
.guest-manage-btn {
display: flex;
align-items: center;
justify-content: center;
height: 84px;
margin-top: 30px;
border-radius: 16px;
background: #07C160;
}
.guest-manage-btn-text {
font-size: 30px;
font-weight: 700;
color: #FFFFFF;
}
.guest-manage-list {
margin: 0 24px 24px;
border-radius: 20px;
overflow: hidden;
background: #FFFFFF;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.guest-manage-item {
padding: 26px 24px;
border-bottom: 1px solid #F0F0F0;
&:last-child {
border-bottom: none;
}
}
.guest-manage-item-title {
display: block;
font-size: 30px;
font-weight: 700;
color: #1A1A1A;
}
.guest-manage-item-desc {
display: block;
margin-top: 8px;
font-size: 25px;
line-height: 1.5;
color: #666666;
}
.menu-card {
background: #FFFFFF;
border-radius: 20px;

View File

@@ -106,6 +106,7 @@ export default function Sleep() {
const [familyMemberDisplayName, setFamilyMemberDisplayName] = useState('')
const [parentInfo, setParentInfo] = useState<Parent | null>(null)
const [wechatMpStatus, setWechatMpStatus] = useState<WechatMpBindStatus | null>(null)
const [isGuest, setIsGuest] = useState(false)
const [isLoadingWechatMp, setIsLoadingWechatMp] = useState(false)
const [isStartingWechatMpBind, setIsStartingWechatMpBind] = useState(false)
const [isAuthorizingPhone, setIsAuthorizingPhone] = useState(false)
@@ -135,10 +136,23 @@ export default function Sleep() {
const loadData = async () => {
if (!getToken()) {
Taro.reLaunch({ url: '/pages/login/index' })
setChildren([])
setBindings([])
setBinding(null)
setCurrentChild(null)
setParentInfo(null)
setFirmwareStatus(null)
setDeviceRole(null)
setRoles([])
setCurrentCardGroup(null)
setFamilyInfo(null)
setWechatMpStatus(null)
setIsGuest(true)
setLoading(false)
return
}
setIsGuest(false)
setLoading(true)
try {
const context = await loadCurrentChildBindingContext()
@@ -798,7 +812,7 @@ export default function Sleep() {
clearSelectedBindingDeviceId()
clearSelectedChildId()
Taro.removeStorageSync('userInfo')
Taro.reLaunch({ url: '/pages/login/index' })
void loadData()
},
})
}
@@ -951,6 +965,47 @@ export default function Sleep() {
)
}
if (isGuest) {
return (
<View className='manage-page'>
<View className='page-header'>
<Text className='page-title'></Text>
</View>
<View className='guest-manage-card'>
<Text className='guest-manage-icon'></Text>
<Text className='guest-manage-title'></Text>
<Text className='guest-manage-desc'></Text>
<View
className='guest-manage-btn'
onClick={() => {
Taro.setStorageSync('postLoginRedirect', '/pages/sleep/index')
Taro.navigateTo({ url: '/pages/login/index' })
}}
>
<Text className='guest-manage-btn-text'></Text>
</View>
</View>
<View className='guest-manage-list'>
<View className='guest-manage-item'>
<Text className='guest-manage-item-title'></Text>
<Text className='guest-manage-item-desc'></Text>
</View>
<View className='guest-manage-item'>
<Text className='guest-manage-item-title'></Text>
<Text className='guest-manage-item-desc'> NFC </Text>
</View>
<View className='guest-manage-item'>
<Text className='guest-manage-item-title'></Text>
<Text className='guest-manage-item-desc'></Text>
</View>
</View>
{systemBanner}
</View>
)
}
const parentDisplayName = parentInfo?.nickname?.trim() || '家长'
const parentPhone = String(parentInfo?.phone || '').trim()
const parentPhoneLabel = parentPhone ? `${parentPhone.slice(0, 3)}****${parentPhone.slice(-4)}` : '未开启'