npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@litianxiang/portal-core

v0.2.6

Published

门户子系统通用内核,统一提供:

Readme

@litianxiang/portal-core

门户子系统通用内核,统一提供:

  • 路由守卫与动态菜单挂载
  • SSO 登录态恢复与统一退出
  • 认证 HTTP 拦截器(Loading、401、自动刷新 token)
  • 菜单/权限/用户状态工具
  • Tab、Loading、时间、数字、主题、i18n 工具

安装

pnpm add @litianxiang/portal-core
# 或
npm i @litianxiang/portal-core

升级说明

0.2.0(Breaking Change)

  • 移除 createPortalHttpClient
  • 统一使用 createAuthHttpClient
  • 业务项目原来通过 createPortalHttpClient 传入的 baseURL: ''timeout: 10000 可以不再显式传递,createAuthHttpClient 内部已提供相同默认值。

迁移示例:

import { createAuthHttpClient } from '@litianxiang/portal-core'

const { http, logoutToAuth } = createAuthHttpClient({
  getUserStore: () => useUserStore(),
  getLoadingStore: () => useLoadingStore(),
  onShowError: (msg) => console.error(msg)
})

快速开始

1) 路由接入

import { createAppRouter } from '@litianxiang/portal-core'
import { staticRoutes } from './routes'
import { useUserStore } from '@/stores/user'

const router = createAppRouter({
  staticRoutes,
  getUserStore: () => useUserStore(),
  getFirstPage: (allMenu) => allMenu.find((i: any) => i.category === 'page')?.path,
  authLoginUrl: import.meta.env.VITE_AUTH_LOGIN_URL
})

export default router

2) HTTP 接入

import axios from 'axios'
import { createAuthHttpClient } from '@litianxiang/portal-core'
import { useUserStore } from '@/stores/user'
import { useLoadingStore } from '@/stores/loading'

export const { http, logoutToAuth } = createAuthHttpClient({
  axios,
  getUserStore: () => useUserStore(),
  getLoadingStore: () => useLoadingStore(),
  onShowError: (msg) => console.error(msg)
})

如果业务项目只需要一个标准站点 http 模块,也可以直接使用更薄的封装:

import { createSiteHttpModule } from '@litianxiang/portal-core'

export const { http, logoutToAuth } = createSiteHttpModule({
  getUserStore: () => useUserStore(),
  getLoadingStore: () => useLoadingStore(),
  onShowError: (msg) => console.error(msg)
})

导出方法说明(逐方法)

app

  • setupApp(options):统一执行应用启动编排(插件注册、图标注册、beforeMount、mount)。
  • setupRuntime(options):统一执行挂载前的运行时初始化(i18n、登录态同步、菜单状态、portal-ui http 绑定)。

最小示例:

import { setupApp } from '@litianxiang/portal-core'

setupApp({
  app,
  plugins: [router, pinia],
  elementIcons: ElementPlusIconsVue,
  mountSelector: '#app'
})

router

  • createAppRouter(options):创建统一路由实例,内置登录态校验、菜单动态挂载、首屏跳转逻辑。
    • 菜单类别为 page 的动态路由会挂载到 main 下。
    • 菜单类别为 flow-page 的动态路由会挂载为顶级路由(不进入 main 主框架)。

最小示例:

import { createAppRouter } from '@litianxiang/portal-core'

const router = createAppRouter({
  staticRoutes,
  getUserStore: () => useUserStore(),
  getFirstPage: (allMenu) => allMenu.find((m: any) => m.category === 'page')?.path
})
  • createStaticAppRouter(options):创建静态站点路由,可选统一登录守卫,适用于 portal.web、portal.auth.web 这类无动态菜单站点。

最小示例:

import { createStaticAppRouter } from '@litianxiang/portal-core'

const router = createStaticAppRouter({
  staticRoutes,
  getUserStore: () => useUserStore(),
  requireAuth: true
})

auth

  • createLogoutToAuth(options):生成统一退出函数,负责清理用户态并跳转到认证站。
  • calcInactivityAction(lastActiveTime, now, config):根据空闲时长计算是否应刷新 token 或强制退出。

最小示例:

import { createLogoutToAuth, calcInactivityAction } from '@litianxiang/portal-core'

const logoutToAuth = createLogoutToAuth({
  getUserStore: () => useUserStore(),
  getLoadingStore: () => useLoadingStore()
})

const { shouldLogout, shouldRefresh } = calcInactivityAction(lastActiveTime, Date.now())
if (shouldLogout) logoutToAuth()
if (shouldRefresh) {
  // 执行刷新 token
}

http

  • createAuthHttpClient(options):创建带认证拦截器的 HTTP 客户端(Loading、token 注入、401 处理、刷新 token)。

最小示例:

import axios from 'axios'
import { createAuthHttpClient } from '@litianxiang/portal-core'

const { http } = createAuthHttpClient({
  axios,
  getUserStore: () => useUserStore(),
  getLoadingStore: () => useLoadingStore()
})

await http.post('/api/demo', {})

config

  • getDefaultAuthLoginUrl():基于当前域名生成默认统一登录地址。

最小示例:

import { getDefaultAuthLoginUrl } from '@litianxiang/portal-core'

const authLoginUrl = getDefaultAuthLoginUrl()

permission

  • createPermissionHelper(options):创建权限判断工具(页面权限、按钮权限、任一权限命中)。

最小示例:

import { createPermissionHelper } from '@litianxiang/portal-core'

const permission = createPermissionHelper({ getUserStore: () => useUserStore() })
const canVisit = permission.hasPagePermission('/system/user')
const canCreate = permission.hasButtonPermission('/system/user:add')

menu

  • createMenuHelper(options):创建菜单辅助工具,提供路径查找、标题、父链、面包屑能力。
  • transformMenuTree(menuList, options):将后端菜单转换为标准 CoreMenuItem 结构。
  • buildAllMenuTree(menuList):构建完整菜单树(含页面与按钮)。
  • buildPageMenuList(menuList):从完整树裁剪 page/button 结构。
  • findFirstPagePath(menuList):递归查找第一个可访问页面路径。

最小示例:

import {
  createMenuHelper,
  transformMenuTree,
  buildAllMenuTree,
  buildPageMenuList,
  findFirstPagePath
} from '@litianxiang/portal-core'

const tree = transformMenuTree(rawMenu)
const allMenu = buildAllMenuTree(tree)
const pageMenu = buildPageMenuList(allMenu)
const firstPath = findFirstPagePath(allMenu)

const helper = createMenuHelper({ getMenuTree: () => allMenu })
const breadcrumb = helper.getBreadcrumb('/system/user')

user

  • createBaseUserState():创建基础用户状态(不含菜单)。
  • createBaseUserGetters():创建基础用户 getters(工号/姓名/token/最后活跃时间)。
  • createBaseUserActions(options?):创建基础用户 actions(set/clear/syncFromAuthStore)。
  • createMenuUserState():创建管理站点用户状态(含 all_menu/sidebar_menu/menuLoaded)。
  • createMenuUserGetters():创建管理站点用户 getters(含菜单相关 getters)。
  • createMenuUserActions(options):创建管理站点 actions(菜单加载、SSO 恢复、状态维护)。
  • filterSidebarMenu(menus, options?):递归过滤不应在侧边栏展示的菜单类别。
  • syncFromAuthStoreToStore(store, options?):从统一登录缓存恢复用户信息到 store。
  • fetchUserMenuForStore(store, options):请求后端菜单并写入 all_menu/sidebar_menu

最小示例:

import {
  createBaseUserState,
  createBaseUserGetters,
  createBaseUserActions,
  createMenuUserState,
  createMenuUserGetters,
  createMenuUserActions,
  filterSidebarMenu,
  syncFromAuthStoreToStore,
  fetchUserMenuForStore
} from '@litianxiang/portal-core'

// 轻量站点
const baseState = createBaseUserState()
const baseGetters = createBaseUserGetters()
const baseActions = createBaseUserActions()

// 管理站点
const menuState = createMenuUserState()
const menuGetters = createMenuUserGetters()
const menuActions = createMenuUserActions({ http, site: 'manage', transformMenuData, getALLMenu })

const restored = syncFromAuthStoreToStore(menuState)
const sidebar = filterSidebarMenu(menuState.all_menu)
await fetchUserMenuForStore(menuState, { http, site: 'manage', transformMenuData, getALLMenu })

tab

  • addTab(tabs, tab):新增标签页(同路径不重复)。
  • removeTab(tabs, path):按路径删除标签页。
  • closeOthers(tabs, path, homePath?):关闭除当前与首页外的标签页。
  • closeLeft(tabs, path, homePath?):关闭当前左侧标签页(首页保留)。
  • closeRight(tabs, path, homePath?):关闭当前右侧标签页(首页保留)。
  • closeAll(tabs):关闭全部可关闭标签页。
  • createTabStoreOptions(homePath?):生成可直接用于 Pinia 的 Tab Store 配置。

最小示例:

import {
  addTab,
  removeTab,
  closeOthers,
  closeLeft,
  closeRight,
  closeAll,
  createTabStoreOptions
} from '@litianxiang/portal-core'

let tabs = addTab([], { title: '首页', path: '/main/home', closable: false, keepAlive: true })
tabs = addTab(tabs, { title: '用户管理', path: '/system/user', closable: true, keepAlive: true })
tabs = closeOthers(tabs, '/system/user')
tabs = closeLeft(tabs, '/system/user')
tabs = closeRight(tabs, '/system/user')
tabs = removeTab(tabs, '/system/user')
tabs = closeAll(tabs)

const tabStoreOptions = createTabStoreOptions('/main/home')

loading

  • createLoadingStoreOptions():生成可直接用于 Pinia 的 Loading Store 配置。

最小示例:

import { createLoadingStoreOptions } from '@litianxiang/portal-core'

const loadingStoreOptions = createLoadingStoreOptions()

time

  • formatTime(date, format?):按指定格式输出时间字符串。
  • humanizeTime(date):输出相对时间(如“3分钟前”)。
  • timeDiff(start, end?):计算时间差(毫秒)。
  • formatToMonthDay(date):格式化为 MM-DD
  • createRange(start, end):生成有序时间区间(自动处理先后)。
  • formatRange(start, end, format?):格式化时间区间字符串。
  • getPresetRange(key, now?):获取常用预设区间(今日/近7天/本月等)。
  • isInRange(value, range):判断时间是否落在区间内(含边界)。
  • useTime():组合导出以上时间工具。

最小示例:

import {
  formatTime,
  humanizeTime,
  timeDiff,
  formatToMonthDay,
  createRange,
  formatRange,
  getPresetRange,
  isInRange,
  useTime
} from '@litianxiang/portal-core'

const t1 = formatTime(new Date())
const t2 = humanizeTime(Date.now() - 60 * 1000)
const diff = timeDiff('2026-01-01')
const md = formatToMonthDay(new Date())
const range = createRange('2026-01-01', '2026-01-31')
const text = formatRange('2026-01-01', '2026-01-31')
const preset = getPresetRange('last7days')
const inRange = isInRange(new Date(), range)
const timeUtils = useTime()

number

  • formatToYi(value, decimals?):按“亿元”单位格式化数值。
  • formatToWanShou(value, decimals?):按“万手”单位格式化数值。
  • formatWanYuanToYi(value, decimals?):将“万元”转换并格式化为“亿元”。

最小示例:

import { formatToYi, formatToWanShou, formatWanYuanToYi } from '@litianxiang/portal-core'

const a = formatToYi(123000000)
const b = formatToWanShou(560000)
const c = formatWanYuanToYi(50000)

theme

  • initTheme():从本地缓存读取主题配置并初始化。
  • applyTheme(config):将主题变量应用到根节点 CSS 变量。
  • useThemeWatcher(config):监听主题变化并自动持久化。

最小示例:

import { initTheme, applyTheme, useThemeWatcher } from '@litianxiang/portal-core'

const theme = initTheme()
applyTheme(theme)
useThemeWatcher(theme)

i18n

  • createPortalI18n(options):创建并返回统一配置的 vue-i18n 实例。

最小示例:

import { createPortalI18n } from '@litianxiang/portal-core'

const i18n = createPortalI18n({
  messages: {
    'zh-CN': { common: { ok: '确定' } },
    'en-US': { common: { ok: 'OK' } }
  }
})

业务方约定

userStore(最小能力)

  • getUserAccessToken
  • getUserRefreshToken
  • getUserLastActiveTime
  • setUserAccessToken(token)
  • setUserRefreshToken(token)
  • setUserLastActiveTime(ts)
  • syncFromAuthStore()
  • clearUserInfo()

管理站点再补充:

  • isMenuLoaded
  • resetMenuLoaded()
  • fetchUserMenu()
  • getUserALLMenu

路由结构约定

createAppRouter 会把动态菜单路由挂到 name: 'main' 的节点下,业务静态路由需提供该节点。

视图解析约定

菜单 path 会按以下优先级解析视图:

  1. /src/views/main${path}.vue
  2. /src/views${path}.vue

常见问题

1) 刷新后跳登录

先检查 syncFromAuthStore() 是否正确从统一登录缓存恢复 token。

2) 401 循环跳转

检查是否重复创建多个 http 实例,或在 401 处理中重复手动跳转。

3) 菜单加载后白屏

检查菜单 path 与实际视图文件是否匹配(含 /main 约定)。

本地开发

pnpm install
pnpm run build
pnpm run pack:check

发布流程

  1. 更新版本号(patch 递增)
  2. 执行 pnpm run pack:check
  3. 发布:npm publish --access public --registry=https://registry.npmjs.org/