@kine-design/core 0.0.1-beta.4 → 0.0.1-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vlaude/last-session-id +1 -0
- package/components/base/affix/useAffix.ts +2 -1
- package/components/base/anchor/useAnchor.ts +2 -1
- package/components/base/autoComplete/useAutoComplete.ts +2 -1
- package/components/base/button/api.ts +1 -1
- package/components/base/button/props.d.ts +3 -1
- package/components/base/carousel/useCarousel.ts +2 -1
- package/components/base/cascader/useCascader.ts +2 -1
- package/components/base/checkbox/useCheckbox.ts +2 -1
- package/components/base/collapse/useCollapse.ts +2 -1
- package/components/base/datePicker/__tests__/useDatePicker.test.ts +239 -0
- package/components/base/datePicker/api.ts +4 -0
- package/components/base/datePicker/props.d.ts +26 -0
- package/components/base/dropdown/useDropdown.ts +2 -1
- package/components/base/image/__tests__/useImage.test.ts +174 -0
- package/components/base/input/api.ts +5 -1
- package/components/base/input/props.d.ts +26 -1
- package/components/base/input/useInput.ts +12 -2
- package/components/base/inputNumber/__tests__/useInputNumber.test.ts +153 -0
- package/components/base/inputNumber/api.ts +2 -1
- package/components/base/inputNumber/props.d.ts +9 -1
- package/components/base/inputNumber/useInputNumber.ts +53 -11
- package/components/base/loading/api.ts +1 -1
- package/components/base/loading/props.d.ts +3 -1
- package/components/base/popover/usePopover.ts +4 -3
- package/components/base/progress/api.ts +3 -1
- package/components/base/progress/props.d.ts +15 -0
- package/components/base/rate/useRate.ts +2 -1
- package/components/base/select/api.ts +3 -0
- package/components/base/select/props.d.ts +21 -1
- package/components/base/select/useSelect.ts +2 -1
- package/components/base/slider/api.ts +3 -1
- package/components/base/slider/props.d.ts +14 -0
- package/components/base/slider/useSlider.ts +6 -2
- package/components/base/steps/__tests__/useSteps.test.ts +46 -0
- package/components/base/switch/useSwitch.tsx +2 -1
- package/components/base/tabs/useTabs.ts +2 -1
- package/components/base/tag/api.ts +1 -1
- package/components/base/tag/props.d.ts +3 -1
- package/components/base/timePicker/__tests__/useTimePicker.test.ts +118 -0
- package/components/base/timePicker/api.ts +4 -1
- package/components/base/timePicker/props.d.ts +20 -0
- package/components/base/tooltip/api.ts +1 -0
- package/components/base/tooltip/props.d.ts +6 -0
- package/components/base/transfer/useTransfer.ts +2 -1
- package/components/base/tree/__tests__/tree.test.ts +214 -0
- package/components/message/dialog/api.ts +2 -1
- package/components/message/dialog/props.d.ts +7 -0
- package/components/message/drawer/api.ts +1 -0
- package/components/message/drawer/props.d.ts +7 -0
- package/components/message/notification/__tests__/useNotification.test.ts +129 -0
- package/components/message/popover/usePopover.ts +4 -4
- package/components/other/darkMode/useDarkMode.ts +2 -2
- package/components/template/menu/__tests__/useMenu.test.ts +157 -0
- package/components/template/pagination/__tests__/usePagination.test.ts +138 -0
- package/components/template/table/__tests__/useTable.test.ts +139 -0
- package/components/template/table/useTable.ts +2 -4
- package/components/types/hook.d.ts +11 -0
- package/components/types/props.d.ts +5 -0
- package/compositions/common/__tests__/useDebounceFn.test.ts +62 -0
- package/compositions/common/__tests__/useEventListener.test.ts +71 -0
- package/compositions/common/__tests__/usePopover.test.ts +38 -0
- package/compositions/common/__tests__/useTeleport.test.ts +25 -0
- package/compositions/common/useComponentSize.ts +17 -0
- package/compositions/common/useEventListener.ts +3 -3
- package/compositions/common/useResizeObserver.ts +6 -2
- package/compositions/input/__tests__/useBooleanInput.test.ts +73 -0
- package/compositions/modal/__tests__/useModal.test.ts +92 -0
- package/compositions/modal/useModal.ts +2 -2
- package/compositions/popper/useClickAway.ts +4 -4
- package/compositions/utils/__tests__/filters.test.ts +136 -0
- package/compositions/virtualList/__tests__/useHeightCache.test.ts +97 -0
- package/dist/components/base/affix/useAffix.d.ts +2 -1
- package/dist/components/base/anchor/useAnchor.d.ts +2 -1
- package/dist/components/base/autoComplete/useAutoComplete.d.ts +2 -1
- package/dist/components/base/carousel/useCarousel.d.ts +2 -1
- package/dist/components/base/cascader/useCascader.d.ts +2 -1
- package/dist/components/base/checkbox/useCheckbox.d.ts +2 -1
- package/dist/components/base/collapse/useCollapse.d.ts +2 -1
- package/dist/components/base/datePicker/__tests__/useDatePicker.test.d.ts +1 -0
- package/dist/components/base/dropdown/useDropdown.d.ts +2 -1
- package/dist/components/base/image/__tests__/useImage.test.d.ts +1 -0
- package/dist/components/base/input/useInput.d.ts +4 -1
- package/dist/components/base/inputNumber/__tests__/useInputNumber.test.d.ts +1 -0
- package/dist/components/base/inputNumber/useInputNumber.d.ts +2 -1
- package/dist/components/base/popover/usePopover.d.ts +2 -1
- package/dist/components/base/rate/useRate.d.ts +2 -1
- package/dist/components/base/select/useSelect.d.ts +2 -1
- package/dist/components/base/slider/useSlider.d.ts +2 -1
- package/dist/components/base/steps/__tests__/useSteps.test.d.ts +1 -0
- package/dist/components/base/switch/useSwitch.d.ts +2 -1
- package/dist/components/base/tabs/useTabs.d.ts +2 -1
- package/dist/components/base/timePicker/__tests__/useTimePicker.test.d.ts +1 -0
- package/dist/components/base/transfer/useTransfer.d.ts +2 -1
- package/dist/components/base/tree/__tests__/tree.test.d.ts +1 -0
- package/dist/components/message/notification/__tests__/useNotification.test.d.ts +1 -0
- package/dist/components/message/popover/usePopover.d.ts +1 -1
- package/dist/components/other/darkMode/useDarkMode.d.ts +2 -3
- package/dist/components/template/menu/__tests__/useMenu.test.d.ts +1 -0
- package/dist/components/template/pagination/__tests__/usePagination.test.d.ts +1 -0
- package/dist/components/template/table/__tests__/useTable.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/useDebounceFn.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/useEventListener.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/usePopover.test.d.ts +1 -0
- package/dist/compositions/common/__tests__/useTeleport.test.d.ts +1 -0
- package/dist/compositions/common/useComponentSize.d.ts +6 -0
- package/dist/compositions/common/useEventListener.d.ts +2 -2
- package/dist/compositions/input/__tests__/useBooleanInput.test.d.ts +1 -0
- package/dist/compositions/modal/__tests__/useModal.test.d.ts +1 -0
- package/dist/compositions/modal/useModal.d.ts +2 -1
- package/dist/compositions/popper/useClickAway.d.ts +3 -3
- package/dist/compositions/utils/__tests__/filters.test.d.ts +1 -0
- package/dist/compositions/virtualList/__tests__/useHeightCache.test.d.ts +1 -0
- package/dist/core.js +153 -23
- package/dist/tools/__tests__/empty.test.d.ts +1 -0
- package/dist/tools/empty.d.ts +2 -2
- package/dist/tools/types.d.ts +1 -1
- package/dist/vitest.config.d.ts +10 -0
- package/package.json +6 -2
- package/tools/__tests__/empty.test.ts +72 -0
- package/tools/empty.ts +2 -2
- package/tools/types.ts +1 -1
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
f4377795-16b1-4f6c-a26e-f3f958bef0f4
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
9
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
10
|
+
import { type HookContext } from '../../types/hook';
|
|
10
11
|
import { AffixProps } from './props';
|
|
11
12
|
|
|
12
13
|
export function useAffix(
|
|
13
14
|
props: Required<AffixProps>,
|
|
14
|
-
emit:
|
|
15
|
+
emit: HookContext['emit'],
|
|
15
16
|
) {
|
|
16
17
|
const affixed = ref(false);
|
|
17
18
|
// 固定后占位元素的高度(撑开文档流)
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
9
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
10
|
+
import { type HookContext } from '../../types/hook';
|
|
10
11
|
import { AnchorProps } from './props';
|
|
11
12
|
|
|
12
13
|
export function useAnchor(
|
|
13
14
|
props: Required<AnchorProps>,
|
|
14
|
-
emit:
|
|
15
|
+
emit: HookContext['emit'],
|
|
15
16
|
) {
|
|
16
17
|
const currentLink = ref('');
|
|
17
18
|
// 由子组件注册的 href 列表(按 DOM 顺序)
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { ref, toRef, watch, onBeforeUnmount } from 'vue';
|
|
16
16
|
import { AutoCompleteProps, AutoCompleteOption } from './props';
|
|
17
|
+
import { type HookContext } from '../../types/hook';
|
|
17
18
|
|
|
18
|
-
export function useAutoComplete(props: AutoCompleteProps, ctx:
|
|
19
|
+
export function useAutoComplete(props: AutoCompleteProps, ctx: HookContext) {
|
|
19
20
|
const modelValueRef = toRef(() => props.modelValue ?? '');
|
|
20
21
|
|
|
21
22
|
// --- 状态 ---
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* 公司的业务千篇一律,复杂的代码好几百行。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { KineSize } from '../../types/props';
|
|
15
|
+
|
|
14
16
|
export declare type ButtonProps = {
|
|
15
17
|
/**
|
|
16
18
|
* @description button inline text, will replace by slot
|
|
@@ -56,7 +58,7 @@ export declare type ButtonProps = {
|
|
|
56
58
|
* @default medium
|
|
57
59
|
* @enum large|medium|small
|
|
58
60
|
*/
|
|
59
|
-
size?:
|
|
61
|
+
size?: KineSize,
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
export declare type ButtonEvents = {
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
9
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
|
10
|
+
import { type HookContext } from '../../types/hook';
|
|
10
11
|
import { CarouselProps } from './props';
|
|
11
12
|
|
|
12
|
-
export function useCarousel(props: Required<CarouselProps>, emit:
|
|
13
|
+
export function useCarousel(props: Required<CarouselProps>, emit: HookContext['emit'], count: () => number) {
|
|
13
14
|
// 逻辑索引(0..N-1),用于指示器和 v-model
|
|
14
15
|
const activeIndex = ref(props.modelValue ?? props.initialIndex);
|
|
15
16
|
// track 位置索引(loop 时含克隆偏移);初始时若 loop 启用则假定有克隆
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { computed, ref, toRef, watch } from 'vue';
|
|
16
16
|
import { CascaderOption, CascaderProps } from './props';
|
|
17
|
+
import { type HookContext } from '../../types/hook';
|
|
17
18
|
|
|
18
19
|
/** 扁平化后的叶子路径,用于搜索 */
|
|
19
20
|
export interface FlatCascaderOption {
|
|
@@ -64,7 +65,7 @@ const findPathByValues = (
|
|
|
64
65
|
return subPath ? [found, ...subPath] : null;
|
|
65
66
|
};
|
|
66
67
|
|
|
67
|
-
export function useCascader(props: CascaderProps, ctx:
|
|
68
|
+
export function useCascader(props: CascaderProps, ctx: HookContext) {
|
|
68
69
|
const optionsRef = toRef(() => props.options ?? []);
|
|
69
70
|
const modelValueRef = toRef(() => props.modelValue ?? []);
|
|
70
71
|
const multipleRef = toRef(() => props.multiple ?? false);
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
import { ref, watch } from 'vue';
|
|
10
10
|
import { getNewModelValue, initChecked } from '../../../compositions/input/useBooleanInput.ts';
|
|
11
11
|
import { CheckboxProps } from './props';
|
|
12
|
+
import { type HookContext } from '../../types/hook';
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
export function useCheckbox<
|
|
15
16
|
Props extends Required<CheckboxProps>,
|
|
16
|
-
>(props: Props, ctx:
|
|
17
|
+
>(props: Props, ctx: HookContext) {
|
|
17
18
|
const checkboxClass = ['m-checkbox', { 'm-disabled': props.disabled, 'm-indeterminate': props.indeterminate }];
|
|
18
19
|
|
|
19
20
|
const checked = ref(initChecked(props));
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { computed } from 'vue';
|
|
10
10
|
import { CollapseProps } from './props';
|
|
11
|
+
import { type HookContext } from '../../types/hook';
|
|
11
12
|
|
|
12
|
-
export function useCollapse(props: Required<CollapseProps>, ctx:
|
|
13
|
+
export function useCollapse(props: Required<CollapseProps>, ctx: HookContext) {
|
|
13
14
|
const { emit } = ctx;
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description useDatePicker 测试
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/23
|
|
5
|
+
* @version v1.0.0
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { useDatePicker, generateTimeColumn, toDayjs, BASE_WEEK_NAME, BASE_MONTH_NAME } from '../useDatePicker';
|
|
11
|
+
|
|
12
|
+
describe('toDayjs', () => {
|
|
13
|
+
it('字符串转 dayjs', () => {
|
|
14
|
+
const d = toDayjs('2026-03-15');
|
|
15
|
+
expect(d.year()).toBe(2026);
|
|
16
|
+
expect(d.month()).toBe(2); // 0-indexed
|
|
17
|
+
expect(d.date()).toBe(15);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('Date 对象转 dayjs', () => {
|
|
21
|
+
const d = toDayjs(new Date(2026, 5, 10));
|
|
22
|
+
expect(d.year()).toBe(2026);
|
|
23
|
+
expect(d.month()).toBe(5);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('undefined 返回当前时间', () => {
|
|
27
|
+
const d = toDayjs(undefined);
|
|
28
|
+
expect(d.isValid()).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('generateTimeColumn', () => {
|
|
33
|
+
it('生成 24 小时列', () => {
|
|
34
|
+
const hours = generateTimeColumn(24, 1);
|
|
35
|
+
expect(hours).toHaveLength(24);
|
|
36
|
+
expect(hours[0]).toBe(0);
|
|
37
|
+
expect(hours[23]).toBe(23);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('步进为 2', () => {
|
|
41
|
+
const hours = generateTimeColumn(24, 2);
|
|
42
|
+
expect(hours).toEqual([0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('生成 60 分钟列,步进 15', () => {
|
|
46
|
+
const mins = generateTimeColumn(60, 15);
|
|
47
|
+
expect(mins).toEqual([0, 15, 30, 45]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('常量', () => {
|
|
52
|
+
it('BASE_WEEK_NAME 有 7 天', () => {
|
|
53
|
+
expect(BASE_WEEK_NAME).toHaveLength(7);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('BASE_MONTH_NAME 有 12 个月', () => {
|
|
57
|
+
expect(BASE_MONTH_NAME).toHaveLength(12);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('useDatePicker', () => {
|
|
62
|
+
it('初始化空值显示 placeholder', () => {
|
|
63
|
+
const dp = useDatePicker({ type: 'date' });
|
|
64
|
+
expect(dp.displayValue.value).toBe('请选择日期...');
|
|
65
|
+
expect(dp.spanClass.value).toContain('m-date-picker-placeholder');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('初始化有效日期正确解析', () => {
|
|
69
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-06-15' });
|
|
70
|
+
expect(dp.displayValue.value).toBe('2026-06-15');
|
|
71
|
+
expect(dp.dateRef.value.year).toBe(2026);
|
|
72
|
+
expect(dp.dateRef.value.month).toBe(6);
|
|
73
|
+
expect(dp.dateRef.value.day).toBe(15);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('自定义 placeholder', () => {
|
|
77
|
+
const dp = useDatePicker({ type: 'date', placeholder: '选择日期' });
|
|
78
|
+
expect(dp.displayValue.value).toBe('选择日期');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('updateDateRef 更新状态', () => {
|
|
82
|
+
const dp = useDatePicker({ type: 'date' });
|
|
83
|
+
dp.updateDateRef('2025-12-25');
|
|
84
|
+
|
|
85
|
+
expect(dp.dateRef.value.year).toBe(2025);
|
|
86
|
+
expect(dp.dateRef.value.month).toBe(12);
|
|
87
|
+
expect(dp.dateRef.value.day).toBe(25);
|
|
88
|
+
expect(dp.displayValue.value).toBe('2025-12-25');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('updateDateRef 空值恢复 placeholder', () => {
|
|
92
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-01-01' });
|
|
93
|
+
dp.updateDateRef('');
|
|
94
|
+
|
|
95
|
+
expect(dp.displayValue.value).toBe('请选择日期...');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('getCalendar 返回 6 行 7 列', () => {
|
|
99
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
100
|
+
const calendar = dp.getCalendar(dp.dateRef);
|
|
101
|
+
|
|
102
|
+
expect(calendar).toHaveLength(6);
|
|
103
|
+
calendar.forEach(row => {
|
|
104
|
+
expect(row).toHaveLength(7);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('getCalendar 包含当月天数', () => {
|
|
109
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-02-15' });
|
|
110
|
+
const calendar = dp.getCalendar(dp.dateRef);
|
|
111
|
+
const allDays = calendar.flat();
|
|
112
|
+
const currentMonthDays = allDays.filter(d => d.isCurrentMonth);
|
|
113
|
+
|
|
114
|
+
// 2026 年 2 月有 28 天
|
|
115
|
+
expect(currentMonthDays).toHaveLength(28);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('getCalendar 标记当前选中日', () => {
|
|
119
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
120
|
+
const calendar = dp.getCalendar(dp.dateRef);
|
|
121
|
+
const allDays = calendar.flat();
|
|
122
|
+
const currentDay = allDays.find(d => d.isCurrent);
|
|
123
|
+
|
|
124
|
+
expect(currentDay).toBeDefined();
|
|
125
|
+
expect(currentDay!.day).toBe(15);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('toPrevMonth 切换上月', () => {
|
|
129
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
130
|
+
dp.toPrevMonth();
|
|
131
|
+
expect(dp.dateRef.value.month).toBe(2);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('toPrevMonth 跨年', () => {
|
|
135
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-01-15' });
|
|
136
|
+
dp.toPrevMonth();
|
|
137
|
+
expect(dp.dateRef.value.month).toBe(12);
|
|
138
|
+
expect(dp.dateRef.value.year).toBe(2025);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('toNextMonth 切换下月', () => {
|
|
142
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
143
|
+
dp.toNextMonth();
|
|
144
|
+
expect(dp.dateRef.value.month).toBe(4);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('toNextMonth 跨年', () => {
|
|
148
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-12-15' });
|
|
149
|
+
dp.toNextMonth();
|
|
150
|
+
expect(dp.dateRef.value.month).toBe(1);
|
|
151
|
+
expect(dp.dateRef.value.year).toBe(2027);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('toPrevYear / toNextYear', () => {
|
|
155
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-06-15' });
|
|
156
|
+
dp.toPrevYear();
|
|
157
|
+
expect(dp.dateRef.value.year).toBe(2025);
|
|
158
|
+
dp.toNextYear();
|
|
159
|
+
dp.toNextYear();
|
|
160
|
+
expect(dp.dateRef.value.year).toBe(2027);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('getValue 返回格式化日期字符串', () => {
|
|
164
|
+
const dp = useDatePicker({ type: 'date' });
|
|
165
|
+
const value = dp.getValue({ day: 20, month: 6, year: 2026, isCurrentMonth: true });
|
|
166
|
+
expect(value).toBe('2026-06-20');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('month 类型使用 YYYY-MM 格式', () => {
|
|
170
|
+
const dp = useDatePicker({ type: 'month', modelValue: '2026-03' });
|
|
171
|
+
expect(dp.displayValue.value).toBe('2026-03');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('自定义 format', () => {
|
|
175
|
+
const dp = useDatePicker({ type: 'date', format: 'YYYY/MM/DD', modelValue: '2026-03-15' });
|
|
176
|
+
expect(dp.displayValue.value).toBe('2026/03/15');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('clickCurrentYear 切换到年份选择模式', () => {
|
|
180
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
181
|
+
dp.clickCurrentYear(2026);
|
|
182
|
+
expect(dp.calendarTypeRef.value).toBe('year');
|
|
183
|
+
expect(dp.yearsRef.value).toHaveLength(12);
|
|
184
|
+
expect(dp.yearsRef.value).toContain(2026);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('clickYearItem 更新年份并切换回日期模式', () => {
|
|
188
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
189
|
+
dp.clickCurrentYear(2026);
|
|
190
|
+
dp.clickYearItem(2028);
|
|
191
|
+
expect(dp.dateRef.value.year).toBe(2028);
|
|
192
|
+
expect(dp.calendarTypeRef.value).toBe('date');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('clickCurrentMonth 切换到月份选择模式', () => {
|
|
196
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
197
|
+
dp.clickCurrentMonth(3);
|
|
198
|
+
expect(dp.calendarTypeRef.value).toBe('month');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('clickMonthItem 更新月份并切换回日期模式', () => {
|
|
202
|
+
const dp = useDatePicker({ type: 'date', modelValue: '2026-03-15' });
|
|
203
|
+
dp.clickCurrentMonth(3);
|
|
204
|
+
dp.clickMonthItem(8);
|
|
205
|
+
expect(dp.dateRef.value.month).toBe(8);
|
|
206
|
+
expect(dp.calendarTypeRef.value).toBe('date');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// datetime 模式
|
|
210
|
+
it('datetime 模式初始化包含时间', () => {
|
|
211
|
+
const dp = useDatePicker({ type: 'datetime', modelValue: '2026-03-15 14:30:45' });
|
|
212
|
+
expect(dp.timeRef.value.hours).toBe(14);
|
|
213
|
+
expect(dp.timeRef.value.minutes).toBe(30);
|
|
214
|
+
expect(dp.timeRef.value.seconds).toBe(45);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('selectHour / selectMinute / selectSecond', () => {
|
|
218
|
+
const dp = useDatePicker({ type: 'datetime' });
|
|
219
|
+
dp.selectHour(10);
|
|
220
|
+
expect(dp.timeRef.value.hours).toBe(10);
|
|
221
|
+
|
|
222
|
+
dp.selectMinute(25);
|
|
223
|
+
expect(dp.timeRef.value.minutes).toBe(25);
|
|
224
|
+
|
|
225
|
+
dp.selectSecond(59);
|
|
226
|
+
expect(dp.timeRef.value.seconds).toBe(59);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('getDateTimeValue 组合日期和时间', () => {
|
|
230
|
+
const dp = useDatePicker({ type: 'datetime', modelValue: '2026-03-15 00:00:00' });
|
|
231
|
+
dp.selectHour(8);
|
|
232
|
+
dp.selectMinute(30);
|
|
233
|
+
dp.selectSecond(0);
|
|
234
|
+
|
|
235
|
+
const value = dp.getDateTimeValue();
|
|
236
|
+
expect(value).toContain('2026-03-15');
|
|
237
|
+
expect(value).toContain('08:30:00');
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -15,4 +15,8 @@ export const props: MCOPO<DatePickerProps> = {
|
|
|
15
15
|
format: { type: String, default: undefined },
|
|
16
16
|
type: { type: String as MPropType<'date' | 'month' | 'datetime'>, default: 'date' },
|
|
17
17
|
disabled: { type: Boolean, default: false },
|
|
18
|
+
readonly: { type: Boolean, default: false },
|
|
19
|
+
clearable: { type: Boolean, default: false },
|
|
20
|
+
size: { type: String as unknown as MPropType<NonNullable<DatePickerProps['size']>>, default: undefined },
|
|
21
|
+
disabledDate: { type: Function as MPropType<(date: Date) => boolean>, default: undefined },
|
|
18
22
|
};
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { KineSize } from '../../types/props';
|
|
11
|
+
|
|
10
12
|
export declare type DatePickerProps = {
|
|
11
13
|
/**
|
|
12
14
|
* @description 日期值
|
|
@@ -38,6 +40,30 @@ export declare type DatePickerProps = {
|
|
|
38
40
|
* @default false
|
|
39
41
|
*/
|
|
40
42
|
disabled?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* @description 是否只读
|
|
45
|
+
* @type boolean
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
readonly?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* @description whether to show clear button. 是否可清空
|
|
51
|
+
* @type boolean
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
clearable?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* @description 尺寸
|
|
57
|
+
* @type KineSize
|
|
58
|
+
* @default undefined
|
|
59
|
+
*/
|
|
60
|
+
size?: KineSize;
|
|
61
|
+
/**
|
|
62
|
+
* @description callback to determine if a date is disabled. 判断日期是否禁用的回调
|
|
63
|
+
* @type (date: Date) => boolean
|
|
64
|
+
* @default undefined
|
|
65
|
+
*/
|
|
66
|
+
disabledDate?: (date: Date) => boolean;
|
|
41
67
|
};
|
|
42
68
|
|
|
43
69
|
/** 日历面板的显示模式 */
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
|
10
10
|
import { DropdownItemProps, DropdownProps } from './props';
|
|
11
11
|
import useClickAway from '../../../compositions/popper/useClickAway';
|
|
12
|
+
import { type HookContext } from '../../types/hook';
|
|
12
13
|
|
|
13
|
-
export function useDropdown(props: Required<DropdownProps>, ctx:
|
|
14
|
+
export function useDropdown(props: Required<DropdownProps>, ctx: HookContext) {
|
|
14
15
|
const { emit } = ctx;
|
|
15
16
|
|
|
16
17
|
// 下拉菜单是否展开
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description useImage 测试
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/23
|
|
5
|
+
* @version v1.0.0
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
10
|
+
import { useImage } from '../useImage';
|
|
11
|
+
|
|
12
|
+
const defaultProps = {
|
|
13
|
+
src: 'test.png',
|
|
14
|
+
alt: '',
|
|
15
|
+
fit: 'cover' as const,
|
|
16
|
+
lazy: false,
|
|
17
|
+
previewSrcList: [] as string[],
|
|
18
|
+
initialIndex: 0,
|
|
19
|
+
width: '' as string | number,
|
|
20
|
+
height: '' as string | number,
|
|
21
|
+
zIndex: 2000,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('useImage', () => {
|
|
25
|
+
const createEmit = () => vi.fn();
|
|
26
|
+
|
|
27
|
+
it('初始状态为 loading', () => {
|
|
28
|
+
const emit = createEmit();
|
|
29
|
+
const { status } = useImage(defaultProps, emit);
|
|
30
|
+
expect(status.value).toBe('loading');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('handleLoad 设置状态为 loaded 并触发 emit', () => {
|
|
34
|
+
const emit = createEmit();
|
|
35
|
+
const { status, handleLoad } = useImage(defaultProps, emit);
|
|
36
|
+
const event = new Event('load');
|
|
37
|
+
|
|
38
|
+
handleLoad(event);
|
|
39
|
+
expect(status.value).toBe('loaded');
|
|
40
|
+
expect(emit).toHaveBeenCalledWith('load', event);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('handleError 设置状态为 error 并触发 emit', () => {
|
|
44
|
+
const emit = createEmit();
|
|
45
|
+
const { status, handleError } = useImage(defaultProps, emit);
|
|
46
|
+
const event = new Event('error');
|
|
47
|
+
|
|
48
|
+
handleError(event);
|
|
49
|
+
expect(status.value).toBe('error');
|
|
50
|
+
expect(emit).toHaveBeenCalledWith('error', event);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 预览功能
|
|
54
|
+
it('openPreview 无 previewSrcList 时不打开', () => {
|
|
55
|
+
const emit = createEmit();
|
|
56
|
+
const { previewVisible, openPreview } = useImage(defaultProps, emit);
|
|
57
|
+
|
|
58
|
+
openPreview();
|
|
59
|
+
expect(previewVisible.value).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('openPreview 有 previewSrcList 时打开并定位', () => {
|
|
63
|
+
const emit = createEmit();
|
|
64
|
+
const props = { ...defaultProps, previewSrcList: ['a.png', 'test.png', 'c.png'] };
|
|
65
|
+
const { previewVisible, previewIndex, openPreview } = useImage(props, emit);
|
|
66
|
+
|
|
67
|
+
openPreview();
|
|
68
|
+
expect(previewVisible.value).toBe(true);
|
|
69
|
+
expect(previewIndex.value).toBe(1); // test.png 在索引 1
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('openPreview src 不在列表中时定位到 0', () => {
|
|
73
|
+
const emit = createEmit();
|
|
74
|
+
const props = { ...defaultProps, src: 'unknown.png', previewSrcList: ['a.png', 'b.png'] };
|
|
75
|
+
const { previewIndex, openPreview } = useImage(props, emit);
|
|
76
|
+
|
|
77
|
+
openPreview();
|
|
78
|
+
expect(previewIndex.value).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('closePreview 关闭预览', () => {
|
|
82
|
+
const emit = createEmit();
|
|
83
|
+
const props = { ...defaultProps, previewSrcList: ['a.png'] };
|
|
84
|
+
const { previewVisible, openPreview, closePreview } = useImage(props, emit);
|
|
85
|
+
|
|
86
|
+
openPreview();
|
|
87
|
+
closePreview();
|
|
88
|
+
expect(previewVisible.value).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('previewNext / previewPrev 循环切换', () => {
|
|
92
|
+
const emit = createEmit();
|
|
93
|
+
const props = { ...defaultProps, src: 'a.png', previewSrcList: ['a.png', 'b.png', 'c.png'] };
|
|
94
|
+
const { previewIndex, openPreview, previewNext, previewPrev } = useImage(props, emit);
|
|
95
|
+
|
|
96
|
+
openPreview(); // index = 0
|
|
97
|
+
|
|
98
|
+
previewNext(); // index = 1
|
|
99
|
+
expect(previewIndex.value).toBe(1);
|
|
100
|
+
expect(emit).toHaveBeenCalledWith('switch', 1);
|
|
101
|
+
|
|
102
|
+
previewNext(); // index = 2
|
|
103
|
+
expect(previewIndex.value).toBe(2);
|
|
104
|
+
|
|
105
|
+
previewNext(); // 循环回 0
|
|
106
|
+
expect(previewIndex.value).toBe(0);
|
|
107
|
+
|
|
108
|
+
previewPrev(); // 循环到 2
|
|
109
|
+
expect(previewIndex.value).toBe(2);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('zoomIn 放大', () => {
|
|
113
|
+
const emit = createEmit();
|
|
114
|
+
const props = { ...defaultProps, previewSrcList: ['a.png'] };
|
|
115
|
+
const { previewScale, openPreview, zoomIn } = useImage(props, emit);
|
|
116
|
+
|
|
117
|
+
openPreview();
|
|
118
|
+
expect(previewScale.value).toBe(1);
|
|
119
|
+
|
|
120
|
+
zoomIn();
|
|
121
|
+
expect(previewScale.value).toBe(1.25);
|
|
122
|
+
|
|
123
|
+
// 最大 5
|
|
124
|
+
for (let i = 0; i < 20; i++) zoomIn();
|
|
125
|
+
expect(previewScale.value).toBe(5);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('zoomOut 缩小', () => {
|
|
129
|
+
const emit = createEmit();
|
|
130
|
+
const props = { ...defaultProps, previewSrcList: ['a.png'] };
|
|
131
|
+
const { previewScale, openPreview, zoomOut } = useImage(props, emit);
|
|
132
|
+
|
|
133
|
+
openPreview();
|
|
134
|
+
zoomOut();
|
|
135
|
+
expect(previewScale.value).toBe(0.75);
|
|
136
|
+
|
|
137
|
+
// 最小 0.25
|
|
138
|
+
for (let i = 0; i < 20; i++) zoomOut();
|
|
139
|
+
expect(previewScale.value).toBe(0.25);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('rotate 旋转 90°', () => {
|
|
143
|
+
const emit = createEmit();
|
|
144
|
+
const props = { ...defaultProps, previewSrcList: ['a.png'] };
|
|
145
|
+
const { previewRotate, openPreview, rotate } = useImage(props, emit);
|
|
146
|
+
|
|
147
|
+
openPreview();
|
|
148
|
+
rotate();
|
|
149
|
+
expect(previewRotate.value).toBe(90);
|
|
150
|
+
|
|
151
|
+
rotate();
|
|
152
|
+
expect(previewRotate.value).toBe(180);
|
|
153
|
+
|
|
154
|
+
rotate();
|
|
155
|
+
rotate();
|
|
156
|
+
expect(previewRotate.value).toBe(0); // 360 % 360 = 0
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('切换预览图片时重置缩放和旋转', () => {
|
|
160
|
+
const emit = createEmit();
|
|
161
|
+
const props = { ...defaultProps, src: 'a.png', previewSrcList: ['a.png', 'b.png'] };
|
|
162
|
+
const { previewScale, previewRotate, openPreview, zoomIn, rotate, previewNext } = useImage(props, emit);
|
|
163
|
+
|
|
164
|
+
openPreview();
|
|
165
|
+
zoomIn();
|
|
166
|
+
rotate();
|
|
167
|
+
expect(previewScale.value).toBe(1.25);
|
|
168
|
+
expect(previewRotate.value).toBe(90);
|
|
169
|
+
|
|
170
|
+
previewNext();
|
|
171
|
+
expect(previewScale.value).toBe(1);
|
|
172
|
+
expect(previewRotate.value).toBe(0);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
|
-
import { MCOPO } from '../../types/props';
|
|
9
|
+
import { MCOPO, MPropType } from '../../types/props';
|
|
10
10
|
import { InputProps } from './props';
|
|
11
11
|
|
|
12
12
|
export const props: MCOPO<InputProps> = {
|
|
@@ -16,4 +16,8 @@ export const props: MCOPO<InputProps> = {
|
|
|
16
16
|
readonly: { type: Boolean, default: false },
|
|
17
17
|
disabled: { type: Boolean, default: false },
|
|
18
18
|
autofocus: { type: Boolean, default: false },
|
|
19
|
+
clearable: { type: Boolean, default: false },
|
|
20
|
+
size: { type: String as unknown as MPropType<NonNullable<InputProps['size']>>, default: undefined },
|
|
21
|
+
maxlength: { type: Number, default: undefined },
|
|
22
|
+
showWordLimit: { type: Boolean, default: false },
|
|
19
23
|
};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
13
13
|
*/
|
|
14
14
|
import { HTMLElementEvent } from '../../types/template';
|
|
15
|
+
import { KineSize } from '../../types/props';
|
|
15
16
|
|
|
16
17
|
export declare type InputProps = {
|
|
17
18
|
/**
|
|
@@ -50,7 +51,31 @@ export declare type InputProps = {
|
|
|
50
51
|
* @type boolean
|
|
51
52
|
* @default false
|
|
52
53
|
*/
|
|
53
|
-
autofocus?: boolean
|
|
54
|
+
autofocus?: boolean,
|
|
55
|
+
/**
|
|
56
|
+
* @description whether to show clear button. 是否可清空
|
|
57
|
+
* @type boolean
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
clearable?: boolean,
|
|
61
|
+
/**
|
|
62
|
+
* @description input size. 输入框尺寸
|
|
63
|
+
* @type KineSize
|
|
64
|
+
* @default undefined
|
|
65
|
+
*/
|
|
66
|
+
size?: KineSize,
|
|
67
|
+
/**
|
|
68
|
+
* @description max character length. 最大输入长度
|
|
69
|
+
* @type number
|
|
70
|
+
* @default undefined
|
|
71
|
+
*/
|
|
72
|
+
maxlength?: number,
|
|
73
|
+
/**
|
|
74
|
+
* @description whether to show word count. 是否显示字数统计
|
|
75
|
+
* @type boolean
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
showWordLimit?: boolean
|
|
54
79
|
};
|
|
55
80
|
|
|
56
81
|
export declare type InputEvents = {
|