Files
yunzerwebsiteallinone/uniapp/utils/nav.js
T

213 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 菜单 → 页面 的跳转分发
*
* 小程序端「已开发」的只有少数原生页面(记事本、日程、OA 若干),
* 其余菜单在端上都没有对应页面:过去统一丢进 webview 打开 Web 端,
* 而 Web 端没登录态会被路由守卫送到统一认证登录页,用户看到的是「登录」而不是功能。
*
* 现在的判定:命中原生页 → 打开原生页;命中 WEB_RULES(Web 端确已上线、可免登录打开)
* → 走 webview;其余一律进入「功能开发中」占位页。
*/
import { createAppTicket } from '@/api/auth.js'
/** 移动端已有原生页面的功能。匹配顺序即优先级。 */
const NATIVE_RULES = [
// 进销存(移动端原生):按菜单 path 精确分流,列表所需的 doc_type / bill_type 直接挂在路由 query 上
{ pattern: /\/apps\/erp\/(dashboard|workbench)/i, route: '/pages/erp/dashboard/index' },
{ pattern: /\/apps\/erp\/basic\/warehouse/i, route: '/pages/erp/basic/warehouse' },
{ pattern: /\/apps\/erp\/basic\/product/i, route: '/pages/erp/basic/product' },
{ pattern: /\/apps\/erp\/suppliers/i, route: '/pages/erp/partner/index?partner_type=supplier' },
{ pattern: /\/apps\/erp\/customers/i, route: '/pages/erp/partner/index?partner_type=customer' },
{ pattern: /\/apps\/erp\/purchase\/order/i, route: '/pages/erp/document/index?doc_type=purchase_order' },
{ pattern: /\/apps\/erp\/purchase\/inbound/i, route: '/pages/erp/document/index?doc_type=purchase_in' },
{ pattern: /\/apps\/erp\/purchase\/return/i, route: '/pages/erp/document/index?doc_type=purchase_return' },
{ pattern: /\/apps\/erp\/sales\/order/i, route: '/pages/erp/document/index?doc_type=sales_order' },
{ pattern: /\/apps\/erp\/sales\/outbound/i, route: '/pages/erp/document/index?doc_type=sales_out' },
{ pattern: /\/apps\/erp\/sales\/return/i, route: '/pages/erp/document/index?doc_type=sales_return' },
{ pattern: /\/apps\/erp\/stock\/transfer/i, route: '/pages/erp/document/index?doc_type=transfer' },
{ pattern: /\/apps\/erp\/stock\/taking/i, route: '/pages/erp/document/index?doc_type=stock_take' },
{ pattern: /\/apps\/erp\/stock\/inbound/i, route: '/pages/erp/document/index?doc_type=other_in' },
{ pattern: /\/apps\/erp\/stock\/outbound/i, route: '/pages/erp/document/index?doc_type=other_out' },
{ pattern: /\/apps\/erp\/stock\/query/i, route: '/pages/erp/stock/index' },
{ pattern: /\/apps\/erp\/stock\/warning/i, route: '/pages/erp/stock/warning' },
{ pattern: /\/apps\/erp\/finance\/receivable/i, route: '/pages/erp/finance/bill?bill_type=1' },
{ pattern: /\/apps\/erp\/finance\/payable/i, route: '/pages/erp/finance/bill?bill_type=2' },
{ pattern: /\/apps\/erp\/finance\/payment/i, route: '/pages/erp/finance/fund' },
{ pattern: /\/apps\/erp\/report/i, route: '/pages/erp/report/index' },
{ pattern: /记事本|笔记|便签|notebook/i, route: '/pages/tools/notepad/index' },
// 账号管理(密码库):记录个人各平台账号
{ pattern: /账号管理|密码库|passwordStore|password-store/i, route: '/pages/tools/account/index' },
// 「日程提醒」与「工作日程」是两个独立功能:提醒在前,命中"提醒"后即不再落到日历页
{ pattern: /提醒|reminder/i, route: '/pages/tools/schedule/reminder' },
// 自定义审批流(移动端):放在工作日程之前,避免「待办审批」「审批中心」被工作日程吞掉
// 「我发起的审批 / 我的发起」也是同一页(业务口径就是申请人视角的实例列表)
{ pattern: /审批列表|我的审批|我发起的|审批管理|审批申请|approvalList|approval\/list/i, route: '/pages/approval/index' },
{ pattern: /待我审批|我审批的|审批待办|审批中心|approvalTodo|approval\/todo/i, route: '/pages/approval/verify' },
// OA 考勤打卡:必须排在「工作日程」之前——排班类菜单路径含 scheduling,
// 会被 schedule 关键词命中而错误跳到日程页
{ pattern: /考勤打卡|考勤|打卡|attendance|check-?in/i, route: '/pages/oa/attendance/index' },
{ pattern: /工作日程|日程|日历|待办|calendar|schedule/i, route: '/pages/tools/schedule/index' },
// 报销管理(注意放在日程规则之后即可,与日程关键词无冲突)
{ pattern: /报销|reimburse|expense/i, route: '/pages/tools/expense/index' },
// OA 协同办公
{ pattern: /工作日报|日报|dailyreport/i, route: '/pages/oa/dailyreport/index' },
{ 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'
/**
* 确已在 Web 端上线、可以免登录直接打开的功能(按菜单 title 或 path 关键词匹配)。
* 命中 → webview 打开 Web 端;未命中 → 「功能开发中」占位页。
* 新功能上线时把对应关键词加进来即可。
*/
export const WEB_RULES = [
// 审批流配置(Web 端 /backend/approval):端上没有原生实现,直接跳 Web 配置页
'审批设置',
'审批流配置',
'审批流程',
'审批配置'
]
/** 开发中原生占位页 */
const DEVELOPING_PAGE = '/pages/common/developing'
/** 该功能在小程序端尚未开发 */
function openDeveloping(name) {
uni.navigateTo({
url: DEVELOPING_PAGE + '?t=' + encodeURIComponent(name || '功能')
})
}
/** 菜单是否命中移动端原生页面 */
export function resolveNativeRoute(menu = {}) {
const title = String(menu.title || '')
const path = String(menu.path || '')
const haystack = title + ' ' + path
const hit = NATIVE_RULES.find((r) => r.pattern.test(haystack))
// 调试日志:方便排查菜单跳转是否符合预期(HBuilderX / 微信开发者工具 控制台可见)
console.log('[nav] resolveNativeRoute', { title, path, hit: hit ? hit.route : '' })
return hit ? hit.route : ''
}
/** 该功能是否在租户套餐内 */
export function isMenuLocked(menu = {}) {
return menu.locked === true || menu.locked === 1
}
/**
* 打开一个菜单功能
* @param {object} menu /api/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
}
const target = String(menu.path || '').trim()
// 完整外链直接打开,不走开发中判定
if (/^https?:\/\//i.test(target)) {
openRawUrl(target, name)
return
}
// Web 端确已上线的功能仍走 webview(带一次性票据免登录)
const haystack = name + ' ' + target
if (target && WEB_RULES.some((kw) => haystack.indexOf(kw) !== -1)) {
openWebPage(target, name)
return
}
// 端上没有对应页面 = 功能未开发:进占位页,不再跳 Web 的统一认证登录
openDeveloping(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 || '功能')
})
}