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

@qynpm/ui

v1.0.34

Published

起印公共 UI 组件包。

Readme

@qynpm/ui

起印体系内抽离出来的 Vue 3 UI 组件包,当前主要包含基础输入组件、选择器组件、区块容器、底部操作栏,以及上传、弹窗、备注图文等通用能力。

录单拆包的 transform 实际代码盘点见 录单拆包迁移总览。其中记录了 QyImagePreview、凭证上传模式、设计文件管理等后续 UI 缺口。QyRichTextEditor 属于录单追加信息的独立业务组件,不进入当前全局 UI 基础包。

安装

pnpm add @qynpm/ui

Peer Dependencies:

  • vue >= 3.4
  • element-plus >= 2.0
  • @element-plus/icons-vue >= 2.0
  • @vueuse/core >= 10.0
  • clsx >= 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.mjsdist/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 渲染只读空状态,支持 imageimageSizedescription 属性,以及 imagedescription、默认三个原生插槽。组件不判断数据,不内置加载、错误、重试或按钮点击逻辑。

<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 的属性优先级。classstyleidtitleroledata-*aria-* 安全透传到根节点,消费者传入的宽度、高度、margin、padding 均可覆盖,事件监听器和底层实例不对外暴露。

QyScrollbar

QyScrollbar 以 Element Plus 2.14.3 的 ElScrollbar 作为唯一滚动实现。默认插槽直接承载滚动内容,支持固定高度、最大高度、纵向/横向滚动、原生/自定义滚动模式、always、滚动层样式与类名,以及 tabindexidroleariaLabelariaOrientation

<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 返回 topbottomleftright,阈值由 Element Plus 的 distance 处理。实例只公开 scrollTo(x, y)scrollTo(options)setScrollTop(value)setScrollLeft(value)update(),挂载前或卸载后调用均为空操作。不显式暴露内部 ElScrollbar 实例、wrapRefhandleScroll 或 DOM ref;Vue 公共实例自带的 $el 不属于适配器承诺的稳定 API,消费者不得依赖。

根节点仅安全透传 classstyletitledata-* 和不冲突的 aria-*;旧的 widthvertical 属性及未知 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。支持 expandTriggermultiplecheckStrictlyemitPathlazyLoad、过滤、折叠标签、浮层定位、虚拟滚动及键盘操作。

节点默认插槽、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)"
/>

支持字段映射、展开/当前节点、联动和严格勾选、筛选、懒加载、拖拽、自定义节点插槽及本地键盘导航。公开引用仅包含筛选、当前节点、节点路径、勾选/半选读取与设置方法;不提供 appendremoveinsertupdateKeyChildren 等内部数据修改方法。键值方法需要 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

受控开关组件,modelValueactiveValueinactiveValueupdate:modelValue / change payload 均为 boolean | string | number,不进行隐式布尔化。

<QySwitch
  v-model="enabled"
  active-text="启用"
  inactive-text="停用"
  @change="handleChange"
/>

支持 disabledloadingsizeinlinePromptnameidtabindexariaLabel,以及 classstyleroletitledata-*、其它 aria-* 安全属性。只发出 update:modelValuechangefocusblur;不提供 beforeChangeinput、图标/slot 或底层实例、focus/blur 等命令式方法。

QyTag

通用紧凑标签,内部使用 ElTag,公开 tonesizeeffectclosablerounddisabled 和默认 slot。tone 支持 neutralprimarysuccesswarningdangerinfosizesmmdlg,默认分别映射为 Element Plus 的 smalldefaultlarge

<QyTag tone="success" size="sm" closable @close="removeTag">
  已完成
</QyTag>

disabled 会设置 aria-disabled="true"、隐藏关闭按钮并抑制 click / close;非禁用状态下这两个事件各发出一次 MouseEvent。支持 classstyletitleroledata-*aria-* 安全属性,不公开 colorhitdisableTransitions 或底层 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 默认为 99type 默认为 dangershowZero 默认为 trueoffset 默认为 [0, 0]。上限只作用于数字,字符串原样显示;content 插槽收到经过圆点和上限规则处理后的只读 { value: string },组件公开的 content 也只有最终显示内容,不暴露 Element Plus 实例。根节点安全透传 classstyleidroletitledata-*aria-*,不声明事件、v-model 或尺寸参数。

QyTooltip

只读提示组件。默认插槽是唯一触发内容,不增加触发元素包装 DOM;content 插槽(content slot)优先于 content 属性。组件支持 hover / focus、完整 placementlight / 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=falseshowDelay=0hideDelay=200offset=12maxWidth=320。事件只有 open / close,且只在实际可见状态转换时各发一次;不公开 trigger、受控 open、raw HTML、交互内容、Element Plus attrs/listeners 或实例方法。

QyPopover

交互浮层组件。reference 插槽是触发元素,默认插槽是可交互面板内容,不增加触发元素包装 DOM;支持 clickhoverfocuscontextmenu、完整 placementlight / 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=240offset=12showDelay=0hideDelay=200showArrow=truedisabled=falseopen 未传时组件内部管理状态,传入时通过 v-model:open 由父级回传决定状态;update:open 是状态意图,open-change / open / close 只在实际显隐转换时发出。唯一暴露的方法是 close(),不公开 Element Plus 实例、定位更新、teleportedappendTopopperOptions

QyPopconfirm

二次确认浮层,提供 Qy-owned 的确认、取消、加载锁、关闭原因和焦点恢复语义。支持 referencetitleicon 插槽;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:openopen-changeconfirmcancelclose(reason);关闭原因是 confirmcanceloutsideescapetriggerexternaldisabled。不公开 Element Plus 类型、实例、teleport 或 popper 参数。

QySkeletonQySkeletonItem

加载骨架组件基于 Element Plus 的 ElSkeleton / ElSkeletonItem 渲染。loading 由业务受控,加载期间使用 template 插槽,加载完成后使用默认插槽;支持 animatedcountrows 及数字或对象形式的 throttle

<QySkeleton :loading="loading" animated :count="2">
  <template #template>
    <QySkeletonItem variant="rect" style="height: 120px" />
  </template>
  <div>真实内容</div>
</QySkeleton>

QySkeletonItemvariant 支持 ptexth1h3captionbuttonimagecirclerect,默认值为 text。两者均支持 classstyleroletitledata-*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 插槽优先于 imageimage 优先于状态内置视觉;title 同时作为结果标题和根节点原生 titledescriptionactions 也支持同名插槽覆盖属性内容。根入口与 @qynpm/ui/qy-result 均提供 Qy-owned 类型,不公开 Element Plus 类型、实例或事件。

QyWatermark

QyWatermark 在容器内提供 Qy-owned 的平铺文字或图片水印,图片优先且加载失败回退到 contentcontent 支持多行文字,fontrotatewidthheightgapoffset 控制水印单元。水印覆盖层不接收指针事件,默认插槽中的按钮、图片和选择操作仍由消费者处理。

<script setup>
import QyWatermark from '@qynpm/ui/qy-watermark'
</script>

<template>
  <QyWatermark :content="['客户名称', '订单编号']" :rotate="-18">
    <div>订单内容</div>
  </QyWatermark>
</template>

公开 interface 只包含 contentimagezIndexrotatewidthheightgapoffset 和 Qy-owned font,以及安全容器属性 classstyleidtitleroledata-*aria-*;不公开 Element Plus props、实例、DOM、事件或 expose。完整演示位于 src/docs/QyWatermarkDoc.vue,入口为 @qynpm/ui/qy-watermark

QyTabsQyTabPane

QyTabs 使用 Element Plus 的 ElTabs / ElTabPane 处理渲染、焦点与键盘行为。modelValue 传入时保持受控;defaultValue 仅提供初始选择。tab-changetab-removetab-addedit 都是意图事件,不会改写业务页签数组、路由或 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')、tabPositionstretchclosableaddableeditablebeforeLeavetabindexQyTabPane支持label、字符串或数字 nameclosabledisabledlazylabel 插槽。tab-click的首个参数是冻结的QyTabPaneSnapshot,只包含 namelabeldisabledclosablelazyactiveindex`,不会泄漏 Element Plus 页签上下文或实例。

QyDescriptionsQyDescriptionsItem

QyDescriptions 使用 Element Plus 的 ElDescriptions / ElDescriptionsItem 处理描述列表的行列和单元格渲染。QyDescriptionsItem 是无 DOM 声明节点,父组件会递归处理直接子项、v-ifv-fortemplate/Fragment,再交给 Element Plus 完成 spanrowspan、宽度和对齐计算。

<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>

父级支持 bordercolumndirectionsize'' | 'large' | 'default' | 'small')、titleextralabelWidth 以及默认、titleextra 插槽;插槽优先于同名文本属性。描述项支持 labelspanrowspanwidthminWidthlabelWidthalignlabelAlignclassNamelabelClassName 以及默认和 label 插槽。组件只安全透传根节点的 classstyleidtitleroledata-*aria-*,不公开事件、状态或 Element Plus 实例。

QyUpload

上传组件,支持:

  • 图片上传
  • 视频上传
  • 粘贴上传
  • 拖拽上传
  • 文本备注
  • 预览
  • v-model
  • valueFormat
  • 纯图片模式

当前可配置项里,和宿主耦合最强的是上传配置:

  • api
  • tokenKey
  • name
  • uploadRequest

其中:

  • api 为必填
  • tokenKey 不传时默认使用 Admin-Token
  • name 不传时默认使用 file
  • uploadRequest 可接入宿主项目自己的 request、baseURL、鉴权和错误处理;不传时使用组件默认 XHR 上传

值协议:

  • 默认 valueFormat="object"v-model 输出 { files: string[], text: string }
  • valueFormat="array"v-model 输出 string[]
  • valueFormat="string"v-model 输出逗号分隔 URL
  • showText=false 可隐藏文字备注区;如仍需粘贴入口,可开启 showPasteInput
  • defaultValue 仍保留给老页面做非受控初始化;新页面建议优先使用 v-model

QyImageUpload

图片/文件字段上传组件,基于 QyUpload 的薄封装,适合商品图、设计图、成本附图、凭证图片集合、单视频字段等场景。

固定协议:

  • 默认 multiple=falsev-model 为单个 URL 字符串
  • multiple=true 时,v-model 为 URL 数组
  • 单图默认 limit=1,多图默认 limit=9
  • 默认 name="files"
  • 默认 showText=false
  • 默认开启纯粘贴输入条
  • 默认 size="128px"
  • 支持 supportVideofileTypefileSize
  • 支持透传 uploadRequest,用于接入宿主项目上传请求

凭证上传模式:

  • mode="voucher"voucher 可启用凭证图片场景。
  • 默认允许 jpg / jpeg / png / gif / bmp / webp
  • 默认 limit=30,保留纯粘贴入口,适合收款凭证、成本附图等旧 CopyImgUpload 使用点。
  • 暴露 getFullPathList()fullPathList,兼容旧页面 ref.fullPathList.join(',') 的读值方式。
  • 上传成功时额外触发 updateImg(url),兼容旧 CopyImgUpload 的成功通知语义。

QyImageUpload 不保留旧 imageUploadsuccessUpload(fileList) 回调。旧页面迁移时优先改为 v-model;确实依赖旧 CopyImgUpload 读值方式的页面,可先用凭证模式的 fullPathList / getFullPathList() 过渡。

QyImage

图片展示组件,计划承接旧 packages/qiyin/components/Image,并作为 @qynpm/table-schemaimage 列渲染底座。

这是后续替代旧 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 列后续直接组合 QyImageQyImagePreview,不要在 @qynpm/table-schema 里复制一份通用图片组件。

已完成:

  • 创建 @qiyin/UI/qy-image 入口。
  • 创建 src/docs/QyImageDoc.vue playground 文档。
  • 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 做隔离适配,modelValuetrueValuefalseValue 和事件 payload 均保持 boolean | string | number 原始类型,不做隐式布尔转换。

支持 value(为后续 QyCheckboxGroup 保留的选项值)、只读展示状态 indeterminatedisabledsize 和默认 slot。事件只有 update:modelValuechangefocusblur;禁用状态不产生值变更意图,也不公开 Element Plus 实例或命令式方法。

<QyCheckbox
  v-model="ticketValue"
  true-value="Y"
  false-value="N"
  aria-label="开票"
  @change="handleChange"
>
  开票
</QyCheckbox>

classstyleidnameroletitledata-*aria-* 会安全透传;桌面交互区域保持紧凑,窄屏下提升到至少 40px。QyCheckboxGroup、全选/级联和 CheckboxButton 不属于 V1。

QyRadio

受控单选框基础组件,基于 ElRadio 做隔离适配。value 表示选中值,label 只负责展示;默认 slot 优先。modelValue 与事件 payload 保持 boolean | string | number 原始类型,父级不回写时组件不会自行改变状态,也不会通过再次点击取消选中。

支持 disabledsizenameborder 以及 update:modelValuechangefocusblur 事件;安全透传 classstyleroletitledata-*aria-*,不公开 Element Plus 实例或命令式方法。

<QyRadio
  v-model="ticketType"
  value="invoice"
  label="开票"
  name="ticket-type"
  border
  @change="handleChange"
/>

QyCheckboxGroup

受控复选框组,统一管理 QyCheckboxValue[]。支持默认 slot 或 options(两者同时存在时 slot 优先),并透传 disabledminmaxsize。事件只有 update:modelValuechange,每次更新都返回新数组。

<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-schemaedit-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。

编辑类基础组件(待评估)

useColedit-input / edit-autocomplete 有一套“展示态 -> 点击编辑 -> blur/enter 写回”的通用交互。后续有两种选择:

  • 如果表格外也需要该交互,补 QyInlineEditInputQyAutocompleteInput
  • 如果只服务表格列,先放在 @qynpm/table-schema renderer 内部,避免 UI 包过早扩大。

QyDialog

QyDialog 是受控弹窗组件,使用 Qy-owned 协议:

  • open / update:open / open-change 管理显隐
  • confirmcancelclose(reason) 表达关闭意图;reasonconfirm | cancel | header | escape | overlay | external
  • confirm-loading 在宿主异步业务期间锁定确认动作,业务成功后由宿主关闭
  • 默认 footer 由 show-footershow-confirmshow-cancelconfirm-textcancel-text 控制;自定义 footer 时不渲染默认按钮
  • 支持全屏切换、最小化、焦点恢复和顶层弹窗 Escape/遮罩仲裁
<QyDialog
  :open="open"
  title="订单确认"
  :confirm-loading="saving"
  :close-on-confirm="false"
  @update:open="open = $event"
  @confirm="save"
>
  内容区域
</QyDialog>

modelValuelayoutconfirmPropsok 和 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
  • disabledloading 都会阻止 click 事件;loading 会展示内置 loading 状态。
  • active 用于顶部当前系统、tabs 当前操作等选中态。
  • block 撑满父容器,round 显式开启胶囊圆角;默认后台圆角保持紧凑。
  • native-type="button | submit | reset" 控制原生按钮类型,默认 button
  • prefix-icon / suffix-icon 只支持 Vue 组件图标;也可以通过 prefix / suffix 插槽传入前后图标;icon-only 必须提供 aria-labeltitle

样式 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
  • dangeractive 项会输出稳定的 is-danger / is-active class,以及 data-danger / data-active 标记。
  • trigger="click" 为默认触发方式,预留 contextmenu
  • v-model / model-value 支持受控打开状态;受控模式下只触发 update:modelValueopen-change,不私自决定最终打开状态。
  • close-on-select=false 可在选择后保持面板打开。
  • panel-width / widthmax-height 控制面板尺寸。
  • triggeritemheaderfooterempty 插槽用于业务定制展示;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 插槽渲染业务列
  • sidesearchtoolbar 插槽扩展业务区域
  • Element Plus 原生分页

QySelectionDialog 只解决“怎么选”,不解决“选什么”。业务接口、字段映射、权限判断应放在宿主项目 wrapper 中。

迁移业务选择器前,建议先阅读:

选择状态采用“弹窗草稿 + 确认提交”协议:

  • 行点击、勾选、删除标签、粘贴只修改弹窗内部草稿,并触发 selection-change(rows)
  • 点击确认后才触发 update:modelValue(rows)confirm(rows) 和兼容老 Dialogsubmit({ selection, rows, query })
  • 点击取消或关闭会丢弃草稿选择,不会污染宿主表单中的 v-model
  • search / handleQuerySearch 会重置到第一页并查询;分页切换会按当前条件查询。

主要扩展点:

  • columns / cols:表格列,cols 用于兼容老 Dialog
  • side:左侧业务区域,例如分类树、部门树。
  • search:完全自定义查询区,插槽参数为 { query, search, reset }
  • toolbar:搜索区右侧操作区,插槽参数为 { query, search }
  • getTableRefgetTableDatagetSelection:用于业务 wrapper 做必要的桥接。

QyRemarkImage

备注图片组件,承接旧 @qiyin/components/remarkImage 的“备注文本 + 单图”协议。

支持:

  • defaultValue 解析 [url:图片地址]备注文本
  • defaultImage 独立图片默认值
  • change(value, imageUrl) 输出完整备注值和图片地址
  • 粘贴图片上传
  • 悬浮预览和删除图片
  • uploadRequest 自定义上传请求
  • layout="select-prefix" 可把一个基础下拉值合并成输入前缀,用于“下拉值 + 图文备注”这类旧协议字段
  • deleteImage() / clearContent() / getValue() 暴露方法,兼容旧表单重置和读取协议值

默认上传会使用:

  • api,默认 /permission-api/common/uploads
  • tokenKey,默认 Admin-Token
  • uploadName,默认 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 接近的使用方式
  • removeSpace
  • trimMode
  • enter
  • textarea
  • modelValue 支持 string | number | null | undefined,用户编辑事件统一输出 string
  • 兼容 texttextareapasswordsearch 和 Element Plus 常用属性
  • 兼容 prefixsuffixprependappend slots
  • 类型出口包含 QyInputValueQyInputModelValueQyInputPropsQyInputEmitsQyInputExposed
  • 只公开 focusblurselectclearresizeTextarea 方法

QyMoneyInput

金额输入组件,第一版定位为 ElInput 的金额录入包装层,重点支持:

  • 输入过程清洗非法字符
  • 默认失焦格式化为固定小数位
  • precision
  • min / max
  • allowNegative
  • 兼容常见 slots 与 expose 方法

QyInputNumber

数字输入组件,第一版定位为 ElInputNumber 的轻量包装层,重点支持:

  • 保持 number / null 值语义
  • min / max
  • step
  • step-strictly
  • precision
  • controls / controls-position
  • valueOnClear(默认 null,也支持 min / max / number
  • readonly / disabled / formatter / parser / inputmode / align
  • prefix / suffix / increase-icon / decrease-icon slots
  • update:modelValue / input / change / focus / blur 显式事件
  • 兼容 focus / blur expose 方法

金额、费用、税费等财务录入场景请优先使用 QyMoneyInput,不要用 QyInputNumber 做金额格式化。

QyDatePicker

日期选择组件,第一版定位为 ElDatePicker 的轻量包装层,重点支持:

  • 保持和 ElDatePicker 接近的使用方式
  • date / datetime / daterange / datetimerange 等常见类型
  • format / value-format
  • default-time
  • shortcuts
  • disabled-date
  • year / month / date / dates / datetime / week / daterange / datetimerange / monthrange
  • clearable / disabled / readonly / teleported / append-to / popper-class
  • update:modelValue / change / clear / calendar-change / panel-change / visible-change / focus / blur
  • default / range-separator / prev-month / next-month / prev-year / next-year slots
  • 仅公开 focus() / blur() / open() / close() expose 方法

normalize-range-end-time 默认关闭。开启后只处理范围第二项:精确 00:00:00.000Date 克隆为 23:59:59.000,带明确午夜时间的普通/ISO 字符串只替换时间并保留格式及 zone suffix;date-only 字符串、number、非午夜和非范围值保持原引用,不修改父数组或父 Date

QyRadioGroup

单选组组件,第一版定位为 ElRadioGroup 的轻量包装层,重点支持:

  • options 直出
  • labelKey / valueKey / disabledKey 映射
  • 普通单选 / 按钮式单选
  • 选项禁用
  • option 插槽自定义选项文案
  • 事件保持 update:modelValuechange

option 插槽由外部传入,组件会透出当前选项的:

  • option:原始选项对象
  • label:映射后的展示文案
  • value:映射后的选项值
  • disabled:是否禁用

普通 options 模式物理复用 QyRadiotype="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-strictly
  • collapse-tags
  • 默认弹层挂载策略
  • 保留原始节点字段,便于业务 slot 和后续透传使用

QyTreeSelect 只处理树选择器的通用 UI 协议,不内置远程请求、权限过滤或业务接口格式化。节点插槽、回调和事件只提供原始数据与安全快照;公开实例只有 focus()blur(),不会暴露 Element Node、Store 或组件实例。

为降低迁移成本,QyTreeSelectclearablefilterablecheck-strictlyrender-after-expandfit-input-width 等默认行为尽量跟随 ElTreeSelect。业务需要搜索、清空、父子不联动时应显式传参。

QyRemoteSelect

远程选择器组件,基于 QySelect 组合实现,重点支持:

  • fetchMethod(keyword) 远程请求
  • debounce
  • minLength
  • defaultOptions
  • remoteOnFocus
  • 内部请求 loading
  • error
  • options-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

QyCollapseQyCollapseItem 是 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 会移除有效 hreftarget 并抑制 click;非禁用点击只转发一次原生 MouseEventtypeunderline 未传时可继承 Element Plus ConfigProvider;布尔下划线仅作兼容,true 等同 hoverfalse 等同 never。不自动改写 targetrel,外部新窗口链接请显式提供 rel="noopener noreferrer"。无 href、只监听 click 的旧用法可以兼容,但新业务动作应使用 QyButton,路由导航应使用 RouterLink。

QyIframe

QyIframe 用于在当前页面容器中嵌入受信任的独立页面。src 和无障碍 title 必填且去除首尾空白后必须非空;任一为空时不渲染 iframe。支持原生 loadingsandboxallowreferrerPolicyallowFullscreen,并发出无参数的 loaderror 通知,不暴露原生 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 相同,改变 reloadKeystring | number)也会重建 iframe 以触发受控 reload。组件默认填满父容器,不计算应用头部、标签栏或面包屑高度;宿主必须为容器提供明确高度。根节点只安全透传 classstyleidroletabindexdata-*aria-*,未知属性及未声明监听器不对外透传。组件不模拟加载时长、不覆盖全局 resize 监听,也不负责登录会话、地址白名单或跨域通信。

QyDivider

QyDivider 基于 Element Plus ElDivider,支持水平/垂直方向、左中右标题位置和标准 CSS border-style 值。水平模式渲染默认插槽,垂直模式即使传入插槽也保持 Element Plus 行为而不渲染;消费者传入的 widthheightmargin 等根样式会与 Element Plus 的边框 CSS 变量合并。

<QyDivider content-position="left" border-style="dashed">
  分组标题
</QyDivider>
<QyDivider direction="vertical" :style="{ height: '130px', margin: '0 8px' }" />

根节点保留 role="separator",只安全透传 classstyleidtitleroledata-*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;根级只安全透传 classstyleidroletitledata-*aria-*,未知属性、事件监听器、实例、DOM ref 和方法不对外暴露。文档演示位于 src/docs/QyProgressDoc.vue,API 表使用局部横向滚动,不产生页面级横向溢出。

QyStatistic

QyStatistic 基于 Element Plus ElStatistic 展示数值。valueprecisiondecimalSeparatorgroupSeparatorformattervalueStyle 以及 titleprefixsuffix 属性和同名插槽均沿用 Element Plus;插槽优先级不变。

<QyStatistic title="成交额" :value="123456.78" :precision="2" prefix="¥" suffix="元" />
<QyStatistic :value="target" animated :duration="800" :start-value="100" />

动画首次从 startValue 过渡,目标变化时从当前显示值平滑衔接;animated=falseduration<=0prefers-reduced-motion: reduce 时立即显示目标值。根级只安全透传 classstyleidroledata-*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。根级只安全透传 classstyleidroletitletabindexdata-*aria-*,未知属性、未声明事件、实例、DOM ref 和方法不对外暴露。QyAvatar 不接管上传、裁剪、预览、缓存、用户资料保存或头像组;文档演示位于 src/docs/QyAvatarDoc.vue,API 表使用局部横向滚动。

QyAlert

QyAlert 基于 Element Plus ElAlert,用于页面模板中的常驻成功、警告、信息和错误提示。支持 titledescription、五种 type、关闭入口、图标、居中和明暗效果,以及 icontitle、默认三个插槽;关闭时只触发无参数 close 事件,隐藏状态由 Element Plus 管理。

<QyAlert type="warning" title="请检查填写内容" description="修改后即可继续提交" @close="handleClose" />

根级只安全透传 classstyleidroledata-*aria-*;不透传原生 title、未知属性、原始鼠标事件或 Element Plus 实例。QyAlert 只负责常驻提示,不替代 @qynpm/hooks feedback runtime,也不改动 QyFormSection 的现有 tip 协议。

QyCard

QyCard 基于 Element Plus ElCard,用于页面内部可独立识别的小型内容面板。支持 headerfootershadowbodyStyle 以及三个区域类名入口,原生转发 header、默认和 footer 插槽;插槽存在时由 Element Plus 优先于同名文本属性渲染。

<QyCard header="订单概览" shadow="hover" @click="handleCardClick">
  <template #footer>更新于刚刚</template>
  面板正文
</QyCard>

根级只安全透传 classstyleidroletitletabindexdata-*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 开关统一控制,不再要求在 UIform 内手动切换 import。

开关位置:

  • qy-workspace 顶层 vite.config.ts

当前配置:

const ENABLE_LOCAL_QIYIN_DEBUG = true

规则如下:

  1. 当值为 true
  • @qynpm/ui 映射到本地 lib/index.js
  • @qynpm/form / @qiyin/form 映射到本地 ../form/index.ts
  • 消费项目开发时可以直接联调 UIform
  1. 当值为 false
  • 走各自包的正常入口
  • 行为更接近线上

如果切换开关后页面报 Outdated Optimize Dep

  1. 停掉当前 dev 服务
  2. 清理消费项目的 node_modules/.vite
  3. 重新启动

QySteps 与 QyStep

QyStepsQyStep 是 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,横向/纵向、居中、简洁模式、五种步骤状态以及受控 activeQyStep 支持 titledescriptionicon、显式 statusicon / title / description 插槽;插槽内容优先于同名属性。组件不提供 update:active、自动导航或步骤点击事件,业务跳转应在插槽中放置真实按钮或链接并自行处理。

两个入口分别为 @qynpm/ui/qy-steps@qynpm/ui/qy-step;根入口也导出 QyStepsQyStep 及其 Qy 自有类型。根级只安全透传 classstyleidroletitledata-*aria-*,未知属性、未声明事件、Element Plus 实例、内部注册对象和 DOM 方法不对外暴露。文档演示位于 src/docs/QyStepsDoc.vue,宽步骤条和 API 表使用局部横向滚动。

QyRow 与 QyCol

QyRowQyCol 是 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;根入口也导出 QyRowQyCol 及其 Qy 自有类型。QyRow 支持 taggutterjustifyalign 和默认插槽;QyCol 支持 tagspanoffsetpushpullxssmmdlgxl 和默认插槽。数值合法性和异常值表现交由 Element Plus 负责,不对旧项目非法值做兼容映射。

两者根级只安全透传 classstyleidroletitledata-*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 通过 createQyIconRegistrycreateQyIconPlugin 提供自己的 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 规范为 systemicon-[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:modelValuechangeclearupdate:openopen-change;实例只暴露 focus()blur()open()close()。面板使用 listbox/option 语义,支持 Enter/Space 打开、方向键移动、Enter 选择和 Escape 回焦;触摸窄屏网格为两列且每个选项至少 40px。完整演示位于 src/docs/QyIconPickerDoc.vue,入口为 @qynpm/ui/qy-icon-picker

QyColorPicker

QyColorPicker 是受控颜色选择器。format 只接受 hexrgb;未指定时,关闭透明度输出 hex,开启透明度输出 rgb/rgba。拖动和输入只触发 active-change,确认或清空才按 update:modelValuechangevisible-change(false) 顺序提交;Escape、点击外部和失焦会回滚到当前父级值且不发提交事件。

<QyColorPicker
  v-model="brandColor"
  format="rgb"
  show-alpha
  :predefine="['#409eff', '#67c23a']"
  clearable
  @active-change="previewColor = $event"
/>

事件为 update:modelValueactive-changechangevisible-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;根入口也导出 QyContainerQyAsideQyMain 及其 Qy 自有类型。V1 使用内部 Element Plus renderer,公共 wrapper 和声明文件不要求消费者感知 Element Plus。

<QyContainer>
  <QyAside width="240px">侧栏</QyAside>
  <QyMain>主内容</QyMain>
</QyContainer>

QyContainerdirection 默认是 horizontal,只有显式传入 vertical 才进入纵向布局;QyAsidewidth 是默认 300px 的原样字符串,支持合法 CSS 宽度值;QyMain 是语义 main,填充剩余空间、内容溢出时局部滚动并使用默认 20px padding。三者只提供默认插槽,不提供 emits、expose、ref 或实例能力,并且只安全透传 classstyleidroletitledata-*aria-*

完整演示位于 src/docs/QyContainerDoc.vue,覆盖默认横向、自定义侧栏宽度、显式纵向、嵌套组合、安全属性和局部横向滚动 API 表。它们不替代 QyAdminContent:后者仍负责后台页面级内容编排、workspace 和相关页面语义。