@kayak-sj/design
v1.0.2
Published
PC 组件库(Vue3 + Element Plus,对应旧 @kayak/kayak-design-vue2)
Readme
@kayak-sj/design
PC UI 组件库,基于 Vue 3 + Element Plus,提供管理台通用表单、表格、弹窗、查询面板等二次封装。
对应旧工程 @kayak/kayak-design-vue2(Vue 2 + Element UI)。
定位
| 该放 | 不该放 |
| -------------------------------------------------- | --------------------------------------- |
| KButton、KForm、KTable、KDialog 等 UI 组件 | 业务页面(权益预算、ROI 分析) |
| useFormSchema 等表单 composable | 接口请求、store(→ @kayak-sj/http) |
| 组件样式、props/emits 定义 | 权限判断逻辑(→ @kayak-sj/cms-base) |
| Element Plus 二次封装 | 路由、布局框架(→ @kayak-sj/cms-base) |
| 组件文档、单元测试 | 系统管理领域逻辑(→ @kayak-sj/cms-base) |
判断口诀: 换一套业务(会员台)还能原样复用 → design;绑定了「权益」业务语义 → 不放这里。
详见 包职责约定。
技术栈
- Vue 3 + TypeScript(Composition API +
<script setup>) - Element Plus(peer dependency)
- @vueuse/core、dayjs、lodash-es、sortablejs、echarts
目录结构
design/
├── README.md
├── package.json
├── vitest.config.ts
└── src/
├── index.ts # 统一导出 + setupDesign
├── components/
│ ├── index.ts # 组件聚合导出
│ ├── k-button/ # KButton
│ ├── k-dialog/ # KDialog
│ ├── k-form/ # KForm + formComponent 子控件
│ ├── k-handle-page/ # KHandlePage 维护页布局
│ ├── k-link-button/ # KLinkButton 操作列按钮组
│ ├── k-pagination/ # KPagination
│ ├── k-panel/ # KPanel
│ ├── k-panel-query/ # KPanelQuery 查询面板
│ └── k-table/ # KTable + composables
├── styles/
│ ├── index.scss # 全局样式入口 @kayak-sj/design/styles
│ ├── tokens/ # 设计令牌(颜色等)
│ ├── element-plus/ # EP 主题与覆盖
│ ├── base/ # 工具类
│ └── components/ # 各组件 SCSS
└── utils/
└── createStubComponent.ts子路径导出
| 路径 | 说明 |
| --------------------------------------- | ------------------------------------------ |
| @kayak-sj/design | 主入口(组件、composables、类型) |
| @kayak-sj/design/styles | 全局样式(须在 element-plus CSS 之后引入) |
| @kayak-sj/design/styles/tokens | SCSS 设计令牌 |
| @kayak-sj/design/components | 组件独立导出 |
快速开始
1. 安装依赖
{
"dependencies": {
"@kayak-sj/design": "workspace:*",
"element-plus": "^2.9.0",
"vue": "^3.5.0"
}
}2. 引入样式
在应用入口(main.ts)中,先引入 Element Plus 样式,再引入 design 样式:
import 'element-plus/dist/index.css'
import '@kayak-sj/design/styles'3. 按需引用组件(推荐)
<script setup lang="ts">
import { KTable, KLinkButton, type KTableInstance } from '@kayak-sj/design'
import { useTemplateRef } from 'vue'
const tableRef = useTemplateRef<KTableInstance>('tableRef')
</script>
<template>
<KTable ref="tableRef" :action="fetchList" :columns="columns" />
</template>4. 全局注册(可选)
import { setupDesign } from '@kayak-sj/design'
setupDesign(app)组件一览
已迁移
| 组件 | 说明 | 旧工程对应 |
| ------------- | -------------------------------------- | ----------------------- |
| KTable | 声明式列表 + 查询 + 分页 + action 请求 | k-table |
| KForm | 声明式表单,支持 v-model | k-form |
| KPanelQuery | 查询面板(内嵌于 KTable 或独立使用) | k-panel-query |
| KDialog | 弹窗封装 | k-dialog |
| KHandlePage | 维护页布局(内容区 + 底部操作栏) | has-footer + KAffix |
| KLinkButton | 表格操作列按钮组 | LinkButton |
| KButton | 按钮封装 | k-button |
| KPagination | 分页 | k-pagination |
| KPanel | 面板容器 | k-panel |
| KCard | 卡片容器(ElCard 薄封装) | el-card / k-card |
| KUpload | 文件/图片上传、模板下载、裁剪预览 | k-upload |
迁移占位(Stub)
以下组件已导出但为占位实现,调用时会在控制台警告,待后续迁移:
KAffix · KBreadcrumb · KCascader · KColorPicker · KEcharts
KTable 列表页
KTable 采用 声明式 columns + composables 编排,对齐旧版能力并适配 Vue 3:
<script setup lang="ts">
import { KLinkButton, KTable, type KTableInstance, type KTablePageOptions } from '@kayak-sj/design'
import { useTemplateRef } from 'vue'
import { useDictServiceProvider } from '@/composables/useDictServiceProvider'
useDictServiceProvider()
const tableRef = useTemplateRef<KTableInstance>('tableRef')
const tableOptions: KTablePageOptions = {
columns: [
{ prop: 'role_name', label: '角色名称' },
{ prop: 'status', label: '状态', dict: 'status' },
{ type: 'operates', label: '操作', width: 200 }
],
query: {
form: {
formConfigs: [{ id: 'role_name', label: '角色名称', component: 'Input' }]
}
},
tableKey: 'permission-role',
enableColumnCache: true
}
function rowOperates(row: Record<string, unknown>) {
return [{ label: '编辑', onClick: () => openEdit(row) }]
}
</script>
<template>
<KTable ref="tableRef" :action="FETCH_ROLE" v-bind="tableOptions" add-button="新建角色" @add="openAdd">
<template #operates="{ row }">
<KLinkButton :data="rowOperates(row)" />
</template>
</KTable>
</template>常用能力:
- action 模式:传入接口函数,自动合并查询参数与分页
- 列格式化:
dict、showType(date / amount / phone)、自定义formatter - 列显隐缓存:
enableColumnCache+tableKey - 树表懒加载:
load+TreeLoadHandler - 行拖拽排序:
sortprop - 多选 / 单选:
selectionShow、columns[].type: 'radio'
Expose:getTableData / getQueryNode / getCheckedRows / refresh 等(见 KTableInstance)。
页面侧 composable
| 导出 | 用途 |
| ---- | ---- |
| useKTableTemplateRef | 获取 KTable 实例(refresh / toggleRowSelection 等) |
| useKTableCrossPageSelection | 弹窗列表跨页多选回显(打开弹框灌入已选 → 翻页保留 → data-loaded 回勾) |
| useExportKTableReport | 报表导出(见业务迁移文档) |
弹窗选择器(商户 / 产品 / 信用卡等)不要把草稿选择写进 KTable 内部,用 useKTableCrossPageSelection:
<script setup lang="ts">
import { KDialog, KTable, useKTableCrossPageSelection } from '@kayak-sj/design'
import { shallowRef } from 'vue'
const dialogVisible = shallowRef(false)
const { selection, setSelection, onDataLoaded, onSelectionChange } =
useKTableCrossPageSelection<MerchantRow>({ rowKey: 'merchant_id' })
function openDialog(checked: MerchantRow[]) {
setSelection(checked)
dialogVisible.value = true
}
</script>
<template>
<KDialog v-model:visible="dialogVisible" title="选择" @confirm="() => emit('confirm', selection)">
<KTable
ref="tableRef"
:action="FETCH_LIST"
selection-show
row-key="merchant_id"
@data-loaded="onDataLoaded"
@selection-change="onSelectionChange"
/>
</KDialog>
</template>要点:
- 打开弹框时
setSelection(已确认行);确定时用selection写回业务 model - 模板
ref="tableRef"与 composable 默认tableRefKey对齐(内部useKTableTemplateRef) rowKey支持MaybeRefOrGetter(如卡种/卡片切换主键)mode: 'single'时走setRadioSelection回显;多选为默认- 可选传入外部
tableRef(单测 mock 用)
参考实现:pc-rights 的 ChooseMerchant / ChooseProduct / ChooseCreditCard、useChooseRightsDialog。
KForm 表单
采用 Schema / Model 分离,详见 KForm 架构设计。
L1 查询面板(非受控)
由 KPanelQuery 或 KTable 的 query 配置驱动:
<KPanelQuery :query="{ form: { formConfigs } }" @query="handleQuery" />L2 编辑表单(v-model + schema)
<script setup lang="ts">
import { ref } from 'vue'
import { KForm, useFormSchema, type KFormSchemaField } from '@kayak-sj/design'
interface MenuForm {
menu_type: string
grant: string
perms?: string
}
const form = ref<MenuForm>({ menu_type: 'C', grant: '0' })
const baseFields: KFormSchemaField<MenuForm>[] = [
{ id: 'menu_type', component: 'Radio', label: '菜单类型', dict: 'menu_type' },
{
id: 'perms',
component: 'Input',
label: '权限标识',
visibleWhen: (m) => m.menu_type !== 'M'
}
]
const schema = useFormSchema(baseFields, form)
</script>
<template>
<KForm v-model="form" :form-configs="schema" label-width="120px" />
</template>Expose:validateFields / getFieldsValue / getParams / setFieldsValue / resetForm
支持的 formComponent
Input · Select · SelectMultiple · DatePicker · Radio · Checkbox · InputNumber · Cascader · TreeSelect · Text · InputMixInput · InputMixSelect · RadioInput · Complex · ScrollSelect
KHandlePage 维护页
维护页通用布局,替代各 handle 页重复的 footer 样板代码:
<script setup lang="ts">
import { KForm, KHandlePage, useFormSchema, useHandlePageInit } from '@kayak-sj/design'
const { pageLoading } = useHandlePageInit(loadDetail)
</script>
<template>
<KHandlePage :loading="pageLoading" :on-submit="handleSubmit" @cancel="handleCancel">
<KForm v-model="form" :form-configs="schema" />
</KHandlePage>
</template>useHandlePageInit 包裹 initPage,编辑/详情态自动管理局部 loading。
字典服务 DICT_KEY
design 层不直接依赖旧版 $dict 全局插件。字典翻译通过 inject 解耦:
import { DICT_KEY, type DictService } from '@kayak-sj/design'
const dictService: DictService = {
getLabel: (dictType, value) => labelMap[dictType]?.[String(value)] ?? String(value ?? ''),
getGroupDict: (dictType) => groupMap[dictType] ?? []
}
app.provide(DICT_KEY, dictService)@kayak-sj/cms-base 提供 useDictServiceProvider(),列表页在 setup 中调用即可:
import { useDictServiceProvider } from '@kayak-sj/cms-base/...'
useDictServiceProvider()表格列 dict、查询表单 dict 字段均通过 DICT_KEY 解析。
主要 API
| 导出 | 说明 |
| ------------------- | ------------------------------------------------------------ |
| setupDesign | 全局注册所有组件 |
| useFormSchema | 根据 model 派生表单 schema(联动) |
| useHandlePageInit | 维护页 init loading 管理 |
| DICT_KEY | 字典服务注入 Key |
| KTablePageOptions | 列表页声明式配置类型 |
| KFormSchemaField | 带联动声明的表单字段类型 |
与 cms-base 的边界
| 关注点 | 归属 |
| ------------------------------------ | ----------------- |
| HTTP 请求实现 | @kayak-sj/http |
| 登录态、权限判断、请求提示适配 | @kayak-sj/cms-base |
| 表格/表单渲染、列格式化、字典 inject | @kayak-sj/design |
| 系统管理页面编排、API 封装 | @kayak-sj/cms-base |
- design 组件通过
inject(DICT_KEY)获取字典,不 importhasPermission - 权限控制的新建按钮、操作列请在页面层判断后传入 props
- 不要在 design 中引入 axios、Pinia store 或业务 API
开发
# 类型检查
pnpm --filter @kayak-sj/design typecheck
# 单元测试
pnpm --filter @kayak-sj/design test当前已有 useFormSchema、useKFormModel、useKTableCrossPageSelection、useTableData 等 composable 单测(Vitest)。
依赖关系
apps(pc-rights 等)
└─ @kayak-sj/cms-base
└─ @kayak-sj/design ← 本包| 包 | 关系 |
| ----------------- | ---------------------------- |
| @kayak-sj/cms-base | 主要消费方,列表/表单/维护页 |
| @kayak-sj/http | 与 design 解耦,不互相依赖 |
| @kayak-sj/utils | 纯工具,design 可按需引用 |
发版
当前为 workspace 内部包(private: true)。验证通过后可:
- 移除
private: true - 发布到私服(对应旧
@kayak/kayak-design-vue2的 Vue3 版本)
