@yeepay/yee-boss-ui
v0.1.26
Published
运营后台平台公共 UI 与主题基础能力
Keywords
Readme
@yeepay/yee-boss-ui
运营后台平台公共 UI、主题和轻量列表页基础能力。
当前范围
当前提供:
- light/dark 语义化 Theme Tokens。
--yee-*全量公共 CSS Variables。- Ant Design Vue Theme Token 映射。
PlatformConfigProvider。usePlatformTheme只读主题上下文。YeePage页面容器。useYeeGrid与内置 Schema 查询区。YeeDescriptions配置驱动的详情展示。YeeEllipsisText单行/多行省略文本与按需完整提示。YeeFileUpload业务无关的文件选择与上传生命周期。YeeModal统一的 Modal / Drawer 容器。YeeAppShell、YeeHeader、YeeSidebar应用壳层布局。YeeMenu路由菜单样式与YeeTabs标签栏样式。- Tailwind CSS 语义主题 preset。
不提供业务组件或对 Ant Design Vue 基础组件的机械封装,也不依赖任何 @vben-* 包。
安装
基础组件和应用壳层:
pnpm add @yeepay/yee-boss-ui ant-design-vue vueyee-boss-ui 会自动安装 YeeGrid 所需的 VXE 运行时依赖。
使用
组件库同时支持根入口的按需导入和全量导入。根入口按名称导入时,ESM bundler 会通过 tree-shaking 移除未使用组件。
<script setup lang="ts">
import {
PlatformConfigProvider,
type PlatformTheme,
} from '@yeepay/yee-boss-ui'
import '@yeepay/yee-boss-ui/style.css'
const theme: PlatformTheme = {
mode: 'light',
}
</script>
<template>
<PlatformConfigProvider :theme="theme">
<RouterView />
</PlatformConfigProvider>
</template>Portal 负责决定当前主题并通过 Runtime Context 传给子应用;组件库只负责映射和消费。语言与 locale 继续由应用国际化层负责。
全量引入
如果希望一次注册全部公开组件,可以使用插件形式:
import { createApp } from 'vue'
import YeeBossUI from '@yeepay/yee-boss-ui'
import '@yeepay/yee-boss-ui/style.css'
createApp(App).use(YeeBossUI).mount('#app')组件库会自动注册 YeeAppShell、YeeDescriptions、YeeEllipsisText、YeeFileUpload、YeeModal、YeePage 以及应用壳层组件。useYeeGrid 仍按组合式 API 使用,首次调用时会由组件库自动加载 Grid 样式;查询区只在 Grid 内部渲染。
如果只希望在脚本中使用全部导出,也可以从包根入口统一导入:
import {
PlatformConfigProvider,
YeeAppShell,
YeeDescriptions,
YeeEllipsisText,
YeeFileUpload,
YeeGridFormApi,
YeeHeader,
YeeModal,
YeePage,
YeeSidebar,
YeeTabs,
useYeeGrid,
} from '@yeepay/yee-boss-ui'根入口导出全部公共组件和公共类型;VXE 由 yee-boss-ui 作为运行时依赖提供,不需要业务项目重复声明。按需和全量场景都只需要引入 @yeepay/yee-boss-ui/style.css。
按需引入
页面只使用少量能力、需要减少初始体积时,从根入口只导入实际使用的组件:
import { YeeModal, YeePage } from '@yeepay/yee-boss-ui'
import '@yeepay/yee-boss-ui/style.css'style.css 是唯一需要业务引入的组件样式入口。useYeeGrid() 首次执行时,组件库会自动加载内部 Grid 样式;业务不需要区分或引入其它样式文件。
Vite 模板自动导入
如果项目使用 unplugin-vue-components,可以直接复用组件库提供的 resolver。它会按组件的 feature entry 自动注入导入,并生成 GlobalComponents 类型声明:
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
import { YeeBossUIResolver } from '@yeepay/yee-boss-ui/resolver'
Components({
dts: 'src/types/components.d.ts',
resolvers: [
YeeBossUIResolver(),
AntDesignVueResolver(),
],
})该 resolver 只覆盖可以按名称自动导入的公共组件;useYeeGrid() 返回的 Grid 适配器仍应在脚本中显式使用。
应用壳层
应用壳层只提供布局和展示状态,不读取路由、权限、用户 Store 或 Wujie 上下文。菜单权限、路由跳转、标签增删、刷新、主题切换、全屏和用户操作由应用处理,再通过 Props、Events 和插槽接入。
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
YeeAppShell,
YeeHeader,
YeeSidebar,
YeeTabs,
} from '@yeepay/yee-boss-ui'
const sidebarCollapsed = shallowRef(false)
const activeTab = shallowRef('overview')
const menuItems = [
{ key: 'overview', label: '概览' },
{ key: 'settings', label: '系统设置' },
]
const tabs = [
{ key: 'overview', label: '概览', closable: false },
{ key: 'settings', label: '系统设置' },
]
</script>
<template>
<YeeAppShell v-model:sidebar-collapsed="sidebarCollapsed">
<template #header>
<YeeHeader
v-model:sidebar-collapsed="sidebarCollapsed"
title="运营管理平台"
>
<template #content>
<YeeTabs v-model:active-key="activeTab" :items="tabs" />
</template>
<template #actions>
<!-- 可在默认刷新、主题、全屏按钮后追加用户模块等自定义内容。 -->
</template>
</YeeHeader>
</template>
<template #sidebar>
<YeeSidebar
v-model:collapsed="sidebarCollapsed"
:items="menuItems"
:selected-keys="[activeTab]"
/>
</template>
<RouterView />
</YeeAppShell>
</template>YeeSidebar 的默认插槽是菜单区域,未提供时自动渲染 YeeMenu;需要替换或扩展菜单内容时可以使用默认插槽。YeeMenu 同一时间只展开当前父级菜单,打开新的父级菜单会自动收起其它父级菜单;受控或未受控模式下,选中子项都会自动展开其父级。菜单项点击会触发 click 事件,菜单选中变化仍触发 select 事件,因此重复点击当前选中项也可以由业务层处理。extra 插槽固定在菜单区域底部,可放帮助入口、版本信息或快捷操作。YeeHeader 的 brand 插槽可以完整替换左侧品牌区域;不需要完全接管时,可以单独使用 logo 和 title 插槽覆盖默认 Logo 或标题。content 插槽位于 Sidebar 按钮右侧,用于放置 YeeTabs 或任意自定义内容;actions 插槽用于在默认操作后追加用户模块等内容。Header 默认提供刷新、主题切换和全屏按钮,可通过 showRefresh、showThemeToggle、showFullscreen 关闭对应按钮;刷新通过 refresh 事件通知应用,主题通过 themeMode 属性和 theme-change 事件与 PlatformConfigProvider 的受控主题状态连接,全屏由 Header 管理浏览器 Fullscreen API,并通过 fullscreen-change 事件同步状态。默认 Sidebar 宽度为 224px,收起宽度为 64px;在 YeeAppShell 内由 Shell 统一控制,单独使用 YeeHeader 或 YeeSidebar 时再使用它们各自的尺寸 Props。
YeeTabs 固定使用 Vben 风格的卡片式标签样式,支持图标、固定/取消固定、关闭按钮、鼠标滚轮横向滚动、溢出时的左右滚动按钮、激活标签自动定位、中键关闭和桌面端拖拽排序。固定标签始终显示在未固定标签之前,各组内保持调用方传入的相对顺序。标签支持右键菜单,组件右侧也提供同一套更多操作菜单,默认包含关闭、固定/取消固定、刷新、在新窗口打开、关闭左右侧、关闭其它和关闭全部;可以通过 contextMenus 按标签和上下文自定义菜单,或通过 :show-actions="false" 隐藏右侧菜单。刷新只通过 refresh 事件通知应用,组件不自行重新加载页面。组件不直接修改调用方的路由或标签列表,菜单操作、固定状态和排序都通过事件交给应用处理;需要在其它业务入口触发相同操作时,可以使用组件 ref 调用 YeeTabsExpose:
<script setup lang="ts">
import { shallowRef } from 'vue'
import type { YeeTabsExpose } from '@yeepay/yee-boss-ui'
const tabsRef = shallowRef<YeeTabsExpose>()
</script>
<template>
<YeeTabs ref="tabsRef" :items="tabs" v-model:active-key="activeKey"
:draggable="true" :middle-click-to-close="true"
@close="handleClose" @close-left="handleCloseLeft"
@close-right="handleCloseRight" @close-others="handleCloseOthers"
@close-all="handleCloseAll" @pin="handlePin" @sort-tabs="handleSort"
@refresh="handleRefresh" @open-in-new-window="handleOpen" />
</template>YeeTabItem.pinned 表示固定状态;固定标签不能关闭或拖拽,组件展示和 getTabs 返回值都会将固定标签置于未固定标签之前。实例方法包括 activateTab、closeTab、closeLeftTabs、closeRightTabs、closeOtherTabs、closeAllTabs、getTabs、getTab、pinTab、refreshTab 和 openTabInNewWindow。方法只发出操作事件并返回是否找到目标标签,标签数组和路由状态仍由调用方维护。
即使只需要公共主题、不使用组件,也只引入统一样式入口:
import '@yeepay/yee-boss-ui/style.css'style.css 同时包含主题变量和公共组件样式,并在 :root 提供 light 默认值,同时支持 html.dark 和 [data-theme="dark"]。Portal 需要在根节点同步主题标识;子应用不自行维护另一套主题变量。
所有公共变量统一使用 --yee-*,例如:
.custom-card {
color: var(--yee-card-foreground);
background: var(--yee-card);
border: 1px solid var(--yee-border);
border-radius: var(--yee-radius);
}--yee-radius、--yee-radius-lg 与 --yee-radius-sm 默认分别为 4px、8px 与 2px。公共组件使用基础圆角,Ant Design Vue 映射按对应尺寸消费这组语义化变量;需要特殊圆角时由 Portal 通过 theme.tokens 显式覆盖。
TypeScript 类型
组件的公共类型统一从包根入口导入。命名统一使用 组件名 + Props / Emits / Slots;存在组件实例方法时额外提供 Expose,例如 YeeFileUploadExpose。公共字段带中文 JSDoc,业务项目在编写 Props、监听事件、使用插槽或组件 ref 时可以直接获得 IDE 提示。
import type {
YeeFileUploadEmits,
YeeFileUploadExpose,
YeeGridSlots,
YeeModalProps,
} from '@yeepay/yee-boss-ui'YeeModal 的 Props 包含 Ant Design Vue 原生透传属性。useYeeGrid<Row, QueryValues> 会把行类型传递给列、事件、查询函数和具名插槽,不需要在页面中重复断言类型。
Tailwind CSS
// tailwind.config.mjs
import bossUiTailwindPreset from '@yeepay/yee-boss-ui/tailwind-preset'
export default {
presets: [bossUiTailwindPreset],
content: ['./index.html', './src/**/*.{vue,ts}'],
}业务页面可以直接使用 bg-background、bg-background-deep、bg-card、bg-accent、text-foreground、text-muted-foreground 和 border-border,底层统一映射到 --yee-*。
通用业务组件
YeeEllipsisText
通过 line 和 maxWidth 控制单行或多行省略;tooltipWhenEllipsis 开启后,仅在文本实际被截断时显示 Tooltip。expand 支持鼠标、Enter 和 Space 展开/收起,并通过 expandChange 通知状态变化。
<YeeEllipsisText
:line="2"
:max-width="360"
expand
tooltip-when-ellipsis
@expand-change="handleExpandChange"
>
{{ remark }}
<template #tooltip>
完整备注:{{ remark }}
</template>
</YeeEllipsisText>placement 支持 top、right、bottom、left;ellipsisThreshold 默认是 3px。还可使用 tooltipMaxWidth、tooltipOverlayStyle、tooltipBackgroundColor、tooltipColor 和 tooltipFontSize 调整提示内容。默认背景、文字和字号消费 Yee 的 popover、popoverForeground 与 fontSize Theme Tokens;未使用 PlatformConfigProvider 时回退到对应的 --yee-* CSS Variables。
YeeDescriptions
配置项使用稳定的 name 时,可通过 content-${name} 覆盖内容。copyable 仅复制展示值,info 用于补充说明。
<YeeDescriptions
bordered
:items="[
{ label: '订单号', name: 'orderNo', value: orderNo, copyable: true },
{ label: '状态', name: 'status', value: status },
]"
>
<template #content-status="{ item }">
<span>{{ item.value }}</span>
</template>
</YeeDescriptions>YeeFileUpload
组件不接受上传 URL,也不依赖业务请求客户端。自动上传由 customRequest 完成;手动模式监听 file-select,完成后调用组件实例的 addResult(uid, { url, name? }) 或 addError(uid)。
自动上传模式通过回调接入项目统一请求层:
import type { YeeUploadRequestOptions } from '@yeepay/yee-boss-ui'
async function uploadFile(options: YeeUploadRequestOptions): Promise<void> {
try {
const result = await uploadAttachment(options.file, options.onProgress)
options.onSuccess({ name: result.name, url: result.url })
}
catch (error) {
options.onError(error instanceof Error ? error : new Error('上传失败'))
}
}手动模式适合先选择文件,再由页面决定何时请求或回写结果:
<script setup lang="ts">
import { shallowRef } from 'vue'
import {
YeeFileUpload,
type YeeFileSelectPayload,
type YeeFileUploadExpose,
type YeeUploadFile,
} from '@yeepay/yee-boss-ui'
const uploadRef = shallowRef<YeeFileUploadExpose>()
const fileList = shallowRef<YeeUploadFile[]>([])
async function handleFileSelect({ file, uid }: YeeFileSelectPayload): Promise<void> {
try {
const result = await uploadAttachment(file)
uploadRef.value?.addResult(uid, { name: result.name, url: result.url })
}
catch {
uploadRef.value?.addError(uid)
}
}
</script>
<template>
<YeeFileUpload
ref="uploadRef"
v-model:file-list="fileList"
manual
@file-select="handleFileSelect"
/>
</template>组件实例还提供 getFiles(),用于读取当前列表中的原始 File[]。
YeeModal
使用标准 v-model:open。Modal 默认在 header 右侧显示全屏切换按钮,可通过 v-model:fullscreen 控制状态,或通过 :fullscreen-button="false" 隐藏。type="drawer" 适合长详情或复杂表单,不显示全屏按钮;confirm 只通知父组件,不会主动关闭,便于父组件在异步提交成功后再更新 open。
<script setup lang="ts">
import { shallowRef } from 'vue'
import { YeeModal } from '@yeepay/yee-boss-ui'
const open = shallowRef(false)
const submitting = shallowRef(false)
async function submit(): Promise<void> {
submitting.value = true
try {
await saveForm()
open.value = false
}
finally {
submitting.value = false
}
}
</script>
<template>
<YeeModal
v-model:open="open"
:confirm-button-loading="submitting"
title="提交确认"
@confirm="submit"
>
确认提交当前内容?
</YeeModal>
</template>Drawer 模式使用 <YeeModal v-model:open="open" type="drawer" placement="right">,placement、getContainer、zIndex 等原生属性会继续透传。
YeeGrid 查询区
查询区不再作为独立组件提供。useYeeGrid 配置 formOptions 后,YeeGrid 内部会使用 Ant Design Vue Form / FormItem 渲染查询区。Schema 的 component 是判别字段,选择组件后,componentProps 会提示对应 Ant Design Vue 组件或 YeeFileUpload 的原生属性。
查询区默认使用 layout: 'horizontal',标签右对齐;未设置 labelWidth 或传入 labelWidth: 'auto' 时,标签列按当前标签内容自适应。传入数字时使用固定标签宽度,例如 labelWidth: 120;需要上下排列时传入 layout: 'vertical',此时标签左对齐且不使用 labelWidth。当字段的 label 为空字符串时,标签节点和标签间距都会省略,控件会占满当前字段区域。
查询字段默认占用 1 列。可以在字段 Schema 上设置 span 控制字段(包括标签和值)横跨的网格列数,支持 1、2、3,例如 span: 2 表示占用两列:
{
component: 'Textarea',
componentProps: {
autoSize: { minRows: 1, maxRows: 3 },
placeholder: '请输入备注',
},
fieldName: 'remark',
label: '备注',
span: 2,
}网格按屏幕宽度响应:默认(>= 1024px)为 3 列,768px 到 1023px 为 2 列,小于 768px 为 1 列。当 span 大于当前屏幕可用列数时,会自动收窄到当前可用列数,不会产生横向溢出。span 控制的是字段外层网格宽度,labelWidth 只控制水平布局中的标签列宽度。
span 的类型是公开导出的 YeeGridFormFieldSpan(1 | 2 | 3),通常直接在 Schema 字面量中配置即可;需要单独声明布局变量时,可以从包根入口导入该类型:
import type { YeeGridFormFieldSpan } from '@yeepay/yee-boss-ui'
const detailSpan: YeeGridFormFieldSpan = 2启用 showCollapseButton 时,collapsedRows 按实际网格行数计算,跨列字段会消耗对应的列数;字段无法完整放入当前行时会自动换到下一行。展开后仍会显示全部未隐藏字段。gridApi.formApi.updateSchema 也支持动态更新字段的 span。
字段设置 required: true 后会参与必填校验。点击查询、按 Enter 查询或开启 submitOnChange 触发提交时,查询区会先校验所有未隐藏的必填字段;校验失败时不会发起查询,标签显示必填标记,Ant Design Vue 原生支持 status 的控件显示红色错误状态。错误信息不渲染在控件下方;字段修改后会重新校验已经出错的字段,重置查询会清除错误状态。
必填校验将 undefined、null、空字符串(包括只包含空白字符的字符串)、空数组和 NaN 视为空值;false、0 和空对象会被视为有值。隐藏字段不会参与校验。
内置支持:AutoComplete、Cascader、Checkbox、CheckboxGroup、DatePicker、Input、InputNumber、InputPassword、Mentions、Radio、RadioGroup、RangePicker、Rate、Segmented、Select、Slider、Switch、Textarea、TimePicker、TimeRangePicker、TreeSelect 和 Upload。
<script setup lang="ts">
import { useYeeGrid } from '@yeepay/yee-boss-ui'
import type { YeeGridFormOptions, YeeGridOptions, YeeGridPage, YeeGridQueryParams } from '@yeepay/yee-boss-ui'
interface QueryValues {
channel?: 'OFFLINE' | 'ONLINE'
enabled?: boolean
orderNo?: string
remark?: string
}
const formOptions: YeeGridFormOptions<QueryValues> = {
schema: [
{
component: 'Input',
componentProps: { allowClear: true, placeholder: '请输入订单号' },
fieldName: 'orderNo',
label: '订单号',
required: true,
},
{
component: 'Textarea',
componentProps: { autoSize: { minRows: 1, maxRows: 3 }, placeholder: '请输入备注' },
fieldName: 'remark',
label: '备注',
span: 2,
},
{
component: 'Switch',
fieldName: 'enabled',
label: '是否启用',
},
{
component: 'Custom',
fieldName: 'channel',
label: '业务渠道',
},
],
}
const gridOptions: YeeGridOptions<{ orderNo: string }, QueryValues> = {
columns: [{ field: 'orderNo', title: '订单号' }],
proxyConfig: {
ajax: {
query: async (
_params: YeeGridQueryParams<{ orderNo: string }>,
values: QueryValues,
): Promise<YeeGridPage<{ orderNo: string }>> => ({
items: [{ orderNo: values.orderNo ?? '-' }],
total: 1,
}),
},
},
}
const [OrderGrid] = useYeeGrid({ formOptions, gridOptions })
</script>
<template>
<OrderGrid>
<template #form-channel="{ field, setValue, value, values }">
<button type="button" @click="setValue(value === 'ONLINE' ? 'OFFLINE' : 'ONLINE')">
{{ field.label }}:{{ values.channel ?? '未选择' }}
</button>
</template>
</OrderGrid>
</template>查询字段插槽使用 form-${fieldName} 命名,可以覆盖任意内置组件;只使用自定义内容时,将 component 设置为 Custom。插槽提供 field、value、values 和 setValue,其中字段值会根据 QueryValues 自动提示类型,并统一写回 gridApi.formApi。
gridApi.formApi 提供 getFieldValue、getValues、setFieldValue、setValues、resetForm 和 updateSchema;字段名和值会保持 QueryValues 中声明的类型。
例如,字段已经存在时可以只更新它的布局:
gridApi.formApi.updateSchema([
{ fieldName: 'remark', span: 3 },
])YeePage 与 VXE Grid
YeePage auto-content-height 会使用宿主布局提供的 --yee-content-height 作为可选内容区高度;未注入时按 Page 父容器的 100% 计算,适合 Wujie 等嵌入场景。heightOffset 会从 Page 的整体高度中扣除宿主额外占用的空间。宿主如果需要注入该变量,可使用导出的 CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT 常量。
YeeGrid 在普通 YeePage 或未使用 YeePage 时按表格内容自然撑高;放在开启 auto-content-height 的 YeePage 中时会自动占满查询表单之后的剩余高度,工具栏和分页器固定在可用区域内,数据超出后由表格内部滚动。表头和单元格内容超出时默认省略并提供 Tooltip;列宽默认允许拖拽调整,可通过 gridOptions.columnConfig.resizable: false 关闭。显式传入 gridOptions.height 时仍以该配置为准。
style.css 提供公共主题和组件样式;使用 YeeGrid 或 useYeeGrid 时,组件库会在运行时自动加载内部 VXE Grid 样式。
<script setup lang="ts">
import type { YeeGridFormOptions, YeeGridOptions } from '@yeepay/yee-boss-ui'
import { useYeeGrid, YeePage } from '@yeepay/yee-boss-ui'
interface OrderItem {
orderNo: string
}
interface QueryValues {
orderNo?: string
}
const formOptions: YeeGridFormOptions<QueryValues> = {
schema: [
{
component: 'Input',
componentProps: { allowClear: true },
fieldName: 'orderNo',
label: '订单号',
},
],
}
const gridOptions: YeeGridOptions<OrderItem, QueryValues> = {
columns: [
{
field: 'orderNo',
title: '订单号',
slots: { default: 'orderNo' },
},
],
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return getOrderPage({
...formValues,
pageNo: page.currentPage,
pageSize: page.pageSize,
})
},
},
},
}
const [YeeGrid, gridApi] = useYeeGrid({
formOptions,
gridOptions,
})
function openDetail(row: OrderItem): void {
console.info(row.orderNo)
}
function createOrder(): void {
console.info('create order')
}
defineExpose({ reload: gridApi.reload })
</script>
<template>
<yee-page auto-content-height>
<yee-grid table-title="订单明细">
<template #orderNo="{ row }">
<a-button type="link" @click="openDetail(row)">
{{ row.orderNo }}
</a-button>
</template>
<template #toolbar-tools>
<a-button type="primary" @click="createOrder">
新增
</a-button>
</template>
</yee-grid>
</yee-page>
</template>table-title、toolbar-actions、toolbar-tools 以及列配置中的具名插槽都有类型提示,其中列插槽的 row 会保持 OrderItem 类型。YeeGridApi 提供 query、reload、setGridOptions、setLoading、setState 和 toggleSearchForm;需要隐藏查询区时,可在 useYeeGrid 配置中设置 showSearchForm: false,不再提供工具栏显隐按钮。
组件的 TypeScript 导出使用 YeeGrid(由 useYeeGrid 返回)和 YeePage,Vue 模板标签对应 yee-grid、yee-page。查询区只通过 YeeGrid 的 formOptions 配置,不再提供独立查询表单组件。查询接口统一返回 { items, total }。
开发
pnpm install
pnpm check
pnpm pack --dry-run组件演示
在线预览:Yee Boss UI 组件演示
本地演示入口覆盖当前公共组件与 light/dark 主题切换:
pnpm example执行 pnpm build:example 可验证演示入口的生产构建。演示产物独立部署,不会打入 npm 包。
