mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-04-03 08:53:23 +08:00
!11 follow后端发布
* docs: readme * fix: missing formPath * chore: 去除锁定的esbuild版本 * perf: 去除debug组件 * perf: 参数键值 自动高度 * refactor: 代码生成配置页面重构 去除步骤条 * perf: 移除文件 * docs: 文件夹说明 * chore: 移除一些配置项 * chore: 注释优化 * refactor: 移除ele和naive目录 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * perf: request support to set how to return response (#5436) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: 登录超时的i18n * fix: requestClient缺失i18n内容 * refactor: 优化oss下载进度提示 * feat: 下载进度loading * fix: antd button icon style (#5421) * feat: oss下载进度(已下载的KB 无法作为进度显示 total返回为null) * fix: 下载文件时(responseType === 'blob')需要判断下载失败(返回json而非二进制)的情况 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: 新增后跳转到未发布流程 * fix: same name route * chore: 调整为部署json类型 * fix: mouse events ignored on modal loading (#5409) * docs: update docs (#5408) * refactor: 移除已经弃用的方法 * refactor: follow官方handleRangeTimeValue更新 * chore: 删除文件夹(前端路由需要的) * chore: 修改本地路由写法(新版)/新增本地菜单图标 * fix: form update state error before form mounted (#5406) * fix: demos route fixed (#5405) * chore: 不使用基础布局(仅在顶级生效) * feat: modal state locked on submitting (#5401) * chore: 修改zIndex * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * refactor: fix popup component zIndex (#5397) * style: element plus loading style fixed (#5393) * perf: improve fieldMappingTime to support format function (#5392) * fix: hide root route in breadcrumb * feat: support set default props for drawer and modal (#5390) * fix: root router config fixed (#5389) * fix: 修改Vxe默认zIndex为995 解决右上角全屏后modal/drawer(zIndex: 1000)被遮挡 * feat: add `noBasicLayout` in route meta (#5386) * chore: wechat image * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 改为全局参数配置 去除局部参数 * fix: spinner may stop playing animation after dismiss (#5365) * style: popover bgColor is too close to common (#5364) * docs: version update * docs: changelog * chore: 文件上传 描述 * ci: retry deploy while faild * feat: 文件上传 进度条+提示文字 * feat: 文件上传 进度条 * feat: 上传文件格式说明 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * fix: useEcharts return invalid instance (#5360) * feat: popup component support overlay blur effect (#5359) * feat: improve `tippy` demo (#5357) * feat: integrate new component `Tippy` with demo (#5355) * chore: 优化表格图片显示 * perf: add nested modal demo (#5353) * chore: 默认显示右边的滚动条 防止出现滚动条被挤压 * perf: modal and drawer api support chain calls (#5351) * feat: allow close tab when mouse middle button click (#5347) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * refactor: 重构显示total的逻辑 * chore: 调整高度自适应代码 * chore: vxe升级4.10.0版本(锁定) * fix: 添加失效的option * fix: 需要为数组 * fix: locale switching logic correction (#5344) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow * chore: 导入类型优化 解决eslint报红 * refactor: type/注释优化 去除大量any * fix: vxeGrid init without search form (#5342) * chore: 锁屏默认false 关闭该功能 * chore: 调整接口 * chore: update deps * fix: primaryColor calculation (#5337) * fix: form valid-error style in naive (#5336) * fix: form `fieldMappingTime` improve and `modelPropName` support (#5335) * fix: code lint * fix: form `fieldMappingTime` is not working (#5333) * chore: 选人组件样式 * fix: download from url triggered twice sometimes (#5319) * chore: 优化代码 * chore: 动态类名(无效)改为style * refactor: 字典相关功能重构 采用一个Map储存字典(之前为两个Map) * feat: 字典支持number类型存储 * chore: 调整样式 * chore: 修改选中border为1px * chore: 字段 * chore: 改为新窗口打开(适用于pdf/图片)而非直接下载 * chore: 更新样式 * chore: 更新字段 * chore: 改为computed * chore: 跳转到未发布流程tab * chore: 优化样式 * docs: readme * fix: name重复导致的404 * Merge branch 'dev' of https://gitee.com/dapppp/ruoyi-plus-vben5 into warmflow * chore: 使用legacy来保证copy的兼容性 * chore: 去除log 添加说明 * chore: 优化代码 * feat: 节点关联/节点独立的切换逻辑 * chore: remove logic * chore: vxe可编辑表格demo * chore: 不允许在按钮下添加数据 * docs: changelog * fix: wrong code * chore: 移除测试菜单 * chore: 优化代码 * refactor: 租户套餐菜单替换为新版 * refactor: 使用新版菜单勾选 * chore: 点行会勾选/取消全部权限 点权限不会勾选行 * chore: 全屏引导+样式优化 * chore: 调整间距 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: useEcharts exports echarts instance#5294 (#5299) * chore: update quick-start.md (#5303) * chore: updateCheckedNumber * refactor: 优化代码 * chore: 优化代码 * chore: 优化样式 * chore: keys依赖于menu 需要先加载menu * chore: 菜单加载完毕再显示 * feat: 新的菜单选择组件(beta) * chore: $t * chore: 测试菜单页面 * chore: 优化代码 * feat: 对ossId回显的支持 * chore: 只获取一次默认密码而非每次打开modal都获取 * fix: vben select placeholder color (#5286) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * perf: format code with better style (#5283) * chore: 工作流演示站 * fix: sidebar preferences fixed (#5276) * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: breadcrumb setting not valid for `header-sidebar-nav` layout (#5275) * fix: header logo may not be collapsed in `header-sidebar-nav` layout (#5274) * feat: new layout `sidebar nav with full header` (#5270) * feat: drawer close icon placement (#5269) * docs: update dialog and drawer docs * feat: drawer support destroy on close * feat: drawer support `onOpened` & `onClosed` * feat: modal support destroy on close * fix: wrong boolean * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 调整tab位置 * chore: 删除历史流程 改为tab切换 * fix: header-mixed layout side-menu active (#5265) * feat: header mixed layout (#5263) * chore: release 5.5.2 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: downgrade vue-tsc version * feat: header menu align support (#5256) * chore: update deps * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: add apiSelect remote search demo (#5246) * chore: 审批改为description而非disabled的表单 * chore: 改为ts * chore: 错误的conetnt * refactor: 终止/转办/委托支持填写意见 * chore: 第一次拿到的是readonly的数据 如果需要修改 需要cloneDeep * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: grid form submit button locale switch (#5205) * chore: 调整驳回 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: build error (#5199) * fix: esbuild自动升级导致运行/打包报错 * fix: esbuild自动升级导致运行/打包报错 * chore: 流程定义 激活改为switch * chore: 流程申请支持上传文件 * chore: title 审批通过 * fix: vxeGrid top padding (#5193) * fix: 表格排序翻页会丢失排序参数 * chore: 去除log打印 * chore: 流程监控 待办任务 * chore: 我发起的 * chore: 去除已经移除的菜单页面 * chore: 我的已办 * chore: 页面优化 * chore: 重置tooltip * feat: 我的抄送搜索/优化重复触发的接口 * feat: 流程定义 历史 * chore: 修改分类 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * fix: grid tools in toolbar config not working as expected (#5190) * feat: add `resizable` and `ColPage` component (#5188) * chore: 条件 * chore: break-all * feat: 流程分类 搜索 * chore: 弹窗关闭后仍然显示表单浮层 * chore: 选人组件的样式 * chore: 搜索的样式 * chore: 漏掉的导入 * chore: 最大显示的头像数量 超过显示为省略号头像 * fix: 选人的一些问题 * Merge branch 'warmflow' of https://gitee.com/dapppp/ruoyi-plus-vben5 i… * chore: 没有更多数据了 * fix: sidebar header height (#5183) * chore: 搜索表单布局+申请人 * fix: remove the overlap caused by border-b (#5160) * docs: fix typos (#5169) * fix: resolve eslint errors as well as TS type errors (#5172) * chore: enter提交表单 * chore: 修改文案 * chore: 默认全部展开 * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: page content class override (#5179) * fix: sidebar style on focus (#5178) * fix: 抄送选人 最右侧已选中删除item无效 * feat: 复制 * chore: 昵称过长的显示 * chore: 默认选中第一个 * chore: 修改relative位置 * chore: 搜索 * feat: 我的待办 - 搜索条件 * chore: 流程监控 - 待办任务页面的id不唯一 改为前端处理 * feat: 修改办理人 * chore: 流程干预 - 加签/减签 * chore: avatar大小 * chore: 抄送需要手动添加createByName * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * chore: 审批通过 抄送 * feat: 流程实例-流程预览 * chore: spell * chore: clientid * chore: 分类条件 * chore: 修改办理人 * chore: 更改postMessage参数 * chore: 内嵌iframe高度根据表单高度调整 * chore: 流程详情 * feat: 抄送选择 * chore: 调整分类树 * fix: user homePath no effect sometimes (#5166) * feat: form compact mode support (#5165) * fix: form auto submit no effect when showDefaultActions is false (#5163) * chore: 修改width * feat: 待办任务 * feat: 我的抄送 * chore: 流程定义 样式 * chore: 退回后重新申请 * chore: 请假申请布局 * chore: 请假申请-并行会签网关 * chore: 分类去除根目录 * chore: 详情modal(未完成) * chore: 请假申请根据不同状态显示按钮 * chore: 流程删除/撤销 * chore: 审批完成后刷新当前页 * feat: 选人组件(未完成) 加签减签 * docs: fix docs-link and add `EllipsisText` docs (#5158) * chore: 新窗口打开文件 * chore: 审批通过 * chore: 使用useEventListener替换原生 * chore: 字段错误 * chore: iframe通信 加载完毕后才显示表单 解决卡顿问题 * chore: 审批终止/驳回 * chore: 附件图标 * chore: process_running显示按钮 * chore: label错误 * chore: 保存的事件 * chore: 需要加上clientId * Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev * feat: form `colon` support (#5156) * chore: 完善请假申请 * feat: improve code login demo (#5154) * chore: 客户端管理 行高自适应 * chore: 内嵌表单的路径 * chore: 修改avatar背景色 * chore: 注释 * chore: activePath * chore: leave表单 * chore: 修改请假demo路径 * chore: categoryId * chore: 我的已办 * chore: 我发起的 * chore: loading * chore: 历史版本 * chore: 完善task api * chore: 隐藏'菜单加载中' * chore: missing import * feat: add demo for modify menu badge data * chore: 流程实例 * chore: 审批附件 * chore: 我的待办 提取公共组件 * chore: 流程部署 * chore: 新增/编辑/导出xml * chore: 流程定义(除历史版本) * feat: `autoActivateChild` support more layout mode (#5148) * feat: auto activate subMenu on select root menu (#5147) * fix: `disabledOnChangeListener` not work in form (#5146) * fix: login expired modal z-index (#5145) * feat: user-dropdown support `hover` trigger (#5143) * fix: pinInput value synchronous update (#5142) * fix: vxeGrid default sort data no effect in first query (#5141) * fix: vscode debug profile (#5140) * fix: form component events bind (#5137) * chore: 在线用户样式 开启虚拟滚动 * chore: 去掉个人中心 在线设备的分页 * chore: 去掉在线用户的分页 * chore: changelog * refactor: 获取字典的方法 提取公共函数 减少冗余代码 * fix: element plus validate failed style (#5130) * chore: 使用私有桶的提示 * feat: tabbar support mouse wheel vertical (#5129) * fix: form support `disabledOnInputListener` (#5127) * fix: form submission not appropriate (#5126) * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: release 5.5.1 * feat: table search form visible control (#5121) * chore: 需要隐藏菜单 * chore: 我的待办 & 请假 * chore: 流程定义(未完成) * chore: 流程定义(开发中) * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: version * chore: 锁定vxe-table版本 4.9.8版本存在样式问题 * chore: 暂时锁定cspell版本 * refactor: 由于不能输入 需要使用watch监听 * chore: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB7ANL * chore: 移除冗余代码 * chore: 组件卸载时移除emitter * fix: the route path did not synchronize with the page (#4947) * style: typo (#4948) * chore: 替换为commonDownloadExcel * fix: 左边部门树错误emit导致会调用两次列表api * chore: label样式 * chore: 改为Textarea * chore: 滚动条宽度 * chore: 审批样式 * chore: 部门及以下或本人数据权限 * Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev * chore: 个人中心强退设备接口路径
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/design",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -81,3 +81,7 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.z-popup {
|
||||
z-index: var(--popup-z-index);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
/* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */
|
||||
--popover: 222.82deg 8.43% 12.27%;
|
||||
|
||||
/* --popover: 222.82deg 8.43% 12.27%; */
|
||||
|
||||
/* 弹出层的背景色与主题区域背景色太过接近 */
|
||||
--popover: 0 0 14.2%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
/* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
:root {
|
||||
/** 弹出层的基础层级 **/
|
||||
--popup-z-index: 2000;
|
||||
--font-family: -apple-system, blinkmacsystemfont, 'Segoe UI', roboto,
|
||||
'Helvetica Neue', arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
|
||||
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/icons",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -28,6 +28,7 @@ export {
|
||||
Fullscreen,
|
||||
Github,
|
||||
Grip,
|
||||
GripVertical,
|
||||
Info,
|
||||
InspectionPanel,
|
||||
Languages,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shared",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -45,7 +45,7 @@
|
||||
"default": "./dist/store.mjs"
|
||||
},
|
||||
"./global-state": {
|
||||
"types": "./dist/global-state.d.ts",
|
||||
"types": "./src/global-state.ts",
|
||||
"development": "./src/global-state.ts",
|
||||
"default": "./dist/global-state.mjs"
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@ export enum DictEnum {
|
||||
SYS_YES_NO = 'sys_yes_no', // 是否
|
||||
WF_BUSINESS_STATUS = 'wf_business_status', // 业务状态
|
||||
WF_FORM_TYPE = 'wf_form_type', // 表单类型
|
||||
WF_TASK_STATUS = 'wf_task_status', // 任务状态
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import type { ClassValue } from 'clsx';
|
||||
|
||||
import { clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
function cn(...inputs: ClassValue[]) {
|
||||
|
||||
@@ -31,6 +31,7 @@ export async function downloadFileFromUrl({
|
||||
|
||||
if (isChrome || isSafari) {
|
||||
triggerDownload(source, resolveFileName(source, fileName));
|
||||
return;
|
||||
}
|
||||
if (!source.includes('?')) {
|
||||
source += '?download';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/typings",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
4
packages/@core/base/typings/src/app.d.ts
vendored
4
packages/@core/base/typings/src/app.d.ts
vendored
@@ -1,6 +1,8 @@
|
||||
type LayoutType =
|
||||
| 'full-content'
|
||||
| 'header-mixed-nav'
|
||||
| 'header-nav'
|
||||
| 'header-sidebar-nav'
|
||||
| 'mixed-nav'
|
||||
| 'sidebar-mixed-nav'
|
||||
| 'sidebar-nav';
|
||||
@@ -38,6 +40,7 @@ type BuiltinThemeType =
|
||||
type ContentCompactType = 'compact' | 'wide';
|
||||
|
||||
type LayoutHeaderModeType = 'auto' | 'auto-scroll' | 'fixed' | 'static';
|
||||
type LayoutHeaderMenuAlignType = 'center' | 'end' | 'start';
|
||||
|
||||
/**
|
||||
* 登录过期模式
|
||||
@@ -95,6 +98,7 @@ export type {
|
||||
BreadcrumbStyleType,
|
||||
BuiltinThemeType,
|
||||
ContentCompactType,
|
||||
LayoutHeaderMenuAlignType,
|
||||
LayoutHeaderModeType,
|
||||
LayoutType,
|
||||
LoginExpiredModeType,
|
||||
|
||||
5
packages/@core/base/typings/src/helper.d.ts
vendored
5
packages/@core/base/typings/src/helper.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { type ComputedRef, type MaybeRef } from 'vue';
|
||||
import type { ComputedRef, MaybeRef } from 'vue';
|
||||
|
||||
/**
|
||||
* 深层递归所有属性为可选
|
||||
@@ -109,6 +109,8 @@ type MergeAll<
|
||||
|
||||
type EmitType = (name: Name, ...args: any[]) => void;
|
||||
|
||||
type MaybePromise<T> = Promise<T> | T;
|
||||
|
||||
export type {
|
||||
AnyFunction,
|
||||
AnyNormalFunction,
|
||||
@@ -118,6 +120,7 @@ export type {
|
||||
EmitType,
|
||||
IntervalHandle,
|
||||
MaybeComputedRef,
|
||||
MaybePromise,
|
||||
MaybeReadonlyRef,
|
||||
Merge,
|
||||
MergeAll,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
/**
|
||||
* 扩展路由原始对象
|
||||
|
||||
14
packages/@core/base/typings/src/vue-router.d.ts
vendored
14
packages/@core/base/typings/src/vue-router.d.ts
vendored
@@ -1,6 +1,5 @@
|
||||
import type { Router, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
import type { Router, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
interface RouteMeta {
|
||||
/**
|
||||
@@ -98,6 +97,10 @@ interface RouteMeta {
|
||||
* 菜单可以看到,但是访问会被重定向到403
|
||||
*/
|
||||
menuVisibleWithForbidden?: boolean;
|
||||
/**
|
||||
* 不使用基础布局(仅在顶级生效)
|
||||
*/
|
||||
noBasicLayout?: boolean;
|
||||
/**
|
||||
* 在新窗口打开
|
||||
*/
|
||||
@@ -117,10 +120,13 @@ interface RouteMeta {
|
||||
}
|
||||
|
||||
// 定义递归类型以将 RouteRecordRaw 的 component 属性更改为 string
|
||||
type RouteRecordStringComponent<T = string> = {
|
||||
type RouteRecordStringComponent<T = string> = Omit<
|
||||
RouteRecordRaw,
|
||||
'children' | 'component'
|
||||
> & {
|
||||
children?: RouteRecordStringComponent<T>[];
|
||||
component: T;
|
||||
} & Omit<RouteRecordRaw, 'children' | 'component'>;
|
||||
};
|
||||
|
||||
type ComponentRecordType = Record<string, () => Promise<Component>>;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/composables",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { VisibleDomRect } from '@vben-core/shared/utils';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT,
|
||||
@@ -7,12 +7,9 @@ import {
|
||||
CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT,
|
||||
CSS_VARIABLE_LAYOUT_HEADER_HEIGHT,
|
||||
} from '@vben-core/shared/constants';
|
||||
import {
|
||||
getElementVisibleRect,
|
||||
type VisibleDomRect,
|
||||
} from '@vben-core/shared/utils';
|
||||
|
||||
import { getElementVisibleRect } from '@vben-core/shared/utils';
|
||||
import { useCssVar, useDebounceFn } from '@vueuse/core';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
/**
|
||||
* @zh_CN content style
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import { computed, getCurrentInstance, unref, useAttrs, useSlots } from 'vue';
|
||||
|
||||
import {
|
||||
getFirstNonNullOrUndefined,
|
||||
kebabToCamelCase,
|
||||
} from '@vben-core/shared/utils';
|
||||
import { computed, getCurrentInstance, unref, useAttrs, useSlots } from 'vue';
|
||||
|
||||
/**
|
||||
* 依次从插槽、attrs、props、state 中获取值
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Locale } from './messages';
|
||||
|
||||
import { createSharedComposable } from '@vueuse/core';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { getMessages, type Locale } from './messages';
|
||||
import { getMessages } from './messages';
|
||||
|
||||
export const useSimpleLocale = createSharedComposable(() => {
|
||||
const currentLocale = ref<Locale>('zh-CN');
|
||||
|
||||
@@ -46,6 +46,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
||||
"header": {
|
||||
"enable": true,
|
||||
"hidden": false,
|
||||
"menuAlign": "start",
|
||||
"mode": "fixed",
|
||||
},
|
||||
"logo": {
|
||||
@@ -65,11 +66,12 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
||||
"globalSearch": true,
|
||||
},
|
||||
"sidebar": {
|
||||
"autoActivateChild": false,
|
||||
"collapsed": false,
|
||||
"collapsedShowTitle": false,
|
||||
"enable": true,
|
||||
"expandOnHover": true,
|
||||
"extraCollapse": true,
|
||||
"extraCollapse": false,
|
||||
"hidden": false,
|
||||
"width": 224,
|
||||
},
|
||||
@@ -78,11 +80,13 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
||||
"enable": true,
|
||||
"height": 38,
|
||||
"keepAlive": true,
|
||||
"middleClickToClose": false,
|
||||
"persist": true,
|
||||
"showIcon": true,
|
||||
"showMaximize": true,
|
||||
"showMore": true,
|
||||
"styleType": "chrome",
|
||||
"wheelable": true,
|
||||
},
|
||||
"theme": {
|
||||
"builtinType": "default",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/preferences",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -46,6 +46,7 @@ const defaultPreferences: Preferences = {
|
||||
header: {
|
||||
enable: true,
|
||||
hidden: false,
|
||||
menuAlign: 'start',
|
||||
mode: 'fixed',
|
||||
},
|
||||
logo: {
|
||||
@@ -65,11 +66,12 @@ const defaultPreferences: Preferences = {
|
||||
globalSearch: true,
|
||||
},
|
||||
sidebar: {
|
||||
autoActivateChild: false,
|
||||
collapsed: false,
|
||||
collapsedShowTitle: false,
|
||||
enable: true,
|
||||
expandOnHover: true,
|
||||
extraCollapse: true,
|
||||
extraCollapse: false,
|
||||
hidden: false,
|
||||
width: 224,
|
||||
},
|
||||
@@ -78,11 +80,13 @@ const defaultPreferences: Preferences = {
|
||||
enable: true,
|
||||
height: 38,
|
||||
keepAlive: true,
|
||||
middleClickToClose: false,
|
||||
persist: true,
|
||||
showIcon: true,
|
||||
showMaximize: true,
|
||||
showMore: true,
|
||||
styleType: 'chrome',
|
||||
wheelable: true,
|
||||
},
|
||||
theme: {
|
||||
builtinType: 'default',
|
||||
@@ -105,7 +109,7 @@ const defaultPreferences: Preferences = {
|
||||
fullscreen: true,
|
||||
globalSearch: true,
|
||||
languageToggle: true,
|
||||
lockScreen: true,
|
||||
lockScreen: false,
|
||||
notification: true,
|
||||
refresh: true,
|
||||
sidebarToggle: true,
|
||||
|
||||
@@ -2,16 +2,14 @@ import type { DeepPartial } from '@vben-core/typings';
|
||||
|
||||
import type { InitialOptions, Preferences } from './types';
|
||||
|
||||
import { markRaw, reactive, readonly, watch } from 'vue';
|
||||
|
||||
import { StorageManager } from '@vben-core/shared/cache';
|
||||
import { isMacOs, merge } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
breakpointsTailwind,
|
||||
useBreakpoints,
|
||||
useDebounceFn,
|
||||
} from '@vueuse/core';
|
||||
import { markRaw, reactive, readonly, watch } from 'vue';
|
||||
|
||||
import { defaultPreferences } from './config';
|
||||
import { updateCSSVariables } from './update-css-variables';
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
BuiltinThemeType,
|
||||
ContentCompactType,
|
||||
DeepPartial,
|
||||
LayoutHeaderMenuAlignType,
|
||||
LayoutHeaderModeType,
|
||||
LayoutType,
|
||||
LoginExpiredModeType,
|
||||
@@ -104,6 +105,8 @@ interface HeaderPreferences {
|
||||
enable: boolean;
|
||||
/** 顶栏是否隐藏,css-隐藏 */
|
||||
hidden: boolean;
|
||||
/** 顶栏菜单位置 */
|
||||
menuAlign: LayoutHeaderMenuAlignType;
|
||||
/** header显示模式 */
|
||||
mode: LayoutHeaderModeType;
|
||||
}
|
||||
@@ -125,6 +128,8 @@ interface NavigationPreferences {
|
||||
}
|
||||
|
||||
interface SidebarPreferences {
|
||||
/** 点击目录时自动激活子菜单 */
|
||||
autoActivateChild: boolean;
|
||||
/** 侧边栏是否折叠 */
|
||||
collapsed: boolean;
|
||||
/** 侧边栏折叠时,是否显示title */
|
||||
@@ -163,6 +168,8 @@ interface TabbarPreferences {
|
||||
height: number;
|
||||
/** 开启标签页缓存功能 */
|
||||
keepAlive: boolean;
|
||||
/** 是否点击中键时关闭标签 */
|
||||
middleClickToClose: boolean;
|
||||
/** 是否持久化标签 */
|
||||
persist: boolean;
|
||||
/** 是否开启多标签页图标 */
|
||||
@@ -173,6 +180,8 @@ interface TabbarPreferences {
|
||||
showMore: boolean;
|
||||
/** 标签页风格 */
|
||||
styleType: TabsStyleType;
|
||||
/** 是否开启鼠标滚轮响应 */
|
||||
wheelable: boolean;
|
||||
}
|
||||
|
||||
interface ThemePreferences {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { diff } from '@vben-core/shared/utils';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { preferencesManager } from './preferences';
|
||||
import { isDarkTheme } from './update-css-variables';
|
||||
@@ -82,6 +81,20 @@ function usePreferences() {
|
||||
() => appPreferences.value.layout === 'header-nav',
|
||||
);
|
||||
|
||||
/**
|
||||
* @zh_CN 是否为头部混合导航模式
|
||||
*/
|
||||
const isHeaderMixedNav = computed(
|
||||
() => appPreferences.value.layout === 'header-mixed-nav',
|
||||
);
|
||||
|
||||
/**
|
||||
* @zh_CN 是否为顶部通栏+侧边导航模式
|
||||
*/
|
||||
const isHeaderSidebarNav = computed(
|
||||
() => appPreferences.value.layout === 'header-sidebar-nav',
|
||||
);
|
||||
|
||||
/**
|
||||
* @zh_CN 是否为混合导航模式
|
||||
*/
|
||||
@@ -93,7 +106,13 @@ function usePreferences() {
|
||||
* @zh_CN 是否包含侧边导航模式
|
||||
*/
|
||||
const isSideMode = computed(() => {
|
||||
return isMixedNav.value || isSideMixedNav.value || isSideNav.value;
|
||||
return (
|
||||
isMixedNav.value ||
|
||||
isSideMixedNav.value ||
|
||||
isSideNav.value ||
|
||||
isHeaderMixedNav.value ||
|
||||
isHeaderSidebarNav.value
|
||||
);
|
||||
});
|
||||
|
||||
const sidebarCollapsed = computed(() => {
|
||||
@@ -214,7 +233,9 @@ function usePreferences() {
|
||||
globalSearchShortcutKey,
|
||||
isDark,
|
||||
isFullContent,
|
||||
isHeaderMixedNav,
|
||||
isHeaderNav,
|
||||
isHeaderSidebarNav,
|
||||
isMixedNav,
|
||||
isMobile,
|
||||
isSideMixedNav,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/form-ui",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -3,12 +3,7 @@ import { computed, toRaw, unref, watch } from 'vue';
|
||||
|
||||
import { useSimpleLocale } from '@vben-core/composables';
|
||||
import { VbenExpandableArrow } from '@vben-core/shadcn-ui';
|
||||
import {
|
||||
cn,
|
||||
formatDate,
|
||||
isFunction,
|
||||
triggerWindowResize,
|
||||
} from '@vben-core/shared/utils';
|
||||
import { cn, isFunction, triggerWindowResize } from '@vben-core/shared/utils';
|
||||
|
||||
import { COMPONENT_MAP } from '../config';
|
||||
import { injectFormProps } from '../use-form-context';
|
||||
@@ -58,7 +53,7 @@ async function handleSubmit(e: Event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const values = handleRangeTimeValue(toRaw(form.values));
|
||||
const values = toRaw(await unref(rootProps).formApi?.getValues());
|
||||
await unref(rootProps).handleSubmit?.(values);
|
||||
}
|
||||
|
||||
@@ -67,13 +62,7 @@ async function handleReset(e: Event) {
|
||||
e?.stopPropagation();
|
||||
const props = unref(rootProps);
|
||||
|
||||
const values = toRaw(form.values);
|
||||
// 清理时间字段
|
||||
props.fieldMappingTime &&
|
||||
props.fieldMappingTime.forEach(([_, [startTimeKey, endTimeKey]]) => {
|
||||
delete values[startTimeKey];
|
||||
delete values[endTimeKey];
|
||||
});
|
||||
const values = toRaw(props.formApi?.getValues());
|
||||
|
||||
if (isFunction(props.handleReset)) {
|
||||
await props.handleReset?.(values);
|
||||
@@ -82,44 +71,6 @@ async function handleReset(e: Event) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleRangeTimeValue(values: Record<string, any>) {
|
||||
const fieldMappingTime = unref(rootProps).fieldMappingTime;
|
||||
|
||||
if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) {
|
||||
return values;
|
||||
}
|
||||
|
||||
fieldMappingTime.forEach(
|
||||
([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => {
|
||||
if (startTimeKey && endTimeKey && values[field] === null) {
|
||||
delete values[startTimeKey];
|
||||
delete values[endTimeKey];
|
||||
}
|
||||
|
||||
if (!values[field]) {
|
||||
delete values[field];
|
||||
return;
|
||||
}
|
||||
|
||||
const [startTime, endTime] = values[field];
|
||||
const [startTimeFormat, endTimeFormat] = Array.isArray(format)
|
||||
? format
|
||||
: [format, format];
|
||||
|
||||
values[startTimeKey] = startTime
|
||||
? formatDate(startTime, startTimeFormat)
|
||||
: undefined;
|
||||
values[endTimeKey] = endTime
|
||||
? formatDate(endTime, endTimeFormat)
|
||||
: undefined;
|
||||
|
||||
delete values[field];
|
||||
},
|
||||
);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => collapsed.value,
|
||||
() => {
|
||||
@@ -138,7 +89,11 @@ defineExpose({
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn('col-span-full w-full pb-6 text-right', rootProps.actionWrapperClass)
|
||||
cn(
|
||||
'col-span-full w-full text-right',
|
||||
rootProps.compact ? 'pb-2' : 'pb-6',
|
||||
rootProps.actionWrapperClass,
|
||||
)
|
||||
"
|
||||
:style="queryFormStyle"
|
||||
>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
FormCommonConfig,
|
||||
VbenFormAdapterOptions,
|
||||
} from './types';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import {
|
||||
VbenButton,
|
||||
VbenCheckbox,
|
||||
@@ -17,8 +16,8 @@ import {
|
||||
VbenSelect,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { globalShareState } from '@vben-core/shared/global-state';
|
||||
|
||||
import { defineRule } from 'vee-validate';
|
||||
import { h } from 'vue';
|
||||
|
||||
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
|
||||
|
||||
@@ -46,11 +45,15 @@ export function setupVbenForm<
|
||||
>(options: VbenFormAdapterOptions<T>) {
|
||||
const { config, defineRules } = options;
|
||||
|
||||
const { disabledOnChangeListener = false, emptyStateValue = undefined } =
|
||||
(config || {}) as FormCommonConfig;
|
||||
const {
|
||||
disabledOnChangeListener = true,
|
||||
disabledOnInputListener = true,
|
||||
emptyStateValue = undefined,
|
||||
} = (config || {}) as FormCommonConfig;
|
||||
|
||||
Object.assign(DEFAULT_FORM_COMMON_CONFIG, {
|
||||
disabledOnChangeListener,
|
||||
disabledOnInputListener,
|
||||
emptyStateValue,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Recordable } from '@vben-core/typings';
|
||||
import type {
|
||||
FormState,
|
||||
GenericObject,
|
||||
@@ -6,6 +5,8 @@ import type {
|
||||
ValidationOptions,
|
||||
} from 'vee-validate';
|
||||
|
||||
import type { Recordable } from '@vben-core/typings';
|
||||
|
||||
import type { FormActions, FormSchema, VbenFormProps } from './types';
|
||||
|
||||
import { toRaw } from 'vue';
|
||||
@@ -14,6 +15,7 @@ import { Store } from '@vben-core/shared/store';
|
||||
import {
|
||||
bindMethods,
|
||||
createMerge,
|
||||
formatDate,
|
||||
isDate,
|
||||
isDayjsObject,
|
||||
isFunction,
|
||||
@@ -45,20 +47,20 @@ function getDefaultState(): VbenFormProps {
|
||||
}
|
||||
|
||||
export class FormApi {
|
||||
// 最后一次点击提交时的表单值
|
||||
private latestSubmissionValues: null | Recordable<any> = null;
|
||||
private prevState: null | VbenFormProps = null;
|
||||
|
||||
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
|
||||
public form = {} as FormActions;
|
||||
isMounted = false;
|
||||
|
||||
public state: null | VbenFormProps = null;
|
||||
|
||||
stateHandler: StateHandler;
|
||||
|
||||
public store: Store<VbenFormProps>;
|
||||
|
||||
// 最后一次点击提交时的表单值
|
||||
private latestSubmissionValues: null | Recordable<any> = null;
|
||||
|
||||
private prevState: null | VbenFormProps = null;
|
||||
|
||||
constructor(options: VbenFormProps = {}) {
|
||||
const { ...storeState } = options;
|
||||
|
||||
@@ -83,40 +85,6 @@ export class FormApi {
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
private async getForm() {
|
||||
if (!this.isMounted) {
|
||||
// 等待form挂载
|
||||
await this.stateHandler.waitForCondition();
|
||||
}
|
||||
if (!this.form?.meta) {
|
||||
throw new Error('<VbenForm /> is not mounted');
|
||||
}
|
||||
return this.form;
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
const currentSchema = this.state?.schema ?? [];
|
||||
const prevSchema = this.prevState?.schema ?? [];
|
||||
// 进行了删除schema操作
|
||||
if (currentSchema.length < prevSchema.length) {
|
||||
const currentFields = new Set(
|
||||
currentSchema.map((item) => item.fieldName),
|
||||
);
|
||||
const deletedSchema = prevSchema.filter(
|
||||
(item) => !currentFields.has(item.fieldName),
|
||||
);
|
||||
|
||||
for (const schema of deletedSchema) {
|
||||
this.form?.setFieldValue(schema.fieldName, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果需要多次更新状态,可以使用 batch 方法
|
||||
batchStore(cb: () => void) {
|
||||
this.store.batch(cb);
|
||||
}
|
||||
|
||||
getLatestSubmissionValues() {
|
||||
return this.latestSubmissionValues || {};
|
||||
}
|
||||
@@ -127,7 +95,12 @@ export class FormApi {
|
||||
|
||||
async getValues() {
|
||||
const form = await this.getForm();
|
||||
return form.values;
|
||||
return form.values ? this.handleRangeTimeValue(form.values) : {};
|
||||
}
|
||||
|
||||
async isFieldValid(fieldName: string) {
|
||||
const form = await this.getForm();
|
||||
return form.isFieldValid(fieldName);
|
||||
}
|
||||
|
||||
merge(formApi: FormApi) {
|
||||
@@ -145,12 +118,11 @@ export class FormApi {
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
chain.map(async (api) => {
|
||||
const form = await api.getForm();
|
||||
const validateResult = await api.validate();
|
||||
if (!validateResult.valid) {
|
||||
return;
|
||||
}
|
||||
const rawValues = toRaw(form.values || {});
|
||||
const rawValues = toRaw((await api.getValues()) || {});
|
||||
return rawValues;
|
||||
}),
|
||||
);
|
||||
@@ -175,7 +147,9 @@ export class FormApi {
|
||||
if (!this.isMounted) {
|
||||
Object.assign(this.form, formActions);
|
||||
this.stateHandler.setConditionTrue();
|
||||
this.setLatestSubmissionValues({ ...toRaw(this.form.values) });
|
||||
this.setLatestSubmissionValues({
|
||||
...toRaw(this.handleRangeTimeValue(this.form.values)),
|
||||
});
|
||||
this.isMounted = true;
|
||||
}
|
||||
}
|
||||
@@ -281,7 +255,7 @@ export class FormApi {
|
||||
e?.stopPropagation();
|
||||
const form = await this.getForm();
|
||||
await form.submitForm();
|
||||
const rawValues = toRaw(form.values || {});
|
||||
const rawValues = toRaw(await this.getValues());
|
||||
await this.state?.handleSubmit?.(rawValues);
|
||||
|
||||
return rawValues;
|
||||
@@ -348,4 +322,91 @@ export class FormApi {
|
||||
}
|
||||
return await this.submitForm();
|
||||
}
|
||||
|
||||
async validateField(fieldName: string, opts?: Partial<ValidationOptions>) {
|
||||
const form = await this.getForm();
|
||||
const validateResult = await form.validateField(fieldName, opts);
|
||||
|
||||
if (Object.keys(validateResult?.errors ?? {}).length > 0) {
|
||||
console.error('validate error', validateResult?.errors);
|
||||
}
|
||||
return validateResult;
|
||||
}
|
||||
|
||||
private async getForm() {
|
||||
if (!this.isMounted) {
|
||||
// 等待form挂载
|
||||
await this.stateHandler.waitForCondition();
|
||||
}
|
||||
if (!this.form?.meta) {
|
||||
throw new Error('<VbenForm /> is not mounted');
|
||||
}
|
||||
return this.form;
|
||||
}
|
||||
|
||||
private handleRangeTimeValue = (originValues: Record<string, any>) => {
|
||||
const values = { ...originValues };
|
||||
const fieldMappingTime = this.state?.fieldMappingTime;
|
||||
|
||||
if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) {
|
||||
return values;
|
||||
}
|
||||
|
||||
fieldMappingTime.forEach(
|
||||
([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => {
|
||||
if (startTimeKey && endTimeKey && values[field] === null) {
|
||||
Reflect.deleteProperty(values, startTimeKey);
|
||||
Reflect.deleteProperty(values, endTimeKey);
|
||||
// delete values[startTimeKey];
|
||||
// delete values[endTimeKey];
|
||||
}
|
||||
|
||||
if (!values[field]) {
|
||||
Reflect.deleteProperty(values, field);
|
||||
// delete values[field];
|
||||
return;
|
||||
}
|
||||
|
||||
const [startTime, endTime] = values[field];
|
||||
if (format === null) {
|
||||
values[startTimeKey] = startTime;
|
||||
values[endTimeKey] = endTime;
|
||||
} else if (isFunction(format)) {
|
||||
values[startTimeKey] = format(startTime, startTimeKey);
|
||||
values[endTimeKey] = format(endTime, endTimeKey);
|
||||
} else {
|
||||
const [startTimeFormat, endTimeFormat] = Array.isArray(format)
|
||||
? format
|
||||
: [format, format];
|
||||
|
||||
values[startTimeKey] = startTime
|
||||
? formatDate(startTime, startTimeFormat)
|
||||
: undefined;
|
||||
values[endTimeKey] = endTime
|
||||
? formatDate(endTime, endTimeFormat)
|
||||
: undefined;
|
||||
}
|
||||
// delete values[field];
|
||||
Reflect.deleteProperty(values, field);
|
||||
},
|
||||
);
|
||||
return values;
|
||||
};
|
||||
|
||||
private updateState() {
|
||||
const currentSchema = this.state?.schema ?? [];
|
||||
const prevSchema = this.prevState?.schema ?? [];
|
||||
// 进行了删除schema操作
|
||||
if (currentSchema.length < prevSchema.length) {
|
||||
const currentFields = new Set(
|
||||
currentSchema.map((item) => item.fieldName),
|
||||
);
|
||||
const deletedSchema = prevSchema.filter(
|
||||
(item) => !currentFields.has(item.fieldName),
|
||||
);
|
||||
for (const schema of deletedSchema) {
|
||||
this.form?.setFieldValue?.(schema.fieldName, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { isEventObjectLike } from './helper';
|
||||
interface Props extends FormSchema {}
|
||||
|
||||
const {
|
||||
colon,
|
||||
commonComponentProps,
|
||||
component,
|
||||
componentProps,
|
||||
@@ -33,18 +34,20 @@ const {
|
||||
description,
|
||||
disabled,
|
||||
disabledOnChangeListener,
|
||||
disabledOnInputListener,
|
||||
emptyStateValue,
|
||||
fieldName,
|
||||
formFieldProps,
|
||||
label,
|
||||
labelClass,
|
||||
labelWidth,
|
||||
modelPropName,
|
||||
renderComponentContent,
|
||||
rules,
|
||||
} = defineProps<
|
||||
{
|
||||
Props & {
|
||||
commonComponentProps: MaybeComponentProps;
|
||||
} & Props
|
||||
}
|
||||
>();
|
||||
|
||||
const { componentBindEventMap, componentMap, isVertical } = useFormContext();
|
||||
@@ -53,7 +56,7 @@ const values = useFormValues();
|
||||
const errors = useFieldError(fieldName);
|
||||
const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
|
||||
const formApi = formRenderProps.form;
|
||||
|
||||
const compact = formRenderProps.compact;
|
||||
const isInValid = computed(() => errors.value?.length > 0);
|
||||
|
||||
const FieldComponent = computed(() => {
|
||||
@@ -200,9 +203,9 @@ function fieldBindEvent(slotProps: Record<string, any>) {
|
||||
const modelValue = slotProps.componentField.modelValue;
|
||||
const handler = slotProps.componentField['onUpdate:modelValue'];
|
||||
|
||||
const bindEventField = isString(component)
|
||||
? componentBindEventMap.value?.[component]
|
||||
: null;
|
||||
const bindEventField =
|
||||
modelPropName ||
|
||||
(isString(component) ? componentBindEventMap.value?.[component] : null);
|
||||
|
||||
let value = modelValue;
|
||||
// antd design 的一些组件会传递一个 event 对象
|
||||
@@ -227,10 +230,13 @@ function fieldBindEvent(slotProps: Record<string, any>) {
|
||||
|
||||
return onChange?.(e?.target?.[bindEventField] ?? e);
|
||||
},
|
||||
onInput: () => {},
|
||||
...(disabledOnInputListener ? { onInput: undefined } : {}),
|
||||
};
|
||||
}
|
||||
return {};
|
||||
return {
|
||||
...(disabledOnInputListener ? { onInput: undefined } : {}),
|
||||
...(disabledOnChangeListener ? { onChange: undefined } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function createComponentProps(slotProps: Record<string, any>) {
|
||||
@@ -276,8 +282,10 @@ function autofocus() {
|
||||
'form-valid-error': isInValid,
|
||||
'flex-col': isVertical,
|
||||
'flex-row items-center': !isVertical,
|
||||
'pb-6': !compact,
|
||||
'pb-2': compact,
|
||||
}"
|
||||
class="flex pb-6"
|
||||
class="flex"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<FormLabel
|
||||
@@ -296,7 +304,10 @@ function autofocus() {
|
||||
:required="shouldRequired && !hideRequiredMark"
|
||||
:style="labelStyle"
|
||||
>
|
||||
{{ label }}
|
||||
<template v-if="label">
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="colon" class="ml-[2px]">:</span>
|
||||
</template>
|
||||
</FormLabel>
|
||||
<div :class="cn('relative flex w-full items-center', wrapperClass)">
|
||||
<FormControl :class="cn(controlClass)">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { GenericObject } from 'vee-validate';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
|
||||
import type {
|
||||
@@ -13,8 +14,6 @@ import { computed } from 'vue';
|
||||
import { Form } from '@vben-core/shadcn-ui';
|
||||
import { cn, isString, mergeWithArrayOverride } from '@vben-core/shared/utils';
|
||||
|
||||
import { type GenericObject } from 'vee-validate';
|
||||
|
||||
import { provideFormRenderProps } from './context';
|
||||
import { useExpandable } from './expandable';
|
||||
import FormField from './form-field.vue';
|
||||
@@ -23,7 +22,7 @@ import { getBaseRules, getDefaultValueInZodStack } from './helper';
|
||||
interface Props extends FormRenderProps {}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ globalCommonConfig?: FormCommonConfig } & Props>(),
|
||||
defineProps<Props & { globalCommonConfig?: FormCommonConfig }>(),
|
||||
{
|
||||
collapsedRows: 1,
|
||||
commonConfig: () => ({}),
|
||||
@@ -81,15 +80,17 @@ const formCollapsed = computed(() => {
|
||||
});
|
||||
|
||||
const computedSchema = computed(
|
||||
(): ({
|
||||
(): (Omit<FormSchema, 'formFieldProps'> & {
|
||||
commonComponentProps: Record<string, any>;
|
||||
formFieldProps: Record<string, any>;
|
||||
} & Omit<FormSchema, 'formFieldProps'>)[] => {
|
||||
})[] => {
|
||||
const {
|
||||
colon = false,
|
||||
componentProps = {},
|
||||
controlClass = '',
|
||||
disabled,
|
||||
disabledOnChangeListener = false,
|
||||
disabledOnChangeListener = true,
|
||||
disabledOnInputListener = true,
|
||||
emptyStateValue = undefined,
|
||||
formFieldProps = {},
|
||||
formItemClass = '',
|
||||
@@ -97,6 +98,7 @@ const computedSchema = computed(
|
||||
hideRequiredMark = false,
|
||||
labelClass = '',
|
||||
labelWidth = 100,
|
||||
modelPropName = '',
|
||||
wrapperClass = '',
|
||||
} = mergeWithArrayOverride(props.commonConfig, props.globalCommonConfig);
|
||||
return (props.schema || []).map((schema, index) => {
|
||||
@@ -109,12 +111,15 @@ const computedSchema = computed(
|
||||
: false;
|
||||
|
||||
return {
|
||||
colon,
|
||||
disabled,
|
||||
disabledOnChangeListener,
|
||||
disabledOnInputListener,
|
||||
emptyStateValue,
|
||||
hideLabel,
|
||||
hideRequiredMark,
|
||||
labelWidth,
|
||||
modelPropName,
|
||||
wrapperClass,
|
||||
...schema,
|
||||
commonComponentProps: componentProps,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { VbenButtonProps } from '@vben-core/shadcn-ui';
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type { FieldOptions, FormContext, GenericObject } from 'vee-validate';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
|
||||
import type { FormApi } from './form-api';
|
||||
|
||||
import type { Component, HtmlHTMLAttributes, Ref } from 'vue';
|
||||
|
||||
import type { VbenButtonProps } from '@vben-core/shadcn-ui';
|
||||
import type { ClassType, MaybeComputedRef } from '@vben-core/typings';
|
||||
|
||||
import type { FormApi } from './form-api';
|
||||
|
||||
export type FormLayout = 'horizontal' | 'vertical';
|
||||
|
||||
export type BaseFormComponentType =
|
||||
@@ -19,7 +20,7 @@ export type BaseFormComponentType =
|
||||
| 'VbenSelect'
|
||||
| (Record<never, never> & string);
|
||||
|
||||
type Breakpoints = '' | '2xl:' | '3xl:' | 'lg:' | 'md:' | 'sm:' | 'xl:';
|
||||
type Breakpoints = '2xl:' | '3xl:' | '' | 'lg:' | 'md:' | 'sm:' | 'xl:';
|
||||
|
||||
type GridCols = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
|
||||
|
||||
@@ -35,12 +36,12 @@ export type FormItemClassType =
|
||||
| WrapperClassType;
|
||||
|
||||
export type FormFieldOptions = Partial<
|
||||
{
|
||||
FieldOptions & {
|
||||
validateOnBlur?: boolean;
|
||||
validateOnChange?: boolean;
|
||||
validateOnInput?: boolean;
|
||||
validateOnModelUpdate?: boolean;
|
||||
} & FieldOptions
|
||||
}
|
||||
>;
|
||||
|
||||
export interface FormShape {
|
||||
@@ -136,6 +137,10 @@ type ComponentProps =
|
||||
| MaybeComponentProps;
|
||||
|
||||
export interface FormCommonConfig {
|
||||
/**
|
||||
* 在Label后显示一个冒号
|
||||
*/
|
||||
colon?: boolean;
|
||||
/**
|
||||
* 所有表单项的props
|
||||
*/
|
||||
@@ -151,9 +156,14 @@ export interface FormCommonConfig {
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* 是否禁用所有表单项的change事件监听
|
||||
* @default false
|
||||
* @default true
|
||||
*/
|
||||
disabledOnChangeListener?: boolean;
|
||||
/**
|
||||
* 是否禁用所有表单项的input事件监听
|
||||
* @default true
|
||||
*/
|
||||
disabledOnInputListener?: boolean;
|
||||
/**
|
||||
* 所有表单项的空状态值,默认都是undefined,naive-ui的空状态值是null
|
||||
*/
|
||||
@@ -187,6 +197,11 @@ export interface FormCommonConfig {
|
||||
* 所有表单项的label宽度
|
||||
*/
|
||||
labelWidth?: number;
|
||||
/**
|
||||
* 所有表单项的model属性名
|
||||
* @default "modelValue"
|
||||
*/
|
||||
modelPropName?: string;
|
||||
/**
|
||||
* 所有表单项的wrapper样式
|
||||
*/
|
||||
@@ -209,7 +224,12 @@ export type HandleResetFn = (
|
||||
export type FieldMappingTime = [
|
||||
string,
|
||||
[string, string],
|
||||
([string, string] | string)?,
|
||||
(
|
||||
| ((value: any, fieldName: string) => any)
|
||||
| [string, string]
|
||||
| null
|
||||
| string
|
||||
)?,
|
||||
][];
|
||||
|
||||
export interface FormSchema<
|
||||
@@ -264,6 +284,10 @@ export interface FormRenderProps<
|
||||
* 表单项通用后备配置,当子项目没配置时使用这里的配置,子项目配置优先级高于此配置
|
||||
*/
|
||||
commonConfig?: FormCommonConfig;
|
||||
/**
|
||||
* 紧凑模式(移除表单每一项底部为校验信息预留的空间)
|
||||
*/
|
||||
compact?: boolean;
|
||||
/**
|
||||
* 组件v-model事件绑定
|
||||
*/
|
||||
@@ -297,7 +321,7 @@ export interface FormRenderProps<
|
||||
|
||||
export interface ActionButtonOptions extends VbenButtonProps {
|
||||
[key: string]: any;
|
||||
content?: string;
|
||||
content?: MaybeComputedRef<string>;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
@@ -316,7 +340,7 @@ export interface VbenFormProps<
|
||||
*/
|
||||
actionWrapperClass?: ClassType;
|
||||
/**
|
||||
* 表单字段映射成时间格式
|
||||
* 表单字段映射
|
||||
*/
|
||||
fieldMappingTime?: FieldMappingTime;
|
||||
/**
|
||||
@@ -359,11 +383,11 @@ export interface VbenFormProps<
|
||||
submitOnEnter?: boolean;
|
||||
}
|
||||
|
||||
export type ExtendedFormApi = {
|
||||
export type ExtendedFormApi = FormApi & {
|
||||
useStore: <T = NoInfer<VbenFormProps>>(
|
||||
selector?: (state: NoInfer<VbenFormProps>) => T,
|
||||
) => Readonly<Ref<T>>;
|
||||
} & FormApi;
|
||||
};
|
||||
|
||||
export interface VbenFormAdapterOptions<
|
||||
T extends BaseFormComponentType = BaseFormComponentType,
|
||||
@@ -371,6 +395,7 @@ export interface VbenFormAdapterOptions<
|
||||
config?: {
|
||||
baseModelPropName?: string;
|
||||
disabledOnChangeListener?: boolean;
|
||||
disabledOnInputListener?: boolean;
|
||||
emptyStateValue?: null | undefined;
|
||||
modelPropNameMap?: Partial<Record<T, string>>;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import type { FormActions, VbenFormProps } from './types';
|
||||
import type { ZodRawShape } from 'zod';
|
||||
|
||||
import { computed, type ComputedRef, unref, useSlots } from 'vue';
|
||||
import type { ComputedRef } from 'vue';
|
||||
|
||||
import type { ExtendedFormApi, FormActions, VbenFormProps } from './types';
|
||||
|
||||
import { computed, unref, useSlots } from 'vue';
|
||||
|
||||
import { createContext } from '@vben-core/shadcn-ui';
|
||||
import { isString } from '@vben-core/shared/utils';
|
||||
|
||||
import { useForm } from 'vee-validate';
|
||||
import { object, type ZodRawShape } from 'zod';
|
||||
import { object } from 'zod';
|
||||
import { getDefaultsForSchema } from 'zod-defaults';
|
||||
|
||||
type ExtendFormProps = VbenFormProps & { formApi: ExtendedFormApi };
|
||||
|
||||
export const [injectFormProps, provideFormProps] =
|
||||
createContext<[ComputedRef<VbenFormProps> | VbenFormProps, FormActions]>(
|
||||
createContext<[ComputedRef<ExtendFormProps> | ExtendFormProps, FormActions]>(
|
||||
'VbenFormProps',
|
||||
);
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
import type { ExtendedFormApi, VbenFormProps } from './types';
|
||||
|
||||
// import { toRaw, watch } from 'vue';
|
||||
|
||||
import { useForwardPriorityValues } from '@vben-core/composables';
|
||||
import { nextTick, onMounted, watch } from 'vue';
|
||||
// import { isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
import { toRaw, useTemplateRef, watch } from 'vue';
|
||||
import { useForwardPriorityValues } from '@vben-core/composables';
|
||||
import { cloneDeep } from '@vben-core/shared/utils';
|
||||
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
@@ -25,8 +25,6 @@ interface Props extends VbenFormProps {
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const formActionsRef = useTemplateRef<typeof FormActions>('formActionsRef');
|
||||
|
||||
const state = props.formApi?.useStore?.();
|
||||
|
||||
const forward = useForwardPriorityValues(props, state);
|
||||
@@ -42,11 +40,7 @@ const handleUpdateCollapsed = (value: boolean) => {
|
||||
};
|
||||
|
||||
function handleKeyDownEnter(event: KeyboardEvent) {
|
||||
if (
|
||||
!state.value.submitOnEnter ||
|
||||
!formActionsRef.value ||
|
||||
!formActionsRef.value.handleSubmit
|
||||
) {
|
||||
if (!state.value.submitOnEnter || !forward.value.formApi?.isMounted) {
|
||||
return;
|
||||
}
|
||||
// 如果是 textarea 不阻止默认行为,否则会导致无法换行。
|
||||
@@ -56,17 +50,21 @@ function handleKeyDownEnter(event: KeyboardEvent) {
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
formActionsRef.value?.handleSubmit?.();
|
||||
forward.value.formApi.validateAndSubmitForm();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => form.values,
|
||||
useDebounceFn(() => {
|
||||
forward.value.handleValuesChange?.(toRaw(form.values));
|
||||
state.value.submitOnChange && props.formApi?.submitForm();
|
||||
}, 300),
|
||||
{ deep: true },
|
||||
);
|
||||
const handleValuesChangeDebounced = useDebounceFn(async () => {
|
||||
forward.value.handleValuesChange?.(
|
||||
cloneDeep(await forward.value.formApi.getValues()),
|
||||
);
|
||||
state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
|
||||
}, 300);
|
||||
|
||||
onMounted(async () => {
|
||||
// 只在挂载后开始监听,form.values会有一个初始化的过程
|
||||
await nextTick();
|
||||
watch(() => form.values, handleValuesChangeDebounced, { deep: true });
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -90,7 +88,6 @@ watch(
|
||||
<slot v-bind="slotProps">
|
||||
<FormActions
|
||||
v-if="forward.showDefaultActions"
|
||||
ref="formActionsRef"
|
||||
:model-value="state.collapsed"
|
||||
@update:model-value="handleUpdateCollapsed"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/layout-ui",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContentCompactType } from '@vben-core/typings';
|
||||
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useLayoutContentStyle } from '@vben-core/composables';
|
||||
import { Slot } from '@vben-core/shadcn-ui';
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed, useSlots } from 'vue';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed, shallowRef, useSlots, watchEffect } from 'vue';
|
||||
|
||||
import { VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useScrollLock } from '@vueuse/core';
|
||||
import { computed, shallowRef, useSlots, watchEffect } from 'vue';
|
||||
|
||||
import { SidebarCollapseButton, SidebarFixedButton } from './widgets';
|
||||
|
||||
@@ -166,7 +165,7 @@ const headerStyle = computed((): CSSProperties => {
|
||||
|
||||
return {
|
||||
...(isSidebarMixed ? { display: 'flex', justifyContent: 'center' } : {}),
|
||||
height: `${headerHeight}px`,
|
||||
height: `${headerHeight - 1}px`,
|
||||
...contentWidthStyle.value,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -29,11 +29,23 @@ export function useLayout(props: VbenLayoutProps) {
|
||||
/**
|
||||
* 是否为混合导航模式
|
||||
*/
|
||||
const isMixedNav = computed(() => currentLayout.value === 'mixed-nav');
|
||||
const isMixedNav = computed(
|
||||
() =>
|
||||
currentLayout.value === 'mixed-nav' ||
|
||||
currentLayout.value === 'header-sidebar-nav',
|
||||
);
|
||||
|
||||
/**
|
||||
* 是否为头部混合模式
|
||||
*/
|
||||
const isHeaderMixedNav = computed(
|
||||
() => currentLayout.value === 'header-mixed-nav',
|
||||
);
|
||||
|
||||
return {
|
||||
currentLayout,
|
||||
isFullContent,
|
||||
isHeaderMixedNav,
|
||||
isHeaderNav,
|
||||
isMixedNav,
|
||||
isSidebarMixedNav,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenLayoutProps } from './vben-layout';
|
||||
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import type { VbenLayoutProps } from './vben-layout';
|
||||
|
||||
import {
|
||||
SCROLL_FIXED_CLASS,
|
||||
@@ -12,8 +11,8 @@ import {
|
||||
import { Menu } from '@vben-core/icons';
|
||||
import { VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';
|
||||
|
||||
import { useMouse, useScroll, useThrottleFn } from '@vueuse/core';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
LayoutContent,
|
||||
@@ -87,6 +86,7 @@ const { y: mouseY } = useMouse({ target: contentRef, type: 'client' });
|
||||
const {
|
||||
currentLayout,
|
||||
isFullContent,
|
||||
isHeaderMixedNav,
|
||||
isHeaderNav,
|
||||
isMixedNav,
|
||||
isSidebarMixedNav,
|
||||
@@ -112,7 +112,9 @@ const getSideCollapseWidth = computed(() => {
|
||||
const { sidebarCollapseShowTitle, sidebarMixedWidth, sideCollapseWidth } =
|
||||
props;
|
||||
|
||||
return sidebarCollapseShowTitle || isSidebarMixedNav.value
|
||||
return sidebarCollapseShowTitle ||
|
||||
isSidebarMixedNav.value ||
|
||||
isHeaderMixedNav.value
|
||||
? sidebarMixedWidth
|
||||
: sideCollapseWidth;
|
||||
});
|
||||
@@ -145,12 +147,15 @@ const getSidebarWidth = computed(() => {
|
||||
|
||||
if (
|
||||
!sidebarEnableState.value ||
|
||||
(sidebarHidden && !isSidebarMixedNav.value && !isMixedNav.value)
|
||||
(sidebarHidden &&
|
||||
!isSidebarMixedNav.value &&
|
||||
!isMixedNav.value &&
|
||||
!isHeaderMixedNav.value)
|
||||
) {
|
||||
return width;
|
||||
}
|
||||
|
||||
if (isSidebarMixedNav.value && !isMobile) {
|
||||
if ((isHeaderMixedNav.value || isSidebarMixedNav.value) && !isMobile) {
|
||||
width = sidebarMixedWidth;
|
||||
} else if (sidebarCollapse.value) {
|
||||
width = isMobile ? 0 : getSideCollapseWidth.value;
|
||||
@@ -176,7 +181,9 @@ const isSideMode = computed(
|
||||
() =>
|
||||
currentLayout.value === 'mixed-nav' ||
|
||||
currentLayout.value === 'sidebar-mixed-nav' ||
|
||||
currentLayout.value === 'sidebar-nav',
|
||||
currentLayout.value === 'sidebar-nav' ||
|
||||
currentLayout.value === 'header-mixed-nav' ||
|
||||
currentLayout.value === 'header-sidebar-nav',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -208,12 +215,13 @@ const mainStyle = computed(() => {
|
||||
headerFixed.value &&
|
||||
currentLayout.value !== 'header-nav' &&
|
||||
currentLayout.value !== 'mixed-nav' &&
|
||||
currentLayout.value !== 'header-sidebar-nav' &&
|
||||
showSidebar.value &&
|
||||
!props.isMobile
|
||||
) {
|
||||
// fixed模式下生效
|
||||
const isSideNavEffective =
|
||||
isSidebarMixedNav.value &&
|
||||
(isSidebarMixedNav.value || isHeaderMixedNav.value) &&
|
||||
sidebarExpandOnHover.value &&
|
||||
sidebarExtraVisible.value;
|
||||
|
||||
@@ -476,7 +484,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||
:extra-width="sidebarExtraWidth"
|
||||
:fixed-extra="sidebarExpandOnHover"
|
||||
:header-height="isMixedNav ? 0 : headerHeight"
|
||||
:is-sidebar-mixed="isSidebarMixedNav"
|
||||
:is-sidebar-mixed="isSidebarMixedNav || isHeaderMixedNav"
|
||||
:margin-top="sidebarMarginTop"
|
||||
:mixed-width="sidebarMixedWidth"
|
||||
:show="showSidebar"
|
||||
@@ -489,7 +497,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||
<slot name="logo"></slot>
|
||||
</template>
|
||||
|
||||
<template v-if="isSidebarMixedNav">
|
||||
<template v-if="isSidebarMixedNav || isHeaderMixedNav">
|
||||
<slot name="mixed-menu"></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/menu-ui",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UseResizeObserverReturn } from '@vueuse/core';
|
||||
|
||||
import type { SetupContext, VNodeArrayChildren } from 'vue';
|
||||
|
||||
import type {
|
||||
MenuItemClicked,
|
||||
MenuItemRegistered,
|
||||
@@ -15,7 +17,6 @@ import {
|
||||
ref,
|
||||
toRef,
|
||||
useSlots,
|
||||
type VNodeArrayChildren,
|
||||
watch,
|
||||
watchEffect,
|
||||
} from 'vue';
|
||||
@@ -54,7 +55,7 @@ const emit = defineEmits<{
|
||||
|
||||
const { b, is } = useNamespace('menu');
|
||||
const menuStyle = useMenuStyle();
|
||||
const slots = useSlots();
|
||||
const slots: SetupContext['slots'] = useSlots();
|
||||
const menu = ref<HTMLUListElement>();
|
||||
const sliceIndex = ref(-1);
|
||||
const openedMenus = ref<MenuProvider['openedMenus']>(
|
||||
@@ -332,6 +333,7 @@ function removeMenuItem(item: MenuItemRegistered) {
|
||||
is(theme, true),
|
||||
is('rounded', rounded),
|
||||
is('collapse', collapse),
|
||||
is('menu-align', mode === 'horizontal'),
|
||||
]"
|
||||
:style="menuStyle"
|
||||
role="menu"
|
||||
@@ -423,6 +425,10 @@ $namespace: vben;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.is-menu-align {
|
||||
justify-content: var(--menu-align, start);
|
||||
}
|
||||
|
||||
.#{$namespace}-menu__popup-container,
|
||||
.#{$namespace}-menu {
|
||||
--menu-title-width: 140px;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as MenuBadge } from './components/menu-badge.vue';
|
||||
export * from './components/normal-menu';
|
||||
export { default as Menu } from './menu.vue';
|
||||
export type * from './types';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { MenuRecordBadgeRaw, ThemeModeType } from '@vben-core/typings';
|
||||
|
||||
import type { Component, Ref } from 'vue';
|
||||
|
||||
interface MenuProps {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
VNodeChild,
|
||||
VNodeNormalizedChildren,
|
||||
} from 'vue';
|
||||
|
||||
import { isVNode } from 'vue';
|
||||
|
||||
type VNodeChildAtom = Exclude<VNodeChild, Array<any>>;
|
||||
|
||||
@@ -100,14 +100,18 @@ describe('drawerApi', () => {
|
||||
expect(onOpenChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should batch state updates', () => {
|
||||
const batchSpy = vi.spyOn(drawerApi.store, 'batch');
|
||||
drawerApi.batchStore(() => {
|
||||
drawerApi.setState({ title: 'Batch Title' });
|
||||
drawerApi.setState({ confirmText: 'Batch Confirm' });
|
||||
});
|
||||
expect(batchSpy).toHaveBeenCalled();
|
||||
expect(drawerApi.store.state.title).toBe('Batch Title');
|
||||
expect(drawerApi.store.state.confirmText).toBe('Batch Confirm');
|
||||
it('should call onClosed callback when provided', () => {
|
||||
const onClosed = vi.fn();
|
||||
const drawerApiWithHook = new DrawerApi({ onClosed });
|
||||
drawerApiWithHook.onClosed();
|
||||
expect(onClosed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onOpened callback when provided', () => {
|
||||
const onOpened = vi.fn();
|
||||
const drawerApiWithHook = new DrawerApi({ onOpened });
|
||||
drawerApiWithHook.open();
|
||||
drawerApiWithHook.onOpened();
|
||||
expect(onOpened).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,27 +4,34 @@ import { Store } from '@vben-core/shared/store';
|
||||
import { bindMethods, isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
export class DrawerApi {
|
||||
private api: Pick<
|
||||
DrawerApiOptions,
|
||||
'onBeforeClose' | 'onCancel' | 'onConfirm' | 'onOpenChange'
|
||||
>;
|
||||
// private prevState!: DrawerState;
|
||||
private state!: DrawerState;
|
||||
|
||||
// 共享数据
|
||||
public sharedData: Record<'payload', any> = {
|
||||
payload: {},
|
||||
};
|
||||
|
||||
public store: Store<DrawerState>;
|
||||
|
||||
private api: Pick<
|
||||
DrawerApiOptions,
|
||||
| 'onBeforeClose'
|
||||
| 'onCancel'
|
||||
| 'onClosed'
|
||||
| 'onConfirm'
|
||||
| 'onOpenChange'
|
||||
| 'onOpened'
|
||||
>;
|
||||
|
||||
// private prevState!: DrawerState;
|
||||
private state!: DrawerState;
|
||||
|
||||
constructor(options: DrawerApiOptions = {}) {
|
||||
const {
|
||||
connectedComponent: _,
|
||||
onBeforeClose,
|
||||
onCancel,
|
||||
onClosed,
|
||||
onConfirm,
|
||||
onOpenChange,
|
||||
onOpened,
|
||||
...storeState
|
||||
} = options;
|
||||
|
||||
@@ -68,17 +75,14 @@ export class DrawerApi {
|
||||
this.api = {
|
||||
onBeforeClose,
|
||||
onCancel,
|
||||
onClosed,
|
||||
onConfirm,
|
||||
onOpenChange,
|
||||
onOpened,
|
||||
};
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
// 如果需要多次更新状态,可以使用 batch 方法
|
||||
batchStore(cb: () => void) {
|
||||
this.store.batch(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
@@ -114,6 +118,15 @@ export class DrawerApi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗关闭动画播放完毕后的回调
|
||||
*/
|
||||
onClosed() {
|
||||
if (!this.state.isOpen) {
|
||||
this.api.onClosed?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认操作
|
||||
*/
|
||||
@@ -121,12 +134,22 @@ export class DrawerApi {
|
||||
this.api.onConfirm?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗打开动画播放完毕后的回调
|
||||
*/
|
||||
onOpened() {
|
||||
if (this.state.isOpen) {
|
||||
this.api.onOpened?.();
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.store.setState((prev) => ({ ...prev, isOpen: true }));
|
||||
}
|
||||
|
||||
setData<T>(payload: T) {
|
||||
this.sharedData.payload = payload;
|
||||
return this;
|
||||
}
|
||||
|
||||
setState(
|
||||
@@ -139,5 +162,6 @@ export class DrawerApi {
|
||||
} else {
|
||||
this.store.setState((prev) => ({ ...prev, ...stateOrFn }));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { Component, Ref } from 'vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import type { DrawerApi } from './drawer-api';
|
||||
|
||||
import type { Component, Ref } from 'vue';
|
||||
|
||||
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';
|
||||
|
||||
export type CloseIconPlacement = 'left' | 'right';
|
||||
|
||||
export interface DrawerProps {
|
||||
/**
|
||||
* 是否挂载到内容区域
|
||||
@@ -18,10 +20,14 @@ export interface DrawerProps {
|
||||
cancelText?: string;
|
||||
class?: ClassType;
|
||||
/**
|
||||
* 是否显示右上角的关闭按钮
|
||||
* 是否显示关闭按钮
|
||||
* @default true
|
||||
*/
|
||||
closable?: boolean;
|
||||
/**
|
||||
* 关闭按钮的位置
|
||||
*/
|
||||
closeIconPlacement?: CloseIconPlacement;
|
||||
/**
|
||||
* 点击弹窗遮罩是否关闭弹窗
|
||||
* @default true
|
||||
@@ -79,12 +85,16 @@ export interface DrawerProps {
|
||||
* 是否自动聚焦
|
||||
*/
|
||||
openAutoFocus?: boolean;
|
||||
/**
|
||||
* 弹窗遮罩模糊效果
|
||||
*/
|
||||
overlayBlur?: number;
|
||||
|
||||
/**
|
||||
* 抽屉位置
|
||||
* @default right
|
||||
*/
|
||||
placement?: DrawerPlacement;
|
||||
|
||||
/**
|
||||
* 是否显示取消按钮
|
||||
* @default true
|
||||
@@ -118,17 +128,21 @@ export interface DrawerState extends DrawerProps {
|
||||
sharedData?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type ExtendedDrawerApi = {
|
||||
export type ExtendedDrawerApi = DrawerApi & {
|
||||
useStore: <T = NoInfer<DrawerState>>(
|
||||
selector?: (state: NoInfer<DrawerState>) => T,
|
||||
) => Readonly<Ref<T>>;
|
||||
} & DrawerApi;
|
||||
};
|
||||
|
||||
export interface DrawerApiOptions extends DrawerState {
|
||||
/**
|
||||
* 独立的弹窗组件
|
||||
* 独立的抽屉组件
|
||||
*/
|
||||
connectedComponent?: Component;
|
||||
/**
|
||||
* 在关闭时销毁抽屉。仅在使用 connectedComponent 时有效
|
||||
*/
|
||||
destroyOnClose?: boolean;
|
||||
/**
|
||||
* 关闭前的回调,返回 false 可以阻止关闭
|
||||
* @returns
|
||||
@@ -138,6 +152,11 @@ export interface DrawerApiOptions extends DrawerState {
|
||||
* 点击取消按钮的回调
|
||||
*/
|
||||
onCancel?: () => void;
|
||||
/**
|
||||
* 弹窗关闭动画结束的回调
|
||||
* @returns
|
||||
*/
|
||||
onClosed?: () => void;
|
||||
/**
|
||||
* 点击确定按钮的回调
|
||||
*/
|
||||
@@ -148,4 +167,9 @@ export interface DrawerApiOptions extends DrawerState {
|
||||
* @returns
|
||||
*/
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
/**
|
||||
* 弹窗打开动画结束的回调
|
||||
* @returns
|
||||
*/
|
||||
onOpened?: () => void;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '@vben-core/composables';
|
||||
import { X } from '@vben-core/icons';
|
||||
import {
|
||||
Separator,
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
@@ -33,6 +34,7 @@ interface Props extends DrawerProps {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
appendToMain: false,
|
||||
closeIconPlacement: 'right',
|
||||
drawerApi: undefined,
|
||||
zIndex: 1000,
|
||||
});
|
||||
@@ -66,6 +68,7 @@ const {
|
||||
loading: showLoading,
|
||||
modal,
|
||||
openAutoFocus,
|
||||
overlayBlur,
|
||||
placement,
|
||||
showCancelButton,
|
||||
showConfirmButton,
|
||||
@@ -138,11 +141,14 @@ const getAppendTo = computed(() => {
|
||||
:open="state?.isOpen"
|
||||
:side="placement"
|
||||
:z-index="zIndex"
|
||||
:overlay-blur="overlayBlur"
|
||||
@close-auto-focus="handleFocusOutside"
|
||||
@closed="() => drawerApi?.onClosed()"
|
||||
@escape-key-down="escapeKeyDown"
|
||||
@focus-outside="handleFocusOutside"
|
||||
@interact-outside="interactOutside"
|
||||
@open-auto-focus="handerOpenAutoFocus"
|
||||
@opened="() => drawerApi?.onOpened()"
|
||||
@pointer-down-outside="pointerDownOutside"
|
||||
>
|
||||
<SheetHeader
|
||||
@@ -153,11 +159,29 @@ const getAppendTo = computed(() => {
|
||||
headerClass,
|
||||
{
|
||||
'px-4 py-3': closable,
|
||||
'pl-2': closable && closeIconPlacement === 'left',
|
||||
},
|
||||
)
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
<SheetClose
|
||||
v-if="closable && closeIconPlacement === 'left'"
|
||||
as-child
|
||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<slot name="close-icon">
|
||||
<VbenIconButton>
|
||||
<X class="size-4" />
|
||||
</VbenIconButton>
|
||||
</slot>
|
||||
</SheetClose>
|
||||
<Separator
|
||||
v-if="closable && closeIconPlacement === 'left'"
|
||||
class="ml-1 mr-2 h-8"
|
||||
decorative
|
||||
orientation="vertical"
|
||||
/>
|
||||
<SheetTitle v-if="title" class="text-left">
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
@@ -182,13 +206,15 @@ const getAppendTo = computed(() => {
|
||||
<div class="flex-center">
|
||||
<slot name="extra"></slot>
|
||||
<SheetClose
|
||||
v-if="closable"
|
||||
v-if="closable && closeIconPlacement === 'right'"
|
||||
as-child
|
||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<VbenIconButton>
|
||||
<X class="size-4" />
|
||||
</VbenIconButton>
|
||||
<slot name="close-icon">
|
||||
<VbenIconButton>
|
||||
<X class="size-4" />
|
||||
</VbenIconButton>
|
||||
</slot>
|
||||
</SheetClose>
|
||||
</div>
|
||||
</SheetHeader>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export type * from './drawer';
|
||||
export { default as VbenDrawer } from './drawer.vue';
|
||||
export { useVbenDrawer } from './use-drawer';
|
||||
export { setDefaultDrawerProps, useVbenDrawer } from './use-drawer';
|
||||
|
||||
@@ -4,15 +4,29 @@ import type {
|
||||
ExtendedDrawerApi,
|
||||
} from './drawer';
|
||||
|
||||
import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
inject,
|
||||
nextTick,
|
||||
provide,
|
||||
reactive,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import { useStore } from '@vben-core/shared/store';
|
||||
|
||||
import VbenDrawer from './drawer.vue';
|
||||
import { DrawerApi } from './drawer-api';
|
||||
import VbenDrawer from './drawer.vue';
|
||||
|
||||
const USER_DRAWER_INJECT_KEY = Symbol('VBEN_DRAWER_INJECT');
|
||||
|
||||
const DEFAULT_DRAWER_PROPS: Partial<DrawerProps> = {};
|
||||
|
||||
export function setDefaultDrawerProps(props: Partial<DrawerProps>) {
|
||||
Object.assign(DEFAULT_DRAWER_PROPS, props);
|
||||
}
|
||||
|
||||
export function useVbenDrawer<
|
||||
TParentDrawerProps extends DrawerProps = DrawerProps,
|
||||
>(options: DrawerApiOptions = {}) {
|
||||
@@ -22,6 +36,7 @@ export function useVbenDrawer<
|
||||
const { connectedComponent } = options;
|
||||
if (connectedComponent) {
|
||||
const extendedApi = reactive({});
|
||||
const isDrawerReady = ref(true);
|
||||
const Drawer = defineComponent(
|
||||
(props: TParentDrawerProps, { attrs, slots }) => {
|
||||
provide(USER_DRAWER_INJECT_KEY, {
|
||||
@@ -31,13 +46,23 @@ export function useVbenDrawer<
|
||||
Object.setPrototypeOf(extendedApi, api);
|
||||
},
|
||||
options,
|
||||
async reCreateDrawer() {
|
||||
isDrawerReady.value = false;
|
||||
await nextTick();
|
||||
isDrawerReady.value = true;
|
||||
},
|
||||
});
|
||||
checkProps(extendedApi as ExtendedDrawerApi, {
|
||||
...props,
|
||||
...attrs,
|
||||
...slots,
|
||||
});
|
||||
return () => h(connectedComponent, { ...props, ...attrs }, slots);
|
||||
return () =>
|
||||
h(
|
||||
isDrawerReady.value ? connectedComponent : 'div',
|
||||
{ ...props, ...attrs },
|
||||
slots,
|
||||
);
|
||||
},
|
||||
{
|
||||
inheritAttrs: false,
|
||||
@@ -50,6 +75,7 @@ export function useVbenDrawer<
|
||||
const injectData = inject<any>(USER_DRAWER_INJECT_KEY, {});
|
||||
|
||||
const mergedOptions = {
|
||||
...DEFAULT_DRAWER_PROPS,
|
||||
...injectData.options,
|
||||
...options,
|
||||
} as DrawerApiOptions;
|
||||
@@ -58,6 +84,14 @@ export function useVbenDrawer<
|
||||
options.onOpenChange?.(isOpen);
|
||||
injectData.options?.onOpenChange?.(isOpen);
|
||||
};
|
||||
|
||||
const onClosed = mergedOptions.onClosed;
|
||||
mergedOptions.onClosed = () => {
|
||||
onClosed?.();
|
||||
if (mergedOptions.destroyOnClose) {
|
||||
injectData.reCreateDrawer?.();
|
||||
}
|
||||
};
|
||||
const api = new DrawerApi(mergedOptions);
|
||||
|
||||
const extendedApi: ExtendedDrawerApi = api as never;
|
||||
|
||||
@@ -100,17 +100,6 @@ describe('modalApi', () => {
|
||||
expect(onOpenChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should batch state updates', () => {
|
||||
const batchSpy = vi.spyOn(modalApi.store, 'batch');
|
||||
modalApi.batchStore(() => {
|
||||
modalApi.setState({ title: 'Batch Title' });
|
||||
modalApi.setState({ confirmText: 'Batch Confirm' });
|
||||
});
|
||||
expect(batchSpy).toHaveBeenCalled();
|
||||
expect(modalApi.store.state.title).toBe('Batch Title');
|
||||
expect(modalApi.store.state.confirmText).toBe('Batch Confirm');
|
||||
});
|
||||
|
||||
it('should call onClosed callback when provided', () => {
|
||||
const onClosed = vi.fn();
|
||||
const modalApiWithHook = new ModalApi({ onClosed });
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export type * from './modal';
|
||||
export { default as VbenModal } from './modal.vue';
|
||||
export { useVbenModal } from './use-modal';
|
||||
export { setDefaultModalProps, useVbenModal } from './use-modal';
|
||||
|
||||
@@ -4,6 +4,12 @@ import { Store } from '@vben-core/shared/store';
|
||||
import { bindMethods, isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
export class ModalApi {
|
||||
// 共享数据
|
||||
public sharedData: Record<'payload', any> = {
|
||||
payload: {},
|
||||
};
|
||||
public store: Store<ModalState>;
|
||||
|
||||
private api: Pick<
|
||||
ModalApiOptions,
|
||||
| 'onBeforeClose'
|
||||
@@ -13,16 +19,10 @@ export class ModalApi {
|
||||
| 'onOpenChange'
|
||||
| 'onOpened'
|
||||
>;
|
||||
|
||||
// private prevState!: ModalState;
|
||||
private state!: ModalState;
|
||||
|
||||
// 共享数据
|
||||
public sharedData: Record<'payload', any> = {
|
||||
payload: {},
|
||||
};
|
||||
|
||||
public store: Store<ModalState>;
|
||||
|
||||
constructor(options: ModalApiOptions = {}) {
|
||||
const {
|
||||
connectedComponent: _,
|
||||
@@ -93,20 +93,20 @@ export class ModalApi {
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
// 如果需要多次更新状态,可以使用 batch 方法
|
||||
batchStore(cb: () => void) {
|
||||
this.store.batch(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
* @description 关闭弹窗时会调用 onBeforeClose 钩子函数,如果 onBeforeClose 返回 false,则不关闭弹窗
|
||||
*/
|
||||
close() {
|
||||
async close() {
|
||||
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
|
||||
// 如果 onBeforeClose 返回 false,则不关闭弹窗
|
||||
const allowClose = this.api.onBeforeClose?.() ?? true;
|
||||
const allowClose = (await this.api.onBeforeClose?.()) ?? true;
|
||||
if (allowClose) {
|
||||
this.store.setState((prev) => ({ ...prev, isOpen: false }));
|
||||
this.store.setState((prev) => ({
|
||||
...prev,
|
||||
isOpen: false,
|
||||
submitting: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,15 @@ export class ModalApi {
|
||||
return (this.sharedData?.payload ?? {}) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定弹窗状态(用于提交过程中的等待状态)
|
||||
* @description 锁定状态将禁用默认的取消按钮,使用spinner覆盖弹窗内容,隐藏关闭按钮,阻止手动关闭弹窗,将默认的提交按钮标记为loading状态
|
||||
* @param isLocked 是否锁定
|
||||
*/
|
||||
lock(isLocked = true) {
|
||||
return this.setState({ submitting: isLocked });
|
||||
}
|
||||
|
||||
modalLoading(loading: boolean) {
|
||||
this.store.setState((prev) => ({
|
||||
...prev,
|
||||
@@ -164,6 +173,7 @@ export class ModalApi {
|
||||
|
||||
setData<T>(payload: T) {
|
||||
this.sharedData.payload = payload;
|
||||
return this;
|
||||
}
|
||||
|
||||
setState(
|
||||
@@ -176,5 +186,6 @@ export class ModalApi {
|
||||
} else {
|
||||
this.store.setState((prev) => ({ ...prev, ...stateOrFn }));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { ModalApi } from './modal-api';
|
||||
|
||||
import type { Component, Ref } from 'vue';
|
||||
|
||||
import type { MaybePromise } from '@vben-core/typings';
|
||||
|
||||
import type { ModalApi } from './modal-api';
|
||||
|
||||
export interface ModalProps {
|
||||
/**
|
||||
* 是否要挂载到内容区域
|
||||
@@ -99,6 +101,10 @@ export interface ModalProps {
|
||||
* 是否自动聚焦
|
||||
*/
|
||||
openAutoFocus?: boolean;
|
||||
/**
|
||||
* 弹窗遮罩模糊效果
|
||||
*/
|
||||
overlayBlur?: number;
|
||||
/**
|
||||
* 是否显示取消按钮
|
||||
* @default true
|
||||
@@ -109,6 +115,10 @@ export interface ModalProps {
|
||||
* @default true
|
||||
*/
|
||||
showConfirmButton?: boolean;
|
||||
/**
|
||||
* 提交中(锁定弹窗状态)
|
||||
*/
|
||||
submitting?: boolean;
|
||||
/**
|
||||
* 弹窗标题
|
||||
*/
|
||||
@@ -132,22 +142,26 @@ export interface ModalState extends ModalProps {
|
||||
sharedData?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type ExtendedModalApi = {
|
||||
export type ExtendedModalApi = ModalApi & {
|
||||
useStore: <T = NoInfer<ModalState>>(
|
||||
selector?: (state: NoInfer<ModalState>) => T,
|
||||
) => Readonly<Ref<T>>;
|
||||
} & ModalApi;
|
||||
};
|
||||
|
||||
export interface ModalApiOptions extends ModalState {
|
||||
/**
|
||||
* 独立的弹窗组件
|
||||
*/
|
||||
connectedComponent?: Component;
|
||||
/**
|
||||
* 在关闭时销毁弹窗。仅在使用 connectedComponent 时有效
|
||||
*/
|
||||
destroyOnClose?: boolean;
|
||||
/**
|
||||
* 关闭前的回调,返回 false 可以阻止关闭
|
||||
* @returns
|
||||
*/
|
||||
onBeforeClose?: () => void;
|
||||
onBeforeClose?: () => MaybePromise<boolean | undefined>;
|
||||
/**
|
||||
* 点击取消按钮的回调
|
||||
*/
|
||||
|
||||
@@ -77,8 +77,10 @@ const {
|
||||
loading: showLoading,
|
||||
modal,
|
||||
openAutoFocus,
|
||||
overlayBlur,
|
||||
showCancelButton,
|
||||
showConfirmButton,
|
||||
submitting,
|
||||
title,
|
||||
titleTooltip,
|
||||
zIndex,
|
||||
@@ -114,9 +116,9 @@ watch(
|
||||
);
|
||||
|
||||
watch(
|
||||
() => showLoading.value,
|
||||
(v) => {
|
||||
if (v && wrapperRef.value) {
|
||||
() => [showLoading.value, submitting.value],
|
||||
([l, s]) => {
|
||||
if ((s || l) && wrapperRef.value) {
|
||||
wrapperRef.value.scrollTo({
|
||||
// behavior: 'smooth',
|
||||
top: 0,
|
||||
@@ -134,13 +136,13 @@ function handleFullscreen() {
|
||||
});
|
||||
}
|
||||
function interactOutside(e: Event) {
|
||||
if (!closeOnClickModal.value) {
|
||||
if (!closeOnClickModal.value || submitting.value) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
function escapeKeyDown(e: KeyboardEvent) {
|
||||
if (!closeOnPressEscape.value) {
|
||||
if (!closeOnPressEscape.value || submitting.value) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -155,7 +157,11 @@ function handerOpenAutoFocus(e: Event) {
|
||||
function pointerDownOutside(e: Event) {
|
||||
const target = e.target as HTMLElement;
|
||||
const isDismissableModal = target?.dataset.dismissableModal;
|
||||
if (!closeOnClickModal.value || isDismissableModal !== id) {
|
||||
if (
|
||||
!closeOnClickModal.value ||
|
||||
isDismissableModal !== id ||
|
||||
submitting.value
|
||||
) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -173,7 +179,7 @@ const getAppendTo = computed(() => {
|
||||
<Dialog
|
||||
:modal="false"
|
||||
:open="state?.isOpen"
|
||||
@update:open="() => modalApi?.close()"
|
||||
@update:open="() => (!submitting ? modalApi?.close() : undefined)"
|
||||
>
|
||||
<DialogContent
|
||||
ref="contentRef"
|
||||
@@ -194,8 +200,9 @@ const getAppendTo = computed(() => {
|
||||
"
|
||||
:modal="modal"
|
||||
:open="state?.isOpen"
|
||||
:show-close="closable"
|
||||
:show-close="submitting ? false : closable"
|
||||
:z-index="zIndex"
|
||||
:overlay-blur="overlayBlur"
|
||||
close-class="top-3"
|
||||
@close-auto-focus="handleFocusOutside"
|
||||
@closed="() => modalApi?.onClosed()"
|
||||
@@ -245,12 +252,12 @@ const getAppendTo = computed(() => {
|
||||
ref="wrapperRef"
|
||||
:class="
|
||||
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
|
||||
'pointer-events-none overflow-hidden': showLoading,
|
||||
'overflow-hidden': showLoading || submitting,
|
||||
})
|
||||
"
|
||||
>
|
||||
<VbenLoading
|
||||
v-if="showLoading"
|
||||
v-if="showLoading || submitting"
|
||||
class="size-full h-auto min-h-full"
|
||||
spinning
|
||||
/>
|
||||
@@ -285,6 +292,7 @@ const getAppendTo = computed(() => {
|
||||
:is="components.DefaultButton || VbenButton"
|
||||
v-if="showCancelButton"
|
||||
variant="ghost"
|
||||
:disabled="submitting"
|
||||
@click="() => modalApi?.onCancel()"
|
||||
>
|
||||
<slot name="cancelText">
|
||||
@@ -296,7 +304,7 @@ const getAppendTo = computed(() => {
|
||||
:is="components.PrimaryButton || VbenButton"
|
||||
v-if="showConfirmButton"
|
||||
:disabled="confirmDisabled"
|
||||
:loading="confirmLoading"
|
||||
:loading="confirmLoading || submitting"
|
||||
@click="() => modalApi?.onConfirm()"
|
||||
>
|
||||
<slot name="confirmText">
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
* 调整部分细节
|
||||
*/
|
||||
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, watchEffect } from 'vue';
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
|
||||
import { unrefElement } from '@vueuse/core';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, watchEffect } from 'vue';
|
||||
|
||||
export function useModalDraggable(
|
||||
targetRef: Ref<HTMLElement | undefined>,
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
|
||||
|
||||
import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
inject,
|
||||
nextTick,
|
||||
provide,
|
||||
reactive,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import { useStore } from '@vben-core/shared/store';
|
||||
|
||||
import VbenModal from './modal.vue';
|
||||
import { ModalApi } from './modal-api';
|
||||
import VbenModal from './modal.vue';
|
||||
|
||||
const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT');
|
||||
|
||||
const DEFAULT_MODAL_PROPS: Partial<ModalProps> = {};
|
||||
|
||||
export function setDefaultModalProps(props: Partial<ModalProps>) {
|
||||
Object.assign(DEFAULT_MODAL_PROPS, props);
|
||||
}
|
||||
|
||||
export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
||||
options: ModalApiOptions = {},
|
||||
) {
|
||||
@@ -18,6 +32,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
||||
const { connectedComponent } = options;
|
||||
if (connectedComponent) {
|
||||
const extendedApi = reactive({});
|
||||
const isModalReady = ref(true);
|
||||
const Modal = defineComponent(
|
||||
(props: TParentModalProps, { attrs, slots }) => {
|
||||
provide(USER_MODAL_INJECT_KEY, {
|
||||
@@ -27,6 +42,11 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
||||
Object.setPrototypeOf(extendedApi, api);
|
||||
},
|
||||
options,
|
||||
async reCreateModal() {
|
||||
isModalReady.value = false;
|
||||
await nextTick();
|
||||
isModalReady.value = true;
|
||||
},
|
||||
});
|
||||
checkProps(extendedApi as ExtendedModalApi, {
|
||||
...props,
|
||||
@@ -35,7 +55,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
||||
});
|
||||
return () =>
|
||||
h(
|
||||
connectedComponent,
|
||||
isModalReady.value ? connectedComponent : 'div',
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
@@ -54,6 +74,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
||||
const injectData = inject<any>(USER_MODAL_INJECT_KEY, {});
|
||||
|
||||
const mergedOptions = {
|
||||
...DEFAULT_MODAL_PROPS,
|
||||
...injectData.options,
|
||||
...options,
|
||||
} as ModalApiOptions;
|
||||
@@ -62,6 +83,15 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
||||
options.onOpenChange?.(isOpen);
|
||||
injectData.options?.onOpenChange?.(isOpen);
|
||||
};
|
||||
|
||||
const onClosed = mergedOptions.onClosed;
|
||||
|
||||
mergedOptions.onClosed = () => {
|
||||
onClosed?.();
|
||||
if (mergedOptions.destroyOnClose) {
|
||||
injectData.reCreateModal?.();
|
||||
}
|
||||
};
|
||||
const api = new ModalApi(mergedOptions);
|
||||
|
||||
const extendedApi: ExtendedModalApi = api as never;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shadcn-ui",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.2",
|
||||
"#main": "./dist/index.mjs",
|
||||
"#module": "./dist/index.mjs",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
|
||||
@@ -10,7 +10,7 @@ import { computed } from 'vue';
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '../../ui';
|
||||
|
||||
interface Props extends AvatarRootProps, AvatarFallbackProps, AvatarImageProps {
|
||||
interface Props extends AvatarFallbackProps, AvatarImageProps, AvatarRootProps {
|
||||
alt?: string;
|
||||
class?: ClassType;
|
||||
dot?: boolean;
|
||||
|
||||
@@ -32,7 +32,7 @@ const { handleClick, visible } = useBackTop(props);
|
||||
<VbenButton
|
||||
v-if="visible"
|
||||
:style="backTopStyle"
|
||||
class="dark:bg-accent dark:hover:bg-heavy bg-background hover:bg-heavy data shadow-float fixed bottom-10 z-[1000] size-10 rounded-full duration-500"
|
||||
class="dark:bg-accent dark:hover:bg-heavy bg-background hover:bg-heavy data shadow-float z-popup fixed bottom-10 size-10 rounded-full duration-500"
|
||||
size="icon"
|
||||
variant="icon"
|
||||
@click="handleClick"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { BreadcrumbStyleType } from '@vben-core/typings';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export interface IBreadcrumb {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { AsTag } from 'radix-vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { ButtonVariants, ButtonVariantSize } from '../../ui';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export interface VbenButtonProps {
|
||||
/**
|
||||
* The element or component this component should render as. Can be overwrite by `asChild`
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type {
|
||||
ContextMenuContentProps,
|
||||
ContextMenuRootEmits,
|
||||
ContextMenuRootProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import type { IContextMenuItem } from './interface';
|
||||
|
||||
import { computed } from 'vue';
|
||||
@@ -22,14 +23,14 @@ import {
|
||||
} from '../../ui/context-menu';
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
ContextMenuRootProps & {
|
||||
class?: ClassType;
|
||||
contentClass?: ClassType;
|
||||
contentProps?: ContextMenuContentProps;
|
||||
handlerData?: Record<string, any>;
|
||||
itemClass?: ClassType;
|
||||
menus: (data: any) => IContextMenuItem[];
|
||||
} & ContextMenuRootProps
|
||||
}
|
||||
>();
|
||||
|
||||
const emits = defineEmits<ContextMenuRootEmits>();
|
||||
@@ -68,7 +69,7 @@ function handleClick(menu: IContextMenuItem) {
|
||||
<ContextMenuContent
|
||||
:class="contentClass"
|
||||
v-bind="contentProps"
|
||||
class="side-content z-[1000]"
|
||||
class="side-content z-popup"
|
||||
>
|
||||
<template v-for="menu in menusView" :key="menu.key">
|
||||
<ContextMenuItem
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type {
|
||||
HoverCardContentProps,
|
||||
HoverCardRootEmits,
|
||||
HoverCardRootProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'radix-vue';
|
||||
@@ -46,7 +47,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
<HoverCardContent
|
||||
:class="contentClass"
|
||||
v-bind="contentProps"
|
||||
class="side-content z-[1000]"
|
||||
class="side-content z-popup"
|
||||
>
|
||||
<slot></slot>
|
||||
</HoverCardContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { type Component, computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { IconDefault, IconifyIcon } from '@vben-core/icons';
|
||||
import {
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isObject,
|
||||
isString,
|
||||
} from '@vben-core/shared/utils';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
// 没有是否显示默认图标
|
||||
|
||||
@@ -47,6 +47,10 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(inputValue, (val) => {
|
||||
modelValue.value = val.join('');
|
||||
});
|
||||
|
||||
function handleComplete(e: string[]) {
|
||||
modelValue.value = e.join('');
|
||||
emit('complete');
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type {
|
||||
PopoverContentProps,
|
||||
PopoverRootEmits,
|
||||
PopoverRootProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits } from 'radix-vue';
|
||||
@@ -47,7 +48,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
|
||||
<PopoverContent
|
||||
:class="contentClass"
|
||||
class="side-content z-[1000]"
|
||||
class="side-content z-popup"
|
||||
v-bind="contentProps"
|
||||
>
|
||||
<slot></slot>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { Component, PropType } from 'vue';
|
||||
import { defineComponent, h } from 'vue';
|
||||
|
||||
import { isFunction, isObject } from '@vben-core/shared/utils';
|
||||
import { defineComponent, h } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RenderContent',
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { TabsIndicatorProps } from 'radix-vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { TabsIndicator, useForwardProps } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & TabsIndicatorProps>();
|
||||
|
||||
|
||||
@@ -29,3 +29,9 @@ const props = defineProps<Props>();
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
button[role='combobox'][data-placeholder] {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -73,12 +73,23 @@ function onTransitionEnd() {
|
||||
@transitionend="onTransitionEnd"
|
||||
>
|
||||
<div
|
||||
:class="{ paused: !renderSpinner }"
|
||||
class="loader before:bg-primary/50 after:bg-primary relative size-12 before:absolute before:left-0 before:top-[60px] before:h-[5px] before:w-12 before:rounded-[50%] before:content-[''] after:absolute after:left-0 after:top-0 after:h-full after:w-full after:rounded after:content-['']"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.paused {
|
||||
&::before {
|
||||
animation-play-state: paused !important;
|
||||
}
|
||||
|
||||
&::after {
|
||||
animation-play-state: paused !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
&::before {
|
||||
animation: loader-shadow-ani 0.5s linear infinite;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '@vben-core/typings';
|
||||
import type { TooltipContentProps } from 'radix-vue';
|
||||
|
||||
import type { StyleValue } from 'vue';
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AccordionRoot,
|
||||
type AccordionRootEmits,
|
||||
type AccordionRootProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import type { AccordionRootEmits, AccordionRootProps } from 'radix-vue';
|
||||
|
||||
import { AccordionRoot, useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AccordionRootProps>();
|
||||
const emits = defineEmits<AccordionRootEmits>();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { AccordionContentProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { AccordionContent, type AccordionContentProps } from 'radix-vue';
|
||||
import { AccordionContent } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & AccordionContentProps>();
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { AccordionItemProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
AccordionItem,
|
||||
type AccordionItemProps,
|
||||
useForwardProps,
|
||||
} from 'radix-vue';
|
||||
import { AccordionItem, useForwardProps } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & AccordionItemProps>();
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { AccordionTriggerProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ChevronDown } from 'lucide-vue-next';
|
||||
import {
|
||||
AccordionHeader,
|
||||
AccordionTrigger,
|
||||
type AccordionTriggerProps,
|
||||
} from 'radix-vue';
|
||||
import { AccordionHeader, AccordionTrigger } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & AccordionTriggerProps>();
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
import type { AvatarVariants } from './avatar';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
import { AvatarRoot } from 'radix-vue';
|
||||
|
||||
import { avatarVariant, type AvatarVariants } from './avatar';
|
||||
import { avatarVariant } from './avatar';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { AvatarFallback, type AvatarFallbackProps } from 'radix-vue';
|
||||
import type { AvatarFallbackProps } from 'radix-vue';
|
||||
|
||||
import { AvatarFallback } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AvatarFallbackProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { AvatarImage, type AvatarImageProps } from 'radix-vue';
|
||||
import type { AvatarImageProps } from 'radix-vue';
|
||||
|
||||
import { AvatarImage } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AvatarImageProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const avatarVariant = cva(
|
||||
'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden',
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { BadgeVariants } from './badge';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { type BadgeVariants, badgeVariants } from './badge';
|
||||
import { badgeVariants } from './badge';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: any;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-md border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
import type { PrimitiveProps } from 'radix-vue';
|
||||
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
import { Primitive } from 'radix-vue';
|
||||
|
||||
const props = withDefaults(defineProps<{ class?: any } & PrimitiveProps>(), {
|
||||
as: 'a',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'radix-vue';
|
||||
|
||||
import type { ButtonVariants, ButtonVariantSize } from './types';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue';
|
||||
import { Primitive } from 'radix-vue';
|
||||
|
||||
import { buttonVariants } from './button';
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ export const buttonVariants = cva(
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
icon: 'h-8 w-8 rounded-sm px-1 text-lg',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-4',
|
||||
sm: 'h-8 rounded-md px-2 text-xs',
|
||||
xs: 'h-8 w-8 rounded-sm px-1 text-xs',
|
||||
},
|
||||
variant: {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import {
|
||||
CheckboxIndicator,
|
||||
CheckboxRoot,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & CheckboxRootProps>();
|
||||
const emits = defineEmits<CheckboxRootEmits>();
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type {
|
||||
ContextMenuCheckboxItemEmits,
|
||||
ContextMenuCheckboxItemProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import {
|
||||
ContextMenuCheckboxItem,
|
||||
type ContextMenuCheckboxItemEmits,
|
||||
type ContextMenuCheckboxItemProps,
|
||||
ContextMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ContextMenuCheckboxItemProps>();
|
||||
const emits = defineEmits<ContextMenuCheckboxItemEmits>();
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ContextMenuContentEmits,
|
||||
ContextMenuContentProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
ContextMenuContent,
|
||||
type ContextMenuContentEmits,
|
||||
type ContextMenuContentProps,
|
||||
ContextMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ContextMenuContentProps>();
|
||||
const props = defineProps<ContextMenuContentProps & { class?: any }>();
|
||||
const emits = defineEmits<ContextMenuContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
@@ -29,7 +32,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-border z-[1000] min-w-32 overflow-hidden rounded-md border p-1 shadow-md',
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-border z-popup min-w-32 overflow-hidden rounded-md border p-1 shadow-md',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ContextMenuGroup, type ContextMenuGroupProps } from 'radix-vue';
|
||||
import type { ContextMenuGroupProps } from 'radix-vue';
|
||||
|
||||
import { ContextMenuGroup } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuGroupProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { ContextMenuItemEmits, ContextMenuItemProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
ContextMenuItem,
|
||||
type ContextMenuItemEmits,
|
||||
type ContextMenuItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import { ContextMenuItem, useForwardPropsEmits } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: any; inset?: boolean } & ContextMenuItemProps
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { ContextMenuLabelProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { ContextMenuLabel, type ContextMenuLabelProps } from 'radix-vue';
|
||||
import { ContextMenuLabel } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: any; inset?: boolean } & ContextMenuLabelProps
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ContextMenuPortal, type ContextMenuPortalProps } from 'radix-vue';
|
||||
import type { ContextMenuPortalProps } from 'radix-vue';
|
||||
|
||||
import { ContextMenuPortal } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuPortalProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ContextMenuRadioGroup,
|
||||
type ContextMenuRadioGroupEmits,
|
||||
type ContextMenuRadioGroupProps,
|
||||
useForwardPropsEmits,
|
||||
import type {
|
||||
ContextMenuRadioGroupEmits,
|
||||
ContextMenuRadioGroupProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { ContextMenuRadioGroup, useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuRadioGroupProps>();
|
||||
const emits = defineEmits<ContextMenuRadioGroupEmits>();
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type {
|
||||
ContextMenuRadioItemEmits,
|
||||
ContextMenuRadioItemProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { Circle } from 'lucide-vue-next';
|
||||
import {
|
||||
ContextMenuItemIndicator,
|
||||
ContextMenuRadioItem,
|
||||
type ContextMenuRadioItemEmits,
|
||||
type ContextMenuRadioItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ContextMenuRadioItemProps>();
|
||||
const emits = defineEmits<ContextMenuRadioItemEmits>();
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { ContextMenuSeparatorProps } from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
ContextMenuSeparator,
|
||||
type ContextMenuSeparatorProps,
|
||||
} from 'radix-vue';
|
||||
import { ContextMenuSeparator } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & ContextMenuSeparatorProps>();
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ContextMenuSub,
|
||||
type ContextMenuSubEmits,
|
||||
type ContextMenuSubProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import type { ContextMenuSubEmits, ContextMenuSubProps } from 'radix-vue';
|
||||
|
||||
import { ContextMenuSub, useForwardPropsEmits } from 'radix-vue';
|
||||
|
||||
const props = defineProps<ContextMenuSubProps>();
|
||||
const emits = defineEmits<ContextMenuSubEmits>();
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type {
|
||||
DropdownMenuSubContentEmits,
|
||||
DropdownMenuSubContentProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
ContextMenuSubContent,
|
||||
type DropdownMenuSubContentEmits,
|
||||
type DropdownMenuSubContentProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
import { ContextMenuSubContent, useForwardPropsEmits } from 'radix-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{ class?: any } & DropdownMenuSubContentProps>();
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user