feat:tabbar
This commit is contained in:
1 parent
f4adf95e7a
commit
6d267bcfc5
11 files changed
+141
-25
No files matched your search
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"appid": "",
|
"appid": "wx11771c7bf2913cb2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"versionName": "1.0.0",
|
"versionName": "1.0.0",
|
||||||
"versionCode": "100",
|
"versionCode": "100",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"quickapp": {},
|
"quickapp": {},
|
||||||
/* 小程序特有相关 */
|
/* 小程序特有相关 */
|
||||||
"mp-weixin": {
|
"mp-weixin": {
|
||||||
"appid": "",
|
"appid": "wx11771c7bf2913cb2",
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": false
|
"urlCheck": false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<crud-layout title="通知公告" :bg="filePath(SERVICE_IMAGE.blueBg)">
|
<crud-layout title="通知公告" :bg="filePath(SERVICE_IMAGE.blueBg)">
|
||||||
|
<view class="h-30rpx" />
|
||||||
<view class="notice-container">
|
<view class="notice-container">
|
||||||
<view class="notice-list">
|
<view class="notice-list">
|
||||||
<view v-if="noticeList.length === 0" class="empty-tip"> 暂无公告 </view>
|
<view v-if="noticeList.length === 0" class="empty-tip"> 暂无公告 </view>
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ const doMenu = async (v: any) => {
|
|||||||
// 是否是具体页面
|
// 是否是具体页面
|
||||||
const routePath = v.page.split("?")[0]
|
const routePath = v.page.split("?")[0]
|
||||||
if (v.page.startsWith("/") && __ROUTES__.some((route) => route.path === routePath)) {
|
if (v.page.startsWith("/") && __ROUTES__.some((route) => route.path === routePath)) {
|
||||||
|
// 直接页面入口也要先组装所属业务 Tabbar,再进入目标页面。
|
||||||
|
await tabbarStore.componetTabbarList(v.page)
|
||||||
router.jump(v.page)
|
router.jump(v.page)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,55 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from "vue"
|
import { reactive } from "vue"
|
||||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
import { onLoad } from "@dcloudio/uni-app"
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
url: ""
|
url: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
onLoad((options: any) => {
|
onLoad((options: any) => {
|
||||||
if (options.url) {
|
if (options.url) {
|
||||||
state.url = decodeURIComponent(options.url)
|
state.url = decodeURIComponent(options.url)
|
||||||
|
// 只记录地址结构,不输出 token,便于定位微信 WebView 域名校验问题。
|
||||||
|
try {
|
||||||
|
const url = new URL(state.url)
|
||||||
|
console.log("[web-view] 加载地址:", {
|
||||||
|
origin: url.origin,
|
||||||
|
pathname: url.pathname,
|
||||||
|
queryKeys: Array.from(url.searchParams.keys()),
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[web-view] 地址解析失败:", error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleWebViewLoad = (event: any) => {
|
||||||
|
console.log("[web-view] 加载完成:", event?.detail?.src || state.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWebViewError = (event: any) => {
|
||||||
|
console.error("[web-view] 加载失败:", {
|
||||||
|
url: state.url,
|
||||||
|
detail: event?.detail,
|
||||||
|
})
|
||||||
|
uni.showToast({ title: "第三方页面加载失败,请检查业务域名配置", icon: "none" })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<crud-layout mainIsScroll tabBarType="just-flot-back">
|
<crud-layout mainIsScroll tabBarType="just-flot-back">
|
||||||
<web-view :update-title="false" :fullscreen="false" :webview-styles="{
|
<web-view class="mall-web-view" :update-title="false" :fullscreen="false" @load="handleWebViewLoad"
|
||||||
width: '100%',
|
@error="handleWebViewError" :webview-styles="{
|
||||||
height: '100%',
|
width: '100%',
|
||||||
backgroundColor: '#fff'
|
height: '100%',
|
||||||
}" :src="state.url" />
|
backgroundColor: '#fff'
|
||||||
|
}" :src="state.url" />
|
||||||
</crud-layout>
|
</crud-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mall-web-view {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -33,8 +33,18 @@ function normalizeMallUrl(rawUrl: string) {
|
|||||||
|
|
||||||
const [, origin, rawPathname = "/", search = "", hash = ""] = matched
|
const [, origin, rawPathname = "/", search = "", hash = ""] = matched
|
||||||
const queryItems = search.slice(1).split("&").filter(Boolean)
|
const queryItems = search.slice(1).split("&").filter(Boolean)
|
||||||
const queryKeys = queryItems.map((item) => decodeURIComponent(item.split("=")[0] || ""))
|
const decodeQueryPart = (value: string) => {
|
||||||
const hasToken = queryItems.some((item) => /^token=.+/i.test(item))
|
try {
|
||||||
|
return decodeURIComponent(value)
|
||||||
|
} catch {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryKeys = queryItems.map((item) => decodeQueryPart(item.split("=")[0] || ""))
|
||||||
|
const hasToken = queryItems.some((item) => {
|
||||||
|
const [key, ...valueParts] = item.split("=")
|
||||||
|
return decodeQueryPart(key || "").toLowerCase() === "token" && valueParts.join("=")
|
||||||
|
})
|
||||||
let pathname = rawPathname
|
let pathname = rawPathname
|
||||||
|
|
||||||
// 宁波银行正常登录页是 /h5/thirdApp;部分环境会错误返回带 token 的根路径。
|
// 宁波银行正常登录页是 /h5/thirdApp;部分环境会错误返回带 token 的根路径。
|
||||||
@@ -51,7 +61,22 @@ function normalizeMallUrl(rawUrl: string) {
|
|||||||
if (pathname === "/" || !hasToken) {
|
if (pathname === "/" || !hasToken) {
|
||||||
throw new Error("商城登录地址异常,请联系管理员")
|
throw new Error("商城登录地址异常,请联系管理员")
|
||||||
}
|
}
|
||||||
return `${origin}${pathname}${search}${hash}`
|
|
||||||
|
// 宁波银行 H5 通过 platform 判断是否运行在微信小程序中。设置为 XR 后,
|
||||||
|
// H5 左上角返回按钮会调用 wx.miniProgram.navigateBack(),直接返回小程序上一页,
|
||||||
|
// 不会先消耗宁波银行 H5 自己的 history。覆盖旧值,避免后端返回其他平台参数。
|
||||||
|
const queryWithoutPlatform = queryItems.filter((item) => {
|
||||||
|
const key = decodeQueryPart(item.split("=")[0] || "").toLowerCase()
|
||||||
|
return key !== "platform"
|
||||||
|
})
|
||||||
|
queryWithoutPlatform.push("platform=XR")
|
||||||
|
const normalizedSearch = `?${queryWithoutPlatform.join("&")}`
|
||||||
|
console.log("[happyMall] 跳转平台:", {
|
||||||
|
platform: "XR",
|
||||||
|
queryKeys: queryWithoutPlatform.map((item) => decodeQueryPart(item.split("=")[0] || "")),
|
||||||
|
})
|
||||||
|
|
||||||
|
return `${origin}${pathname}${normalizedSearch}${hash}`
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(async () => {
|
onLoad(async () => {
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ function initBackStyle() {
|
|||||||
function tabbarChange(index: number) {
|
function tabbarChange(index: number) {
|
||||||
const target = tabbarStore.tabbarList[index]
|
const target = tabbarStore.tabbarList[index]
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
if (target.pagePath === "/pages/root/dashboard") {
|
||||||
|
// 从业务导航返回工作台时,让首页根 Tabbar 同步选中“首页”。
|
||||||
|
tabbarStore.setRootActiveIndex(0)
|
||||||
|
}
|
||||||
jump(target.pagePath, {}, 3)
|
jump(target.pagePath, {}, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+65
-12
@@ -118,16 +118,62 @@ const TABBAR_CONSTANT: Array<TabbarItem> = [
|
|||||||
launchParam: ["integrated"],
|
launchParam: ["integrated"],
|
||||||
auth: ["user"],
|
auth: ["user"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pagePath: "/pages/integrated/notice/index",
|
||||||
|
text: "通知公告",
|
||||||
|
iconPath: "/static/tabbar/notice.png",
|
||||||
|
selectedIconPath: "/static/tabbar/notice-select.png",
|
||||||
|
launchParam: ["notice"],
|
||||||
|
auth: ["user"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: "/pages/integrated/feedback/index",
|
||||||
|
text: "意见反馈",
|
||||||
|
iconPath: "/static/tabbar/feedback.png",
|
||||||
|
selectedIconPath: "/static/tabbar/feedback-select.png",
|
||||||
|
launchParam: ["feedback"],
|
||||||
|
auth: ["user"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pagePath: "/pages/tabbar/my",
|
pagePath: "/pages/tabbar/my",
|
||||||
text: "我的",
|
text: "我的",
|
||||||
iconPath: "/static/tabbar/my.png",
|
iconPath: "/static/tabbar/my.png",
|
||||||
selectedIconPath: "/static/tabbar/my-select.png",
|
selectedIconPath: "/static/tabbar/my-select.png",
|
||||||
launchParam: ["canteen", "selfMerchant", "onlineShop", "coopMerchant", "integrated", "prosperityShop", "prosperityShopMchList"],
|
launchParam: [
|
||||||
|
"canteen",
|
||||||
|
"selfMerchant",
|
||||||
|
"onlineShop",
|
||||||
|
"coopMerchant",
|
||||||
|
"integrated",
|
||||||
|
"prosperityShop",
|
||||||
|
"prosperityShopMchList",
|
||||||
|
"notice",
|
||||||
|
"feedback",
|
||||||
|
],
|
||||||
auth: ["user", "merchant", "doorAdmin"],
|
auth: ["user", "merchant", "doorAdmin"],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const WORKBENCH_TABBAR_ITEM: TabbarItem = {
|
||||||
|
pagePath: "/pages/root/dashboard",
|
||||||
|
text: "工作台",
|
||||||
|
iconPath: "/static/tabbar/home.png",
|
||||||
|
selectedIconPath: "/static/tabbar/home-select.png",
|
||||||
|
launchParam: [],
|
||||||
|
auth: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTabbarLaunchParam(url?: string) {
|
||||||
|
const launchParam = getLaunchParam(url)
|
||||||
|
if (launchParam) return launchParam
|
||||||
|
if (!url) return ""
|
||||||
|
|
||||||
|
// 常用功能也可能直接配置 Tabbar 页面路径,需要由页面反推出所属业务场景。
|
||||||
|
const pagePath = url.split("?")[0]
|
||||||
|
const tabbarItem = TABBAR_CONSTANT.find((item) => item.pagePath === pagePath)
|
||||||
|
return tabbarItem?.launchParam[0] || ""
|
||||||
|
}
|
||||||
|
|
||||||
const defaultRootTabbarList: Array<TabbarItem> = [
|
const defaultRootTabbarList: Array<TabbarItem> = [
|
||||||
{
|
{
|
||||||
pagePath: "/pages/root/dashboard",
|
pagePath: "/pages/root/dashboard",
|
||||||
@@ -166,13 +212,17 @@ export const useTabbarStore = defineStore("tabbarStore", {
|
|||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isDashboardPage(state) {
|
isDashboardPage(state) {
|
||||||
return `/${state.currentRoute}` === "/pages/root/dashboard" || `/${state.currentRoute}` === "/pages/tabbar/my"
|
const currentRoute = state.currentRoute.startsWith("/") ? state.currentRoute : `/${state.currentRoute}`
|
||||||
|
return currentRoute === "/pages/root/dashboard" || currentRoute === "/pages/tabbar/my"
|
||||||
},
|
},
|
||||||
isTabbar(state) {
|
isTabbar(state) {
|
||||||
return state.tabbarList.some((item) => item.pagePath === `/${state.currentRoute}`)
|
const currentRoute = state.currentRoute.startsWith("/") ? state.currentRoute : `/${state.currentRoute}`
|
||||||
|
// 工作台只作为业务 Tabbar 的返回入口,进入页面后仍展示首页自己的根 Tabbar。
|
||||||
|
return currentRoute !== WORKBENCH_TABBAR_ITEM.pagePath && state.tabbarList.some((item) => item.pagePath === currentRoute)
|
||||||
},
|
},
|
||||||
activeIndex(state) {
|
activeIndex(state) {
|
||||||
return state.tabbarList.findIndex((item) => item.pagePath === `/${state.currentRoute}`)
|
const currentRoute = state.currentRoute.startsWith("/") ? state.currentRoute : `/${state.currentRoute}`
|
||||||
|
return state.tabbarList.findIndex((item) => item.pagePath === currentRoute)
|
||||||
},
|
},
|
||||||
pageTabbarList(state) {
|
pageTabbarList(state) {
|
||||||
return state.tabbarList.map((item) => {
|
return state.tabbarList.map((item) => {
|
||||||
@@ -196,22 +246,25 @@ export const useTabbarStore = defineStore("tabbarStore", {
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async componetTabbarList(launchParam?: string) {
|
async componetTabbarList(launchParam?: string) {
|
||||||
let _launchParam = ""
|
const _launchParam = getTabbarLaunchParam(launchParam)
|
||||||
if (launchParam) {
|
const hasMyTabbar = this.tabbarList.some((item) => item.pagePath === "/pages/tabbar/my")
|
||||||
_launchParam = getLaunchParam(launchParam)
|
const hasWorkbenchTabbar = this.tabbarList.some((item) => item.pagePath === WORKBENCH_TABBAR_ITEM.pagePath)
|
||||||
} else {
|
// 热更新或旧状态中可能已有相同场景但尚未加入工作台,此时仍需重新组装。
|
||||||
_launchParam = getLaunchParam()
|
if (this.launchParam === _launchParam && (!hasMyTabbar || hasWorkbenchTabbar)) return
|
||||||
}
|
|
||||||
if (this.launchParam === _launchParam) return
|
|
||||||
const appInfo = await getAppInfo()
|
const appInfo = await getAppInfo()
|
||||||
if (!_launchParam || !appInfo) return
|
if (!_launchParam || !appInfo) return
|
||||||
const tabbarList: Array<TabbarItem> = []
|
const tabbarList: Array<TabbarItem> = []
|
||||||
TABBAR_CONSTANT.forEach((item, index) => {
|
TABBAR_CONSTANT.forEach((item) => {
|
||||||
const visible = item.launchParam.includes(_launchParam)
|
const visible = item.launchParam.includes(_launchParam)
|
||||||
if (visible) {
|
if (visible) {
|
||||||
tabbarList.push(item)
|
tabbarList.push(item)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const myTabbarIndex = tabbarList.findIndex((item) => item.pagePath === "/pages/tabbar/my")
|
||||||
|
if (myTabbarIndex !== -1) {
|
||||||
|
// 仅对带“我的”的业务导航生效,并保证“工作台”紧邻其左侧。
|
||||||
|
tabbarList.splice(myTabbarIndex, 0, WORKBENCH_TABBAR_ITEM)
|
||||||
|
}
|
||||||
this.tabbarList = tabbarList
|
this.tabbarList = tabbarList
|
||||||
this.launchParam = _launchParam
|
this.launchParam = _launchParam
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Reference in new issue
Block a user