keel-uni-app-kit
v0.0.1
Published
uni-app 项目基础设施工具包 — HTTP 客户端、AES-256-CBC+HMAC-SHA256 加密、状态管理、路由、通用组件、通用工具等
Maintainers
Readme
keel-uni-app-kit
uni-app 项目基础设施工具包 — 为 Vue 3 + TypeScript + uni-app 项目提供开箱即用的核心能力。
特性
- HTTP 客户端 — 基于
uni.request封装,支持重试、取消、拦截器、加密 - AES-256-CBC + HMAC-SHA256 加密 — 环境自动检测:H5 使用 Web Crypto API(性能最优),小程序降级到 crypto-js(兼容性最好)
- 通用组件 — 18 个开箱即用的 UI 组件(空状态、弹窗、导航栏、列表容器等)
- 通用工具 — 缓存、存储、日志、错误处理、HTML 清理、格式化、验证
- Vue Composables — 屏幕信息、分页列表、登录守卫、定时器、防抖、倒计时等
- Store 辅助 — Pinia 初始化 + uni-app 持久化适配器 + 用户/应用 Store 工厂
- 路由模块 — 路由拦截器框架、RouteHelper 基类
- Tabbar — 自定义 Tabbar 状态管理
- 配置工厂 — UnoCSS / Vite / TypeScript / ESLint / Commitlint 共享配置
- 共享样式 — 主题变量、通用样式、骨架屏、响应式工具、mixins
安装
# pnpm workspace 本地引用
pnpm add keel-uni-app-kit
# 需要的 peer dependencies
pnpm add @dcloudio/uni-app dayjs vue-i18n pinia pinia-plugin-persistedstate0.0.2 迁移说明
KitLoadMore移除loading/noMore布尔兼容入参,仅保留status(loading | noMore | error | more)。setAutoCurIdx()不再依赖getCurrentPages()回退检查,未命中时直接归零,行为更可预测。createUnoConfig()移除@unocss/preset-legacy-compat预设,项目侧可删除该依赖。components子路径类型导出改为src/components/index.ts内联导出,不再从.vue文件透传类型。
模块导入路径
| 模块 | 导入路径 | 说明 |
|------|----------|------|
| HTTP 客户端 | keel-uni-app-kit/http | createHttpClient, 请求辅助 |
| AES-256-CBC + HMAC-SHA256 加密 | keel-uni-app-kit/encrypt | createAESCBCHMACAdapter, getCryptoProvider, isUsingNativeProvider |
| 通用工具 | keel-uni-app-kit/utils | storage, logger, debounce 等 |
| Composables | keel-uni-app-kit/composables | useScreen, usePaginatedList 等 |
| Store 辅助 | keel-uni-app-kit/store | createUserStore, createAppStore |
| 路由 | keel-uni-app-kit/router | createRouteInterceptor, RouteHelper |
| Tabbar | keel-uni-app-kit/tabbar | registerTabbarPaths, setCurIdx |
| i18n | keel-uni-app-kit/locale | createAppI18n |
| 插件 | keel-uni-app-kit/plugins | createPluginInstaller |
| 组件 | keel-uni-app-kit/components | KitEmptyState, KitCustomModal 等 |
| 常量 | keel-uni-app-kit/constants | API_CONSTANTS, PAGINATION 等 |
| 样式 | keel-uni-app-kit/style/* | SCSS 文件 |
| 配置工厂 | keel-uni-app-kit/config/* | vite, eslint, commitlint, uno |
| Vite 插件 | keel-uni-app-kit/vite-plugins | 原生资源复制, manifest 同步 |
| 类型定义 | keel-uni-app-kit/types | PageReq, PageRes 等 |
| Tabbar 配置 | keel-uni-app-kit/tabbar/config | TABBAR_STRATEGY_MAP |
新项目接入指南
第 1 步:添加依赖
cd your-uni-app
pnpm add keel-uni-app-kit
# 或本地链接
pnpm link ../../keel-uni-app-kit第 2 步:配置 ESLint + Commitlint
// eslint.config.mjs
export { default } from 'keel-uni-app-kit/config/eslint'
// .commitlintrc.cjs
module.exports = require('keel-uni-app-kit/config/commitlint')第 3 步:配置 Vite
// vite.config.ts
import {
getBuildConfig,
getCrossOriginIsolationHeaders,
getEsbuildConfig,
getServerProxy,
getVueDefineFlags,
} from 'keel-uni-app-kit/config/vite';
export default defineConfig(({ mode }) => ({
build: getBuildConfig(mode),
esbuild: getEsbuildConfig(VITE_DELETE_CONSOLE === 'true'),
define: getVueDefineFlags(),
server: {
headers: getCrossOriginIsolationHeaders(),
proxy: getServerProxy(VITE_SERVER_BASEURL, VITE_API_PREFIX),
},
}));第 4 步:引入共享样式
在 App.vue 中引入(仅在此处引入一次,勿在 style/index.scss 中重复引入):
<style lang="scss">
// SDK 共享样式(主题变量、通用工具类、响应式工具)
@import 'keel-uni-app-kit/style/index.scss';
// 项目自有样式(覆盖 SDK 默认值)
@import '@/style/common.scss';
</style>第 5 步:初始化核心模块
// src/utils/auth.ts — 创建认证工具
import { createAuthUtils } from 'keel-uni-app-kit/utils';
export const { getSessionId, setSessionId, clearSessionId, isSessionValid } = createAuthUtils({
sessionDuration: 30 * 24 * 60 * 60 * 1000,
storageKeys: { sessionId: 'SESSION_ID', sessionExpiry: 'SESSION_EXPIRE' },
});
// src/http/client.ts — 创建 HTTP 客户端
import { createHttpClient, createAESCBCHMACAdapter } from 'keel-uni-app-kit/http';
export const { http, httpGet, httpPost } = createHttpClient({
baseUrl: () => import.meta.env.VITE_SERVER_BASEURL,
auth: {
headerName: 'Authorization',
getSessionId: () => getSessionId(),
setSessionId: (id) => setSessionId(id),
clearSession: () => clearSessionId(),
},
encryption: createAESCBCHMACAdapter({
requestKeyHex: import.meta.env.VITE_AES_REQUEST_KEY_HEX,
responseKeyHex: import.meta.env.VITE_AES_RESPONSE_KEY_HEX,
}),
});
// src/store/user.ts — 创建用户 Store
import { createUserStore } from 'keel-uni-app-kit/store';
export const useUserStore = createUserStore({
loginApi: AuthApi.loginByPassword,
profileApi: AuthApi.getCurrentUser,
wxLoginApi: AuthApi.wxMiniappLogin,
logoutApi: AuthApi.logout,
mapProfileToState: (profile) => ({
nickName: profile.nickName,
avatarUrl: profile.avatarUrl,
phone: profile.phone,
}),
});组件使用指南
可用组件列表
| 组件名 | 导入标识 | 说明 |
|--------|----------|------|
| 空状态 | KitEmptyState | 预设类型(搜索、网络、错误等),支持主题和变体 |
| 自定义弹窗 | KitCustomModal | 多类型(success/warning/error/info/login/confirm) |
| 导航栏 | KitNavBar | 状态栏适配,固定定位,支持沉浸式 |
| 列表容器 | KitListContainer | 加载状态、空状态、加载更多、错误重试 |
| 加载更多 | KitLoadMore | 四种状态(loading/noMore/error/more) |
| 筛选弹窗 | KitFilterPopup | 多分组、单选/多选、重置 |
| 标签页 | KitTabs | 可滚动标签,下划线指示器 |
| 筛选标签 | KitFilterTabs | 水平滚动筛选 |
| 搜索栏 | KitSearchBar | 输入框 + 防抖搜索,支持地区选择、fixed、glass/transparent 变体 |
| 操作栏 | KitActionBar | 底部操作栏,支持 fixed/placeholder/border、left 插槽、buttons 模式 |
| 菜单项 | KitMenuItem | 列表菜单项,左图标/标题/右箭头 |
| 倒计时 | KitCountdown | 时间/格式化/slot 自定义 |
| 价格展示 | KitPriceDisplay | 分转元、划线价、面议 |
| 状态标签 | KitStatusTag | 多颜色类型(primary/success/warning/danger) |
| 骨架屏 | KitSkeletonLoader | 可配置行数和动画 |
| 加载状态 | KitLoadingState | 全页面加载 spinner |
| 图片占位 | KitImagePlaceholder | 图片加载中/失败占位 |
| 自定义 TabBar | KitTabBar | 完整自定义底部导航 |
组件引入方式(推荐)
方式一:模板包装层(推荐) — 在项目中创建一个包装组件,通过 <template> 渲染 kit 组件:
<!-- src/components/common/empty-state/index.vue -->
<!-- 无插槽的组件 -->
<template>
<KitEmptyState v-bind="$attrs" />
</template>
<script setup lang="ts">
import { KitEmptyState } from 'keel-uni-app-kit/components';
defineOptions({ inheritAttrs: false });
</script><!-- src/components/common/custom-modal/index.vue -->
<!-- 有插槽的组件:需要显式转发每个插槽 -->
<template>
<KitCustomModal v-bind="$attrs">
<template #default><slot /></template>
</KitCustomModal>
</template>
<script setup lang="ts">
import { KitCustomModal } from 'keel-uni-app-kit/components';
defineOptions({ inheritAttrs: false });
</script><!-- src/components/common/nav-bar/index.vue -->
<!-- 多插槽组件:需要转发所有具名插槽 -->
<template>
<KitNavBar v-bind="$attrs">
<template #default><slot /></template>
<template #left><slot name="left" /></template>
<template #right><slot name="right" /></template>
</KitNavBar>
</template>
<script setup lang="ts">
import { KitNavBar } from 'keel-uni-app-kit/components';
defineOptions({ inheritAttrs: false });
</script>重要:小程序编译器要求每个组件都必须有
<template>生成对应的 WXML。纯 JSexport default KitXxx不会生成模板文件,会导致 "component not found" 错误。v-bind="$attrs"会自动透传所有 props 和事件监听器,配合inheritAttrs: false避免重复绑定。建议:项目页面与业务组件统一只导入本地包装层
.vue;仅包装层中从keel-uni-app-kit/components导入,避免小程序组件解析不稳定。
方式二:直接引入 .vue 文件 — 适用于页面中直接使用(非组件注册场景):
<script setup lang="ts">
import KitEmptyState from 'keel-uni-app-kit/src/components/empty-state.vue';
</script>
<template>
<KitEmptyState preset="search" action-text="重新搜索" @action="handleRetry" />
</template>类型导入
import type {
KitModalType,
LoadMoreStatus,
FilterTabOption,
FilterOption,
FilterGroup,
FilterValues,
StatusType,
KitTabItem,
ButtonConfig,
SearchBarVariant,
EmptyPreset,
EmptyVariant,
} from 'keel-uni-app-kit/components';工具函数与 Composables 使用指南
推荐的导出模式
在项目中统一通过 index.ts 桶文件管理导出,避免创建纯 re-export 中间文件:
// src/utils/index.ts
export * from './auth'; // 有业务定制
export * from './format'; // 有业务定制
export * from './page'; // 有业务定制
// 直接从 kit re-export(无需中间文件)
export { debounce, throttle } from 'keel-uni-app-kit/utils';
export { logger, createLogger } from 'keel-uni-app-kit/utils';
export { storage } from 'keel-uni-app-kit/utils';
export { stripHtmlTags } from 'keel-uni-app-kit/utils';
export { handleError } from 'keel-uni-app-kit/utils';// src/composables/common/index.ts
export { useCustomModal } from './use-custom-modal'; // 有业务定制
// 直接从 kit re-export(无需中间文件)
export { useScreen, getSafeAreaBottom } from 'keel-uni-app-kit/composables';
export { useDebounce } from 'keel-uni-app-kit/composables';
export { useInterval } from 'keel-uni-app-kit/composables';
export { useTimeout } from 'keel-uni-app-kit/composables';
export { usePaginatedList } from 'keel-uni-app-kit/composables';
export { useCountdown } from 'keel-uni-app-kit/composables';
export { useForm } from 'keel-uni-app-kit/composables';判断何时需要中间文件
| 场景 | 是否需要中间文件 | 示例 |
|------|----------------|------|
| 纯 re-export,无任何业务逻辑 | 不需要 — 直接在 index.ts 从 kit 导出 | debounce, logger, storage |
| 使用工厂函数注入业务配置 | 需要 | auth.ts(注入 storageKeys), http/client.ts(注入 auth adapter) |
| 继承/扩展基类 | 需要 | router/routes.ts(继承 RouteHelper) |
| 组合 kit 能力 + 业务逻辑 | 需要 | use-theme.ts(注入 AppStore + 颜色配置) |
| 有大量直接引用者的转发文件 | 保留 — 改动引用路径风险高 | error-handler.ts(30+ 处引用) |
Store 模块使用
createUserStore — 用户 Store 工厂
import { createUserStore } from 'keel-uni-app-kit/store';
export const useUserStore = createUserStore({
loginApi: AuthApi.loginByPassword,
profileApi: AuthApi.getCurrentUser,
wxLoginApi: AuthApi.wxMiniappLogin,
logoutApi: AuthApi.logout,
mapProfileToState: (profile) => ({
nickName: profile.nickName,
avatarUrl: profile.avatarUrl,
}),
extraState: {
customField: '',
},
persistPaths: ['token', 'customField'],
});createAppStore — 应用 Store 工厂
import { createAppStore } from 'keel-uni-app-kit/store';
export const useAppStore = createAppStore({
extraState: {
inviteConfig: null,
},
});createStoreSetup — Store 初始化
import { createStoreSetup, uniStorageAdapter } from 'keel-uni-app-kit/store';
export const setupStores = createStoreSetup({
storageAdapter: uniStorageAdapter,
afterSetup: () => {
// 注册认证处理器到 HTTP 模块
},
});路由模块使用
// src/router/interceptor.ts
import { createRouteInterceptor } from 'keel-uni-app-kit/router';
export const initRouteInterceptor = createRouteInterceptor({
needLoginPaths: ['/pages-user/*'],
loginPath: '/pages/common/login/index',
isLoggedIn: () => useUserStore().isLoggedIn,
onNeedLogin: (redirectPath) => {
uni.navigateTo({ url: `/pages/common/login/index?redirect=${redirectPath}` });
},
});
// src/router/routes.ts
import { RouteHelper } from 'keel-uni-app-kit/router';
class AppRoutes extends RouteHelper {
toHome() { this.switchTab('/pages/tab/home/index'); }
toDetail(id: string) { this.navigateTo(`/pages/detail/index?id=${id}`); }
}
export const routes = new AppRoutes();Tabbar 模块使用
// src/tabbar/store.ts
import {
curIdx,
tabbarItems,
registerTabbarPaths,
isPageTabbar,
setCurIdx,
setAutoCurIdx,
} from 'keel-uni-app-kit/tabbar';
// 注册 Tab 路径
registerTabbarPaths(
['/pages/tab/home/index', '/pages/tab/list/index', '/pages/tab/user/index'],
{
persist: true,
storage: { getJSON: storage.getJSON, setJSON: storage.setJSON },
items: [
{ pagePath: '/pages/tab/home/index', text: '首页', icon: 'i-home' },
{ pagePath: '/pages/tab/list/index', text: '列表', icon: 'i-list' },
{ pagePath: '/pages/tab/user/index', text: '我的', icon: 'i-user' },
],
},
);样式使用注意事项
仅在
App.vue中引入一次 kit 样式,勿在style/index.scss中重复引入,否则会产生冗余 CSS:<!-- App.vue --> <style lang="scss"> @import 'keel-uni-app-kit/style/index.scss'; @import '@/style/common.scss'; </style>主题变量覆盖:在项目的
style/variables.scss中覆盖 kit 的 CSS 变量:page { --theme-primary: #6366f1; --theme-primary-light: #818cf8; --theme-bg-color: #ffffff; }响应式工具:kit 提供 Pad 适配的 CSS 变量和工具类:
// 使用 kit 的 mixins @use 'keel-uni-app-kit/style/_mixins' as *; .my-component { @include spinner; // 加载动画 @include spinner-sm; // 小尺寸加载动画 }
发布与开发指南
依赖引用策略
本项目采用 link 开发 + 版本发布 双模式:
- 日常开发:消费项目使用
"keel-uni-app-kit": "link:../../keel-uni-app-kit"本地链接,改动即时生效,无需发版 - 生产发布:通过
deploy.py脚本一键发布到 npm,脚本会自动处理 link ↔ 版本号的切换
本包为源码分发包(直接发布 TypeScript 源码),消费项目通过自身的 Vite 工具链编译,无需构建步骤。
一键发布(推荐)
cd keel-uni-app-kit
# 自动 patch +1 并发布(0.0.1 → 0.0.2)
python deploy.py patch
# 自动 minor +1 并发布(0.0.2 → 0.1.0)
python deploy.py minor
# 指定版本号发布
python deploy.py 1.0.0
# 模拟发布(不实际执行)
python deploy.py patch --dry-run
# 发布 beta 版本
python deploy.py patch --tag betadeploy.py 会自动:① 更新版本号 → ② 将消费项目 link: 切为 ^x.y.z → ③ 发布到 npm → ④ 恢复消费项目为 link: 。无需手动操作。
本地开发联调
消费项目 package.json 中默认已配置 link:,直接开发即可:
# 源码包,改动直接生效,无需 watch
cd ruiying/ruiying-app
pnpm dev常规操作步骤
新增工具函数
- 在
src/utils/下创建文件(如my-util.ts) - 在
src/utils/index.ts中添加导出 - 消费项目中通过
import { myFunc } from 'keel-uni-app-kit/utils'使用
新增 Composable
- 在
src/composables/下创建文件(如use-my-hook.ts) - 在
src/composables/index.ts中添加导出 - 消费项目中通过
import { useMyHook } from 'keel-uni-app-kit/composables'使用
新增组件
- 在
src/components/下创建.vue文件 - 在
src/components/index.ts中添加组件和类型导出 - 消费项目中创建薄包装文件或直接导入
修改共享样式
- 修改
src/style/下的 SCSS 文件 - 注意不要引入破坏性变更(如删除 CSS 变量),优先新增
- 所有消费项目会自动获取变更
修改共享配置
- 修改
config/下的配置文件 - 注意:
config/vite.mjs是正式导出文件,config/vite.ts仅作为 TypeScript 源码参考
消费此包的项目
| 项目 | 路径 | 说明 |
|------|------|------|
| ruiying-app | ruiying/ruiying-app | 无人机俱乐部 uni-app |
| mc-revenue-guide-app | mc-guide/mc-revenue-guide-app | 创收指南 uni-app |
目录结构
keel-uni-app-kit/
├── config/ # 共享配置
│ ├── vite.mjs # Vite 配置辅助函数(正式导出)
│ ├── vite.ts # Vite 配置 TypeScript 源码
│ ├── eslint.mjs # ESLint 配置
│ ├── commitlint.cjs # Commitlint 配置
│ ├── uno.ts # UnoCSS 配置
│ └── tsconfig.base.json # TypeScript 基础配置
├── vite-plugins/ # Vite 插件
│ ├── copy-native-resources.ts
│ └── sync-manifest-plugins.ts
├── src/
│ ├── components/ # 通用 UI 组件(18 个)
│ ├── composables/ # Vue Composables(24 个)
│ ├── constants/ # 常量定义
│ ├── encrypt/ # AES-256-CBC + HMAC-SHA256 加密模块(环境自动检测)
│ │ ├── crypto-native.ts # Web Crypto API 原生实现(H5 性能优化)
│ │ ├── crypto-provider.ts # crypto-js 降级实现(小程序兼容)
│ │ ├── crypto-provider-factory.ts # 加密器工厂(环境自动检测)
│ │ ├── aes-cbc-hmac.ts # 加密适配器
│ │ ├── config.ts # 白名单与配置
│ │ ├── hash.ts # 密码哈希
│ │ └── types.ts # 类型定义
│ ├── http/ # HTTP 客户端
│ ├── locale/ # i18n 初始化
│ ├── plugins/ # 插件安装器
│ ├── router/ # 路由拦截器
│ ├── store/ # Store 工厂
│ ├── style/ # SCSS 共享样式
│ ├── tabbar/ # Tabbar 状态管理
│ ├── types/ # TypeScript 类型
│ └── utils/ # 工具函数(26 个)
├── scripts/ # 脚本工具
├── templates/ # 模板文件
├── package.json
├── README.md
└── CHANGELOG.md技术栈
- Vue 3.5+ / TypeScript 5.9+
- uni-app 3.0+
- Pinia 2.1+ / vue-i18n 9.0+
- UnoCSS / Vite 5+
- AES-256-CBC + HMAC-SHA256 加密(环境自动检测:H5 使用 Web Crypto API,小程序降级到 crypto-js)
加密方案说明
SDK 自动检测运行环境并选择最优加密实现:
| 环境 | 使用的实现 | 性能 | 兼容性 | |------|-----------|------|--------| | H5(支持 Web Crypto API) | Web Crypto API 原生 | ⚡ 性能最优 | ✅ 现代浏览器 | | H5(不支持 Web Crypto API) | crypto-js 纯 JS | 🟢 性能中等 | ✅ 全浏览器 | | 小程序 / App | crypto-js 纯 JS | 🟢 性能中等 | ✅ 全端兼容 |
检测 API
import { getCryptoProvider, isUsingNativeProvider, getProviderType } from 'keel-uni-app-kit/encrypt';
// 获取当前加密器类型
const providerType = getProviderType(); // 'native' | 'fallback'
// 检测是否使用原生加密器
if (isUsingNativeProvider()) {
console.log('H5 端使用 Web Crypto API(性能最优)');
} else {
console.log('使用 crypto-js 降级方案(兼容性最好)');
}License
MIT
