Files
airz-uniapp/services/layout.uts
T
hujun 4a69357606 feat: add account profile, search, help and security features
1. add default avatar and multiple notification icons
2. update finance archive note and add form styles
3. add new pages: edit-profile, security, password, change-phone, search, help
4. refactor navigation action layout to support multiple actions
5. add search button to home page
6. add security code component and account service APIs
7. add help and search content services
8. update message notice to use svg icons
9. update asset section in finance assessment
10. update manifest appid and pack options
2026-09-21 15:04:59 +08:00

104 lines
4.6 KiB
Plaintext

import { ref, onMounted, onUnmounted } from 'vue'
import { onResize } from '@dcloudio/uni-app'
export type PageLayout = {
pageStyle: () => string,
contentStyle: (offset: number, minimum: boolean) => string,
statusStyle: (offset: number) => string,
navigationStyle: () => string,
navigationTitleStyle: () => string,
navigationActionStyle: (headerHeight: number, actionCount: number) => string,
navigationBrandStyle: (headerHeight: number) => string,
bottomStyle: (padding: number) => string
}
// 原生渲染不支持 vh/calc,统一以窗口尺寸计算,并随旋转更新。
export function usePageLayout(): PageLayout {
const windowInfo = ref(uni.getWindowInfo())
const capsuleLeft = ref(-1)
const capsuleTop = ref(0)
const capsuleHeight = ref(0)
const readCapsule = () => {
// #ifdef MP-WEIXIN
try {
const capsule = uni.getMenuButtonBoundingClientRect()
const valid = capsule.width > 0 && capsule.height > 0 && capsule.left > 0 && capsule.left < windowInfo.value.windowWidth
capsuleLeft.value = valid ? capsule.left : -1
capsuleTop.value = valid ? capsule.top : 0
capsuleHeight.value = valid ? capsule.height : 0
} catch (_error) { capsuleLeft.value = -1; capsuleTop.value = 0; capsuleHeight.value = 0 }
// #endif
}
readCapsule()
const refresh = () => { windowInfo.value = uni.getWindowInfo(); readCapsule() }
onMounted(() => refresh())
// #ifdef APP
onResize((_options) => { refresh() })
// #endif
// #ifndef APP
const handleResize = (_options: OnWindowResizeCallbackResult) => { refresh() }
uni.onWindowResize(handleResize)
onUnmounted(() => { uni.offWindowResize(handleResize) })
// #endif
const rpx = (value: number): number => value * windowInfo.value.windowWidth / 750
const status = (): number => Math.max(windowInfo.value.statusBarHeight ?? 0, windowInfo.value.safeAreaInsets?.top ?? 0)
const navigationStyle = (): string => {
// 只在微信端横向避让胶囊,保持业务导航原有高度。
// #ifdef MP-WEIXIN
const reserved = capsuleLeft.value > 0 ? windowInfo.value.windowWidth - capsuleLeft.value + 8 : 104
return 'padding-right:' + reserved + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const navigationTitleStyle = (): string => {
// 标题左右使用相同留白,以屏幕中心对齐,同时避开微信胶囊。
// #ifdef MP-WEIXIN
const reserved = capsuleLeft.value > 0 ? windowInfo.value.windowWidth - capsuleLeft.value + 8 : 104
const inset = Math.max(rpx(112), reserved)
return 'left:' + inset + 'px;right:' + inset + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const navigationActionStyle = (headerHeight: number, actionCount: number): string => {
// 微信操作按钮与胶囊等高、顶部对齐,整组向左展开;App 保持导航行内居中。
// #ifdef MP-WEIXIN
const size = capsuleHeight.value > 0 ? capsuleHeight.value : 32
const top = capsuleHeight.value > 0 ? Math.max(0, capsuleTop.value - status()) : Math.max(0, (rpx(headerHeight) - size) / 2)
const right = capsuleLeft.value > 0 ? windowInfo.value.windowWidth - capsuleLeft.value + 8 : 104
const width = size * actionCount + rpx(16) * Math.max(0, actionCount - 1)
return 'position:absolute;top:' + top + 'px;right:' + right + 'px;width:' + width + 'px;height:' + size + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const navigationBrandStyle = (headerHeight: number): string => {
// Logo 保留自身比例,与胶囊共享垂直中心,不改变登录页和启动页的 Logo。
// #ifdef MP-WEIXIN
const height = rpx(68)
const center = capsuleHeight.value > 0 ? capsuleTop.value - status() + capsuleHeight.value / 2 : rpx(headerHeight) / 2
return 'align-self:flex-start;margin-top:' + Math.max(0, center - height / 2) + 'px;height:' + height + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const pageStyle = (): string => 'min-height:' + windowInfo.value.windowHeight + 'px;'
const contentStyle = (offset: number, minimum: boolean): string => {
// #ifdef WEB
return (minimum ? 'min-height:' : 'height:') + 'calc(100vh - ' + status() + 'px - ' + offset + 'rpx);'
// #endif
// #ifndef WEB
const height = Math.max(0, windowInfo.value.windowHeight - status() - rpx(offset))
return (minimum ? 'min-height:' : 'height:') + height + 'px;'
// #endif
}
const statusStyle = (offset: number): string => 'height:' + (status() + rpx(offset)) + 'px;'
const bottomStyle = (padding: number): string => 'padding-bottom:' + (rpx(padding) + (windowInfo.value.safeAreaInsets?.bottom ?? 0)) + 'px;'
return { pageStyle, contentStyle, statusStyle, navigationStyle, navigationTitleStyle, navigationActionStyle, navigationBrandStyle, bottomStyle }
}