!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/access",
|
||||
"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": {
|
||||
|
@@ -23,6 +23,8 @@ async function generateAccessible(
|
||||
// 生成路由
|
||||
const accessibleRoutes = await generateRoutes(mode, options);
|
||||
|
||||
const root = router.getRoutes().find((item) => item.path === '/');
|
||||
|
||||
// 动态添加到router实例内
|
||||
accessibleRoutes.forEach((route) => {
|
||||
/**
|
||||
@@ -31,9 +33,26 @@ async function generateAccessible(
|
||||
if (/^https?:\/\//.test(route.path)) {
|
||||
return;
|
||||
}
|
||||
router.addRoute(route);
|
||||
if (root && !route.meta?.noBasicLayout) {
|
||||
// 为了兼容之前的版本用法,如果包含子路由,则将component移除,以免出现多层BasicLayout
|
||||
// 如果你的项目已经跟进了本次修改,移除了所有自定义菜单首级的BasicLayout,可以将这段if代码删除
|
||||
// TODO: 这里后期需要follow更新
|
||||
if (route.children && route.children.length > 0) {
|
||||
delete route.component;
|
||||
}
|
||||
root.children?.push(route);
|
||||
} else {
|
||||
router.addRoute(route);
|
||||
}
|
||||
});
|
||||
|
||||
if (root) {
|
||||
if (root.name) {
|
||||
router.removeRoute(root.name);
|
||||
}
|
||||
router.addRoute(root);
|
||||
}
|
||||
|
||||
// 生成菜单
|
||||
const accessibleMenus = await generateMenus(accessibleRoutes, options.router);
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/common-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": {
|
||||
@@ -42,11 +42,13 @@
|
||||
"@vueuse/integrations": "catalog:",
|
||||
"codemirror": "6.0.1",
|
||||
"qrcode": "catalog:",
|
||||
"tippy.js": "catalog:",
|
||||
"vditor": "3.10.7",
|
||||
"vue": "catalog:",
|
||||
"vue-codemirror6": "1.3.4",
|
||||
"vue-json-pretty": "^2.4.0",
|
||||
"vue-router": "catalog:"
|
||||
"vue-router": "catalog:",
|
||||
"vue-tippy": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qrcode": "catalog:"
|
||||
|
@@ -1,9 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { AnyPromiseFunction } from '@vben/types';
|
||||
|
||||
import { type Component, computed, ref, unref, useAttrs, watch } from 'vue';
|
||||
import { computed, ref, unref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { LoaderCircle } from '@vben/icons';
|
||||
|
||||
import { get, isEqual, isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
import { objectOmit } from '@vueuse/core';
|
||||
@@ -118,7 +121,7 @@ const bindProps = computed(() => {
|
||||
[`onUpdate:${props.modelPropName}`]: (val: string) => {
|
||||
modelValue.value = val;
|
||||
},
|
||||
...objectOmit(attrs, ['onUpdate:value']),
|
||||
...objectOmit(attrs, [`onUpdate:${props.modelPropName}`]),
|
||||
...(props.visibleEvent
|
||||
? {
|
||||
[props.visibleEvent]: handleFetchForVisible,
|
||||
@@ -188,18 +191,16 @@ function emitChange() {
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div v-bind="{ ...$attrs }">
|
||||
<component
|
||||
:is="component"
|
||||
v-bind="bindProps"
|
||||
:placeholder="$attrs.placeholder"
|
||||
>
|
||||
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<template v-if="loadingSlot && loading" #[loadingSlot]>
|
||||
<LoaderCircle class="animate-spin" />
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
<component
|
||||
:is="component"
|
||||
v-bind="bindProps"
|
||||
:placeholder="$attrs.placeholder"
|
||||
>
|
||||
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<template v-if="loadingSlot && loading" #[loadingSlot]>
|
||||
<LoaderCircle class="animate-spin" />
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
@@ -1,8 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { PointSelectionCaptchaCardProps } from '../types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import {
|
||||
Card,
|
||||
@@ -11,6 +9,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = withDefaults(defineProps<PointSelectionCaptchaCardProps>(), {
|
||||
height: '220px',
|
||||
|
@@ -5,12 +5,10 @@ import type {
|
||||
SliderRotateVerifyPassingData,
|
||||
} from '../types';
|
||||
|
||||
import { reactive, unref, useTemplateRef, watch, watchEffect } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
import { useTimeoutFn } from '@vueuse/core';
|
||||
import { reactive, unref, useTemplateRef, watch, watchEffect } from 'vue';
|
||||
|
||||
import SliderCaptchaAction from './slider-captcha-action.vue';
|
||||
import SliderCaptchaBar from './slider-captcha-bar.vue';
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
|
||||
import { Check, ChevronsRight } from '@vben/icons';
|
||||
import { Slot } from '@vben-core/shadcn-ui';
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
actionStyle: CSSProperties;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type CSSProperties, ref, useTemplateRef } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
barStyle: CSSProperties;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
|
||||
import { VbenSpineText } from '@vben-core/shadcn-ui';
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
contentStyle: CSSProperties;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type { ClassType } from '@vben/types';
|
||||
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
export interface CaptchaData {
|
||||
|
107
packages/effects/common-ui/src/components/col-page/col-page.vue
Normal file
107
packages/effects/common-ui/src/components/col-page/col-page.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ColPageProps } from './types';
|
||||
|
||||
import { computed, ref, useSlots } from 'vue';
|
||||
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import Page from '../page/page.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'ColPage',
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<ColPageProps>(), {
|
||||
leftWidth: 30,
|
||||
rightWidth: 70,
|
||||
resizable: true,
|
||||
});
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { leftWidth: _, ...delegated } = props;
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const delegatedSlots = computed(() => {
|
||||
const resultSlots: string[] = [];
|
||||
|
||||
for (const key of Object.keys(slots)) {
|
||||
if (!['default', 'left'].includes(key)) {
|
||||
resultSlots.push(key);
|
||||
}
|
||||
}
|
||||
return resultSlots;
|
||||
});
|
||||
|
||||
const leftPanelRef = ref<InstanceType<typeof ResizablePanel>>();
|
||||
|
||||
function expandLeft() {
|
||||
leftPanelRef.value?.expand();
|
||||
}
|
||||
|
||||
function collapseLeft() {
|
||||
leftPanelRef.value?.collapse();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
expandLeft,
|
||||
collapseLeft,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Page v-bind="delegatedProps">
|
||||
<!-- 继承默认的slot -->
|
||||
<template
|
||||
v-for="slotName in delegatedSlots"
|
||||
:key="slotName"
|
||||
#[slotName]="slotProps"
|
||||
>
|
||||
<slot :name="slotName" v-bind="slotProps"></slot>
|
||||
</template>
|
||||
|
||||
<ResizablePanelGroup class="w-full" direction="horizontal">
|
||||
<ResizablePanel
|
||||
ref="leftPanelRef"
|
||||
:collapsed-size="leftCollapsedWidth"
|
||||
:collapsible="leftCollapsible"
|
||||
:default-size="leftWidth"
|
||||
:max-size="leftMaxWidth"
|
||||
:min-size="leftMinWidth"
|
||||
>
|
||||
<template #default="slotProps">
|
||||
<slot
|
||||
name="left"
|
||||
v-bind="{
|
||||
...slotProps,
|
||||
expand: expandLeft,
|
||||
collapse: collapseLeft,
|
||||
}"
|
||||
></slot>
|
||||
</template>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle
|
||||
v-if="resizable"
|
||||
:style="{ backgroundColor: splitLine ? undefined : 'transparent' }"
|
||||
:with-handle="splitHandle"
|
||||
/>
|
||||
<ResizablePanel
|
||||
:collapsed-size="rightCollapsedWidth"
|
||||
:collapsible="rightCollapsible"
|
||||
:default-size="rightWidth"
|
||||
:max-size="rightMaxWidth"
|
||||
:min-size="rightMinWidth"
|
||||
>
|
||||
<template #default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</Page>
|
||||
</template>
|
@@ -0,0 +1,2 @@
|
||||
export { default as ColPage } from './col-page.vue';
|
||||
export * from './types';
|
26
packages/effects/common-ui/src/components/col-page/types.ts
Normal file
26
packages/effects/common-ui/src/components/col-page/types.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { PageProps } from '../page/types';
|
||||
|
||||
export interface ColPageProps extends PageProps {
|
||||
/**
|
||||
* 左侧宽度
|
||||
* @default 30
|
||||
*/
|
||||
leftWidth?: number;
|
||||
leftMinWidth?: number;
|
||||
leftMaxWidth?: number;
|
||||
leftCollapsedWidth?: number;
|
||||
leftCollapsible?: boolean;
|
||||
/**
|
||||
* 右侧宽度
|
||||
* @default 70
|
||||
*/
|
||||
rightWidth?: number;
|
||||
rightMinWidth?: number;
|
||||
rightCollapsedWidth?: number;
|
||||
rightMaxWidth?: number;
|
||||
rightCollapsible?: boolean;
|
||||
|
||||
resizable?: boolean;
|
||||
splitLine?: boolean;
|
||||
splitHandle?: boolean;
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type CSSProperties, ref, watchEffect } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import { VbenTooltip } from '@vben-core/shadcn-ui';
|
||||
import { computed, ref, watchEffect } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, h, ref, type VNode, watch, watchEffect } from 'vue';
|
||||
import type { VNode } from 'vue';
|
||||
|
||||
import { usePagination } from '@vben/hooks';
|
||||
import { EmptyIcon, Grip, listIcons } from '@vben/icons';
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
VbenIconButton,
|
||||
VbenPopover,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { refDebounced } from '@vueuse/core';
|
||||
import { computed, h, ref, watch, watchEffect } from 'vue';
|
||||
|
||||
interface Props {
|
||||
pageSize?: number;
|
||||
|
@@ -1,12 +1,14 @@
|
||||
export * from './api-component';
|
||||
export * from './captcha';
|
||||
export * from './code-mirror';
|
||||
export * from './col-page';
|
||||
export * from './ellipsis-text';
|
||||
export * from './icon-picker';
|
||||
export * from './json-preview';
|
||||
export * from './markdown';
|
||||
export * from './page';
|
||||
export * from './resize';
|
||||
export * from './tippy';
|
||||
export * from '@vben-core/form-ui';
|
||||
export * from '@vben-core/popup-ui';
|
||||
|
||||
|
@@ -1 +1,2 @@
|
||||
export { default as Page } from './page.vue';
|
||||
export * from './types';
|
||||
|
@@ -1,33 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
ref,
|
||||
type StyleValue,
|
||||
useTemplateRef,
|
||||
} from 'vue';
|
||||
import type { StyleValue } from 'vue';
|
||||
|
||||
import type { PageProps } from './types';
|
||||
|
||||
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/shared/constants';
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
contentClass?: string;
|
||||
/**
|
||||
* 根据content可见高度自适应
|
||||
*/
|
||||
autoContentHeight?: boolean;
|
||||
headerClass?: string;
|
||||
footerClass?: string;
|
||||
}
|
||||
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'Page',
|
||||
});
|
||||
|
||||
const { autoContentHeight = false } = defineProps<Props>();
|
||||
const { autoContentHeight = false } = defineProps<PageProps>();
|
||||
|
||||
const headerHeight = ref(0);
|
||||
const footerHeight = ref(0);
|
||||
@@ -100,7 +84,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="contentClass" :style="contentStyle" class="h-full p-4">
|
||||
<div :class="cn('h-full p-4', contentClass)" :style="contentStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
|
11
packages/effects/common-ui/src/components/page/types.ts
Normal file
11
packages/effects/common-ui/src/components/page/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface PageProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
contentClass?: string;
|
||||
/**
|
||||
* 根据content可见高度自适应
|
||||
*/
|
||||
autoContentHeight?: boolean;
|
||||
headerClass?: string;
|
||||
footerClass?: string;
|
||||
}
|
100
packages/effects/common-ui/src/components/tippy/directive.ts
Normal file
100
packages/effects/common-ui/src/components/tippy/directive.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import type { ComputedRef, Directive } from 'vue';
|
||||
|
||||
import { useTippy } from 'vue-tippy';
|
||||
|
||||
export default function useTippyDirective(isDark: ComputedRef<boolean>) {
|
||||
const directive: Directive = {
|
||||
mounted(el, binding, vnode) {
|
||||
const opts =
|
||||
typeof binding.value === 'string'
|
||||
? { content: binding.value }
|
||||
: binding.value || {};
|
||||
|
||||
const modifiers = Object.keys(binding.modifiers || {});
|
||||
const placement = modifiers.find((modifier) => modifier !== 'arrow');
|
||||
const withArrow = modifiers.includes('arrow');
|
||||
|
||||
if (placement) {
|
||||
opts.placement = opts.placement || placement;
|
||||
}
|
||||
|
||||
if (withArrow) {
|
||||
opts.arrow = opts.arrow === undefined ? true : opts.arrow;
|
||||
}
|
||||
|
||||
if (vnode.props && vnode.props.onTippyShow) {
|
||||
opts.onShow = function (...args: any[]) {
|
||||
return vnode.props?.onTippyShow(...args);
|
||||
};
|
||||
}
|
||||
|
||||
if (vnode.props && vnode.props.onTippyShown) {
|
||||
opts.onShown = function (...args: any[]) {
|
||||
return vnode.props?.onTippyShown(...args);
|
||||
};
|
||||
}
|
||||
|
||||
if (vnode.props && vnode.props.onTippyHidden) {
|
||||
opts.onHidden = function (...args: any[]) {
|
||||
return vnode.props?.onTippyHidden(...args);
|
||||
};
|
||||
}
|
||||
|
||||
if (vnode.props && vnode.props.onTippyHide) {
|
||||
opts.onHide = function (...args: any[]) {
|
||||
return vnode.props?.onTippyHide(...args);
|
||||
};
|
||||
}
|
||||
|
||||
if (vnode.props && vnode.props.onTippyMount) {
|
||||
opts.onMount = function (...args: any[]) {
|
||||
return vnode.props?.onTippyMount(...args);
|
||||
};
|
||||
}
|
||||
|
||||
if (el.getAttribute('title') && !opts.content) {
|
||||
opts.content = el.getAttribute('title');
|
||||
el.removeAttribute('title');
|
||||
}
|
||||
|
||||
if (el.getAttribute('content') && !opts.content) {
|
||||
opts.content = el.getAttribute('content');
|
||||
}
|
||||
|
||||
useTippy(el, opts);
|
||||
},
|
||||
unmounted(el) {
|
||||
if (el.$tippy) {
|
||||
el.$tippy.destroy();
|
||||
} else if (el._tippy) {
|
||||
el._tippy.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
updated(el, binding) {
|
||||
const opts =
|
||||
typeof binding.value === 'string'
|
||||
? { content: binding.value, theme: isDark.value ? '' : 'light' }
|
||||
: Object.assign(
|
||||
{ theme: isDark.value ? '' : 'light' },
|
||||
binding.value,
|
||||
);
|
||||
|
||||
if (el.getAttribute('title') && !opts.content) {
|
||||
opts.content = el.getAttribute('title');
|
||||
el.removeAttribute('title');
|
||||
}
|
||||
|
||||
if (el.getAttribute('content') && !opts.content) {
|
||||
opts.content = el.getAttribute('content');
|
||||
}
|
||||
|
||||
if (el.$tippy) {
|
||||
el.$tippy.setProps(opts || {});
|
||||
} else if (el._tippy) {
|
||||
el._tippy.setProps(opts || {});
|
||||
}
|
||||
},
|
||||
};
|
||||
return directive;
|
||||
}
|
67
packages/effects/common-ui/src/components/tippy/index.ts
Normal file
67
packages/effects/common-ui/src/components/tippy/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { DefaultProps, Props } from 'tippy.js';
|
||||
|
||||
import type { App, SetupContext } from 'vue';
|
||||
|
||||
import { h, watchEffect } from 'vue';
|
||||
import { setDefaultProps, Tippy as TippyComponent } from 'vue-tippy';
|
||||
|
||||
import { usePreferences } from '@vben-core/preferences';
|
||||
|
||||
import useTippyDirective from './directive';
|
||||
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import 'tippy.js/dist/backdrop.css';
|
||||
import 'tippy.js/themes/light.css';
|
||||
import 'tippy.js/animations/scale.css';
|
||||
import 'tippy.js/animations/shift-toward.css';
|
||||
import 'tippy.js/animations/shift-away.css';
|
||||
import 'tippy.js/animations/perspective.css';
|
||||
|
||||
const { isDark } = usePreferences();
|
||||
export type TippyProps = Partial<
|
||||
Props & {
|
||||
animation?:
|
||||
| 'fade'
|
||||
| 'perspective'
|
||||
| 'scale'
|
||||
| 'shift-away'
|
||||
| 'shift-toward'
|
||||
| boolean;
|
||||
theme?: 'auto' | 'dark' | 'light';
|
||||
}
|
||||
>;
|
||||
|
||||
export function initTippy(app: App<Element>, options?: DefaultProps) {
|
||||
setDefaultProps({
|
||||
allowHTML: true,
|
||||
delay: [500, 200],
|
||||
theme: isDark.value ? '' : 'light',
|
||||
...options,
|
||||
});
|
||||
if (!options || !Reflect.has(options, 'theme') || options.theme === 'auto') {
|
||||
watchEffect(() => {
|
||||
setDefaultProps({ theme: isDark.value ? '' : 'light' });
|
||||
});
|
||||
}
|
||||
|
||||
app.directive('tippy', useTippyDirective(isDark));
|
||||
}
|
||||
|
||||
export const Tippy = (props: any, { attrs, slots }: SetupContext) => {
|
||||
let theme: string = (attrs.theme as string) ?? 'auto';
|
||||
if (theme === 'auto') {
|
||||
theme = isDark.value ? '' : 'light';
|
||||
}
|
||||
if (theme === 'dark') {
|
||||
theme = '';
|
||||
}
|
||||
return h(
|
||||
TippyComponent,
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
theme,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
};
|
@@ -1,2 +1,3 @@
|
||||
export * from './components';
|
||||
export * from './ui';
|
||||
export { VbenAvatar } from '@vben-core/shadcn-ui';
|
||||
|
@@ -1,14 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { AboutProps, DescriptionItem } from './about';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import {
|
||||
VBEN_DOC_URL,
|
||||
VBEN_GITHUB_URL,
|
||||
VBEN_PREVIEW_URL,
|
||||
} from '@vben/constants';
|
||||
import { VbenRenderContent } from '@vben-core/shadcn-ui';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { Page } from '../../components';
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenFormSchema } from '@vben-core/form-ui';
|
||||
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
import { VbenButton } from '@vben-core/shadcn-ui';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenFormSchema } from '@vben-core/form-ui';
|
||||
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
import { VbenButton } from '@vben-core/shadcn-ui';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
|
@@ -1,21 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import type { AuthenticationProps } from './types';
|
||||
|
||||
import { watch } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben-core/popup-ui';
|
||||
import { Slot, VbenAvatar } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props extends AuthenticationProps {
|
||||
avatar?: string;
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'LoginExpiredModal',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
avatar: '',
|
||||
zIndex: 0,
|
||||
});
|
||||
|
||||
const open = defineModel<boolean>('open');
|
||||
@@ -28,6 +30,26 @@ watch(
|
||||
modalApi.setState({ isOpen: val });
|
||||
},
|
||||
);
|
||||
|
||||
const getZIndex = computed(() => {
|
||||
return props.zIndex || calcZIndex();
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取最大的zIndex值
|
||||
*/
|
||||
function calcZIndex() {
|
||||
let maxZ = 0;
|
||||
const elements = document.querySelectorAll('*');
|
||||
[...elements].forEach((element) => {
|
||||
const style = window.getComputedStyle(element);
|
||||
const zIndex = style.getPropertyValue('z-index');
|
||||
if (zIndex && !Number.isNaN(Number.parseInt(zIndex))) {
|
||||
maxZ = Math.max(maxZ, Number.parseInt(zIndex));
|
||||
}
|
||||
});
|
||||
return maxZ + 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -39,6 +61,7 @@ watch(
|
||||
:footer="false"
|
||||
:fullscreen-button="false"
|
||||
:header="false"
|
||||
:z-index="getZIndex"
|
||||
class="border-none px-10 py-6 text-center shadow-xl sm:w-[600px] sm:rounded-2xl md:h-[unset]"
|
||||
>
|
||||
<VbenAvatar :src="avatar" class="mx-auto mb-6 size-20" />
|
||||
|
@@ -3,13 +3,12 @@ import type { VbenFormSchema } from '@vben-core/form-ui';
|
||||
|
||||
import type { AuthenticationProps, LoginAndRegisterParams } from './types';
|
||||
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
import { VbenButton, VbenCheckbox } from '@vben-core/shadcn-ui';
|
||||
import { cloneDeep } from '@vben-core/shared/utils';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
import ThirdPartyLogin from './third-party-login.vue';
|
||||
|
@@ -1,11 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { VbenButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
|
@@ -2,12 +2,11 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
import type { VbenFormSchema } from '@vben-core/form-ui';
|
||||
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
import { VbenButton } from '@vben-core/shadcn-ui';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
|
@@ -38,7 +38,7 @@ defineEmits(['click']);
|
||||
'border-b-0': index < 3,
|
||||
'pb-4': index > 2,
|
||||
}"
|
||||
class="border-border group w-full cursor-pointer border-b border-r border-t p-4 transition-all hover:shadow-xl md:w-1/2 lg:w-1/3"
|
||||
class="border-border group w-full cursor-pointer border-r border-t p-4 transition-all hover:shadow-xl md:w-1/2 lg:w-1/3"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<VbenIcon
|
||||
|
@@ -38,7 +38,7 @@ defineEmits(['click']);
|
||||
'pb-4': index > 2,
|
||||
'border-b-0': index < 3,
|
||||
}"
|
||||
class="flex-col-center border-border group w-1/3 cursor-pointer border-b border-r border-t py-8 hover:shadow-xl"
|
||||
class="flex-col-center border-border group w-1/3 cursor-pointer border-r border-t py-8 hover:shadow-xl"
|
||||
@click="$emit('click', item)"
|
||||
>
|
||||
<VbenIcon
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { FallbackProps } from './fallback';
|
||||
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { ArrowLeft, RotateCw } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { VbenButton } from '@vben-core/shadcn-ui';
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
interface Props extends FallbackProps {}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/hooks",
|
||||
"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": {
|
||||
@@ -25,6 +25,7 @@
|
||||
"@vben/stores": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"watermark-js-plus": "catalog:"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
export * from './use-app-config';
|
||||
export * from './use-content-maximize';
|
||||
export * from './use-design-tokens';
|
||||
export * from './use-hover-toggle';
|
||||
export * from './use-pagination';
|
||||
export * from './use-refresh';
|
||||
export * from './use-tabs';
|
||||
|
@@ -25,6 +25,7 @@ export function useAntdDesignTokens() {
|
||||
colorSuccess: '',
|
||||
colorTextBase: '',
|
||||
colorWarning: '',
|
||||
zIndexPopupBase: 2000, // 调整基础弹层层级,避免下拉等组件被弹窗或者最大化状态下的表格遮挡
|
||||
});
|
||||
|
||||
const getCssVariableValue = (variable: string, isColor: boolean = true) => {
|
||||
|
61
packages/effects/hooks/src/use-hover-toggle.ts
Normal file
61
packages/effects/hooks/src/use-hover-toggle.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Arrayable, MaybeElementRef } from '@vueuse/core';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { isFunction } from '@vben/utils';
|
||||
import { useMouseInElement } from '@vueuse/core';
|
||||
import { computed, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
/**
|
||||
* 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false
|
||||
* @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true
|
||||
* @param delay 延迟更新状态的时间
|
||||
* @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用
|
||||
*/
|
||||
export function useHoverToggle(
|
||||
refElement: Arrayable<MaybeElementRef>,
|
||||
delay: (() => number) | number = 500,
|
||||
) {
|
||||
const isOutsides: Array<Ref<boolean>> = [];
|
||||
const value = ref(false);
|
||||
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
|
||||
const refs = Array.isArray(refElement) ? refElement : [refElement];
|
||||
refs.forEach((refEle) => {
|
||||
const listener = useMouseInElement(refEle, { handleOutside: true });
|
||||
isOutsides.push(listener.isOutside);
|
||||
});
|
||||
const isOutsideAll = computed(() => isOutsides.every((v) => v.value));
|
||||
|
||||
function setValueDelay(val: boolean) {
|
||||
timer.value && clearTimeout(timer.value);
|
||||
timer.value = setTimeout(
|
||||
() => {
|
||||
value.value = val;
|
||||
timer.value = undefined;
|
||||
},
|
||||
isFunction(delay) ? delay() : delay,
|
||||
);
|
||||
}
|
||||
|
||||
const watcher = watch(
|
||||
isOutsideAll,
|
||||
(val) => {
|
||||
setValueDelay(!val);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const controller = {
|
||||
enable() {
|
||||
watcher.resume();
|
||||
},
|
||||
disable() {
|
||||
watcher.pause();
|
||||
},
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
timer.value && clearTimeout(timer.value);
|
||||
});
|
||||
|
||||
return [value, controller] as [typeof value, typeof controller];
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { computed, ref, unref } from 'vue';
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { type RouteLocationNormalized, useRoute, useRouter } from 'vue-router';
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
import { useTabbarStore } from '@vben/stores';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
export function useTabs() {
|
||||
const router = useRouter();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/layouts",
|
||||
"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,14 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VNode } from 'vue';
|
||||
import type {
|
||||
RouteLocationNormalizedLoaded,
|
||||
RouteLocationNormalizedLoadedGeneric,
|
||||
} from 'vue-router';
|
||||
|
||||
import { type VNode } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
|
||||
import { preferences, usePreferences } from '@vben/preferences';
|
||||
import { storeToRefs, useTabbarStore } from '@vben/stores';
|
||||
import { RouterView } from 'vue-router';
|
||||
|
||||
import { IFrameRouterView } from '../../iframe';
|
||||
|
||||
|
@@ -1,11 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useSlots } from 'vue';
|
||||
|
||||
import { useRefresh } from '@vben/hooks';
|
||||
import { RotateCw } from '@vben/icons';
|
||||
import { preferences, usePreferences } from '@vben/preferences';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
import { VbenFullScreen, VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
import { computed, useSlots } from 'vue';
|
||||
|
||||
import {
|
||||
GlobalSearch,
|
||||
@@ -133,7 +132,10 @@ function clearPreferencesAndLogout() {
|
||||
>
|
||||
<slot :name="slot.name"></slot>
|
||||
</template>
|
||||
<div class="flex h-full min-w-0 flex-1 items-center">
|
||||
<div
|
||||
:class="`menu-align-${preferences.header.menuAlign}`"
|
||||
class="flex h-full min-w-0 flex-1 items-center"
|
||||
>
|
||||
<slot name="menu"></slot>
|
||||
</div>
|
||||
<div class="flex h-full min-w-0 flex-shrink-0 items-center">
|
||||
@@ -166,3 +168,16 @@ function clearPreferencesAndLogout() {
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.menu-align-start {
|
||||
--menu-align: start;
|
||||
}
|
||||
|
||||
.menu-align-center {
|
||||
--menu-align: center;
|
||||
}
|
||||
|
||||
.menu-align-end {
|
||||
--menu-align: end;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,10 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SetupContext } from 'vue';
|
||||
|
||||
import type { MenuRecordRaw } from '@vben/types';
|
||||
|
||||
import { computed, useSlots, watch } from 'vue';
|
||||
|
||||
import { useRefresh } from '@vben/hooks';
|
||||
import { $t } from '@vben/locales';
|
||||
import { $t, i18n } from '@vben/locales';
|
||||
import {
|
||||
preferences,
|
||||
updatePreferences,
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
} from '@vben/preferences';
|
||||
import { useLockStore } from '@vben/stores';
|
||||
import { cloneDeep, mapTree } from '@vben/utils';
|
||||
|
||||
import { VbenAdminLayout } from '@vben-core/layout-ui';
|
||||
import { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
|
||||
|
||||
@@ -39,6 +42,8 @@ const {
|
||||
isMixedNav,
|
||||
isMobile,
|
||||
isSideMixedNav,
|
||||
isHeaderMixedNav,
|
||||
isHeaderSidebarNav,
|
||||
layout,
|
||||
preferencesButtonPosition,
|
||||
sidebarCollapsed,
|
||||
@@ -80,16 +85,32 @@ const logoCollapsed = computed(() => {
|
||||
if (isMobile.value && sidebarCollapsed.value) {
|
||||
return true;
|
||||
}
|
||||
if (isHeaderNav.value || isMixedNav.value) {
|
||||
if (isHeaderNav.value || isMixedNav.value || isHeaderSidebarNav.value) {
|
||||
return false;
|
||||
}
|
||||
return sidebarCollapsed.value || isSideMixedNav.value;
|
||||
return (
|
||||
sidebarCollapsed.value || isSideMixedNav.value || isHeaderMixedNav.value
|
||||
);
|
||||
});
|
||||
|
||||
const showHeaderNav = computed(() => {
|
||||
return !isMobile.value && (isHeaderNav.value || isMixedNav.value);
|
||||
return (
|
||||
!isMobile.value &&
|
||||
(isHeaderNav.value || isMixedNav.value || isHeaderMixedNav.value)
|
||||
);
|
||||
});
|
||||
|
||||
const {
|
||||
handleMenuSelect,
|
||||
handleMenuOpen,
|
||||
headerActive,
|
||||
headerMenus,
|
||||
sidebarActive,
|
||||
sidebarMenus,
|
||||
mixHeaderMenus,
|
||||
sidebarVisible,
|
||||
} = useMixedMenu();
|
||||
|
||||
// 侧边多列菜单
|
||||
const {
|
||||
extraActiveMenu,
|
||||
@@ -99,16 +120,7 @@ const {
|
||||
handleMixedMenuSelect,
|
||||
handleSideMouseLeave,
|
||||
sidebarExtraVisible,
|
||||
} = useExtraMenu();
|
||||
|
||||
const {
|
||||
handleMenuSelect,
|
||||
headerActive,
|
||||
headerMenus,
|
||||
sidebarActive,
|
||||
sidebarMenus,
|
||||
sidebarVisible,
|
||||
} = useMixedMenu();
|
||||
} = useExtraMenu(mixHeaderMenus);
|
||||
|
||||
/**
|
||||
* 包装菜单,翻译菜单名称
|
||||
@@ -151,9 +163,10 @@ watch(
|
||||
);
|
||||
|
||||
// 语言更新后,刷新页面
|
||||
watch(() => preferences.app.locale, refresh);
|
||||
// i18n.global.locale会在preference.app.locale变更之后才会更新,因此watchpreference.app.locale是不合适的,刷新页面时可能语言配置尚未完全加载完成
|
||||
watch(i18n.global.locale, refresh, { flush: 'post' });
|
||||
|
||||
const slots = useSlots();
|
||||
const slots: SetupContext['slots'] = useSlots();
|
||||
const headerSlots = computed(() => {
|
||||
return Object.keys(slots).filter((key) => key.startsWith('header-'));
|
||||
});
|
||||
@@ -260,13 +273,14 @@ const headerSlots = computed(() => {
|
||||
:rounded="isMenuRounded"
|
||||
:theme="sidebarTheme"
|
||||
mode="vertical"
|
||||
@open="handleMenuOpen"
|
||||
@select="handleMenuSelect"
|
||||
/>
|
||||
</template>
|
||||
<template #mixed-menu>
|
||||
<LayoutMixedMenu
|
||||
:active-path="extraActiveMenu"
|
||||
:menus="wrapperMenus(headerMenus, false)"
|
||||
:menus="wrapperMenus(mixHeaderMenus, false)"
|
||||
:rounded="isMenuRounded"
|
||||
:theme="sidebarTheme"
|
||||
@default-select="handleDefaultSelect"
|
||||
|
@@ -2,9 +2,8 @@
|
||||
import type { MenuRecordRaw } from '@vben/types';
|
||||
import type { MenuProps } from '@vben-core/menu-ui';
|
||||
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { Menu } from '@vben-core/menu-ui';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useNavigation } from './use-navigation';
|
||||
|
||||
|
@@ -14,12 +14,17 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
open: [string, string[]];
|
||||
select: [string, string?];
|
||||
}>();
|
||||
|
||||
function handleMenuSelect(key: string) {
|
||||
emit('select', key, props.mode);
|
||||
}
|
||||
|
||||
function handleMenuOpen(key: string, path: string[]) {
|
||||
emit('open', key, path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -32,6 +37,7 @@ function handleMenuSelect(key: string) {
|
||||
:mode="mode"
|
||||
:rounded="rounded"
|
||||
:theme="theme"
|
||||
@open="handleMenuOpen"
|
||||
@select="handleMenuSelect"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -2,11 +2,10 @@
|
||||
import type { MenuRecordRaw } from '@vben/types';
|
||||
import type { NormalMenuProps } from '@vben-core/menu-ui';
|
||||
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { findMenuByPath } from '@vben/utils';
|
||||
import { NormalMenu } from '@vben-core/menu-ui';
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
interface Props extends NormalMenuProps {}
|
||||
|
||||
|
@@ -1,24 +1,30 @@
|
||||
import type { MenuRecordRaw } from '@vben/types';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { ComputedRef } from 'vue';
|
||||
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
import { findRootMenuByPath } from '@vben/utils';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useNavigation } from './use-navigation';
|
||||
|
||||
function useExtraMenu() {
|
||||
function useExtraMenu(useRootMenus?: ComputedRef<MenuRecordRaw[]>) {
|
||||
const accessStore = useAccessStore();
|
||||
const { navigation } = useNavigation();
|
||||
|
||||
const menus = computed(() => accessStore.accessMenus);
|
||||
const menus = computed(() => useRootMenus?.value ?? accessStore.accessMenus);
|
||||
|
||||
/** 记录当前顶级菜单下哪个子菜单最后激活 */
|
||||
const defaultSubMap = new Map<string, string>();
|
||||
const extraRootMenus = ref<MenuRecordRaw[]>([]);
|
||||
const route = useRoute();
|
||||
const extraMenus = ref<MenuRecordRaw[]>([]);
|
||||
const sidebarExtraVisible = ref<boolean>(false);
|
||||
const extraActiveMenu = ref('');
|
||||
const parentLevel = computed(() =>
|
||||
preferences.app.layout === 'header-mixed-nav' ? 1 : 0,
|
||||
);
|
||||
|
||||
/**
|
||||
* 选择混合菜单事件
|
||||
@@ -26,12 +32,18 @@ function useExtraMenu() {
|
||||
*/
|
||||
const handleMixedMenuSelect = async (menu: MenuRecordRaw) => {
|
||||
extraMenus.value = menu?.children ?? [];
|
||||
extraActiveMenu.value = menu.parents?.[0] ?? menu.path;
|
||||
extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;
|
||||
const hasChildren = extraMenus.value.length > 0;
|
||||
|
||||
sidebarExtraVisible.value = hasChildren;
|
||||
if (!hasChildren) {
|
||||
await navigation(menu.path);
|
||||
} else if (preferences.sidebar.autoActivateChild) {
|
||||
await navigation(
|
||||
defaultSubMap.has(menu.path)
|
||||
? (defaultSubMap.get(menu.path) as string)
|
||||
: menu.path,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,12 +52,12 @@ function useExtraMenu() {
|
||||
* @param menu
|
||||
* @param rootMenu
|
||||
*/
|
||||
const handleDefaultSelect = (
|
||||
const handleDefaultSelect = async (
|
||||
menu: MenuRecordRaw,
|
||||
rootMenu?: MenuRecordRaw,
|
||||
) => {
|
||||
extraMenus.value = rootMenu?.children ?? [];
|
||||
extraActiveMenu.value = menu.parents?.[0] ?? menu.path;
|
||||
extraMenus.value = rootMenu?.children ?? extraRootMenus.value ?? [];
|
||||
extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;
|
||||
|
||||
if (preferences.sidebar.expandOnHover) {
|
||||
sidebarExtraVisible.value = extraMenus.value.length > 0;
|
||||
@@ -59,7 +71,6 @@ function useExtraMenu() {
|
||||
if (preferences.sidebar.expandOnHover) {
|
||||
return;
|
||||
}
|
||||
sidebarExtraVisible.value = false;
|
||||
|
||||
const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(
|
||||
menus.value,
|
||||
@@ -73,24 +84,31 @@ function useExtraMenu() {
|
||||
if (!preferences.sidebar.expandOnHover) {
|
||||
const { findMenu } = findRootMenuByPath(menus.value, menu.path);
|
||||
extraMenus.value = findMenu?.children ?? [];
|
||||
extraActiveMenu.value = menu.parents?.[0] ?? menu.path;
|
||||
extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;
|
||||
sidebarExtraVisible.value = extraMenus.value.length > 0;
|
||||
}
|
||||
};
|
||||
|
||||
function calcExtraMenus(path: string) {
|
||||
const currentPath = route.meta?.activePath || path;
|
||||
const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(
|
||||
menus.value,
|
||||
currentPath,
|
||||
parentLevel.value,
|
||||
);
|
||||
extraRootMenus.value = rootMenu?.children ?? [];
|
||||
if (rootMenuPath) defaultSubMap.set(rootMenuPath, currentPath);
|
||||
extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? '';
|
||||
extraMenus.value = rootMenu?.children ?? [];
|
||||
if (preferences.sidebar.expandOnHover) {
|
||||
sidebarExtraVisible.value = extraMenus.value.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
const currentPath = route.meta?.activePath || path;
|
||||
// if (preferences.sidebar.expandOnHover) {
|
||||
// return;
|
||||
// }
|
||||
const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(
|
||||
menus.value,
|
||||
currentPath,
|
||||
);
|
||||
extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? '';
|
||||
extraMenus.value = rootMenu?.children ?? [];
|
||||
() => [route.path, preferences.app.layout],
|
||||
([path]) => {
|
||||
calcExtraMenus(path || '');
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
@@ -15,11 +15,16 @@ function useMixedMenu() {
|
||||
const route = useRoute();
|
||||
const splitSideMenus = ref<MenuRecordRaw[]>([]);
|
||||
const rootMenuPath = ref<string>('');
|
||||
|
||||
const { isMixedNav } = usePreferences();
|
||||
const mixedRootMenuPath = ref<string>('');
|
||||
const mixExtraMenus = ref<MenuRecordRaw[]>([]);
|
||||
/** 记录当前顶级菜单下哪个子菜单最后激活 */
|
||||
const defaultSubMap = new Map<string, string>();
|
||||
const { isMixedNav, isHeaderMixedNav } = usePreferences();
|
||||
|
||||
const needSplit = computed(
|
||||
() => preferences.navigation.split && isMixedNav.value,
|
||||
() =>
|
||||
(preferences.navigation.split && isMixedNav.value) ||
|
||||
isHeaderMixedNav.value,
|
||||
);
|
||||
|
||||
const sidebarVisible = computed(() => {
|
||||
@@ -53,6 +58,10 @@ function useMixedMenu() {
|
||||
return needSplit.value ? splitSideMenus.value : menus.value;
|
||||
});
|
||||
|
||||
const mixHeaderMenus = computed(() => {
|
||||
return isHeaderMixedNav.value ? sidebarMenus.value : headerMenus.value;
|
||||
});
|
||||
|
||||
/**
|
||||
* 侧边菜单激活路径
|
||||
*/
|
||||
@@ -86,6 +95,25 @@ function useMixedMenu() {
|
||||
splitSideMenus.value = rootMenu?.children ?? [];
|
||||
if (splitSideMenus.value.length === 0) {
|
||||
navigation(key);
|
||||
} else if (rootMenu && preferences.sidebar.autoActivateChild) {
|
||||
navigation(
|
||||
defaultSubMap.has(rootMenu.path)
|
||||
? (defaultSubMap.get(rootMenu.path) as string)
|
||||
: rootMenu.path,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 侧边菜单展开事件
|
||||
* @param key 路由路径
|
||||
* @param parentsPath 父级路径
|
||||
*/
|
||||
const handleMenuOpen = (key: string, parentsPath: string[]) => {
|
||||
if (parentsPath.length <= 1 && preferences.sidebar.autoActivateChild) {
|
||||
navigation(
|
||||
defaultSubMap.has(key) ? (defaultSubMap.get(key) as string) : key,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,6 +126,9 @@ function useMixedMenu() {
|
||||
if (!rootMenu) {
|
||||
rootMenu = menus.value.find((item) => item.path === path);
|
||||
}
|
||||
const result = findRootMenuByPath(rootMenu?.children || [], path, 1);
|
||||
mixedRootMenuPath.value = result.rootMenuPath ?? '';
|
||||
mixExtraMenus.value = result.rootMenu?.children ?? [];
|
||||
rootMenuPath.value = rootMenu?.path ?? '';
|
||||
splitSideMenus.value = rootMenu?.children ?? [];
|
||||
}
|
||||
@@ -107,6 +138,8 @@ function useMixedMenu() {
|
||||
(path) => {
|
||||
const currentPath = (route?.meta?.activePath as string) ?? path;
|
||||
calcSideMenus(currentPath);
|
||||
if (rootMenuPath.value)
|
||||
defaultSubMap.set(rootMenuPath.value, currentPath);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -118,10 +151,13 @@ function useMixedMenu() {
|
||||
|
||||
return {
|
||||
handleMenuSelect,
|
||||
handleMenuOpen,
|
||||
headerActive,
|
||||
headerMenus,
|
||||
sidebarActive,
|
||||
sidebarMenus,
|
||||
mixHeaderMenus,
|
||||
mixExtraMenus,
|
||||
sidebarVisible,
|
||||
};
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { type RouteRecordNormalized, useRouter } from 'vue-router';
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
|
||||
import { isHttpUrl, openRouteInNewWindow, openWindow } from '@vben/utils';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
function useNavigation() {
|
||||
const router = useRouter();
|
||||
|
@@ -5,6 +5,7 @@ import { useRoute } from 'vue-router';
|
||||
import { useContentMaximize, useTabs } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useTabbarStore } from '@vben/stores';
|
||||
|
||||
import { TabsToolMore, TabsToolScreen, TabsView } from '@vben-core/tabs-ui';
|
||||
|
||||
import { useTabbar } from './use-tabbar';
|
||||
@@ -55,6 +56,8 @@ if (!preferences.tabbar.persist) {
|
||||
:show-icon="showIcon"
|
||||
:style-type="preferences.tabbar.styleType"
|
||||
:tabs="currentTabs"
|
||||
:wheelable="preferences.tabbar.wheelable"
|
||||
:middle-click-to-close="preferences.tabbar.middleClickToClose"
|
||||
@close="handleClose"
|
||||
@sort-tabs="tabbarStore.sortTabs"
|
||||
@unpin="unpinTab"
|
||||
|
@@ -2,9 +2,6 @@ import type { TabDefinition } from '@vben/types';
|
||||
import type { IContextMenuItem } from '@vben-core/tabs-ui';
|
||||
import type { RouteLocationNormalizedGeneric } from 'vue-router';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { useContentMaximize, useTabs } from '@vben/hooks';
|
||||
import {
|
||||
ArrowLeftToLine,
|
||||
@@ -22,6 +19,8 @@ import {
|
||||
import { $t, useI18n } from '@vben/locales';
|
||||
import { useAccessStore, useTabbarStore } from '@vben/stores';
|
||||
import { filterTree } from '@vben/utils';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
export function useTabbar() {
|
||||
const router = useRouter();
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useTabbarStore } from '@vben/stores';
|
||||
import { VbenSpinner } from '@vben-core/shadcn-ui';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
defineOptions({ name: 'IFrameRouterView' });
|
||||
|
||||
|
@@ -2,11 +2,10 @@
|
||||
import type { BreadcrumbStyleType } from '@vben/types';
|
||||
import type { IBreadcrumb } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { VbenBreadcrumbView } from '@vben-core/shadcn-ui';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
interface Props {
|
||||
hideWhenOnlyOne?: boolean;
|
||||
|
@@ -1,8 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useVbenModal } from '@vben-core/popup-ui';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
interface Props {
|
||||
// 轮训时间,分钟
|
||||
|
@@ -1,8 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenuRecordRaw } from '@vben/types';
|
||||
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
@@ -13,8 +11,8 @@ import {
|
||||
import { $t } from '@vben/locales';
|
||||
import { isWindowsOs } from '@vben/utils';
|
||||
import { useVbenModal } from '@vben-core/popup-ui';
|
||||
|
||||
import { useMagicKeys, whenever } from '@vueuse/core';
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import SearchPanel from './search-panel.vue';
|
||||
|
||||
|
@@ -1,16 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenuRecordRaw } from '@vben/types';
|
||||
|
||||
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { SearchX, X } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { mapTree, traverseTreeValues, uniqueByField } from '@vben/utils';
|
||||
import { VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
import { isHttpUrl } from '@vben-core/shared/utils';
|
||||
|
||||
import { onKeyStroke, useLocalStorage, useThrottleFn } from '@vueuse/core';
|
||||
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
defineOptions({
|
||||
name: 'SearchPanel',
|
||||
|
@@ -2,8 +2,6 @@
|
||||
import type { AuthPageLayoutType } from '@vben/types';
|
||||
import type { VbenDropdownMenuItem } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { InspectionPanel, PanelLeft, PanelRight } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import {
|
||||
@@ -12,6 +10,7 @@ import {
|
||||
usePreferences,
|
||||
} from '@vben/preferences';
|
||||
import { VbenDropdownRadioMenu, VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
import { computed } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationLayoutToggle',
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, reactive } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useVbenForm, z } from '@vben-core/form-ui';
|
||||
import { useVbenModal } from '@vben-core/popup-ui';
|
||||
import { VbenAvatar, VbenButton } from '@vben-core/shadcn-ui';
|
||||
import { computed, reactive } from 'vue';
|
||||
|
||||
interface Props {
|
||||
avatar?: string;
|
||||
|
@@ -1,14 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
|
||||
import { LockKeyhole } from '@vben/icons';
|
||||
import { $t, useI18n } from '@vben/locales';
|
||||
import { storeToRefs, useLockStore } from '@vben/stores';
|
||||
import { useScrollLock } from '@vben-core/composables';
|
||||
import { useVbenForm, z } from '@vben-core/form-ui';
|
||||
import { VbenAvatar, VbenButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useDateFormat, useNow } from '@vueuse/core';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
|
||||
interface Props {
|
||||
avatar?: string;
|
||||
|
@@ -9,7 +9,6 @@ import {
|
||||
VbenPopover,
|
||||
VbenScrollbar,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useToggle } from '@vueuse/core';
|
||||
|
||||
interface Props {
|
||||
|
@@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { SelectOption } from '@vben/types';
|
||||
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
import { CircleHelp } from '@vben/icons';
|
||||
import { Input, VbenTooltip } from '@vben-core/shadcn-ui';
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceSelectItem',
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { type Component, computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { ContentCompact, ContentWide } from '../../icons';
|
||||
|
||||
|
@@ -1,15 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { LayoutHeaderModeType, SelectOption } from '@vben/types';
|
||||
import type {
|
||||
LayoutHeaderMenuAlignType,
|
||||
LayoutHeaderModeType,
|
||||
SelectOption,
|
||||
} from '@vben/types';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import SelectItem from '../select-item.vue';
|
||||
import SwitchItem from '../switch-item.vue';
|
||||
import ToggleItem from '../toggle-item.vue';
|
||||
|
||||
defineProps<{ disabled: boolean }>();
|
||||
|
||||
const headerEnable = defineModel<boolean>('headerEnable');
|
||||
const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
|
||||
const headerMenuAlign =
|
||||
defineModel<LayoutHeaderMenuAlignType>('headerMenuAlign');
|
||||
|
||||
const localeItems: SelectOption[] = [
|
||||
{
|
||||
@@ -29,6 +36,21 @@ const localeItems: SelectOption[] = [
|
||||
value: 'auto-scroll',
|
||||
},
|
||||
];
|
||||
|
||||
const headerMenuAlignItems: SelectOption[] = [
|
||||
{
|
||||
label: $t('preferences.header.menuAlignStart'),
|
||||
value: 'start',
|
||||
},
|
||||
{
|
||||
label: $t('preferences.header.menuAlignCenter'),
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
label: $t('preferences.header.menuAlignEnd'),
|
||||
value: 'end',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -42,4 +64,11 @@ const localeItems: SelectOption[] = [
|
||||
>
|
||||
{{ $t('preferences.mode') }}
|
||||
</SelectItem>
|
||||
<ToggleItem
|
||||
v-model="headerMenuAlign"
|
||||
:disabled="!headerEnable"
|
||||
:items="headerMenuAlignItems"
|
||||
>
|
||||
{{ $t('preferences.header.menuAlign') }}
|
||||
</ToggleItem>
|
||||
</template>
|
||||
|
@@ -1,15 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { LayoutType } from '@vben/types';
|
||||
|
||||
import { type Component, computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { CircleHelp } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { VbenTooltip } from '@vben-core/shadcn-ui';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import {
|
||||
FullContent,
|
||||
HeaderMixedNav,
|
||||
HeaderNav,
|
||||
HeaderSidebarNav,
|
||||
MixedNav,
|
||||
SidebarMixedNav,
|
||||
SidebarNav,
|
||||
@@ -33,6 +35,8 @@ const components: Record<LayoutType, Component> = {
|
||||
'mixed-nav': MixedNav,
|
||||
'sidebar-mixed-nav': SidebarMixedNav,
|
||||
'sidebar-nav': SidebarNav,
|
||||
'header-mixed-nav': HeaderMixedNav,
|
||||
'header-sidebar-nav': HeaderSidebarNav,
|
||||
};
|
||||
|
||||
const PRESET = computed((): PresetItem[] => [
|
||||
@@ -51,11 +55,21 @@ const PRESET = computed((): PresetItem[] => [
|
||||
tip: $t('preferences.horizontalTip'),
|
||||
type: 'header-nav',
|
||||
},
|
||||
{
|
||||
name: $t('preferences.headerSidebarNav'),
|
||||
tip: $t('preferences.headerSidebarNavTip'),
|
||||
type: 'header-sidebar-nav',
|
||||
},
|
||||
{
|
||||
name: $t('preferences.mixedMenu'),
|
||||
tip: $t('preferences.mixedMenuTip'),
|
||||
type: 'mixed-nav',
|
||||
},
|
||||
{
|
||||
name: $t('preferences.headerTwoColumn'),
|
||||
tip: $t('preferences.headerTwoColumnTip'),
|
||||
type: 'header-mixed-nav',
|
||||
},
|
||||
{
|
||||
name: $t('preferences.fullContent'),
|
||||
tip: $t('preferences.fullContentTip'),
|
||||
|
@@ -1,17 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import type { LayoutType } from '@vben/types';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import NumberFieldItem from '../number-field-item.vue';
|
||||
import SwitchItem from '../switch-item.vue';
|
||||
|
||||
defineProps<{ disabled: boolean }>();
|
||||
defineProps<{ currentLayout?: LayoutType; disabled: boolean }>();
|
||||
|
||||
const sidebarEnable = defineModel<boolean>('sidebarEnable');
|
||||
const sidebarWidth = defineModel<number>('sidebarWidth');
|
||||
const sidebarCollapsedShowTitle = defineModel<boolean>(
|
||||
'sidebarCollapsedShowTitle',
|
||||
);
|
||||
const sidebarAutoActivateChild = defineModel<boolean>(
|
||||
'sidebarAutoActivateChild',
|
||||
);
|
||||
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
|
||||
const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -21,12 +27,32 @@ const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
|
||||
<SwitchItem v-model="sidebarCollapsed" :disabled="!sidebarEnable || disabled">
|
||||
{{ $t('preferences.sidebar.collapsed') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem
|
||||
v-model="sidebarExpandOnHover"
|
||||
:disabled="!sidebarEnable || disabled || !sidebarCollapsed"
|
||||
:tip="$t('preferences.sidebar.expandOnHoverTip')"
|
||||
>
|
||||
{{ $t('preferences.sidebar.expandOnHover') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem
|
||||
v-model="sidebarCollapsedShowTitle"
|
||||
:disabled="!sidebarEnable || disabled || !sidebarCollapsed"
|
||||
>
|
||||
{{ $t('preferences.sidebar.collapsedShowTitle') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem
|
||||
v-model="sidebarAutoActivateChild"
|
||||
:disabled="
|
||||
!sidebarEnable ||
|
||||
!['sidebar-mixed-nav', 'mixed-nav', 'header-mixed-nav'].includes(
|
||||
currentLayout as string,
|
||||
) ||
|
||||
disabled
|
||||
"
|
||||
:tip="$t('preferences.sidebar.autoActivateChildTip')"
|
||||
>
|
||||
{{ $t('preferences.sidebar.autoActivateChild') }}
|
||||
</SwitchItem>
|
||||
<NumberFieldItem
|
||||
v-model="sidebarWidth"
|
||||
:disabled="!sidebarEnable || disabled"
|
||||
|
@@ -18,9 +18,13 @@ const tabbarEnable = defineModel<boolean>('tabbarEnable');
|
||||
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
|
||||
const tabbarPersist = defineModel<boolean>('tabbarPersist');
|
||||
const tabbarDraggable = defineModel<boolean>('tabbarDraggable');
|
||||
const tabbarWheelable = defineModel<boolean>('tabbarWheelable');
|
||||
const tabbarStyleType = defineModel<string>('tabbarStyleType');
|
||||
const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
|
||||
const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
|
||||
const tabbarMiddleClickToClose = defineModel<boolean>(
|
||||
'tabbarMiddleClickToClose',
|
||||
);
|
||||
|
||||
const styleItems = computed((): SelectOption[] => [
|
||||
{
|
||||
@@ -53,6 +57,16 @@ const styleItems = computed((): SelectOption[] => [
|
||||
<SwitchItem v-model="tabbarDraggable" :disabled="!tabbarEnable">
|
||||
{{ $t('preferences.tabbar.draggable') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem
|
||||
v-model="tabbarWheelable"
|
||||
:disabled="!tabbarEnable"
|
||||
:tip="$t('preferences.tabbar.wheelableTip')"
|
||||
>
|
||||
{{ $t('preferences.tabbar.wheelable') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="tabbarMiddleClickToClose" :disabled="!tabbarEnable">
|
||||
{{ $t('preferences.tabbar.middleClickClose') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
|
||||
{{ $t('preferences.tabbar.icon') }}
|
||||
</SwitchItem>
|
||||
|
@@ -1,8 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { SelectOption } from '@vben/types';
|
||||
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
import { CircleHelp } from '@vben/icons';
|
||||
import {
|
||||
NumberField,
|
||||
@@ -12,6 +10,7 @@ import {
|
||||
NumberFieldInput,
|
||||
VbenTooltip,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceSelectItem',
|
||||
|
@@ -1,8 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { SelectOption } from '@vben/types';
|
||||
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
import { CircleHelp } from '@vben/icons';
|
||||
import {
|
||||
Select,
|
||||
@@ -12,6 +10,7 @@ import {
|
||||
SelectValue,
|
||||
VbenTooltip,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceSelectItem',
|
||||
|
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
import { CircleHelp } from '@vben/icons';
|
||||
import { Switch, VbenTooltip } from '@vben-core/shadcn-ui';
|
||||
import { useSlots } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceSwitchItem',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<{ disabled?: boolean }>(), {
|
||||
withDefaults(defineProps<{ disabled?: boolean; tip?: string }>(), {
|
||||
disabled: false,
|
||||
tip: '',
|
||||
});
|
||||
|
||||
const checked = defineModel<boolean>();
|
||||
@@ -32,11 +32,17 @@ function handleClick() {
|
||||
<span class="flex items-center text-sm">
|
||||
<slot></slot>
|
||||
|
||||
<VbenTooltip v-if="slots.tip" side="bottom">
|
||||
<VbenTooltip v-if="slots.tip || tip" side="bottom">
|
||||
<template #trigger>
|
||||
<CircleHelp class="ml-1 size-3 cursor-help" />
|
||||
</template>
|
||||
<slot name="tip"></slot>
|
||||
<slot name="tip">
|
||||
<template v-if="tip">
|
||||
<p v-for="(line, index) in tip.split('\n')" :key="index">
|
||||
{{ line }}
|
||||
</p>
|
||||
</template>
|
||||
</slot>
|
||||
</VbenTooltip>
|
||||
</span>
|
||||
<span v-if="$slots.shortcut" class="ml-auto mr-2 text-xs opacity-60">
|
||||
|
@@ -1,14 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { BuiltinThemePreset } from '@vben/preferences';
|
||||
import type { BuiltinThemeType } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { UserRoundPen } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import {
|
||||
BUILT_IN_THEME_PRESETS,
|
||||
type BuiltinThemePreset,
|
||||
} from '@vben/preferences';
|
||||
import { BUILT_IN_THEME_PRESETS } from '@vben/preferences';
|
||||
import { convertToHsl, TinyColor } from '@vben/utils';
|
||||
|
||||
defineOptions({
|
||||
@@ -82,11 +80,6 @@ function typeView(name: BuiltinThemeType) {
|
||||
|
||||
function handleSelect(theme: BuiltinThemePreset) {
|
||||
modelValue.value = theme.type;
|
||||
const primaryColor = props.isDark
|
||||
? theme.darkPrimaryColor || theme.primaryColor
|
||||
: theme.primaryColor;
|
||||
|
||||
themeColorPrimary.value = primaryColor || theme.color;
|
||||
}
|
||||
|
||||
function handleInputChange(e: Event) {
|
||||
@@ -97,6 +90,22 @@ function handleInputChange(e: Event) {
|
||||
function selectColor() {
|
||||
colorInput.value?.[0]?.click?.();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [modelValue.value, props.isDark] as [BuiltinThemeType, boolean],
|
||||
([themeType, isDark]) => {
|
||||
const theme = builtinThemePresets.value.find(
|
||||
(item) => item.type === themeType,
|
||||
);
|
||||
if (theme) {
|
||||
const primaryColor = isDark
|
||||
? theme.darkPrimaryColor || theme.primaryColor
|
||||
: theme.primaryColor;
|
||||
|
||||
themeColorPrimary.value = primaryColor || theme.color;
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { ThemeModeType } from '@vben/types';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { MoonStar, Sun, SunMoon } from '@vben/icons';
|
||||
|
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<svg
|
||||
class="custom-radio-image"
|
||||
fill="none"
|
||||
height="66"
|
||||
width="104"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<rect
|
||||
id="svg_1"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.02"
|
||||
height="66"
|
||||
rx="4"
|
||||
stroke="null"
|
||||
width="104"
|
||||
x="0.13514"
|
||||
y="0.13514"
|
||||
/>
|
||||
<path
|
||||
id="svg_2"
|
||||
d="m-3.37838,3.7543a1.93401,4.02457 0 0 1 1.93401,-4.02457l11.3488,0l0,66.40541l-11.3488,0a1.93401,4.02457 0 0 1 -1.93401,-4.02457l0,-58.35627z"
|
||||
fill="hsl(var(--primary))"
|
||||
stroke="null"
|
||||
/>
|
||||
<rect
|
||||
id="svg_3"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="5.47439"
|
||||
x="1.64059"
|
||||
y="15.46086"
|
||||
/>
|
||||
<rect
|
||||
id="svg_4"
|
||||
fill="#ffffff"
|
||||
height="7.67897"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="8.18938"
|
||||
x="0.58676"
|
||||
y="1.42154"
|
||||
/>
|
||||
<rect
|
||||
id="svg_8"
|
||||
fill="hsl(var(--primary))"
|
||||
height="9.07027"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="75.91967"
|
||||
x="25.38277"
|
||||
y="1.42876"
|
||||
/>
|
||||
<rect
|
||||
id="svg_9"
|
||||
fill="#b2b2b2"
|
||||
height="4.4"
|
||||
rx="1"
|
||||
stroke="null"
|
||||
width="3.925"
|
||||
x="27.91529"
|
||||
y="3.69284"
|
||||
/>
|
||||
<rect
|
||||
id="svg_10"
|
||||
fill="#b2b2b2"
|
||||
height="4.4"
|
||||
rx="1"
|
||||
stroke="null"
|
||||
width="3.925"
|
||||
x="80.75054"
|
||||
y="3.62876"
|
||||
/>
|
||||
<rect
|
||||
id="svg_11"
|
||||
fill="#b2b2b2"
|
||||
height="4.4"
|
||||
rx="1"
|
||||
stroke="null"
|
||||
width="3.925"
|
||||
x="87.78868"
|
||||
y="3.69981"
|
||||
/>
|
||||
<rect
|
||||
id="svg_12"
|
||||
fill="#b2b2b2"
|
||||
height="4.4"
|
||||
rx="1"
|
||||
stroke="null"
|
||||
width="3.925"
|
||||
x="94.6847"
|
||||
y="3.62876"
|
||||
/>
|
||||
<rect
|
||||
id="svg_13"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
height="21.51892"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="42.9287"
|
||||
x="58.75427"
|
||||
y="14.613"
|
||||
/>
|
||||
<rect
|
||||
id="svg_14"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
height="20.97838"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="28.36894"
|
||||
x="26.14342"
|
||||
y="14.613"
|
||||
/>
|
||||
<rect
|
||||
id="svg_15"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
height="21.65405"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="75.09493"
|
||||
x="26.34264"
|
||||
y="39.68822"
|
||||
/>
|
||||
<rect
|
||||
id="svg_5"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="5.47439"
|
||||
x="1.79832"
|
||||
y="28.39462"
|
||||
/>
|
||||
<rect
|
||||
id="svg_6"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="5.47439"
|
||||
x="1.64059"
|
||||
y="41.80156"
|
||||
/>
|
||||
<rect
|
||||
id="svg_7"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="5.47439"
|
||||
x="1.64059"
|
||||
y="55.36623"
|
||||
/>
|
||||
<rect
|
||||
id="svg_16"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
height="65.72065"
|
||||
stroke="null"
|
||||
width="12.49265"
|
||||
x="9.85477"
|
||||
y="-0.02618"
|
||||
/>
|
||||
<rect
|
||||
id="svg_21"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="7.52486"
|
||||
x="35.14924"
|
||||
y="4.07319"
|
||||
/>
|
||||
<rect
|
||||
id="svg_22"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="7.52486"
|
||||
x="47.25735"
|
||||
y="4.20832"
|
||||
/>
|
||||
<rect
|
||||
id="svg_23"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="7.52486"
|
||||
x="59.23033"
|
||||
y="4.07319"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<svg
|
||||
class="custom-radio-image"
|
||||
fill="none"
|
||||
height="66"
|
||||
width="104"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<rect
|
||||
id="svg_1"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.02"
|
||||
height="66"
|
||||
rx="4"
|
||||
stroke="null"
|
||||
width="104"
|
||||
x="0.13514"
|
||||
y="0.13514"
|
||||
/>
|
||||
<rect
|
||||
id="svg_8"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
height="9.07027"
|
||||
stroke="null"
|
||||
width="104.07934"
|
||||
x="-0.07419"
|
||||
y="-0.05773"
|
||||
/>
|
||||
<rect
|
||||
id="svg_3"
|
||||
fill="#b2b2b2"
|
||||
height="1.689"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="6.52486"
|
||||
x="10.08168"
|
||||
y="3.50832"
|
||||
/>
|
||||
<rect
|
||||
id="svg_10"
|
||||
fill="#b2b2b2"
|
||||
height="4.4"
|
||||
rx="1"
|
||||
stroke="null"
|
||||
width="3.925"
|
||||
x="80.75054"
|
||||
y="2.89362"
|
||||
/>
|
||||
<rect
|
||||
id="svg_11"
|
||||
fill="#b2b2b2"
|
||||
height="4.4"
|
||||
rx="1"
|
||||
stroke="null"
|
||||
width="3.925"
|
||||
x="87.58249"
|
||||
y="2.89362"
|
||||
/>
|
||||
<path
|
||||
id="svg_12"
|
||||
d="m98.19822,2.872c0,-0.54338 0.45662,-1 1,-1l1.925,0c0.54338,0 1,0.45662 1,1l0,2.4c0,0.54338 -0.45662,1 -1,1l-1.925,0c-0.54338,0 -1,-0.45662 -1,-1l0,-2.4z"
|
||||
fill="#ffffff"
|
||||
opacity="undefined"
|
||||
stroke="null"
|
||||
/>
|
||||
<rect
|
||||
id="svg_13"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
height="21.51892"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="44.13071"
|
||||
x="53.37873"
|
||||
y="13.45652"
|
||||
/>
|
||||
<path
|
||||
id="svg_14"
|
||||
d="m19.4393,15.74245c0,-1.08676 0.79001,-2 1.73013,-2l23.18605,0c0.94011,0 1.73013,0.91324 1.73013,2l0,17.24865c0,1.08676 -0.79001,2 -1.73013,2l-23.18605,0c-0.94011,0 -1.73013,-0.91324 -1.73013,-2l0,-17.24865z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
opacity="undefined"
|
||||
stroke="null"
|
||||
/>
|
||||
<rect
|
||||
id="svg_15"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.08"
|
||||
height="21.65405"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="78.39372"
|
||||
x="19.93575"
|
||||
y="39.34689"
|
||||
/>
|
||||
<rect
|
||||
id="svg_21"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="7.52486"
|
||||
x="28.14924"
|
||||
y="3.07319"
|
||||
/>
|
||||
<rect
|
||||
id="svg_22"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="7.52486"
|
||||
x="41.25735"
|
||||
y="3.20832"
|
||||
/>
|
||||
<rect
|
||||
id="svg_23"
|
||||
fill="#e5e5e5"
|
||||
height="2.789"
|
||||
rx="1.395"
|
||||
stroke="null"
|
||||
width="7.52486"
|
||||
x="54.23033"
|
||||
y="3.07319"
|
||||
/>
|
||||
<rect
|
||||
id="svg_4"
|
||||
fill="#ffffff"
|
||||
height="5.13843"
|
||||
rx="2"
|
||||
stroke="null"
|
||||
width="5.78397"
|
||||
x="1.5327"
|
||||
y="1.081"
|
||||
/>
|
||||
<rect
|
||||
id="svg_5"
|
||||
fill="hsl(var(--primary))"
|
||||
height="56.81191"
|
||||
stroke="null"
|
||||
width="15.44642"
|
||||
x="-0.06423"
|
||||
y="9.03113"
|
||||
/>
|
||||
<path
|
||||
id="svg_2"
|
||||
d="m2.38669,15.38074c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z"
|
||||
fill="#fff"
|
||||
opacity="undefined"
|
||||
stroke="null"
|
||||
/>
|
||||
<path
|
||||
id="svg_6"
|
||||
d="m2.38669,28.43336c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z"
|
||||
fill="#fff"
|
||||
opacity="undefined"
|
||||
stroke="null"
|
||||
/>
|
||||
<path
|
||||
id="svg_7"
|
||||
d="m2.17616,41.27545c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z"
|
||||
fill="#fff"
|
||||
opacity="undefined"
|
||||
stroke="null"
|
||||
/>
|
||||
<path
|
||||
id="svg_9"
|
||||
d="m2.17616,54.32806c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z"
|
||||
fill="#fff"
|
||||
opacity="undefined"
|
||||
stroke="null"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@@ -2,6 +2,8 @@ import HeaderNav from './header-nav.vue';
|
||||
|
||||
export { default as ContentCompact } from './content-compact.vue';
|
||||
export { default as FullContent } from './full-content.vue';
|
||||
export { default as HeaderMixedNav } from './header-mixed-nav.vue';
|
||||
export { default as HeaderSidebarNav } from './header-sidebar-nav.vue';
|
||||
export { default as MixedNav } from './mixed-nav.vue';
|
||||
export { default as SidebarMixedNav } from './sidebar-mixed-nav.vue';
|
||||
export { default as SidebarNav } from './sidebar-nav.vue';
|
||||
|
@@ -4,12 +4,14 @@ import type {
|
||||
BreadcrumbStyleType,
|
||||
BuiltinThemeType,
|
||||
ContentCompactType,
|
||||
LayoutHeaderMenuAlignType,
|
||||
LayoutHeaderModeType,
|
||||
LayoutType,
|
||||
NavigationStyleType,
|
||||
PreferencesButtonPositionType,
|
||||
ThemeModeType,
|
||||
} from '@vben/types';
|
||||
|
||||
import type { SegmentedItem } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
resetPreferences,
|
||||
usePreferences,
|
||||
} from '@vben/preferences';
|
||||
|
||||
import { useVbenDrawer } from '@vben-core/popup-ui';
|
||||
import {
|
||||
VbenButton,
|
||||
@@ -87,9 +90,15 @@ const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
|
||||
const sidebarCollapsedShowTitle = defineModel<boolean>(
|
||||
'sidebarCollapsedShowTitle',
|
||||
);
|
||||
const sidebarAutoActivateChild = defineModel<boolean>(
|
||||
'sidebarAutoActivateChild',
|
||||
);
|
||||
const SidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
|
||||
|
||||
const headerEnable = defineModel<boolean>('headerEnable');
|
||||
const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
|
||||
const headerMenuAlign =
|
||||
defineModel<LayoutHeaderMenuAlignType>('headerMenuAlign');
|
||||
|
||||
const breadcrumbEnable = defineModel<boolean>('breadcrumbEnable');
|
||||
const breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon');
|
||||
@@ -105,7 +114,11 @@ const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
|
||||
const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
|
||||
const tabbarPersist = defineModel<boolean>('tabbarPersist');
|
||||
const tabbarDraggable = defineModel<boolean>('tabbarDraggable');
|
||||
const tabbarWheelable = defineModel<boolean>('tabbarWheelable');
|
||||
const tabbarStyleType = defineModel<string>('tabbarStyleType');
|
||||
const tabbarMiddleClickToClose = defineModel<boolean>(
|
||||
'tabbarMiddleClickToClose',
|
||||
);
|
||||
|
||||
const navigationStyleType = defineModel<NavigationStyleType>(
|
||||
'navigationStyleType',
|
||||
@@ -154,6 +167,7 @@ const {
|
||||
isDark,
|
||||
isFullContent,
|
||||
isHeaderNav,
|
||||
isHeaderSidebarNav,
|
||||
isMixedNav,
|
||||
isSideMixedNav,
|
||||
isSideMode,
|
||||
@@ -298,10 +312,13 @@ async function handleReset() {
|
||||
|
||||
<Block :title="$t('preferences.sidebar.title')">
|
||||
<Sidebar
|
||||
v-model:sidebar-auto-activate-child="sidebarAutoActivateChild"
|
||||
v-model:sidebar-collapsed="sidebarCollapsed"
|
||||
v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
|
||||
v-model:sidebar-enable="sidebarEnable"
|
||||
v-model:sidebar-expand-on-hover="SidebarExpandOnHover"
|
||||
v-model:sidebar-width="sidebarWidth"
|
||||
:current-layout="appLayout"
|
||||
:disabled="!isSideMode"
|
||||
/>
|
||||
</Block>
|
||||
@@ -309,6 +326,7 @@ async function handleReset() {
|
||||
<Block :title="$t('preferences.header.title')">
|
||||
<Header
|
||||
v-model:header-enable="headerEnable"
|
||||
v-model:header-menu-align="headerMenuAlign"
|
||||
v-model:header-mode="headerMode"
|
||||
:disabled="isFullContent"
|
||||
/>
|
||||
@@ -332,7 +350,8 @@ async function handleReset() {
|
||||
v-model:breadcrumb-show-icon="breadcrumbShowIcon"
|
||||
v-model:breadcrumb-style-type="breadcrumbStyleType"
|
||||
:disabled="
|
||||
!showBreadcrumbConfig || !(isSideNav || isSideMixedNav)
|
||||
!showBreadcrumbConfig ||
|
||||
!(isSideNav || isSideMixedNav || isHeaderSidebarNav)
|
||||
"
|
||||
/>
|
||||
</Block>
|
||||
@@ -345,6 +364,8 @@ async function handleReset() {
|
||||
v-model:tabbar-show-maximize="tabbarShowMaximize"
|
||||
v-model:tabbar-show-more="tabbarShowMore"
|
||||
v-model:tabbar-style-type="tabbarStyleType"
|
||||
v-model:tabbar-wheelable="tabbarWheelable"
|
||||
v-model:tabbar-middle-click-to-close="tabbarMiddleClickToClose"
|
||||
/>
|
||||
</Block>
|
||||
<Block :title="$t('preferences.widget.title')">
|
||||
|
@@ -1,12 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Settings } from '@vben/icons';
|
||||
import { $t, loadLocaleMessages } from '@vben/locales';
|
||||
import { preferences, updatePreferences } from '@vben/preferences';
|
||||
import { capitalizeFirstLetter } from '@vben/utils';
|
||||
import { useVbenDrawer } from '@vben-core/popup-ui';
|
||||
import { VbenButton } from '@vben-core/shadcn-ui';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import PreferencesDrawer from './preferences-drawer.vue';
|
||||
|
||||
|
@@ -1,9 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { AnyFunction } from '@vben/types';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useHoverToggle } from '@vben/hooks';
|
||||
import { LockKeyhole, LogOut } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { preferences, usePreferences } from '@vben/preferences';
|
||||
@@ -22,8 +21,8 @@ import {
|
||||
VbenAvatar,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useMagicKeys, whenever } from '@vueuse/core';
|
||||
import { computed, useTemplateRef, watch } from 'vue';
|
||||
|
||||
import { LockScreenModal } from '../lock-screen';
|
||||
|
||||
@@ -53,6 +52,10 @@ interface Props {
|
||||
* 文本
|
||||
*/
|
||||
text?: string;
|
||||
/** 触发方式 */
|
||||
trigger?: 'both' | 'click' | 'hover';
|
||||
/** hover触发时,延迟响应的时间 */
|
||||
hoverDelay?: number;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
@@ -67,10 +70,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
showShortcutKey: true,
|
||||
tagText: '',
|
||||
text: '',
|
||||
trigger: 'click',
|
||||
hoverDelay: 500,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ logout: [] }>();
|
||||
const openPopover = ref(false);
|
||||
|
||||
const { globalLockScreenShortcutKey, globalLogoutShortcutKey } =
|
||||
usePreferences();
|
||||
@@ -84,6 +88,27 @@ const [LogoutModal, logoutModalApi] = useVbenModal({
|
||||
},
|
||||
});
|
||||
|
||||
const refTrigger = useTemplateRef('refTrigger');
|
||||
const refContent = useTemplateRef('refContent');
|
||||
const [openPopover, hoverWatcher] = useHoverToggle(
|
||||
[refTrigger, refContent],
|
||||
() => props.hoverDelay,
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.trigger === 'hover' || props.trigger === 'both',
|
||||
(val) => {
|
||||
if (val) {
|
||||
hoverWatcher.enable();
|
||||
} else {
|
||||
hoverWatcher.disable();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
|
||||
|
||||
const enableLogoutShortcutKey = computed(() => {
|
||||
@@ -155,8 +180,8 @@ if (enableShortcutKey.value) {
|
||||
{{ $t('ui.widgets.logoutTip') }}
|
||||
</LogoutModal>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<DropdownMenu v-model:open="openPopover">
|
||||
<DropdownMenuTrigger ref="refTrigger" :disabled="props.trigger === 'hover'">
|
||||
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full p-1.5">
|
||||
<div class="hover:text-accent-foreground flex-center">
|
||||
<VbenAvatar :alt="text" :src="avatar" class="size-8" dot />
|
||||
@@ -164,64 +189,66 @@ if (enableShortcutKey.value) {
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="mr-2 min-w-[240px] p-0 pb-1">
|
||||
<DropdownMenuLabel class="flex items-center p-3">
|
||||
<VbenAvatar
|
||||
:alt="text"
|
||||
:src="avatar"
|
||||
class="size-12"
|
||||
dot
|
||||
dot-class="bottom-0 right-1 border-2 size-4 bg-green-500"
|
||||
/>
|
||||
<div class="ml-2 w-full">
|
||||
<div
|
||||
v-if="tagText || text || $slots.tagText"
|
||||
class="text-foreground mb-1 flex items-center text-sm font-medium"
|
||||
>
|
||||
{{ text }}
|
||||
<slot name="tagText">
|
||||
<Badge v-if="tagText" class="ml-2 text-green-400">
|
||||
{{ tagText }}
|
||||
</Badge>
|
||||
</slot>
|
||||
<div ref="refContent">
|
||||
<DropdownMenuLabel class="flex items-center p-3">
|
||||
<VbenAvatar
|
||||
:alt="text"
|
||||
:src="avatar"
|
||||
class="size-12"
|
||||
dot
|
||||
dot-class="bottom-0 right-1 border-2 size-4 bg-green-500"
|
||||
/>
|
||||
<div class="ml-2 w-full">
|
||||
<div
|
||||
v-if="tagText || text || $slots.tagText"
|
||||
class="text-foreground mb-1 flex items-center text-sm font-medium"
|
||||
>
|
||||
{{ text }}
|
||||
<slot name="tagText">
|
||||
<Badge v-if="tagText" class="ml-2 text-green-400">
|
||||
{{ tagText }}
|
||||
</Badge>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="text-muted-foreground text-xs font-normal">
|
||||
{{ description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted-foreground text-xs font-normal">
|
||||
{{ description }}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator v-if="menus?.length" />
|
||||
<DropdownMenuItem
|
||||
v-for="menu in menus"
|
||||
:key="menu.text"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@click="menu.handler"
|
||||
>
|
||||
<VbenIcon :icon="menu.icon" class="mr-2 size-4" />
|
||||
{{ menu.text }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
v-if="preferences.widget.lockScreen"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@click="handleOpenLock"
|
||||
>
|
||||
<LockKeyhole class="mr-2 size-4" />
|
||||
{{ $t('ui.widgets.lockScreen.title') }}
|
||||
<DropdownMenuShortcut v-if="enableLockScreenShortcutKey">
|
||||
{{ altView }} L
|
||||
</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator v-if="preferences.widget.lockScreen" />
|
||||
<DropdownMenuItem
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<LogOut class="mr-2 size-4" />
|
||||
{{ $t('common.logout') }}
|
||||
<DropdownMenuShortcut v-if="enableLogoutShortcutKey">
|
||||
{{ altView }} Q
|
||||
</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator v-if="menus?.length" />
|
||||
<DropdownMenuItem
|
||||
v-for="menu in menus"
|
||||
:key="menu.text"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@click="menu.handler"
|
||||
>
|
||||
<VbenIcon :icon="menu.icon" class="mr-2 size-4" />
|
||||
{{ menu.text }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
v-if="preferences.widget.lockScreen"
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@click="handleOpenLock"
|
||||
>
|
||||
<LockKeyhole class="mr-2 size-4" />
|
||||
{{ $t('ui.widgets.lockScreen.title') }}
|
||||
<DropdownMenuShortcut v-if="enableLockScreenShortcutKey">
|
||||
{{ altView }} L
|
||||
</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator v-if="preferences.widget.lockScreen" />
|
||||
<DropdownMenuItem
|
||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<LogOut class="mr-2 size-4" />
|
||||
{{ $t('common.logout') }}
|
||||
<DropdownMenuShortcut v-if="enableLogoutShortcutKey">
|
||||
{{ altView }} Q
|
||||
</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/plugins",
|
||||
"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,8 +1,11 @@
|
||||
import type { EChartsOption } from 'echarts';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { Nullable } from '@vben/types';
|
||||
|
||||
import type EchartsUI from './echarts-ui.vue';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, nextTick, watch } from 'vue';
|
||||
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
@@ -49,7 +52,10 @@ function useEcharts(chartRef: Ref<EchartsUIType>) {
|
||||
return chartInstance;
|
||||
};
|
||||
|
||||
const renderEcharts = (options: EChartsOption, clear = true) => {
|
||||
const renderEcharts = (
|
||||
options: EChartsOption,
|
||||
clear = true,
|
||||
): Promise<Nullable<echarts.ECharts>> => {
|
||||
cacheOptions = options;
|
||||
const currentOptions = {
|
||||
...options,
|
||||
@@ -57,9 +63,8 @@ function useEcharts(chartRef: Ref<EchartsUIType>) {
|
||||
};
|
||||
return new Promise((resolve) => {
|
||||
if (chartRef.value?.offsetHeight === 0) {
|
||||
useTimeoutFn(() => {
|
||||
renderEcharts(currentOptions);
|
||||
resolve(null);
|
||||
useTimeoutFn(async () => {
|
||||
resolve(await renderEcharts(currentOptions));
|
||||
}, 30);
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +76,7 @@ function useEcharts(chartRef: Ref<EchartsUIType>) {
|
||||
}
|
||||
clear && chartInstance?.clear();
|
||||
chartInstance?.setOption(currentOptions);
|
||||
resolve(null);
|
||||
resolve(chartInstance);
|
||||
}, 30);
|
||||
});
|
||||
});
|
||||
@@ -110,6 +115,7 @@ function useEcharts(chartRef: Ref<EchartsUIType>) {
|
||||
return {
|
||||
renderEcharts,
|
||||
resize,
|
||||
getChartInstance: () => chartInstance,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -3,15 +3,15 @@ import type { VxeGridInstance } from 'vxe-table';
|
||||
|
||||
import type { VxeGridProps } from './types';
|
||||
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
import { Store } from '@vben-core/shared/store';
|
||||
import {
|
||||
bindMethods,
|
||||
isBoolean,
|
||||
isFunction,
|
||||
mergeWithArrayOverride,
|
||||
StateHandler,
|
||||
} from '@vben-core/shared/utils';
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
function getDefaultState(): VxeGridProps {
|
||||
return {
|
||||
@@ -20,6 +20,7 @@ function getDefaultState(): VxeGridProps {
|
||||
gridOptions: {},
|
||||
gridEvents: {},
|
||||
formOptions: undefined,
|
||||
showSearchForm: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,6 +109,16 @@ export class VxeGridApi {
|
||||
}
|
||||
}
|
||||
|
||||
toggleSearchForm(show?: boolean) {
|
||||
this.setState({
|
||||
showSearchForm: isBoolean(show) ? show : !this.state?.showSearchForm,
|
||||
});
|
||||
// nextTick(() => {
|
||||
// this.grid.recalculate();
|
||||
// });
|
||||
return this.state?.showSearchForm;
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.isMounted = false;
|
||||
this.stateHandler.reset();
|
||||
|
@@ -1,4 +1,10 @@
|
||||
export { setupVbenVxeTable } from './init';
|
||||
export type { VxeTableGridOptions } from './types';
|
||||
export * from './use-vxe-grid';
|
||||
export { default as VbenVxeGrid } from './use-vxe-grid.vue';
|
||||
export type { VxeGridDefines, VxeGridListeners, VxeGridProps } from 'vxe-table';
|
||||
export type {
|
||||
VxeGridDefines,
|
||||
VxeGridListeners,
|
||||
VxeGridProps,
|
||||
VxeGridPropTypes,
|
||||
} from 'vxe-table';
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import type { SetupVxeTable } from './types';
|
||||
|
||||
import { defineComponent, watch } from 'vue';
|
||||
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
|
||||
import { defineComponent, watch } from 'vue';
|
||||
import {
|
||||
VxeButton,
|
||||
VxeCheckbox,
|
||||
@@ -34,7 +32,6 @@ import {
|
||||
// VxeTextarea,
|
||||
} from 'vxe-pc-ui';
|
||||
import enUS from 'vxe-pc-ui/lib/language/en-US';
|
||||
|
||||
// 导入默认的语言
|
||||
import zhCN from 'vxe-pc-ui/lib/language/zh-CN';
|
||||
import {
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import type { ClassType, DeepPartial } from '@vben/types';
|
||||
import type { VbenFormProps } from '@vben-core/form-ui';
|
||||
import type { Ref } from 'vue';
|
||||
import type {
|
||||
VxeGridListeners,
|
||||
VxeGridPropTypes,
|
||||
VxeGridProps as VxeTableGridProps,
|
||||
VxeUIExport,
|
||||
} from 'vxe-table';
|
||||
|
||||
import type { VxeGridApi } from './api';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
|
||||
export interface VxePaginationInfo {
|
||||
@@ -18,6 +18,16 @@ export interface VxePaginationInfo {
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface ToolbarConfigOptions extends VxeGridPropTypes.ToolbarConfig {
|
||||
/** 是否显示切换搜索表单的按钮 */
|
||||
search?: boolean;
|
||||
}
|
||||
|
||||
export interface VxeTableGridOptions<T = any> extends VxeTableGridProps<T> {
|
||||
/** 工具栏配置 */
|
||||
toolbarConfig?: ToolbarConfigOptions;
|
||||
}
|
||||
|
||||
export interface VxeGridProps {
|
||||
/**
|
||||
* 标题
|
||||
@@ -38,7 +48,7 @@ export interface VxeGridProps {
|
||||
/**
|
||||
* vxe-grid 配置
|
||||
*/
|
||||
gridOptions?: DeepPartial<VxeTableGridProps>;
|
||||
gridOptions?: DeepPartial<VxeTableGridOptions>;
|
||||
/**
|
||||
* vxe-grid 事件
|
||||
*/
|
||||
@@ -47,6 +57,10 @@ export interface VxeGridProps {
|
||||
* 表单配置
|
||||
*/
|
||||
formOptions?: VbenFormProps;
|
||||
/**
|
||||
* 显示搜索表单
|
||||
*/
|
||||
showSearchForm?: boolean;
|
||||
}
|
||||
|
||||
export type ExtendedVxeGridApi = {
|
||||
|
@@ -1,10 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormProps } from '@vben-core/form-ui';
|
||||
import type {
|
||||
VxeGridDefines,
|
||||
VxeGridInstance,
|
||||
VxeGridListeners,
|
||||
VxeGridPropTypes,
|
||||
VxeGridProps as VxeTableGridProps,
|
||||
VxeToolbarPropTypes,
|
||||
} from 'vxe-table';
|
||||
|
||||
import type { SetupContext } from 'vue';
|
||||
|
||||
import type { VbenFormProps } from '@vben-core/form-ui';
|
||||
|
||||
import type { ExtendedVxeGridApi, VxeGridProps } from './types';
|
||||
|
||||
import {
|
||||
@@ -23,6 +30,7 @@ import { EmptyIcon } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
import { cloneDeep, cn, mergeWithArrayOverride } from '@vben/utils';
|
||||
|
||||
import { VbenHelpTooltip, VbenLoading } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { VxeGrid, VxeUI } from 'vxe-table';
|
||||
@@ -57,21 +65,23 @@ const {
|
||||
formOptions,
|
||||
tableTitle,
|
||||
tableTitleHelp,
|
||||
showSearchForm,
|
||||
} = usePriorityValues(props, state);
|
||||
|
||||
const { isMobile } = usePreferences();
|
||||
|
||||
const slots = useSlots();
|
||||
const slots: SetupContext['slots'] = useSlots();
|
||||
|
||||
const [Form, formApi] = useTableForm({
|
||||
compact: true,
|
||||
handleSubmit: async () => {
|
||||
const formValues = formApi.form.values;
|
||||
const formValues = await formApi.getValues();
|
||||
formApi.setLatestSubmissionValues(toRaw(formValues));
|
||||
props.api.reload(formValues);
|
||||
},
|
||||
handleReset: async () => {
|
||||
await formApi.resetForm();
|
||||
const formValues = formApi.form.values;
|
||||
const formValues = await formApi.getValues();
|
||||
formApi.setLatestSubmissionValues(formValues);
|
||||
props.api.reload(formValues);
|
||||
},
|
||||
@@ -82,7 +92,7 @@ const [Form, formApi] = useTableForm({
|
||||
},
|
||||
showCollapseButton: true,
|
||||
submitButtonOptions: {
|
||||
content: $t('common.query'),
|
||||
content: computed(() => $t('common.search')),
|
||||
},
|
||||
// enter提交
|
||||
submitOnEnter: true,
|
||||
@@ -104,22 +114,37 @@ const showToolbar = computed(() => {
|
||||
const toolbarOptions = computed(() => {
|
||||
const slotActions = slots[TOOLBAR_ACTIONS]?.();
|
||||
const slotTools = slots[TOOLBAR_TOOLS]?.();
|
||||
const searchBtn: VxeToolbarPropTypes.ToolConfig = {
|
||||
code: 'search',
|
||||
icon: 'vxe-icon--search',
|
||||
circle: true,
|
||||
status: showSearchForm.value ? 'primary' : undefined,
|
||||
title: $t('common.search'),
|
||||
};
|
||||
// 将搜索按钮合并到用户配置的toolbarConfig.tools中
|
||||
const toolbarConfig: VxeGridPropTypes.ToolbarConfig = {
|
||||
tools: (gridOptions.value?.toolbarConfig?.tools ??
|
||||
[]) as VxeToolbarPropTypes.ToolConfig[],
|
||||
};
|
||||
if (gridOptions.value?.toolbarConfig?.search && !!formOptions.value) {
|
||||
toolbarConfig.tools = Array.isArray(toolbarConfig.tools)
|
||||
? [...toolbarConfig.tools, searchBtn]
|
||||
: [searchBtn];
|
||||
}
|
||||
|
||||
if (!showToolbar.value) {
|
||||
return {};
|
||||
return { toolbarConfig };
|
||||
}
|
||||
|
||||
// 强制使用固定的toolbar配置,不允许用户自定义
|
||||
// 减少配置的复杂度,以及后续维护的成本
|
||||
return {
|
||||
toolbarConfig: {
|
||||
slots: {
|
||||
...(slotActions || showTableTitle.value
|
||||
? { buttons: TOOLBAR_ACTIONS }
|
||||
: {}),
|
||||
...(slotTools ? { tools: TOOLBAR_TOOLS } : {}),
|
||||
},
|
||||
},
|
||||
toolbarConfig.slots = {
|
||||
...(slotActions || showTableTitle.value
|
||||
? { buttons: TOOLBAR_ACTIONS }
|
||||
: {}),
|
||||
...(slotTools ? { tools: TOOLBAR_TOOLS } : {}),
|
||||
};
|
||||
return { toolbarConfig };
|
||||
});
|
||||
|
||||
const options = computed(() => {
|
||||
@@ -128,7 +153,7 @@ const options = computed(() => {
|
||||
const mergedOptions: VxeTableGridProps = cloneDeep(
|
||||
mergeWithArrayOverride(
|
||||
{},
|
||||
toolbarOptions.value,
|
||||
toRaw(toolbarOptions.value),
|
||||
toRaw(gridOptions.value),
|
||||
globalGridConfig,
|
||||
),
|
||||
@@ -175,9 +200,19 @@ const options = computed(() => {
|
||||
return mergedOptions;
|
||||
});
|
||||
|
||||
function onToolbarToolClick(event: VxeGridDefines.ToolbarToolClickEventParams) {
|
||||
if (event.code === 'search') {
|
||||
props.api?.toggleSearchForm?.();
|
||||
}
|
||||
(
|
||||
gridEvents.value?.toolbarToolClick as VxeGridListeners['toolbarToolClick']
|
||||
)?.(event);
|
||||
}
|
||||
|
||||
const events = computed(() => {
|
||||
return {
|
||||
...gridEvents.value,
|
||||
toolbarToolClick: onToolbarToolClick,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -215,7 +250,14 @@ async function init() {
|
||||
const autoLoad = defaultGridOptions.proxyConfig?.autoLoad;
|
||||
const enableProxyConfig = options.value.proxyConfig?.enabled;
|
||||
if (enableProxyConfig && autoLoad) {
|
||||
props.api.reload(formApi.form?.values ?? {});
|
||||
// 第一次拿到的是readonly的数据 如果需要修改 需要cloneDeep
|
||||
props.api.grid.commitProxy?.(
|
||||
'_init',
|
||||
cloneDeep(formOptions.value)
|
||||
? (cloneDeep(await formApi.getValues()) ?? {})
|
||||
: {},
|
||||
);
|
||||
// props.api.reload(formApi.form?.values ?? {});
|
||||
}
|
||||
|
||||
// form 由 vben-form代替,所以不适配formConfig,这里给出警告
|
||||
@@ -255,6 +297,10 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
const isCompactForm = computed(() => {
|
||||
return formApi.getState()?.compact;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
props.api?.mount?.(gridRef.value, formApi);
|
||||
init();
|
||||
@@ -272,7 +318,7 @@ onUnmounted(() => {
|
||||
ref="gridRef"
|
||||
:class="
|
||||
cn(
|
||||
'p-2 pt-0',
|
||||
'p-2',
|
||||
{
|
||||
'pt-0': showToolbar && !formOptions,
|
||||
},
|
||||
@@ -306,7 +352,11 @@ onUnmounted(() => {
|
||||
|
||||
<!-- form表单 -->
|
||||
<template #form>
|
||||
<div v-if="formOptions" class="relative rounded py-3 pb-4">
|
||||
<div
|
||||
v-if="formOptions"
|
||||
v-show="showSearchForm !== false"
|
||||
:class="cn('relative rounded py-3', isCompactForm ? 'pb-6' : 'pb-4')"
|
||||
>
|
||||
<slot name="form">
|
||||
<Form>
|
||||
<template
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/request",
|
||||
"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": {
|
||||
|
@@ -48,6 +48,29 @@ describe('requestClient', () => {
|
||||
expect(response.data).toEqual(mockData);
|
||||
});
|
||||
|
||||
it('should return different response types', async () => {
|
||||
const mockData = { code: 0, msg: 'ok', data: 'response' };
|
||||
mock.onGet('/test/diff').reply(200, mockData);
|
||||
|
||||
const responseRaw = await requestClient.get('/test/diff', {
|
||||
responseReturn: 'raw',
|
||||
});
|
||||
expect(responseRaw.status).toBe(200);
|
||||
expect(responseRaw.data).toEqual(mockData);
|
||||
|
||||
const responseBody = await requestClient.get('/test/diff', {
|
||||
responseReturn: 'body',
|
||||
});
|
||||
expect(responseBody.code).toEqual(mockData.code);
|
||||
expect(responseBody.msg).toEqual(mockData.msg);
|
||||
expect(responseBody.data).toEqual(mockData.data);
|
||||
|
||||
const responseData = await requestClient.get('/test/diff', {
|
||||
responseReturn: 'data',
|
||||
});
|
||||
expect(responseData).toEqual(mockData.data);
|
||||
});
|
||||
|
||||
it('should handle network errors', async () => {
|
||||
mock.onGet('/test/error').networkError();
|
||||
try {
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import type { AxiosInstance, AxiosResponse } from 'axios';
|
||||
|
||||
import type {
|
||||
AxiosInstance,
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
CreateAxiosDefaults,
|
||||
} from 'axios';
|
||||
HttpResponse,
|
||||
RequestClientConfig,
|
||||
RequestClientOptions,
|
||||
} from './types';
|
||||
|
||||
import { bindMethods, merge } from '@vben/utils';
|
||||
|
||||
@@ -12,20 +13,19 @@ import axios from 'axios';
|
||||
import { FileDownloader } from './modules/downloader';
|
||||
import { InterceptorManager } from './modules/interceptor';
|
||||
import { FileUploader } from './modules/uploader';
|
||||
import { type RequestClientOptions } from './types';
|
||||
|
||||
class RequestClient {
|
||||
private readonly instance: AxiosInstance;
|
||||
|
||||
public addRequestInterceptor: InterceptorManager['addRequestInterceptor'];
|
||||
public addResponseInterceptor: InterceptorManager['addResponseInterceptor'];
|
||||
|
||||
public addResponseInterceptor: InterceptorManager['addResponseInterceptor'];
|
||||
public download: FileDownloader['download'];
|
||||
|
||||
// 是否正在刷新token
|
||||
public isRefreshing = false;
|
||||
// 刷新token队列
|
||||
public refreshTokenQueue: ((token: string) => void)[] = [];
|
||||
public upload: FileUploader['upload'];
|
||||
private readonly instance: AxiosInstance;
|
||||
|
||||
/**
|
||||
* 构造函数,用于创建Axios实例
|
||||
@@ -33,10 +33,11 @@ class RequestClient {
|
||||
*/
|
||||
constructor(options: RequestClientOptions = {}) {
|
||||
// 合并默认配置和传入的配置
|
||||
const defaultConfig: CreateAxiosDefaults = {
|
||||
const defaultConfig: RequestClientOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
},
|
||||
responseReturn: 'raw',
|
||||
// 默认超时时间
|
||||
timeout: 10_000,
|
||||
};
|
||||
@@ -53,6 +54,24 @@ class RequestClient {
|
||||
this.addResponseInterceptor =
|
||||
interceptorManager.addResponseInterceptor.bind(interceptorManager);
|
||||
|
||||
// 添加基础的响应处理,根据设置决定返回响应的哪一部分
|
||||
this.addResponseInterceptor<HttpResponse>({
|
||||
fulfilled: (response) => {
|
||||
const { config, data: responseData, status } = response;
|
||||
|
||||
if (config.responseReturn === 'raw') {
|
||||
return response;
|
||||
}
|
||||
|
||||
const { code, data } = responseData;
|
||||
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return config.responseReturn === 'body' ? responseData : data;
|
||||
}
|
||||
throw Object.assign({}, response, { response });
|
||||
},
|
||||
});
|
||||
|
||||
// 实例化文件上传器
|
||||
const fileUploader = new FileUploader(this);
|
||||
this.upload = fileUploader.upload.bind(fileUploader);
|
||||
@@ -64,7 +83,10 @@ class RequestClient {
|
||||
/**
|
||||
* DELETE请求方法
|
||||
*/
|
||||
public delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||
public delete<T = any>(
|
||||
url: string,
|
||||
config?: RequestClientConfig,
|
||||
): Promise<T> {
|
||||
return this.request<T>(url, { ...config, method: 'DELETE' });
|
||||
}
|
||||
|
||||
@@ -85,7 +107,7 @@ class RequestClient {
|
||||
/**
|
||||
* GET请求方法
|
||||
*/
|
||||
public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||
public get<T = any>(url: string, config?: RequestClientConfig): Promise<T> {
|
||||
return this.request<T>(url, { ...config, method: 'GET' });
|
||||
}
|
||||
|
||||
@@ -95,7 +117,7 @@ class RequestClient {
|
||||
public post<T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig,
|
||||
config?: RequestClientConfig,
|
||||
): Promise<T> {
|
||||
return this.request<T>(url, { ...config, data, method: 'POST' });
|
||||
}
|
||||
@@ -122,7 +144,7 @@ class RequestClient {
|
||||
public put<T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig,
|
||||
config?: RequestClientConfig,
|
||||
): Promise<T> {
|
||||
return this.request<T>(url, { ...config, data, method: 'PUT' });
|
||||
}
|
||||
@@ -146,7 +168,10 @@ class RequestClient {
|
||||
/**
|
||||
* 通用的请求方法
|
||||
*/
|
||||
public async request<T>(url: string, config: AxiosRequestConfig): Promise<T> {
|
||||
public async request<T>(
|
||||
url: string,
|
||||
config: RequestClientConfig,
|
||||
): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await this.instance({
|
||||
url,
|
||||
|
@@ -1,10 +1,23 @@
|
||||
import type {
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
CreateAxiosDefaults,
|
||||
InternalAxiosRequestConfig,
|
||||
} from 'axios';
|
||||
|
||||
type RequestResponse<T = any> = AxiosResponse<T>;
|
||||
type ExtendOptions = {
|
||||
/** 响应数据的返回方式。
|
||||
* raw: 原始的AxiosResponse,包括headers、status等。
|
||||
* body: 返回响应数据的BODY部分。
|
||||
* data: 解构响应的BODY数据,只返回其中的data节点数据。
|
||||
*/
|
||||
responseReturn?: 'body' | 'data' | 'raw';
|
||||
};
|
||||
type RequestClientConfig<T = any> = AxiosRequestConfig<T> & ExtendOptions;
|
||||
|
||||
type RequestResponse<T = any> = AxiosResponse<T> & {
|
||||
config: RequestClientConfig<T>;
|
||||
};
|
||||
|
||||
type RequestContentType =
|
||||
| 'application/json;charset=utf-8'
|
||||
@@ -12,21 +25,21 @@ type RequestContentType =
|
||||
| 'application/x-www-form-urlencoded;charset=utf-8'
|
||||
| 'multipart/form-data;charset=utf-8';
|
||||
|
||||
type RequestClientOptions = CreateAxiosDefaults;
|
||||
type RequestClientOptions = CreateAxiosDefaults & ExtendOptions;
|
||||
|
||||
interface RequestInterceptorConfig {
|
||||
fulfilled?: (
|
||||
config: InternalAxiosRequestConfig,
|
||||
config: ExtendOptions & InternalAxiosRequestConfig,
|
||||
) =>
|
||||
| InternalAxiosRequestConfig<any>
|
||||
| Promise<InternalAxiosRequestConfig<any>>;
|
||||
| (ExtendOptions & InternalAxiosRequestConfig<any>)
|
||||
| Promise<ExtendOptions & InternalAxiosRequestConfig<any>>;
|
||||
rejected?: (error: any) => any;
|
||||
}
|
||||
|
||||
interface ResponseInterceptorConfig<T = any> {
|
||||
fulfilled?: (
|
||||
response: AxiosResponse<T>,
|
||||
) => AxiosResponse | Promise<AxiosResponse>;
|
||||
response: RequestResponse<T>,
|
||||
) => Promise<RequestResponse> | RequestResponse;
|
||||
rejected?: (error: any) => any;
|
||||
}
|
||||
|
||||
@@ -41,6 +54,7 @@ interface HttpResponse<T = any> {
|
||||
export type {
|
||||
HttpResponse,
|
||||
MakeErrorMessageFn,
|
||||
RequestClientConfig,
|
||||
RequestClientOptions,
|
||||
RequestContentType,
|
||||
RequestInterceptorConfig,
|
||||
|
Reference in New Issue
Block a user