@qynpm/ui
v1.0.34
Published
起印公共 UI 组件包。
Readme
@qynpm/ui
起印体系内抽离出来的 Vue 3 UI 组件包,当前主要包含基础输入组件、选择器组件、区块容器、底部操作栏,以及上传、弹窗、备注图文等通用能力。
录单拆包的 transform 实际代码盘点见 录单拆包迁移总览。其中记录了 QyImagePreview、凭证上传模式、设计文件管理等后续 UI 缺口。QyRichTextEditor 属于录单追加信息的独立业务组件,不进入当前全局 UI 基础包。
安装
pnpm add @qynpm/uiPeer Dependencies:
vue >= 3.4element-plus >= 2.0@element-plus/icons-vue >= 2.0@vueuse/core >= 10.0clsx >= 2.0
注册
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import QyUI from '@qynpm/ui'
const app = createApp(App)
app.use(ElementPlus)
app.use(QyUI)也可以按需引入:
import {
QyUpload,
QyFilePicker,
QySwitch,
QyTag,
QyBadge,
QyCascader,
QyTooltip,
QyPopover,
QyPopconfirm,
QySkeleton,
QySkeletonItem,
QyLoading,
QyTabs,
QyTabPane,
QySteps,
QyStep,
QyIcon,
QyIconPicker,
QyColorPicker,
QyWatermark,
QyLink,
QyIframe,
QyDivider,
QyEmpty,
QyScrollbar,
QyProgress,
QyImage,
QyImagePreview,
QyImageUpload,
QyDialog,
QyButton,
QyDropdown,
QyRemarkImage,
QyInput,
QyInputNumber,
QyMoneyInput,
QyDatePicker,
QyCheckbox,
QyRadio,
QyAutocomplete,
QyMenuActions,
QyTableColumnSettings,
QyRadioGroup,
QySelect,
QyTreeSelect,
QyRemoteSelect,
QySelectionDialog,
QyFormSection,
QyActionBar,
QySearchInput,
QyRecordQuoteSearch
} from '@qynpm/ui'也可以按需引入单个组件入口:
import QyInput from '@qynpm/ui/qy-input'
import QyMoneyInput from '@qynpm/ui/qy-money-input'
import QyRadioGroup from '@qynpm/ui/qy-radio-group'
import QySelect from '@qynpm/ui/qy-select'
import QyTreeSelect from '@qynpm/ui/qy-tree-select'
import QyRemoteSelect from '@qynpm/ui/qy-remote-select'
import QySelectionDialog from '@qynpm/ui/qy-selection-dialog'
import QyFormSection from '@qynpm/ui/qy-form-section'
import QyActionBar from '@qynpm/ui/qy-action-bar'
import QyButton from '@qynpm/ui/qy-button'
import QyDropdown from '@qynpm/ui/qy-dropdown'
import QyIconPicker from '@qynpm/ui/qy-icon-picker'
import QyColorPicker from '@qynpm/ui/qy-color-picker'
import QySearchInput from '@qynpm/ui/qy-search-input'
import QyRecordQuoteSearch from '@qynpm/ui/qy-record-quote-search'
import QyDialog from '@qynpm/ui/qy-dialog'
import QyUpload from '@qynpm/ui/qy-upload'
import QyFilePicker from '@qynpm/ui/qy-file-picker'
import QySwitch from '@qynpm/ui/qy-switch'
import QyTag from '@qynpm/ui/qy-tag'
import QyBadge from '@qynpm/ui/qy-badge'
import QyCascader from '@qynpm/ui/qy-cascader'
import QyTree from '@qynpm/ui/qy-tree'
import QyTooltip from '@qynpm/ui/qy-tooltip'
import QyPopover from '@qynpm/ui/qy-popover'
import QyPopconfirm from '@qynpm/ui/qy-popconfirm'
import QyWatermark from '@qynpm/ui/qy-watermark'
import QySkeleton from '@qynpm/ui/qy-skeleton'
import QySkeletonItem from '@qynpm/ui/qy-skeleton-item'
import QyLoading, { openQyLoading } from '@qynpm/ui/qy-loading'
import QyTabs from '@qynpm/ui/qy-tabs'
import QyTabPane from '@qynpm/ui/qy-tab-pane'
import QySteps from '@qynpm/ui/qy-steps'
import QyStep from '@qynpm/ui/qy-step'
import QyLink from '@qynpm/ui/qy-link'
import QyIframe from '@qynpm/ui/qy-iframe'
import QyEmpty from '@qynpm/ui/qy-empty'
import QyScrollbar from '@qynpm/ui/qy-scrollbar'
import QyProgress from '@qynpm/ui/qy-progress'
import QyImage from '@qynpm/ui/qy-image'
import QyImagePreview from '@qynpm/ui/qy-image-preview'
import QyImageUpload from '@qynpm/ui/qy-image-upload'
import QyRemarkImage from '@qynpm/ui/qy-remark-image'
import QyInputNumber from '@qynpm/ui/qy-input-number'
import QyDatePicker from '@qynpm/ui/qy-date-picker'
import QyCheckbox from '@qynpm/ui/qy-checkbox'
import QyRadio from '@qynpm/ui/qy-radio'
import QyAutocomplete from '@qynpm/ui/qy-autocomplete'
import QyMenuActions from '@qynpm/ui/qy-menu-actions'
import QyTableColumnSettings from '@qynpm/ui/qy-table-column-settings'后台公共包必须通过 @qynpm/ui/<子组件> 使用 UI 能力。每个被后台公共包消费的子入口必须是自包含入口,直接组装对应组件并保留该子入口的命名导出,禁止再转发到 ../index.js 加载 UI 根入口。这样可以保证发布包按需加载,也能让本地源码联调稳定解析到唯一物理模块。
本地 file 依赖联调说明:单组件入口的 package.json exports 指向 dist/qy-*/index.mjs 和 dist/qy-*/index.cjs,而 dist 不提交到仓库。消费项目如果通过 file:../qy-components/packages/ui 引用本包,必须先在 qy-components 执行:
pnpm --filter @qynpm/ui build然后在消费项目刷新 file 依赖安装视图,例如:
pnpm install --frozen-lockfile --ignore-scripts不要依赖本机残留的 packages/ui/dist。如果后续希望免 build 联调,应在消费项目显式配置源码 alias,而不是把包 export 指向未发布源码。
组件说明
QyEmpty
QyEmpty 使用 Element Plus ElEmpty 渲染只读空状态,支持 image、imageSize 和 description 属性,以及 image、description、默认三个原生插槽。组件不判断数据,不内置加载、错误、重试或按钮点击逻辑。
<QyEmpty description="暂无记录">
<template #image><img src="/empty.svg" alt="暂无记录" /></template>
<template #default>
<QyButton @click="createRecord">新建记录</QyButton>
</template>
</QyEmpty>imageSize 直接沿用 Element Plus 的数值语义;image / description 插槽遵循 Element Plus 的属性优先级。class、style、id、title、role、data-* 和 aria-* 安全透传到根节点,消费者传入的宽度、高度、margin、padding 均可覆盖,事件监听器和底层实例不对外暴露。
QyScrollbar
QyScrollbar 以 Element Plus 2.14.3 的 ElScrollbar 作为唯一滚动实现。默认插槽直接承载滚动内容,支持固定高度、最大高度、纵向/横向滚动、原生/自定义滚动模式、always、滚动层样式与类名,以及 tabindex、id、role、ariaLabel 和 ariaOrientation。
<QyScrollbar height="240px" max-height="360px" always aria-label="消息列表">
<div v-for="message in messages" :key="message.id">{{ message.text }}</div>
</QyScrollbar>
<QyScrollbar
height="160px"
:distance="12"
@scroll="handleScroll"
@end-reached="loadMore"
>
<div class="wide-content">横向内容由局部容器滚动</div>
</QyScrollbar>scroll 只返回只读快照 { scrollTop, scrollLeft };end-reached 返回 top、bottom、left 或 right,阈值由 Element Plus 的 distance 处理。实例只公开 scrollTo(x, y)、scrollTo(options)、setScrollTop(value)、setScrollLeft(value) 和 update(),挂载前或卸载后调用均为空操作。不显式暴露内部 ElScrollbar 实例、wrapRef、handleScroll 或 DOM ref;Vue 公共实例自带的 $el 不属于适配器承诺的稳定 API,消费者不得依赖。
根节点仅安全透传 class、style、title、data-* 和不冲突的 aria-*;旧的 width、vertical 属性及未知 on* 监听器会被过滤。自定义模式只映射 --el-scrollbar-bg-color / --el-scrollbar-hover-bg-color 到 admin-theme 的 --qy-scrollbar-thumb / --qy-scrollbar-thumb-hover,不覆盖 Element Plus 滑块尺寸、定位、圆角、显隐或动画;原生模式继续使用 admin-theme 的全局原生滚动条规则。
QyCascader
QyCascader 是 Element Plus Cascader 的受控适配器,保留单值、路径值和多选值的原始形状,不自动改写业务 options。支持 expandTrigger、multiple、checkStrictly、emitPath、lazyLoad、过滤、折叠标签、浮层定位、虚拟滚动及键盘操作。
节点默认插槽、suggestion-item、过滤、懒加载以及 disabled / leaf 回调接收只读的 QyCascaderNodeSnapshot,不会泄漏 Element Plus Node 或组件实例。组件仅暴露 focus()、blur()、open()、close() 四个方法。
<QyCascader
v-model="value"
:options="options"
:props="{ emitPath: true, checkStrictly: true }"
filterable
placeholder="选择地区"
/>完整交互 playground 覆盖基础/路径、严格检查、多选折叠、过滤、懒加载、禁用、插槽、键盘、浮层和移动端 40px 触控目标。
QyTree
QyTree 以 Element Plus ElTree 作为唯一的树结构、勾选/半选、键盘、懒加载和拖拽实现。data 始终是业务唯一数据权威;节点回调、事件、默认插槽和 QyTreeRef.getNode() 都返回当前只读 Qy 快照,不泄漏 Element Node、Store、组件实例或私有 DOM。
<QyTree
ref="treeRef"
:data="departments"
node-key="id"
show-checkbox
default-expand-all
:filter-node-method="(value, data, node) => node.label.includes(value)"
@check="(_, info) => console.log(info.checkedKeys, info.halfCheckedKeys)"
/>支持字段映射、展开/当前节点、联动和严格勾选、筛选、懒加载、拖拽、自定义节点插槽及本地键盘导航。公开引用仅包含筛选、当前节点、节点路径、勾选/半选读取与设置方法;不提供 append、remove、insert 或 updateKeyChildren 等内部数据修改方法。键值方法需要 nodeKey;未提供筛选函数时调用 filter() 会抛出 QyTree 自有错误。
QyFilePicker
单文件选择组件,支持点击选择、拖拽、替换、清空以及后缀和大小校验。
v-model只输出浏览器原始File | null。extensions忽略大小写,可传xls或.xlsx。maxSizeMb的边界值允许,默认 20MB。- 校验失败触发
invalid,并保留原有modelValue。 - 组件只选择本地文件,不发送请求、不读取 token、不生成或保存 URL,也不复用
QyUpload的业务上传协议。
<QyFilePicker
v-model="file"
:extensions="['xls', 'xlsx']"
:max-size-mb="20"
placeholder="选择或拖拽 Excel 文件"
@invalid="handleInvalid"
/>QySwitch
受控开关组件,modelValue、activeValue、inactiveValue 和 update:modelValue / change payload 均为 boolean | string | number,不进行隐式布尔化。
<QySwitch
v-model="enabled"
active-text="启用"
inactive-text="停用"
@change="handleChange"
/>支持 disabled、loading、size、inlinePrompt、name、id、tabindex 和 ariaLabel,以及 class、style、role、title、data-*、其它 aria-* 安全属性。只发出 update:modelValue、change、focus、blur;不提供 beforeChange、input、图标/slot 或底层实例、focus/blur 等命令式方法。
QyTag
通用紧凑标签,内部使用 ElTag,公开 tone、size、effect、closable、round、disabled 和默认 slot。tone 支持 neutral、primary、success、warning、danger、info;size 为 sm、md、lg,默认分别映射为 Element Plus 的 small、default、large。
<QyTag tone="success" size="sm" closable @close="removeTag">
已完成
</QyTag>disabled 会设置 aria-disabled="true"、隐藏关闭按钮并抑制 click / close;非禁用状态下这两个事件各发出一次 MouseEvent。支持 class、style、title、role、data-*、aria-* 安全属性,不公开 color、hit、disableTransitions 或底层 Element Plus 实例。QyTag 不是按钮,不新增键盘按钮语义;需要动作语义时使用 QyButton。
QyBadge
QyBadge 基于 Element Plus ElBadge 渲染只读徽标,不管理未读状态、通知或点击逻辑。支持数字与文字、数字上限、圆点、隐藏、零值策略、五种语义类型、自定义颜色、偏移以及徽标内部样式和类名。
<QyBadge :value="unreadCount" :max="99" type="danger">
<QyButton>消息</QyButton>
</QyBadge>
<QyBadge value="!" is-dot>
<span>待处理</span>
<template #content="{ value }">数量:{{ value }}</template>
</QyBadge>value 默认为空字符串,max 默认为 99,type 默认为 danger,showZero 默认为 true,offset 默认为 [0, 0]。上限只作用于数字,字符串原样显示;content 插槽收到经过圆点和上限规则处理后的只读 { value: string },组件公开的 content 也只有最终显示内容,不暴露 Element Plus 实例。根节点安全透传 class、style、id、role、title、data-* 和 aria-*,不声明事件、v-model 或尺寸参数。
QyTooltip
只读提示组件。默认插槽是唯一触发内容,不增加触发元素包装 DOM;content 插槽(content slot)优先于 content 属性。组件支持 hover / focus、完整 placement、light / dark effect、延迟、偏移和可收缩的 maxWidth。
<QyTooltip content="说明文字" placement="top-start">
<button type="button">查看提示</button>
</QyTooltip>
<QyTooltip content="属性内容">
<button type="button">插槽内容</button>
<template #content>只读插槽内容</template>
</QyTooltip>默认值为 placement="top"、effect="dark"、disabled=false、showDelay=0、hideDelay=200、offset=12、maxWidth=320。事件只有 open / close,且只在实际可见状态转换时各发一次;不公开 trigger、受控 open、raw HTML、交互内容、Element Plus attrs/listeners 或实例方法。
QyPopover
交互浮层组件。reference 插槽是触发元素,默认插槽是可交互面板内容,不增加触发元素包装 DOM;支持 click、hover、focus、contextmenu、完整 placement、light / dark effect、数字或 CSS 字符串 width、延迟、偏移、箭头和窄视口收缩。
<QyPopover placement="bottom-start" :width="280" @open-change="onOpenChange">
<template #reference><button type="button">打开操作</button></template>
<button type="button" @click="submit">确认</button>
</QyPopover>
<QyPopover v-model:open="open" trigger="contextmenu">
<template #reference><div>右键区域</div></template>
<div>受控交互内容</div>
</QyPopover>默认值为 trigger="click"、placement="bottom"、effect="light"、width=240、offset=12、showDelay=0、hideDelay=200、showArrow=true、disabled=false。open 未传时组件内部管理状态,传入时通过 v-model:open 由父级回传决定状态;update:open 是状态意图,open-change / open / close 只在实际显隐转换时发出。唯一暴露的方法是 close(),不公开 Element Plus 实例、定位更新、teleported、appendTo 或 popperOptions。
QyPopconfirm
二次确认浮层,提供 Qy-owned 的确认、取消、加载锁、关闭原因和焦点恢复语义。支持 reference、title、icon 插槽;open 可选受控,确认成功后的父级关闭会以 close('external') 记录,异步失败保持打开并允许重试。
<QyPopconfirm
title="确认删除?"
variant="danger"
confirm-type="danger"
:confirm-loading="saving"
:close-on-confirm="false"
@confirm="save"
@close="onClose"
>
<template #reference><button type="button">删除</button></template>
</QyPopconfirm>事件为 update:open、open-change、confirm、cancel 和 close(reason);关闭原因是 confirm、cancel、outside、escape、trigger、external 或 disabled。不公开 Element Plus 类型、实例、teleport 或 popper 参数。
QySkeleton 与 QySkeletonItem
加载骨架组件基于 Element Plus 的 ElSkeleton / ElSkeletonItem 渲染。loading 由业务受控,加载期间使用 template 插槽,加载完成后使用默认插槽;支持 animated、count、rows 及数字或对象形式的 throttle。
<QySkeleton :loading="loading" animated :count="2">
<template #template>
<QySkeletonItem variant="rect" style="height: 120px" />
</template>
<div>真实内容</div>
</QySkeleton>QySkeletonItem 的 variant 支持 p、text、h1、h3、caption、button、image、circle、rect,默认值为 text。两者均支持 class、style、role、title、data-* 和 aria-*,不公开 Element Plus 实例。
QyLoading
QyLoading 安装后注册 v-qy-loading 指令,并配置命令式 openQyLoading 服务。指令支持布尔值或 { active, text, maskColor, lockScroll } 对象;服务未传 target 时覆盖 body,传入 HTMLElement 时只覆盖该容器。同一目标的多个句柄共享一个遮罩,最后一个句柄调用 close() 后才移除。
<script setup>
import { ref } from 'vue'
import { openQyLoading } from '@qynpm/ui/qy-loading'
const loading = ref(false)
const target = ref(null)
function load() {
const handle = openQyLoading({ target: target.value, text: '加载中' })
// 请求完成后调用一次即可,重复 close 不会影响后续 loading。
handle.close()
}
</script>
<template>
<div ref="target" v-qy-loading="{ active: loading, lockScroll: true }">
容器内容
</div>
</template>QyDrawer
QyDrawer 使用 Qy-owned props、事件和插槽封装抽屉容器,支持四方向、数字尺寸、遮罩/Escape 关闭原因、指定 teleportTo 容器、焦点恢复和重叠抽屉顶层响应。header、默认内容和 footer 插槽均接收 Qy-owned close()。
<script setup>
import { ref } from 'vue'
import QyDrawer from '@qynpm/ui/qy-drawer'
const open = ref(false)
</script>
<template>
<button type="button" @click="open = true">查看订单</button>
<QyDrawer v-model="open" title="订单详情" placement="right" @close="open = false">
<p>抽屉内容</p>
<template #footer="{ close }">
<button type="button" @click="close">关闭</button>
</template>
</QyDrawer>
</template>QyResult
QyResult 统一展示成功、警告、信息、错误和 401/403/404/500 结果。它只负责结果视觉、标题、说明、图片和操作区,不负责路由、返回、刷新、重试、请求或状态管理;业务动作由消费者放入 actions 插槽。
<script setup>
import QyResult from '@qynpm/ui/qy-result'
</script>
<template>
<QyResult status="404" title="页面不存在" description="请检查地址">
<template #actions>
<button type="button" @click="goHome">返回首页</button>
</template>
</QyResult>
</template>visual 插槽优先于 image,image 优先于状态内置视觉;title 同时作为结果标题和根节点原生 title,description 与 actions 也支持同名插槽覆盖属性内容。根入口与 @qynpm/ui/qy-result 均提供 Qy-owned 类型,不公开 Element Plus 类型、实例或事件。
QyWatermark
QyWatermark 在容器内提供 Qy-owned 的平铺文字或图片水印,图片优先且加载失败回退到 content;content 支持多行文字,font、rotate、width、height、gap 和 offset 控制水印单元。水印覆盖层不接收指针事件,默认插槽中的按钮、图片和选择操作仍由消费者处理。
<script setup>
import QyWatermark from '@qynpm/ui/qy-watermark'
</script>
<template>
<QyWatermark :content="['客户名称', '订单编号']" :rotate="-18">
<div>订单内容</div>
</QyWatermark>
</template>公开 interface 只包含 content、image、zIndex、rotate、width、height、gap、offset 和 Qy-owned font,以及安全容器属性 class、style、id、title、role、data-*、aria-*;不公开 Element Plus props、实例、DOM、事件或 expose。完整演示位于 src/docs/QyWatermarkDoc.vue,入口为 @qynpm/ui/qy-watermark。
QyTabs 与 QyTabPane
QyTabs 使用 Element Plus 的 ElTabs / ElTabPane 处理渲染、焦点与键盘行为。modelValue 传入时保持受控;defaultValue 仅提供初始选择。tab-change、tab-remove、tab-add 与 edit 都是意图事件,不会改写业务页签数组、路由或 AdminShell 运行时。
<QyTabs v-model="current" editable @tab-add="addTab" @tab-remove="removeTab">
<QyTabPane label="概览" name="overview">概览内容</QyTabPane>
<QyTabPane label="详情" :name="2" lazy closable>详情内容</QyTabPane>
</QyTabs>支持 type('' | 'card' | 'border-card')、tabPosition、stretch、closable、addable、editable、beforeLeave和tabindex。QyTabPane支持label、字符串或数字 name、closable、disabled、lazy与label 插槽。tab-click的首个参数是冻结的QyTabPaneSnapshot,只包含 name、label、disabled、closable、lazy、active、index`,不会泄漏 Element Plus 页签上下文或实例。
QyDescriptions 与 QyDescriptionsItem
QyDescriptions 使用 Element Plus 的 ElDescriptions / ElDescriptionsItem 处理描述列表的行列和单元格渲染。QyDescriptionsItem 是无 DOM 声明节点,父组件会递归处理直接子项、v-if、v-for 和 template/Fragment,再交给 Element Plus 完成 span、rowspan、宽度和对齐计算。
<QyDescriptions border :column="3" title="订单详情">
<template #extra><QyLink href="/orders">查看全部</QyLink></template>
<QyDescriptionsItem label="编号">SO-2026-001</QyDescriptionsItem>
<QyDescriptionsItem label="摘要" :span="2" :rowspan="2">复杂内容也可放入默认插槽。</QyDescriptionsItem>
<QyDescriptionsItem label="金额" align="right">¥12,800.00</QyDescriptionsItem>
</QyDescriptions>父级支持 border、column、direction、size('' | 'large' | 'default' | 'small')、title、extra、labelWidth 以及默认、title、extra 插槽;插槽优先于同名文本属性。描述项支持 label、span、rowspan、width、minWidth、labelWidth、align、labelAlign、className、labelClassName 以及默认和 label 插槽。组件只安全透传根节点的 class、style、id、title、role、data-*、aria-*,不公开事件、状态或 Element Plus 实例。
QyUpload
上传组件,支持:
- 图片上传
- 视频上传
- 粘贴上传
- 拖拽上传
- 文本备注
- 预览
v-modelvalueFormat- 纯图片模式
当前可配置项里,和宿主耦合最强的是上传配置:
apitokenKeynameuploadRequest
其中:
api为必填tokenKey不传时默认使用Admin-Tokenname不传时默认使用fileuploadRequest可接入宿主项目自己的 request、baseURL、鉴权和错误处理;不传时使用组件默认 XHR 上传
值协议:
- 默认
valueFormat="object",v-model输出{ files: string[], text: string } valueFormat="array",v-model输出string[]valueFormat="string",v-model输出逗号分隔 URLshowText=false可隐藏文字备注区;如仍需粘贴入口,可开启showPasteInputdefaultValue仍保留给老页面做非受控初始化;新页面建议优先使用v-model
QyImageUpload
图片/文件字段上传组件,基于 QyUpload 的薄封装,适合商品图、设计图、成本附图、凭证图片集合、单视频字段等场景。
固定协议:
- 默认
multiple=false,v-model为单个 URL 字符串 multiple=true时,v-model为 URL 数组- 单图默认
limit=1,多图默认limit=9 - 默认
name="files" - 默认
showText=false - 默认开启纯粘贴输入条
- 默认
size="128px" - 支持
supportVideo、fileType、fileSize - 支持透传
uploadRequest,用于接入宿主项目上传请求
凭证上传模式:
mode="voucher"或voucher可启用凭证图片场景。- 默认允许
jpg / jpeg / png / gif / bmp / webp。 - 默认
limit=30,保留纯粘贴入口,适合收款凭证、成本附图等旧CopyImgUpload使用点。 - 暴露
getFullPathList()和fullPathList,兼容旧页面ref.fullPathList.join(',')的读值方式。 - 上传成功时额外触发
updateImg(url),兼容旧CopyImgUpload的成功通知语义。
QyImageUpload 不保留旧 imageUpload 的 successUpload(fileList) 回调。旧页面迁移时优先改为 v-model;确实依赖旧 CopyImgUpload 读值方式的页面,可先用凭证模式的 fullPathList / getFullPathList() 过渡。
QyImage
图片展示组件,计划承接旧 packages/qiyin/components/Image,并作为 @qynpm/table-schema 的 image 列渲染底座。
这是后续替代旧 useCol 的前置组件。旧 useCol/render/imageRender.vue 的表格列渲染不应该在 @qynpm/table-schema 内重复维护一套通用图片组件,而应组合 QyImage。
必须完整覆盖旧能力:
src图片地址。width / height / size缩略图尺寸。fit图片填充方式,透传ElImage。- 加载中状态。
- 加载失败状态。
error事件。placeholder空图占位。compressor压缩缩略图参数。- 旧域名兼容:
ys.diansan.com地址迁移为https://admin.qiyinbz.com。 - 旧缩略图参数:点三域名使用
?small=true&style=image/resize,h_{h},w_{w},其他域名使用?w={w}。 - 失败兜底:缩略图请求失败后请求原图 blob,并用浏览器 canvas 压缩成 dataURL 展示。
preview / previewList / initialIndex图片预览。
失败兜底属于旧公共图片组件能力,不是录单业务逻辑。当前实现不新增 compressorjs,使用浏览器原生 fetch + blob + canvas 完成压缩;如果后续发现必须完全等价旧 compressorjs 的压缩细节,再单独评估是否新增依赖。
建议新组件 API:
type QyImageProps = {
src?: string
width?: number | string
height?: number | string
size?: number
fit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
placeholder?: boolean
compressor?: boolean | {
w?: number
h?: number
q?: number
}
preview?: boolean
previewList?: string[]
initialIndex?: number
}表格 image 列后续直接组合 QyImage 和 QyImagePreview,不要在 @qynpm/table-schema 里复制一份通用图片组件。
已完成:
- 创建
@qiyin/UI/qy-image入口。 - 创建
src/docs/QyImageDoc.vueplayground 文档。 - 补
index.js / index.d.ts / package.json exports / files。 - 补构建入口,确保
@qynpm/ui/qy-image可按需引入。 - 补失败压缩兜底,不新增
compressorjs。 - 补
preview接入,可点击打开QyImagePreview。
QyImagePreview
图片预览组件,承接旧 useCol/render/imageRender.vue 中动态创建 ElImageViewer 的逻辑。
需要支持:
urlList图片列表。initialIndex初始预览索引。- 打开时控制
body.style.overflow = 'hidden'。 - 关闭时恢复 body 滚动并销毁实例。
- 支持直接作为组件使用,也支持函数式调用,例如
openQyImagePreview({ urlList, initialIndex })。
该能力是 @qynpm/table-schema 完整迁移 image 列的前置能力。table-schema 不应该长期维护一份独立图片预览逻辑。
QyCheckbox
受控复选框基础组件,基于 ElCheckbox 做隔离适配,modelValue、trueValue、falseValue 和事件 payload 均保持 boolean | string | number 原始类型,不做隐式布尔转换。
支持 value(为后续 QyCheckboxGroup 保留的选项值)、只读展示状态 indeterminate、disabled、size 和默认 slot。事件只有 update:modelValue、change、focus、blur;禁用状态不产生值变更意图,也不公开 Element Plus 实例或命令式方法。
<QyCheckbox
v-model="ticketValue"
true-value="Y"
false-value="N"
aria-label="开票"
@change="handleChange"
>
开票
</QyCheckbox>class、style、id、name、role、title、data-* 和 aria-* 会安全透传;桌面交互区域保持紧凑,窄屏下提升到至少 40px。QyCheckboxGroup、全选/级联和 CheckboxButton 不属于 V1。
QyRadio
受控单选框基础组件,基于 ElRadio 做隔离适配。value 表示选中值,label 只负责展示;默认 slot 优先。modelValue 与事件 payload 保持 boolean | string | number 原始类型,父级不回写时组件不会自行改变状态,也不会通过再次点击取消选中。
支持 disabled、size、name、border 以及 update:modelValue、change、focus、blur 事件;安全透传 class、style、role、title、data-* 和 aria-*,不公开 Element Plus 实例或命令式方法。
<QyRadio
v-model="ticketType"
value="invoice"
label="开票"
name="ticket-type"
border
@change="handleChange"
/>QyCheckboxGroup
受控复选框组,统一管理 QyCheckboxValue[]。支持默认 slot 或 options(两者同时存在时 slot 优先),并透传 disabled、min、max、size。事件只有 update:modelValue 和 change,每次更新都返回新数组。
<QyCheckboxGroup v-model="selected" :options="[
{ label: '阅读', value: 'read' },
{ label: '写作', value: 'write', disabled: true }
]" />options 模式物理复用 QyCheckbox;不提供 CheckboxButton、全选、indeterminate 推导、树形/层级选择、布局 props 或 Element Plus 实例。
QyAutocomplete
自动完成输入基础组件,基于 ElAutocomplete 做薄封装,支持本地 options 过滤和 Element Plus 原生 fetchSuggestions 透传。
默认协议:
v-model绑定输入值。options支持{ value }列表,也支持通过valueKey映射其它字段。- 默认按
option.value.includes(query)过滤。 - 支持
select / change / input / clear / focus / blur事件。
该组件作为 @qynpm/table-schema 的 edit-autocomplete 渲染器底座,表格 row 写回逻辑仍留在 table-schema。
QyMenuActions
表格行操作 UI 底座,承接旧 useCol/render/menu.vue 的通用展示能力。
支持:
inline横向操作模式。popover更多操作模式。actions列表。hidden / disabled / loading支持布尔值或按行上下文计算。type控制默认、主色、成功、警告、危险操作色。confirmMethod(action, context)由宿主注入确认逻辑。action(action, context)只在可执行且确认通过后触发。
QyMenuActions 不内置权限、路由、缓存、弹窗实例、消息提示,也不直接执行业务方法。权限过滤和业务执行应留在 @qynpm/table-schema adapter 或宿主页面。
QyTableColumnSettings
表格列设置 UI 底座,承接旧 useCol/render/setup.vue 的通用 UI 能力。
支持:
- 列列表展示。
- 当前选中列。
- 置顶、置底、上移、下移。
- 固定左侧、固定右侧、取消固定。
- 列宽编辑。
- 显隐开关。
- 恢复默认。
- 删除缓存。
业务缓存读写仍由 @qynpm/table-schema 的 settings adapter 或宿主 adapter 负责,UI 组件只负责展示和编辑 settings。
编辑类基础组件(待评估)
旧 useCol 的 edit-input / edit-autocomplete 有一套“展示态 -> 点击编辑 -> blur/enter 写回”的通用交互。后续有两种选择:
- 如果表格外也需要该交互,补
QyInlineEditInput和QyAutocompleteInput。 - 如果只服务表格列,先放在
@qynpm/table-schemarenderer 内部,避免 UI 包过早扩大。
QyDialog
QyDialog 是受控弹窗组件,使用 Qy-owned 协议:
open/update:open/open-change管理显隐confirm、cancel和close(reason)表达关闭意图;reason为confirm | cancel | header | escape | overlay | externalconfirm-loading在宿主异步业务期间锁定确认动作,业务成功后由宿主关闭- 默认 footer 由
show-footer、show-confirm、show-cancel、confirm-text和cancel-text控制;自定义footer时不渲染默认按钮 - 支持全屏切换、最小化、焦点恢复和顶层弹窗 Escape/遮罩仲裁
<QyDialog
:open="open"
title="订单确认"
:confirm-loading="saving"
:close-on-confirm="false"
@update:open="open = $event"
@confirm="save"
>
内容区域
</QyDialog>旧 modelValue、layout、confirmProps、ok 和 Element Plus attrs 不属于 QyDialog 公共面;过渡场景使用独立的 @qynpm/qy-dialog-legacy adapter,禁止新业务采用。
QyButton
后台按钮统一入口,后续 AdminShell 和业务页按钮迁移时优先使用该组件,而不是直接写原生 button 或分散使用 ElButton。
核心能力:
type="default | primary | success | warning | danger | text | ghost | link"控制视觉类型。size="sm | md | lg"控制高度、padding、gap、字号和图标尺寸,默认md。disabled和loading都会阻止click事件;loading会展示内置 loading 状态。active用于顶部当前系统、tabs 当前操作等选中态。block撑满父容器,round显式开启胶囊圆角;默认后台圆角保持紧凑。native-type="button | submit | reset"控制原生按钮类型,默认button。prefix-icon / suffix-icon只支持 Vue 组件图标;也可以通过prefix / suffix插槽传入前后图标;icon-only必须提供aria-label或title。
样式 token:
- 尺寸类:
--qy-button-sm-height、--qy-button-md-height、--qy-button-lg-height、--qy-button-*-padding-x、--qy-button-*-gap、--qy-button-*-font-size、--qy-button-*-icon-size。 - 状态类:
--qy-button-default-*、--qy-button-primary-*、--qy-button-success-*、--qy-button-warning-*、--qy-button-danger-*、--qy-button-disabled-*、--qy-button-focus-ring。 - token 默认从 Element Plus 的
--el-*设计变量取值,宿主可覆盖--qy-button-*做主题适配。
QyDropdown
后台下拉菜单统一入口,后续 AdminShell、工具栏和更多操作菜单迁移时优先使用该组件,而不是在业务页面散落手写原生 dropdown DOM。
核心能力:
items支持{ key, label, icon, disabled, danger, hidden, divided, active, title }。hidden项不渲染,disabled项不触发select。danger和active项会输出稳定的is-danger/is-activeclass,以及data-danger/data-active标记。trigger="click"为默认触发方式,预留contextmenu。v-model/model-value支持受控打开状态;受控模式下只触发update:modelValue和open-change,不私自决定最终打开状态。close-on-select=false可在选择后保持面板打开。panel-width/width和max-height控制面板尺寸。trigger、item、header、footer、empty插槽用于业务定制展示;trigger插槽应把attrs绑定到实际触发按钮上。
QySelectionDialog
数据选择弹窗底座,参考老 @qiyin/components/Dialog 的核心能力迁移,但不内置任何 ERP 接口或权限逻辑。
适合业务 wrapper 组合:
- 店铺选择
- 人员选择
- 商品选择
- 物料/工艺/属性选择
核心能力:
QyDialog弹窗容器fetchMethod(query)或兼容老query(query)加载表格数据selectionMode="single | multiple | none",兼容老selection="single | multi | none"modelValue管理确认后的已选行,selected兼容老Dialog初始已选行rowKey控制唯一键labelKey控制已选标签展示searchKey、分类、类型筛选- 类型筛选内部使用
QySelect,可通过typeSelectProps透传下拉属性 columns/cols插槽渲染业务列side、search、toolbar插槽扩展业务区域- Element Plus 原生分页
QySelectionDialog 只解决“怎么选”,不解决“选什么”。业务接口、字段映射、权限判断应放在宿主项目 wrapper 中。
迁移业务选择器前,建议先阅读:
选择状态采用“弹窗草稿 + 确认提交”协议:
- 行点击、勾选、删除标签、粘贴只修改弹窗内部草稿,并触发
selection-change(rows)。 - 点击确认后才触发
update:modelValue(rows)、confirm(rows)和兼容老Dialog的submit({ selection, rows, query })。 - 点击取消或关闭会丢弃草稿选择,不会污染宿主表单中的
v-model。 search/handleQuerySearch会重置到第一页并查询;分页切换会按当前条件查询。
主要扩展点:
columns/cols:表格列,cols用于兼容老Dialog。side:左侧业务区域,例如分类树、部门树。search:完全自定义查询区,插槽参数为{ query, search, reset }。toolbar:搜索区右侧操作区,插槽参数为{ query, search }。getTableRef、getTableData、getSelection:用于业务 wrapper 做必要的桥接。
QyRemarkImage
备注图片组件,承接旧 @qiyin/components/remarkImage 的“备注文本 + 单图”协议。
支持:
defaultValue解析[url:图片地址]备注文本defaultImage独立图片默认值change(value, imageUrl)输出完整备注值和图片地址- 粘贴图片上传
- 悬浮预览和删除图片
uploadRequest自定义上传请求layout="select-prefix"可把一个基础下拉值合并成输入前缀,用于“下拉值 + 图文备注”这类旧协议字段deleteImage()/clearContent()/getValue()暴露方法,兼容旧表单重置和读取协议值
默认上传会使用:
api,默认/permission-api/common/uploadstokenKey,默认Admin-TokenuploadName,默认files
如果宿主项目有统一 request、baseURL、鉴权、错误处理或登录失效处理,应优先传入 uploadRequest,不要在组件内部写死项目请求实现。
组合下拉输入:
<QyRemarkImage
layout="select-prefix"
:select-value="postageValue"
:select-options="[
{ label: '包邮', value: '包邮' },
{ label: '不包邮', value: '不包邮' }
]"
placeholder="备注 / 粘贴图片"
type="text"
@update:select-value="postageValue = $event"
@change="handleRemarkChange"
/>QyInput
输入组件,第一版定位为 ElInput 的轻量包装层,重点支持:
- 保持和
ElInput接近的使用方式 removeSpacetrimModeentertextareamodelValue支持string | number | null | undefined,用户编辑事件统一输出string- 兼容
text、textarea、password、search和 Element Plus 常用属性 - 兼容
prefix、suffix、prepend、appendslots - 类型出口包含
QyInputValue、QyInputModelValue、QyInputProps、QyInputEmits和QyInputExposed - 只公开
focus、blur、select、clear、resizeTextarea方法
QyMoneyInput
金额输入组件,第一版定位为 ElInput 的金额录入包装层,重点支持:
- 输入过程清洗非法字符
- 默认失焦格式化为固定小数位
precisionmin / maxallowNegative- 兼容常见 slots 与 expose 方法
QyInputNumber
数字输入组件,第一版定位为 ElInputNumber 的轻量包装层,重点支持:
- 保持
number / null值语义 min / maxstepstep-strictlyprecisioncontrols / controls-positionvalueOnClear(默认null,也支持min / max / number)readonly / disabled / formatter / parser / inputmode / alignprefix / suffix / increase-icon / decrease-iconslotsupdate:modelValue / input / change / focus / blur显式事件- 兼容
focus / blurexpose 方法
金额、费用、税费等财务录入场景请优先使用 QyMoneyInput,不要用 QyInputNumber 做金额格式化。
QyDatePicker
日期选择组件,第一版定位为 ElDatePicker 的轻量包装层,重点支持:
- 保持和
ElDatePicker接近的使用方式 date / datetime / daterange / datetimerange等常见类型format / value-formatdefault-timeshortcutsdisabled-dateyear / month / date / dates / datetime / week / daterange / datetimerange / monthrangeclearable / disabled / readonly / teleported / append-to / popper-classupdate:modelValue / change / clear / calendar-change / panel-change / visible-change / focus / blurdefault / range-separator / prev-month / next-month / prev-year / next-yearslots- 仅公开
focus() / blur() / open() / close()expose 方法
normalize-range-end-time 默认关闭。开启后只处理范围第二项:精确 00:00:00.000 的 Date 克隆为 23:59:59.000,带明确午夜时间的普通/ISO 字符串只替换时间并保留格式及 zone suffix;date-only 字符串、number、非午夜和非范围值保持原引用,不修改父数组或父 Date。
QyRadioGroup
单选组组件,第一版定位为 ElRadioGroup 的轻量包装层,重点支持:
options直出labelKey / valueKey / disabledKey映射- 普通单选 / 按钮式单选
- 选项禁用
option插槽自定义选项文案- 事件保持
update:modelValue、change
option 插槽由外部传入,组件会透出当前选项的:
option:原始选项对象label:映射后的展示文案value:映射后的选项值disabled:是否禁用
普通 options 模式物理复用 QyRadio;type="button" 继续使用内部 Element Plus button adapter。已发布的 getRadioGroupInstance() 仅为兼容保留并标记为 deprecated,不新增实例能力。
QySelect
受控选择器组件。string、number、boolean、对象值和多选数组会原样通过 update:modelValue / change 返回;组件不会排序、克隆或隐式转换,父级 modelValue 始终是最终状态权威。
options支持labelKey / valueKey / disabledKey字段映射;默认 slot 有实际内容时优先。valueKey只决定 option 的值字段;对象值需要稳定回显时,使用objectIdentityKey指定对象身份字段。- 支持单选、多选、disabled、clearable、filterable、loading、collapse tags、弹层挂载和 popper class。
autoSelectSingle只在 options 模式、当前值为空且唯一 option 未禁用时触发一次update:modelValue后再触发一次change;父值变为非空后再次清空可重新触发。- 保留 prefix、empty、loading、tag 和 default slots,以及 clear、visible-change、focus、blur 事件。
- 只公开
focus()/blur(),不公开 Element Plus 实例或 DOM 引用。
<QySelect
v-model="selectedUser"
:options="userOptions"
label-key="name"
value-key="user"
object-identity-key="id"
/>远程请求、防抖和已选项缓存仍由 QyRemoteSelect 负责;QySelect 不承接业务字典或请求状态编排。
QyTreeSelect
树选择组件以 ElTreeSelect 作为唯一树、勾选、过滤、键盘和浮层实现,重点支持:
data树数据直出,options作为兼容入口;同时传入时data优先,节点与数组保持原始引用labelKey / valueKey / childrenKey / disabledKey快捷字段与props映射(显式props优先)- string、number、boolean、对象和多选数组的受控值;对象值必须配置稳定的
nodeKey - 单选 / 多选
check-strictlycollapse-tags- 默认弹层挂载策略
- 保留原始节点字段,便于业务 slot 和后续透传使用
QyTreeSelect 只处理树选择器的通用 UI 协议,不内置远程请求、权限过滤或业务接口格式化。节点插槽、回调和事件只提供原始数据与安全快照;公开实例只有 focus() 和 blur(),不会暴露 Element Node、Store 或组件实例。
为降低迁移成本,QyTreeSelect 的 clearable、filterable、check-strictly、render-after-expand、fit-input-width 等默认行为尽量跟随 ElTreeSelect。业务需要搜索、清空、父子不联动时应显式传参。
QyRemoteSelect
远程选择器组件,基于 QySelect 组合实现,重点支持:
fetchMethod(keyword)远程请求debounceminLengthdefaultOptionsremoteOnFocus- 内部请求
loading erroroptions-change- 已选项缓存,避免远程 options 清空后回显丢 label
- 继承
QySelect的字段映射、多选、折叠标签、唯一选项自动选中能力
如果只是本地 options 下拉,应优先使用 QySelect;只有需要远程请求、搜索和回显保留时再使用 QyRemoteSelect。
QyFormSection
区块容器组件,适合录单页面的表单区块外壳,重点支持:
- 标题
- 描述
- 操作区 slot
- 提示区
- 简单折叠
- 统一边框、背景和间距
QyActionBar
底部操作栏组件,适合录单页面底部按钮区,重点支持:
- sticky 吸底
- 左右分区
- 窄屏换行
- 边框 / 阴影
- 底部安全区适配
QySearchInput
搜索输入组件,支持:
- 基于
ElAutocomplete的等价封装 - 搜索按钮
- 回车搜索
- 本地建议项
- 远程建议项
- 兼容
fetch-suggestions(queryString, cb)回调风格 - 高亮匹配词
- 默认使用 Element Plus 弹层挂载策略,避免被父级裁剪
- 平铺项和分组项两种建议结构
QyRecordQuoteSearch
录单报价单号搜索组件,支持:
- 报价单号输入
- 报价单号提取与规范化
- 搜索触发
- 建议项选择
- 局部状态提示
loading-change / success / error事件
样式说明
- 包内样式会跟随构建产物一起注入
- 宿主仍然需要自行引入
element-plus/dist/index.css - 当前组件内部仍然依赖宿主提供的 Tailwind / Iconify 体系能力
使用示例
QyUpload
<template>
<QyUpload
v-model="uploadValue"
api="/permission-api/common/uploads"
token-key="Admin-Token"
@change="handleChange"
/>
</template>纯图片上传:
<template>
<QyUpload
v-model="payImgs"
api="/permission-api/common/uploads"
name="files"
value-format="array"
:show-text="false"
:show-paste-input="true"
paste-placeholder="粘贴图片上传"
size="90px"
/>
</template>QyImageUpload
单图字段:
<template>
<QyImageUpload
v-model="imageUrl"
api="/permission-api/common/uploads"
:file-size="5"
/>
</template>QyImage
<template>
<QyImage
src="https://img.example.com/a.jpg"
:size="48"
fit="cover"
preview
:compressor="{ w: 200, h: 200, q: 50 }"
/>
</template>函数式预览:
import { openQyImagePreview } from '@qynpm/ui'
openQyImagePreview({
urlList: ['https://img.example.com/a.jpg'],
initialIndex: 0
})多图字段:
<template>
<QyImageUpload
v-model="payImages"
api="/permission-api/common/uploads"
multiple
:limit="9"
/>
</template>凭证上传模式,迁移旧 CopyImgUpload:
<template>
<QyImageUpload
ref="payImgRef"
v-model="payImages"
mode="voucher"
api="/permission-api/common/uploads"
:multiple="false"
size="100px"
/>
</template>
<script setup>
import { ref } from 'vue'
const payImages = ref('')
const payImgRef = ref(null)
function getForms() {
return {
erpOrderPayImg: payImgRef.value.getFullPathList().join(',')
}
}
</script>接入宿主项目上传请求:
<template>
<QyImageUpload
v-model="payImgs"
name="files"
multiple
:upload-request="uploadFile"
/>
</template>
<script setup lang="ts">
import { request } from '@qiyin/utils'
async function uploadFile({ formData }) {
return request({
url: '/permission-api/common/uploads',
method: 'POST',
data: formData
})
}
</script>QyRemarkImage
默认上传:
<template>
<QyRemarkImage
:default-value="remark"
api="/permission-api/common/uploads"
upload-name="files"
@change="handleRemarkChange"
/>
</template>
<script setup lang="ts">
function handleRemarkChange(value: string, imageUrl?: string) {
remark.value = value
remarkImage.value = imageUrl || ''
}
</script>接入宿主项目上传请求:
<template>
<QyRemarkImage
:default-value="remark"
:upload-request="uploadRemarkImage"
@change="handleRemarkChange"
/>
</template>
<script setup lang="ts">
import { request } from '@qiyin/utils'
async function uploadRemarkImage({ formData }) {
return request({
url: '/permission-api/common/uploads',
method: 'POST',
data: formData
})
}
function handleRemarkChange(value: string, imageUrl?: string) {
remark.value = value
remarkImage.value = imageUrl || ''
}
</script>QyMenuActions
<template>
<QyMenuActions
:row="row"
:row-index="0"
:actions="actions"
:confirm-method="confirmAction"
@action="handleAction"
/>
</template>
<script setup lang="ts">
const row = { id: 1, status: 'draft' }
const actions = [
{ key: 'edit', label: '编辑' },
{ key: 'delete', label: '删除', type: 'danger', confirm: true }
]
function confirmAction(action, context) {
return window.confirm(`确认${action.label}?`)
}
function handleAction(action, context) {
// 业务执行、消息提示和权限判断留在宿主或 table-schema adapter。
}
</script>QyTableColumnSettings
<template>
<QyTableColumnSettings
v-model="visible"
:settings="settings"
:default-settings="defaultSettings"
@confirm="saveSettings"
@delete-cache="removeSettingsCache"
/>
</template>QyButton
<template>
<QyButton type="primary" :prefix-icon="Search" @click="handleSearch">
查询
</QyButton>
<QyButton loading>保存中</QyButton>
<QyButton icon-only :prefix-icon="Refresh" aria-label="刷新当前页" />
</template>QyDropdown
<template>
<QyDropdown
:items="items"
panel-width="168px"
@select="handleSelect"
>
<template #trigger="{ attrs }">
<QyButton v-bind="attrs">
更多
</QyButton>
</template>
</QyDropdown>
</template>
<script setup lang="ts">
import QyButton from '@qynpm/ui/qy-button'
import QyDropdown from '@qynpm/ui/qy-dropdown'
const items = [
{ key: 'profile', label: '个人信息' },
{ key: 'logout', label: '退出登录', danger: true, divided: true }
]
function handleSelect(item) {
// 业务执行、路由跳转和权限判断留在宿主项目。
}
</script>QyDialog
<template>
<QyDialog :open="open" title="标题" close-on-confirm @update:open="open = $event">
内容区域
</QyDialog>
</template>QyInput
<template>
<QyInput
v-model="quoteOrderCode"
remove-space
trim-mode="blur"
placeholder="填写报价单,回车搜索"
@enter="handleSearch"
/>
</template>QyMoneyInput
<template>
<QyMoneyInput
v-model="totalPay"
:precision="2"
:min="0"
placeholder="请输入总金额"
/>
</template>QyInputNumber
<template>
<QyInputNumber
v-model="productNum"
:min="0"
:step="1"
:precision="0"
controls-position="right"
placeholder="请输入数量"
/>
</template>QyDatePicker
<template>
<QyDatePicker
v-model="dateRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
:default-time="defaultTime"
normalize-range-end-time
/>
</template>QyRadio
<template>
<QyRadio
v-model="ticketType"
value="invoice"
label="开票"
/>
</template>QyRadioGroup
<template>
<QyRadioGroup
v-model="openTicket"
:options="ticketOptions"
/>
</template>自定义选项展示:
<template>
<QyRadioGroup v-model="openTicket" :options="ticketOptions">
<template #option="{ option, label }">
<span>{{ label }}</span>
<span v-if="option.fee" style="margin-left: 6px; color: #f56c6c">
{{ option.fee }}
</span>
</template>
</QyRadioGroup>
</template>QySelect
<template>
<QySelect
v-model="shopCode"
:options="shopOptions"
placeholder="请选择店铺"
/>
</template>QyRemoteSelect
<template>
<QyRemoteSelect
v-model="recvAccount"
:fetch-method="fetchFinanceAccounts"
:default-options="accountDefaultOptions"
label-key="accountName"
value-key="accountId"
placeholder="请选择收款账户"
remote-on-focus
@error="handleAccountError"
/>
</template>QyFormSection
<template>
<QyFormSection title="订单信息" description="这里放订单表单区块">
<el-form>...</el-form>
</QyFormSection>
</template>QyActionBar
<template>
<QyActionBar>
<template #left>
<el-button plain>开票</el-button>
</template>
<template #right>
<el-button>保存</el-button>
<el-button type="primary">提交</el-button>
</template>
</QyActionBar>
</template>QySearchInput
<template>
<QySearchInput
v-model="keyword"
:fetch-suggestions-method="querySearch"
:show-button="false"
/>
</template>QyRecordQuoteSearch
<template>
<QyRecordQuoteSearch
v-model="quoteOrderCode"
:fetch-suggestions="fetchQuoteSuggestions"
:search-method="handleQuoteSearch"
@search="handleSearch"
/>
</template>QyCollapse
QyCollapse 与 QyCollapseItem 是 Element Plus 折叠面板的受控适配器。普通模式使用数组值,手风琴模式使用单个字符串或数字;beforeCollapse 同时拦截展开和收起。
<template>
<QyCollapse v-model="activeNames" :before-collapse="beforeCollapse">
<QyCollapseItem title="基本信息" name="base">内容</QyCollapseItem>
<QyCollapseItem title="数字名称" :name="2" />
</QyCollapse>
</template>
<script setup>
import { ref } from 'vue'
import { QyCollapse, QyCollapseItem } from '@qynpm/ui'
const activeNames = ref(['base'])
function beforeCollapse(name) {
return name !== 'locked'
}
</script>通过 ref 可读取只读的 activeNames,并调用 setActiveNames(value) 设置完整公共值;标题和图标插槽都接收 { isActive }。
QyLink
QyLink 基于 Element Plus ElLink,保留真实 <a href> 的浏览器焦点、Enter 激活和默认导航语义。支持六种语义类型、always / never / hover 下划线策略、禁用、原生链接地址和属性、图标属性及默认/图标插槽。
<QyLink
href="https://example.com"
target="_blank"
rel="noopener noreferrer"
type="primary"
underline="hover"
>
打开外部文档
<template #icon>↗</template>
</QyLink>disabled 会移除有效 href 与 target 并抑制 click;非禁用点击只转发一次原生 MouseEvent。type 和 underline 未传时可继承 Element Plus ConfigProvider;布尔下划线仅作兼容,true 等同 hover、false 等同 never。不自动改写 target 或 rel,外部新窗口链接请显式提供 rel="noopener noreferrer"。无 href、只监听 click 的旧用法可以兼容,但新业务动作应使用 QyButton,路由导航应使用 RouterLink。
QyIframe
QyIframe 用于在当前页面容器中嵌入受信任的独立页面。src 和无障碍 title 必填且去除首尾空白后必须非空;任一为空时不渲染 iframe。支持原生 loading、sandbox、allow、referrerPolicy、allowFullscreen,并发出无参数的 load 和 error 通知,不暴露原生 DOM Event。
<div style="height: 640px">
<QyIframe
src="/system/monitor"
title="系统监控"
:reload-key="reloadKey"
loading="lazy"
sandbox="allow-scripts allow-same-origin"
@load="handleLoad"
@error="handleError"
/>
</div>src 变化时更新当前 iframe 地址;即使 src 相同,改变 reloadKey(string | number)也会重建 iframe 以触发受控 reload。组件默认填满父容器,不计算应用头部、标签栏或面包屑高度;宿主必须为容器提供明确高度。根节点只安全透传 class、style、id、role、tabindex、data-* 和 aria-*,未知属性及未声明监听器不对外透传。组件不模拟加载时长、不覆盖全局 resize 监听,也不负责登录会话、地址白名单或跨域通信。
QyDivider
QyDivider 基于 Element Plus ElDivider,支持水平/垂直方向、左中右标题位置和标准 CSS border-style 值。水平模式渲染默认插槽,垂直模式即使传入插槽也保持 Element Plus 行为而不渲染;消费者传入的 width、height、margin 等根样式会与 Element Plus 的边框 CSS 变量合并。
<QyDivider content-position="left" border-style="dashed">
分组标题
</QyDivider>
<QyDivider direction="vertical" :style="{ height: '130px', margin: '0 8px' }" />根节点保留 role="separator",只安全透传 class、style、id、title、role、data-* 和 aria-*;组件没有事件、尺寸参数、公开实例或额外布局规则。
QyProgress
QyProgress 基于 Element Plus ElProgress,用于文件上传、批处理和任务进度的受控展示。支持 line / circle / dashboard 三种形态、percentage、四种 status、显示/隐藏/内部/自定义文字、固定/分段/函数颜色、线宽、端点、圆形画布宽度、不确定动画、条纹、流动条纹和默认插槽;不接管上传、轮询、暂停、恢复、重试或任务状态映射。
<QyProgress :percentage="progress" status="success" />
<QyProgress type="circle" :percentage="progress" :color="colorStops" />
<QyProgress :percentage="progress" :style="{ width: '720px', margin: '8px auto' }">
<template #default="{ percentage }">已完成 {{ percentage }}%</template>
</QyProgress>颜色、文字优先级、合法 percentage、图形和动画全部沿用 Element Plus;根级只安全透传 class、style、id、role、title、data-* 和 aria-*,未知属性、事件监听器、实例、DOM ref 和方法不对外暴露。文档演示位于 src/docs/QyProgressDoc.vue,API 表使用局部横向滚动,不产生页面级横向溢出。
QyStatistic
QyStatistic 基于 Element Plus ElStatistic 展示数值。value、precision、decimalSeparator、groupSeparator、formatter、valueStyle 以及 title、prefix、suffix 属性和同名插槽均沿用 Element Plus;插槽优先级不变。
<QyStatistic title="成交额" :value="123456.78" :precision="2" prefix="¥" suffix="元" />
<QyStatistic :value="target" animated :duration="800" :start-value="100" />动画首次从 startValue 过渡,目标变化时从当前显示值平滑衔接;animated=false、duration<=0 和 prefers-reduced-motion: reduce 时立即显示目标值。根级只安全透传 class、style、id、role、data-* 和 aria-*,未知属性与事件监听器不会透传,也不暴露 Element Plus 实例、DOM ref 或方法。
QyAvatar
QyAvatar 基于 Element Plus ElAvatar,用于用户头像及自定义头像内容展示。支持数字尺寸、small / default / large 命名尺寸、圆形和方形、图片 src / srcSet / alt / fit、图标、默认插槽和图片加载失败后的原生回退。
<QyAvatar :size="80" src="/avatar.png" alt="用户头像" />
<QyAvatar size="large" shape="square" :icon="UserFilled" />
<QyAvatar src="/missing.png" @error="handleAvatarError">
张三
</QyAvatar>图片加载、失败回退、图标与默认插槽优先级全部沿用 Element Plus;error 只转发标准图片 Event。根级只安全透传 class、style、id、role、title、tabindex、data-* 和 aria-*,未知属性、未声明事件、实例、DOM ref 和方法不对外暴露。QyAvatar 不接管上传、裁剪、预览、缓存、用户资料保存或头像组;文档演示位于 src/docs/QyAvatarDoc.vue,API 表使用局部横向滚动。
QyAlert
QyAlert 基于 Element Plus ElAlert,用于页面模板中的常驻成功、警告、信息和错误提示。支持 title、description、五种 type、关闭入口、图标、居中和明暗效果,以及 icon、title、默认三个插槽;关闭时只触发无参数 close 事件,隐藏状态由 Element Plus 管理。
<QyAlert type="warning" title="请检查填写内容" description="修改后即可继续提交" @close="handleClose" />根级只安全透传 class、style、id、role、data-* 和 aria-*;不透传原生 title、未知属性、原始鼠标事件或 Element Plus 实例。QyAlert 只负责常驻提示,不替代 @qynpm/hooks feedback runtime,也不改动 QyFormSection 的现有 tip 协议。
QyCard
QyCard 基于 Element Plus ElCard,用于页面内部可独立识别的小型内容面板。支持 header、footer、shadow、bodyStyle 以及三个区域类名入口,原生转发 header、默认和 footer 插槽;插槽存在时由 Element Plus 优先于同名文本属性渲染。
<QyCard header="订单概览" shadow="hover" @click="handleCardClick">
<template #footer>更新于刚刚</template>
面板正文
</QyCard>根级只安全透传 class、style、id、role、title、tabindex、data-* 和 aria-*;click 只转发一次原生 MouseEvent,不把普通卡片自动改成按钮或链接。QyCard 不替代 QyPage 的页面语义根和页面节奏,也不替代 QyFormSection 的表单标题、说明、动作或折叠;标准列表页和完整页面不要再套一层卡片 workspace。
说明
- 当前版本更适合体系内拆包复用
- 上传默认值仍然保留了现有项目约定,后续如果完全独立对外,可以再继续抽离
QyInput第一版优先兼容ElInput,后续再逐步补统一输入规则QyInputNumber第一版优先兼容ElInputNumber,保持数字输入语义,不承接金额格式化QyRadioGroup第一版优先兼容ElRadioGroup,不内置字典请求或复杂表单联动QySelect第一版优先兼容ElSelect,不内置远程字典或复杂请求能力QyDatePicker第一版优先兼容ElDatePicker,查询结束时间归一化需要显式开启QyRemoteSelect负责远程请求状态和回显保留,不承接具体业务接口协议转换QyFormSection只负责区块壳,不替代@qiyin/form的 schema / 校验能力QyActionBar只负责底部布局壳,不负责编排按钮业务逻辑QySearchInput第一版已经覆盖建议搜索场景,可通过show-button=false关闭默认搜索按钮,但仍然不承接页面级副作用QyRecordQuoteSearch只承接报价单搜索入口逻辑,不直接改整页录单状态QyImage负责通用图片显示,不负责表格列取值、formatter、水印和列设置;这些由@qynpm/table-schema组合
当前联调说明
当前 workspace 联调由顶层 Vite 开关统一控制,不再要求在 UI 或 form 内手动切换 import。
开关位置:
qy-workspace顶层vite.config.ts
当前配置:
const ENABLE_LOCAL_QIYIN_DEBUG = true规则如下:
- 当值为
true时
@qynpm/ui映射到本地 lib/index.js@qynpm/form/@qiyin/form映射到本地 ../form/index.ts- 消费项目开发时可以直接联调
UI与form
- 当值为
false时
- 走各自包的正常入口
- 行为更接近线上
如果切换开关后页面报 Outdated Optimize Dep:
- 停掉当前 dev 服务
- 清理消费项目的
node_modules/.vite - 重新启动
QySteps 与 QyStep
QySteps 和 QyStep 是 Element Plus ElSteps / ElStep 的安全公共入口。布局、父子注册、排序、状态计算和过渡全部由 Element Plus 负责;Qy 层只隔离公共属性、插槽、根级安全属性和 change(newValue, oldValue) 事件。
<QySteps :active="active" direction="horizontal" @change="handleChange">
<QyStep title="创建订单" description="填写订单信息" />
<QyStep title="确认信息">
<template #title><button type="button" @click="openOrder">查看订单</button></template>
</QyStep>
<QyStep title="完成" status="success" />
</QySteps>支持自动、像素和百分比 space,横向/纵向、居中、简洁模式、五种步骤状态以及受控 active。QyStep 支持 title、description、icon、显式 status 和 icon / title / description 插槽;插槽内容优先于同名属性。组件不提供 update:active、自动导航或步骤点击事件,业务跳转应在插槽中放置真实按钮或链接并自行处理。
两个入口分别为 @qynpm/ui/qy-steps 与 @qynpm/ui/qy-step;根入口也导出 QySteps、QyStep 及其 Qy 自有类型。根级只安全透传 class、style、id、role、title、data-* 和 aria-*,未知属性、未声明事件、Element Plus 实例、内部注册对象和 DOM 方法不对外暴露。文档演示位于 src/docs/QyStepsDoc.vue,宽步骤条和 API 表使用局部横向滚动。
QyRow 与 QyCol
QyRow 与 QyCol 是 Element Plus ElRow / ElCol 的安全公共入口。栅格、gutter、对齐、位移、嵌套和五档响应式全部沿用 Element Plus;Qy 层不手写 24 栅格、断点、负边距或列 padding。
<QyRow :gutter="16" justify="space-between" align="middle">
<QyCol :span="12" :xs="24" :md="12">左侧内容</QyCol>
<QyCol :span="12" :offset="1">右侧内容</QyCol>
</QyRow>两个入口分别为 @qynpm/ui/qy-row 与 @qynpm/ui/qy-col;根入口也导出 QyRow、QyCol 及其 Qy 自有类型。QyRow 支持 tag、gutter、justify、align 和默认插槽;QyCol 支持 tag、span、offset、push、pull、xs、sm、md、lg、xl 和默认插槽。数值合法性和异常值表现交由 Element Plus 负责,不对旧项目非法值做兼容映射。
两者根级只安全透传 class、style、id、role、title、data-* 和 aria-*;未知属性、监听器、Element Plus 实例、DOM ref 和方法不对外暴露。完整演示位于 src/docs/QyGridDoc.vue,包含等分列、gutter、justify/align、offset/push/pull、嵌套、自定义 tag、五档响应式和局部横向滚动 API 表。
QyFullscreen
QyFullscreen 统一承接整页或指定局部 HTMLElement 的浏览器全屏进入、退出和状态同步。它使用 QyButton 保留按钮、loading、尺寸和可访问语义,使用 QyIcon 渲染图标;图标未在当前 registry 注册时显示当前 label 文字,不会产生空白按钮。
<QyFullscreen @change="isFullscreen = $event" @error="handleFullscreenError" />
<QyFullscreen :target="previewElement" enter-label="进入局部全屏" />target 未传时使用 document.documentElement,显式传入 null 表示目标暂不可用。全屏期间 target 变化不会自动退出或切换,退出后下一次进入才使用新目标。enter()、exit()、toggle() 通过模板 ref 命令式调用并返回 Promise<boolean>;change 只在浏览器确认实际状态变化后触发,error 返回不含原始 DOMException 的只读快照。完整示例位于 src/docs/QyFullscreenDoc.vue,入口为 @qynpm/ui/qy-fullscreen。
QyIcon 与应用图标注册表
QyIcon 是应用级统一图标入口。每个 Vue app 通过 createQyIconRegistry 与 createQyIconPlugin 提供自己的 registry,支持同文档 SVG symbol、直接 Vue component 和宿主已经生成样式的静态 class;不依赖 Element Plus/Iconify renderer,也不进行远程加载或 v-html。
import { createQyIconPlugin, createQyIconRegistry } from '@qynpm/ui/qy-icon'
const registry = createQyIconRegistry({
icons: [
{ name: 'system', source: 'symbol', value: '#icon-system' },
{ name: 'close', source: 'class', value: 'icon-[mdi--close]' }
],
aliases: { 'el-icon-s-home': 'system' }
})
app.use(createQyIconPlugin(registry))名称大小写不敏感;#icon-system 规范为 system,icon-[mdi--close] 规范为 iconify:mdi:close。冲突、未注册 alias 目标和不安全 symbol/class 值会在注册或合并时抛出错误。QyIcon 默认尺寸为 1em、颜色为 currentColor;提供 label 或消费方 aria-label 时作为 role="img",否则为装饰图标。完整演示位于 src/docs/QyIconDoc.vue,入口为 @qynpm/ui/qy-icon。
QyIconPicker
QyIconPicker 从当前 Vue app 注入的 QyIconRegistry 读取允许图标,统一支持 symbol、Vue component 和静态 class 三种来源。它是单值受控选择器:默认展示 registry 全量,也可以通过 allowedNames 白名单限制;搜索匹配名称、label 和 keywords,旧 alias 当前值只用于解析 active 图标,不会静默改写 modelValue,未知当前值也会保留到消费者主动选择或清空。
<QyIconPicker
v-model="menuIcon"
:allowed-names="['system', 'settings']"
clearable
aria-label="菜单图标"
/>公开事件为 update:modelValue、change、clear、update:open 和 open-change;实例只暴露 focus()、blur()、open()、close()。面板使用 listbox/option 语义,支持 Enter/Space 打开、方向键移动、Enter 选择和 Escape 回焦;触摸窄屏网格为两列且每个选项至少 40px。完整演示位于 src/docs/QyIconPickerDoc.vue,入口为 @qynpm/ui/qy-icon-picker。
QyColorPicker
QyColorPicker 是受控颜色选择器。format 只接受 hex 或 rgb;未指定时,关闭透明度输出 hex,开启透明度输出 rgb/rgba。拖动和输入只触发 active-change,确认或清空才按 update:modelValue、change、visible-change(false) 顺序提交;Escape、点击外部和失焦会回滚到当前父级值且不发提交事件。
<QyColorPicker
v-model="brandColor"
format="rgb"
show-alpha
:predefine="['#409eff', '#67c23a']"
clearable
@active-change="previewColor = $event"
/>事件为 update:modelValue、active-change、change 和 visible-change,颜色 payload 为 string | null;组件不公开 Element Plus 类型、实例或 expose。完整演示位于 src/docs/QyColorPickerDoc.vue,入口为 @qynpm/ui/qy-color-picker。
QyContainer、QyAside 与 QyMain
三个组件是通用布局原语,入口分别为 @qynpm/ui/qy-container、@qynpm/ui/qy-aside 与 @qynpm/ui/qy-main;根入口也导出 QyContainer、QyAside、QyMain 及其 Qy 自有类型。V1 使用内部 Element Plus renderer,公共 wrapper 和声明文件不要求消费者感知 Element Plus。
<QyContainer>
<QyAside width="240px">侧栏</QyAside>
<QyMain>主内容</QyMain>
</QyContainer>QyContainer 的 direction 默认是 horizontal,只有显式传入 vertical 才进入纵向布局;QyAside 的 width 是默认 300px 的原样字符串,支持合法 CSS 宽度值;QyMain 是语义 main,填充剩余空间、内容溢出时局部滚动并使用默认 20px padding。三者只提供默认插槽,不提供 emits、expose、ref 或实例能力,并且只安全透传 class、style、id、role、title、data-*、aria-*。
完整演示位于 src/docs/QyContainerDoc.vue,覆盖默认横向、自定义侧栏宽度、显式纵向、嵌套组合、安全属性和局部横向滚动 API 表。它们不替代 QyAdminContent:后者仍负责后台页面级内容编排、workspace 和相关页面语义。
