140 lines
4.6 KiB
JavaScript
140 lines
4.6 KiB
JavaScript
/**
|
|
* 菜单 → 页面 的跳转分发
|
|
*
|
|
* 聚合平台里绝大多数业务功能(ERP / OA / CRM / CMS)目前只有 Web 端实现,
|
|
* 端上没有对应页面;少数功能(记事本、日程提醒)已有原生页面。
|
|
* 这里统一判定:能走原生就走原生,否则丢进 webview 容器打开 Web 端深链。
|
|
*/
|
|
import { createAppTicket } from '@/api/auth.js'
|
|
|
|
/** 移动端已有原生页面的功能。匹配顺序即优先级。 */
|
|
const NATIVE_RULES = [
|
|
{ pattern: /记事本|笔记|便签|notebook/i, route: '/pages/tools/notepad/index' },
|
|
// 「日程提醒」与「工作日程」是两个独立功能:提醒在前,命中"提醒"后即不再落到日历页
|
|
{ pattern: /提醒|reminder/i, route: '/pages/tools/schedule/reminder' },
|
|
{ pattern: /工作日程|日程|日历|待办|calendar|schedule/i, route: '/pages/tools/schedule/index' },
|
|
// OA 协同办公
|
|
{ pattern: /通知公告|公告|notice/i, route: '/pages/oa/notice/index' },
|
|
{ pattern: /文档库|文档管理|文档图谱|document/i, route: '/pages/oa/document/index' },
|
|
{ pattern: /薪酬|工资|薪资|compensation|payroll/i, route: '/pages/oa/compensation/index' },
|
|
{ pattern: /员工档案|人事档案|档案|employeefile|employeeFile/i, route: '/pages/oa/employeefile/index' },
|
|
{ pattern: /通讯录|联系人|contact/i, route: '/pages/oa/contact/index' }
|
|
]
|
|
|
|
/** Web 端容器页 */
|
|
const WEBVIEW_PAGE = '/pages/apps/webview'
|
|
|
|
/** 菜单是否命中移动端原生页面 */
|
|
export function resolveNativeRoute(menu = {}) {
|
|
const haystack = (menu.title || '') + ' ' + (menu.path || '')
|
|
const hit = NATIVE_RULES.find((r) => r.pattern.test(haystack))
|
|
return hit ? hit.route : ''
|
|
}
|
|
|
|
/** 该功能是否在租户套餐内 */
|
|
export function isMenuLocked(menu = {}) {
|
|
return menu.locked === true || menu.locked === 1
|
|
}
|
|
|
|
/**
|
|
* 打开一个菜单功能
|
|
* @param {object} menu /app/home 返回的菜单项
|
|
* @param {string} [fallbackTitle] 没有菜单时用于提示的功能名
|
|
*/
|
|
export function openMenu(menu, fallbackTitle = '') {
|
|
const name = (menu && (menu.title || menu.name)) || fallbackTitle || '该功能'
|
|
|
|
if (!menu) {
|
|
uni.showToast({ title: name + '暂未开放', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
if (isMenuLocked(menu)) {
|
|
uni.showModal({
|
|
title: '功能未开通',
|
|
content: '「' + name + '」不在当前套餐范围内,请联系管理员开通后再使用。',
|
|
showCancel: false,
|
|
confirmText: '知道了'
|
|
})
|
|
return
|
|
}
|
|
|
|
// 目录节点(还有下级)不直接跳转
|
|
if (menu.leaf === false) {
|
|
uni.showToast({ title: '请选择「' + name + '」下的具体功能', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
const native = resolveNativeRoute(menu)
|
|
if (native) {
|
|
uni.navigateTo({ url: native })
|
|
return
|
|
}
|
|
|
|
if (!menu.path) {
|
|
uni.showToast({ title: name + '暂未开放', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
// 走 Web 端:先换一次性票据实现免登录,失败则降级为直接打开(用户手动登录一次)
|
|
openWebPage(menu.path, name)
|
|
}
|
|
|
|
/**
|
|
* 用 Web 端打开指定业务页。
|
|
*
|
|
* 关键:这里不传「完整 URL」,而是拆成 path + ticket 两个参数传给容器页。
|
|
* 原因是 uniapp 各端对 query 的编解码行为不一致 —— H5 端 vue-router 会先解码一次
|
|
* 并在序列化时把 % 转成 %25,小程序端则不解码;一旦传完整 URL(含 %、#、&),
|
|
* 就会出现「双重编码」或被截断。path 与 ticket 都是纯 ASCII 无特殊字符,
|
|
* 任何端都安全。
|
|
*
|
|
* @param {string} path 菜单 path,如 /apps/oa/dashboard
|
|
* @param {string} title 页面标题
|
|
*/
|
|
export async function openWebPage(path, title = '') {
|
|
const name = title || '功能'
|
|
const target = String(path || '').trim()
|
|
if (!target) {
|
|
uni.showToast({ title: name + '暂未开放', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
let ticket = ''
|
|
try {
|
|
const data = await createAppTicket()
|
|
ticket = (data && data.ticket) || ''
|
|
} catch (e) {
|
|
// 票据失败不影响打开,只是需要在 webview 内登录一次
|
|
console.warn('[nav] 获取免登录票据失败,降级为直接打开:', e)
|
|
}
|
|
|
|
uni.navigateTo({
|
|
url:
|
|
WEBVIEW_PAGE +
|
|
'?p=' +
|
|
encodeURIComponent(target) +
|
|
'&k=' +
|
|
encodeURIComponent(ticket) +
|
|
'&t=' +
|
|
encodeURIComponent(name)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 打开 webview 容器页并直接指定完整地址(通用兜底,非免登录场景)。
|
|
* @param {string} url 完整地址(未编码)
|
|
* @param {string} title 页面标题
|
|
*/
|
|
export function openRawUrl(url, title = '') {
|
|
if (!url) return
|
|
uni.navigateTo({
|
|
url:
|
|
WEBVIEW_PAGE +
|
|
'?p=' +
|
|
encodeURIComponent(url) +
|
|
'&t=' +
|
|
encodeURIComponent(title || '功能')
|
|
})
|
|
}
|