Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin
This commit is contained in:
commit
ca30857eff
@ -3,3 +3,5 @@ node_modules
|
|||||||
.gitignore
|
.gitignore
|
||||||
*.md
|
*.md
|
||||||
dist
|
dist
|
||||||
|
.turbo
|
||||||
|
dist.zip
|
||||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -212,7 +212,6 @@
|
|||||||
"*.env": "$(capture).env.*",
|
"*.env": "$(capture).env.*",
|
||||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
||||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
||||||
"Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*,nginx.conf",
|
|
||||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
||||||
"tailwind.config.mjs": "postcss.*"
|
"tailwind.config.mjs": "postcss.*"
|
||||||
},
|
},
|
||||||
|
@ -83,7 +83,12 @@ export const demoPreviewPlugin = (md: MarkdownRenderer) => {
|
|||||||
if (!state.tokens[index]) {
|
if (!state.tokens[index]) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
const firstString = 'index.vue';
|
||||||
|
childFiles = childFiles.sort((a, b) => {
|
||||||
|
if (a === firstString) return -1;
|
||||||
|
if (b === firstString) return 1;
|
||||||
|
return a.localeCompare(b, 'en', { sensitivity: 'base' });
|
||||||
|
});
|
||||||
state.tokens[index].content =
|
state.tokens[index].content =
|
||||||
`<DemoPreview files="${encodeURIComponent(JSON.stringify(childFiles))}" ><${ComponentName}/>
|
`<DemoPreview files="${encodeURIComponent(JSON.stringify(childFiles))}" ><${ComponentName}/>
|
||||||
`;
|
`;
|
||||||
|
@ -65,3 +65,12 @@ pnpm install
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 其他
|
||||||
|
|
||||||
|
如果你想更进一步精简,你可以删除参考一下文件或者文件夹的作用,判断自己是否需要,不需要删除即可:
|
||||||
|
|
||||||
|
- `.changeset` 文件夹用于管理版本变更
|
||||||
|
- `.github` 文件夹用于存放 GitHub 的配置文件
|
||||||
|
- `.vscode` 文件夹用于存放 VSCode 的配置文件,如果你使用其他编辑器,可以删除
|
||||||
|
- `./scripts/deploy` 文件夹用于存放部署脚本,如果你不需要docker部署,可以删除
|
||||||
|
@ -14,6 +14,7 @@ function generatorColorVariables(colorItems: ColorItem[]) {
|
|||||||
colorItems.forEach(({ alias, color, name }) => {
|
colorItems.forEach(({ alias, color, name }) => {
|
||||||
if (color) {
|
if (color) {
|
||||||
const colorsMap = getColors(new TinyColor(color).toHexString());
|
const colorsMap = getColors(new TinyColor(color).toHexString());
|
||||||
|
|
||||||
let mainColor = colorsMap['500'];
|
let mainColor = colorsMap['500'];
|
||||||
|
|
||||||
const colorKeys = Object.keys(colorsMap);
|
const colorKeys = Object.keys(colorsMap);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { openWindow } from '../window'; // 假设你的函数在 'openWindow' 文件中
|
import { openWindow } from '../window';
|
||||||
|
|
||||||
describe('openWindow', () => {
|
describe('openWindow', () => {
|
||||||
// 保存原始的 window.open 函数
|
// 保存原始的 window.open 函数
|
||||||
|
@ -86,21 +86,22 @@ function updateMainColorVariables(preference: Preferences) {
|
|||||||
{ alias: 'destructive', color: colorDestructive, name: 'red' },
|
{ alias: 'destructive', color: colorDestructive, name: 'red' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (colorPrimary) {
|
// 要设置的 CSS 变量映射
|
||||||
const mainColor = colorVariables['--primary-500'];
|
const colorMappings = {
|
||||||
mainColor &&
|
'--green-500': '--success',
|
||||||
document.documentElement.style.setProperty('--primary', mainColor);
|
'--primary-500': '--primary',
|
||||||
}
|
'--red-500': '--destructive',
|
||||||
|
'--yellow-500': '--warning',
|
||||||
|
};
|
||||||
|
|
||||||
if (colorVariables['--green-500']) {
|
// 统一处理颜色变量的更新
|
||||||
colorVariables['--success'] = colorVariables['--green-500'];
|
Object.entries(colorMappings).forEach(([sourceVar, targetVar]) => {
|
||||||
}
|
const colorValue = colorVariables[sourceVar];
|
||||||
if (colorVariables['--yellow-500']) {
|
if (colorValue) {
|
||||||
colorVariables['--warning'] = colorVariables['--yellow-500'];
|
document.documentElement.style.setProperty(targetVar, colorValue);
|
||||||
}
|
|
||||||
if (colorVariables['--red-500']) {
|
|
||||||
colorVariables['--destructive'] = colorVariables['--red-500'];
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
executeUpdateCSSVariables(colorVariables);
|
executeUpdateCSSVariables(colorVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ export class FormApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.state = null;
|
// this.state = null;
|
||||||
this.isMounted = false;
|
this.isMounted = false;
|
||||||
this.stateHandler.reset();
|
this.stateHandler.reset();
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
import { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';
|
import { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod';
|
import { toTypedSchema } from '@vee-validate/zod';
|
||||||
import { useFormValues } from 'vee-validate';
|
import { useFieldError, useFormValues } from 'vee-validate';
|
||||||
|
|
||||||
import { injectRenderFormProps, useFormContext } from './context';
|
import { injectRenderFormProps, useFormContext } from './context';
|
||||||
import useDependencies from './dependencies';
|
import useDependencies from './dependencies';
|
||||||
@ -43,8 +43,11 @@ const {
|
|||||||
const { componentBindEventMap, componentMap, isVertical } = useFormContext();
|
const { componentBindEventMap, componentMap, isVertical } = useFormContext();
|
||||||
const formRenderProps = injectRenderFormProps();
|
const formRenderProps = injectRenderFormProps();
|
||||||
const values = useFormValues();
|
const values = useFormValues();
|
||||||
|
const errors = useFieldError(fieldName);
|
||||||
const formApi = formRenderProps.form;
|
const formApi = formRenderProps.form;
|
||||||
|
|
||||||
|
const isInValid = computed(() => errors.value?.length > 0);
|
||||||
|
|
||||||
const fieldComponent = computed(() => {
|
const fieldComponent = computed(() => {
|
||||||
const finalComponent = isString(component)
|
const finalComponent = isString(component)
|
||||||
? componentMap.value[component]
|
? componentMap.value[component]
|
||||||
@ -217,6 +220,7 @@ function createComponentProps(slotProps: Record<string, any>) {
|
|||||||
<FormItem
|
<FormItem
|
||||||
v-show="isShow"
|
v-show="isShow"
|
||||||
:class="{
|
:class="{
|
||||||
|
'form-valid-error': isInValid,
|
||||||
'flex-col': isVertical,
|
'flex-col': isVertical,
|
||||||
'flex-row items-center': !isVertical,
|
'flex-row items-center': !isVertical,
|
||||||
}"
|
}"
|
||||||
@ -248,10 +252,14 @@ function createComponentProps(slotProps: Record<string, any>) {
|
|||||||
...slotProps,
|
...slotProps,
|
||||||
...createComponentProps(slotProps),
|
...createComponentProps(slotProps),
|
||||||
disabled: shouldDisabled,
|
disabled: shouldDisabled,
|
||||||
|
isInValid,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="fieldComponent"
|
:is="fieldComponent"
|
||||||
|
:class="{
|
||||||
|
'border-destructive focus:border-destructive': isInValid,
|
||||||
|
}"
|
||||||
v-bind="createComponentProps(slotProps)"
|
v-bind="createComponentProps(slotProps)"
|
||||||
:disabled="shouldDisabled"
|
:disabled="shouldDisabled"
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import type { ModalState } from '../modal';
|
||||||
|
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { ModalApi } from '../modal-api'; // 假设 ModalApi 位于同一目录
|
import { ModalApi } from '../modal-api';
|
||||||
import type { ModalState } from '../modal';
|
|
||||||
|
|
||||||
vi.mock('@vben-core/shared/store', () => {
|
vi.mock('@vben-core/shared/store', () => {
|
||||||
return {
|
return {
|
||||||
|
@ -52,7 +52,9 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
|
<Transition name="fade">
|
||||||
<DialogOverlay v-if="open && modal" @click="() => emits('close')" />
|
<DialogOverlay v-if="open && modal" @click="() => emits('close')" />
|
||||||
|
</Transition>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
ref="contentRef"
|
ref="contentRef"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
|
@ -5,7 +5,7 @@ useScrollLock();
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-overlay fixed inset-0 z-[1000]"
|
class="bg-overlay fixed inset-0 z-[1000]"
|
||||||
data-dismissable-modal="true"
|
data-dismissable-modal="true"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -46,7 +46,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
|
<Transition name="fade">
|
||||||
<SheetOverlay v-if="open && modal" />
|
<SheetOverlay v-if="open && modal" />
|
||||||
|
</Transition>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
:class="cn(sheetVariants({ side }), 'z-[1000]', props.class)"
|
:class="cn(sheetVariants({ side }), 'z-[1000]', props.class)"
|
||||||
v-bind="{ ...forwarded, ...$attrs }"
|
v-bind="{ ...forwarded, ...$attrs }"
|
||||||
|
@ -5,7 +5,7 @@ useScrollLock();
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="bg-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[1000]"
|
class="bg-overlay fixed inset-0 z-[1000]"
|
||||||
data-dismissable-modal="true"
|
data-dismissable-modal="true"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import type { CaptchaPoint } from '../types';
|
||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
export function useCaptchaPoints() {
|
||||||
|
const points = reactive<CaptchaPoint[]>([]);
|
||||||
|
function addPoint(point: CaptchaPoint) {
|
||||||
|
points.push(point);
|
||||||
|
}
|
||||||
|
function clearPoints() {
|
||||||
|
points.splice(0, points.length);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
addPoint,
|
||||||
|
clearPoints,
|
||||||
|
points,
|
||||||
|
};
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CaptchaPoint, PointSelectionCaptchaProps } from './types';
|
import type { CaptchaPoint, PointSelectionCaptchaProps } from './types';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import { RotateCw } from '@vben/icons';
|
import { RotateCw } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { VbenButton, VbenIconButton } from '@vben-core/shadcn-ui';
|
import { VbenButton, VbenIconButton } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
import { CaptchaCard } from '.';
|
import { CaptchaCard } from '.';
|
||||||
|
import { useCaptchaPoints } from './hooks/useCaptchaPoints';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<PointSelectionCaptchaProps>(), {
|
const props = withDefaults(defineProps<PointSelectionCaptchaProps>(), {
|
||||||
height: '220px',
|
height: '220px',
|
||||||
@ -19,44 +18,24 @@ const props = withDefaults(defineProps<PointSelectionCaptchaProps>(), {
|
|||||||
title: '',
|
title: '',
|
||||||
width: '300px',
|
width: '300px',
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
click: [CaptchaPoint];
|
click: [CaptchaPoint];
|
||||||
confirm: [Array<CaptchaPoint>, clear: () => void];
|
confirm: [Array<CaptchaPoint>, clear: () => void];
|
||||||
refresh: [];
|
refresh: [];
|
||||||
}>();
|
}>();
|
||||||
|
const { addPoint, clearPoints, points } = useCaptchaPoints();
|
||||||
|
|
||||||
if (!props.hintImage && !props.hintText) {
|
if (!props.hintImage && !props.hintText) {
|
||||||
throw new Error('At least one of hint image or hint text must be provided');
|
console.warn('At least one of hint image or hint text must be provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const points = ref<CaptchaPoint[]>([]);
|
|
||||||
const POINT_OFFSET = 11;
|
const POINT_OFFSET = 11;
|
||||||
|
|
||||||
function getElementPosition(element: HTMLElement) {
|
function getElementPosition(element: HTMLElement) {
|
||||||
let posX = 0;
|
|
||||||
let posY = 0;
|
|
||||||
if (element.getBoundingClientRect) {
|
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
const doc = document.documentElement;
|
|
||||||
posX =
|
|
||||||
rect.left +
|
|
||||||
Math.max(doc.scrollLeft, document.body.scrollLeft) -
|
|
||||||
doc.clientLeft;
|
|
||||||
posY =
|
|
||||||
rect.top +
|
|
||||||
Math.max(doc.scrollTop, document.body.scrollTop) -
|
|
||||||
doc.clientTop;
|
|
||||||
} else {
|
|
||||||
while (element !== document.body) {
|
|
||||||
posX += element.offsetLeft;
|
|
||||||
posY += element.offsetTop;
|
|
||||||
element = element.offsetParent as HTMLElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
x: posX,
|
x: rect.left + window.scrollX,
|
||||||
y: posY,
|
y: rect.top + window.scrollY,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,25 +46,35 @@ function handleClick(e: MouseEvent) {
|
|||||||
|
|
||||||
const { x: domX, y: domY } = getElementPosition(dom);
|
const { x: domX, y: domY } = getElementPosition(dom);
|
||||||
|
|
||||||
const mouseX = e.pageX || e.clientX;
|
const mouseX = e.clientX + window.scrollX;
|
||||||
const mouseY = e.pageY || e.clientY;
|
const mouseY = e.clientY + window.scrollY;
|
||||||
|
|
||||||
if (mouseX === undefined || mouseY === undefined)
|
if (typeof mouseX !== 'number' || typeof mouseY !== 'number') {
|
||||||
throw new Error('Mouse coordinates not found');
|
throw new TypeError('Mouse coordinates not found');
|
||||||
|
}
|
||||||
|
|
||||||
const xPos = mouseX - domX;
|
const xPos = mouseX - domX;
|
||||||
const yPos = mouseY - domY;
|
const yPos = mouseY - domY;
|
||||||
|
|
||||||
|
const rect = dom.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 点击位置边界校验
|
||||||
|
if (xPos < 0 || yPos < 0 || xPos > rect.width || yPos > rect.height) {
|
||||||
|
console.warn('Click position is out of the valid range');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const x = Math.ceil(xPos);
|
const x = Math.ceil(xPos);
|
||||||
const y = Math.ceil(yPos);
|
const y = Math.ceil(yPos);
|
||||||
|
|
||||||
const point = {
|
const point = {
|
||||||
i: points.value.length,
|
i: points.length,
|
||||||
t: Date.now(),
|
t: Date.now(),
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
};
|
};
|
||||||
points.value.push(point);
|
|
||||||
|
addPoint(point);
|
||||||
|
|
||||||
emit('click', point);
|
emit('click', point);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -97,7 +86,7 @@ function handleClick(e: MouseEvent) {
|
|||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
try {
|
try {
|
||||||
points.value = [];
|
clearPoints();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in clear:', error);
|
console.error('Error in clear:', error);
|
||||||
}
|
}
|
||||||
@ -115,7 +104,7 @@ function handleRefresh() {
|
|||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
if (!props.showConfirm) return;
|
if (!props.showConfirm) return;
|
||||||
try {
|
try {
|
||||||
emit('confirm', points.value, clear);
|
emit('confirm', points, clear);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleConfirm:', error);
|
console.error('Error in handleConfirm:', error);
|
||||||
}
|
}
|
||||||
@ -164,6 +153,7 @@ function handleConfirm() {
|
|||||||
}"
|
}"
|
||||||
class="bg-primary text-primary-50 border-primary-50 absolute z-20 flex h-5 w-5 cursor-default items-center justify-center rounded-full border-2"
|
class="bg-primary text-primary-50 border-primary-50 absolute z-20 flex h-5 w-5 cursor-default items-center justify-center rounded-full border-2"
|
||||||
role="button"
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,6 @@ import { useLockStore } from './lock';
|
|||||||
|
|
||||||
describe('useLockStore', () => {
|
describe('useLockStore', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// 每个测试前重置 Pinia
|
|
||||||
setActivePinia(createPinia());
|
setActivePinia(createPinia());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -63,12 +63,11 @@ describe('useAccessStore', () => {
|
|||||||
store.tabs = [
|
store.tabs = [
|
||||||
{ fullPath: '/home', meta: {}, name: 'Home', path: '/home' },
|
{ fullPath: '/home', meta: {}, name: 'Home', path: '/home' },
|
||||||
] as any;
|
] as any;
|
||||||
router.replace = vi.fn(); // 使用 vitest 的 mock 函数
|
router.replace = vi.fn();
|
||||||
|
|
||||||
await store.closeAllTabs(router);
|
await store.closeAllTabs(router);
|
||||||
|
|
||||||
expect(store.tabs.length).toBe(1); // 假设没有固定的标签页
|
expect(store.tabs.length).toBe(1);
|
||||||
// expect(router.replace).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes a non-affix tab', () => {
|
it('closes a non-affix tab', () => {
|
||||||
@ -161,7 +160,7 @@ describe('useAccessStore', () => {
|
|||||||
await store._bulkCloseByPaths(['/home', '/contact']);
|
await store._bulkCloseByPaths(['/home', '/contact']);
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0].name).toBe('About');
|
expect(store.tabs[0]?.name).toBe('About');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes all tabs to the left of the specified tab', async () => {
|
it('closes all tabs to the left of the specified tab', async () => {
|
||||||
@ -189,7 +188,7 @@ describe('useAccessStore', () => {
|
|||||||
await store.closeLeftTabs(targetTab);
|
await store.closeLeftTabs(targetTab);
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0].name).toBe('Contact');
|
expect(store.tabs[0]?.name).toBe('Contact');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes all tabs except the specified tab', async () => {
|
it('closes all tabs except the specified tab', async () => {
|
||||||
@ -217,7 +216,7 @@ describe('useAccessStore', () => {
|
|||||||
await store.closeOtherTabs(targetTab);
|
await store.closeOtherTabs(targetTab);
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0].name).toBe('About');
|
expect(store.tabs[0]?.name).toBe('About');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes all tabs to the right of the specified tab', async () => {
|
it('closes all tabs to the right of the specified tab', async () => {
|
||||||
@ -245,7 +244,7 @@ describe('useAccessStore', () => {
|
|||||||
await store.closeRightTabs(targetTab);
|
await store.closeRightTabs(targetTab);
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0].name).toBe('Home');
|
expect(store.tabs[0]?.name).toBe('Home');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes the tab with the specified key', async () => {
|
it('closes the tab with the specified key', async () => {
|
||||||
|
@ -24,7 +24,7 @@ describe('useUserStore', () => {
|
|||||||
expect(store.userInfo).not.toBeNull();
|
expect(store.userInfo).not.toBeNull();
|
||||||
expect(store.userRoles.length).toBeGreaterThan(0);
|
expect(store.userRoles.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
store.setUserInfo(null as any); // 重置用户信息
|
store.setUserInfo(null as any);
|
||||||
expect(store.userInfo).toBeNull();
|
expect(store.userInfo).toBeNull();
|
||||||
expect(store.userRoles).toEqual([]);
|
expect(store.userRoles).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -11,3 +11,7 @@
|
|||||||
.ant-notification-notice {
|
.ant-notification-notice {
|
||||||
@apply dark:border-border/60 dark:border;
|
@apply dark:border-border/60 dark:border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-app .form-valid-error .ant-select-selector {
|
||||||
|
border-color: hsl(var(--destructive));
|
||||||
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
|
import type { Router, RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { generateMenus } from '../generate-menus'; // 替换为您的实际路径
|
import { generateMenus } from '../generate-menus';
|
||||||
import {
|
|
||||||
createRouter,
|
|
||||||
createWebHistory,
|
|
||||||
type Router,
|
|
||||||
type RouteRecordRaw,
|
|
||||||
} from 'vue-router';
|
|
||||||
|
|
||||||
// Nested route setup to test child inclusion and hideChildrenInMenu functionality
|
// Nested route setup to test child inclusion and hideChildrenInMenu functionality
|
||||||
|
|
||||||
|
@ -9,4 +9,5 @@ export const overridesPreferences = defineOverridesPreferences({
|
|||||||
app: {
|
app: {
|
||||||
name: import.meta.env.VITE_APP_TITLE,
|
name: import.meta.env.VITE_APP_TITLE,
|
||||||
},
|
},
|
||||||
|
theme: {},
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ async function handleClick(type: LoginExpiredModeType) {
|
|||||||
接口请求遇到401状态码时,需要重新登录。有两种方式:
|
接口请求遇到401状态码时,需要重新登录。有两种方式:
|
||||||
<p>1.转到登录页,登录成功后跳转回原页面</p>
|
<p>1.转到登录页,登录成功后跳转回原页面</p>
|
||||||
<p>
|
<p>
|
||||||
2.弹出重新登录弹窗,登录后关闭弹窗,不进行任何页面跳转(刷新后调整登录页面)
|
2.弹出重新登录弹窗,登录后关闭弹窗,不进行任何页面跳转(刷新后还是会跳转登录页面)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -46,7 +46,10 @@ const handleClick = (point: CaptchaPoint) => {
|
|||||||
:description="$t('page.examples.captcha.pageDescription')"
|
:description="$t('page.examples.captcha.pageDescription')"
|
||||||
:title="$t('page.examples.captcha.pageTitle')"
|
:title="$t('page.examples.captcha.pageTitle')"
|
||||||
>
|
>
|
||||||
<Card :title="$t('page.examples.captcha.basic')" class="mb-4">
|
<Card
|
||||||
|
:title="$t('page.examples.captcha.basic')"
|
||||||
|
class="mb-4 overflow-x-auto"
|
||||||
|
>
|
||||||
<div class="mb-3 flex items-center justify-start">
|
<div class="mb-3 flex items-center justify-start">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="params.title"
|
v-model:value="params.title"
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter';
|
import { useVbenForm } from '#/adapter';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'FormModelDemo',
|
name: 'FormModelDemo',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onSubmit(values: Record<string, any>) {
|
||||||
|
message.info(JSON.stringify(values)); // 只会执行一次
|
||||||
|
}
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
handleSubmit: onSubmit,
|
||||||
schema: [
|
schema: [
|
||||||
{
|
{
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
Loading…
Reference in New Issue
Block a user