feat(common): ✨ 添加工具
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# @r-utils/uview-plus
|
||||
|
||||
基于 [uview-plus](https://uview-plus.jiangruyi.com/) 的 Vue3 组合式 API 工具 Hooks,适用于 uni-app 项目。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pnpm add @r-utils/uview-plus
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
```ts
|
||||
import { usePickerSingle, usePicker, useCalendar } from '@r-utils/uview-plus'
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `usePickerSingle(options)`
|
||||
|
||||
单列 Picker 封装。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `value` | `unknown \| Ref<unknown>` | `null` | 选中的值 |
|
||||
| `show` | `boolean \| Ref<boolean>` | `false` | 是否显示 |
|
||||
| `indexes` | `Array<number \| null> \| Ref<...>` | `[null]` | 选中的索引 |
|
||||
| `list` | `PickerColumns[0] \| Ref<...>` | `[]` | 列数据 |
|
||||
| `textName` | `string` | `'text'` | 显示字段名 |
|
||||
| `valueName` | `string` | `'value'` | 值字段名 |
|
||||
| `placeholder` | `string` | `'请选择'` | 占位文本 |
|
||||
|
||||
**返回值:**
|
||||
|
||||
```ts
|
||||
{
|
||||
value, show, indexes, columns, text, defaultIndex,
|
||||
showPicker, hidePicker, handleConfirm, handleClose
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `usePicker(options)`
|
||||
|
||||
多列 Picker 封装。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `value` | `unknown[] \| Ref<unknown[]>` | `[]` | 选中的值数组 |
|
||||
| `show` | `boolean \| Ref<boolean>` | `false` | 是否显示 |
|
||||
| `indexes` | `Array<number \| null> \| Ref<...>` | `[]` | 选中的索引数组 |
|
||||
| `columns` | `PickerColumns \| Ref<PickerColumns>` | `[]` | 列数据 |
|
||||
| `textName` | `string` | `'text'` | 显示字段名 |
|
||||
| `valueName` | `string` | `'value'` | 值字段名 |
|
||||
| `placeholder` | `string` | `'请选择'` | 占位文本 |
|
||||
| `separator` | `string` | `' '` | 多列值拼接分隔符 |
|
||||
|
||||
**返回值:**
|
||||
|
||||
```ts
|
||||
{
|
||||
value, show, indexes, columns, text, defaultIndex,
|
||||
showPicker, hidePicker, handleConfirm, handleClose
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `useCalendar(options)`
|
||||
|
||||
日历选择封装。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `value` | `string \| string[] \| Ref<...>` | `null` | 选中的日期 |
|
||||
| `show` | `boolean \| Ref<boolean>` | `false` | 是否显示 |
|
||||
| `mode` | `'single' \| 'multiple' \| 'range' \| Ref<...>` | `'single'` | 日历模式 |
|
||||
| `placeholder` | `string` | `'请选择'` | 占位文本 |
|
||||
|
||||
**返回值:**
|
||||
|
||||
```ts
|
||||
{
|
||||
value, show, text,
|
||||
showCalendar, hideCalendar, handleConfirm, handleClose
|
||||
}
|
||||
```
|
||||
|
||||
## 类型声明
|
||||
|
||||
包内置了 `UViewPlus` namespace 类型声明,无需额外引入。
|
||||
|
||||
```ts
|
||||
declare namespace UViewPlus {
|
||||
type PickerColumns = any[][];
|
||||
type PickerValue<T extends PickerColumns = PickerColumns> = T[number][number][];
|
||||
type PickerConfirmEvent<T extends PickerColumns = PickerColumns> = {
|
||||
indexs: number[];
|
||||
value: PickerValue<T>;
|
||||
values: T;
|
||||
};
|
||||
type CalendarConfirmEvent = string[];
|
||||
}
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
ISC
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "@r-utils/uview-plus",
|
||||
"version": "1.2.1",
|
||||
"private": false,
|
||||
"description": "uview-plus 组合式 API Hooks",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"keywords": [
|
||||
"vue3",
|
||||
"uview-plus",
|
||||
"uni-app"
|
||||
],
|
||||
"publishConfig": {
|
||||
"registry": "http://npm.nps.yunvip123.cn"
|
||||
},
|
||||
"author": {
|
||||
"name": "CodiceFabbrica",
|
||||
"email": "randy1924@163.com",
|
||||
"url": "https://gitee.com/codice_fabbrica"
|
||||
},
|
||||
"homepage": "https://gitee.com/codice_fabbrica/r-util-js",
|
||||
"repository": {
|
||||
"url": "https://gitee.com/codice_fabbrica/r-util-js.git",
|
||||
"type": "git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://gitee.com/codice_fabbrica/r-util-js/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0",
|
||||
"pnpm": ">=10.0.0"
|
||||
},
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch",
|
||||
"lint": "eslint --ext .js,ts --fix src",
|
||||
"release": "standard-version",
|
||||
"commit": "cz",
|
||||
"lint-staged": "lint-staged",
|
||||
"test": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"uview-plus": ">=3.0.0",
|
||||
"vue": "^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"uview-plus": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash-es": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-dts": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vue": "^3.5.13"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { toRef, computed } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
interface UseCalendarOptions {
|
||||
value?: string | string[] | Ref<string | string[]>;
|
||||
show?: boolean | Ref<boolean>;
|
||||
mode?: "single" | "multiple";
|
||||
placeholder?: string;
|
||||
separator?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 uview-plus 的 calendar
|
||||
* uview-plus版本:~3.4.51
|
||||
* @param options 选项
|
||||
* @returns
|
||||
*/
|
||||
export function useCalendar(options: UseCalendarOptions) {
|
||||
const value = toRef(options.value ?? null);
|
||||
const show = toRef(options.show ?? false);
|
||||
const mode = toRef(options.mode ?? "single");
|
||||
const placeholder = toRef(options.placeholder ?? "请选择");
|
||||
const separator = toRef(options.separator ?? ",");
|
||||
|
||||
const text = computed(() => {
|
||||
if (mode.value === "multiple") {
|
||||
return (
|
||||
(value.value as string[])?.join(separator.value) ?? placeholder.value
|
||||
);
|
||||
} else {
|
||||
return value.value ?? placeholder.value;
|
||||
}
|
||||
});
|
||||
|
||||
function showCalendar() {
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
function hideCalendar() {
|
||||
show.value = false;
|
||||
}
|
||||
|
||||
function handleConfirm(e: UViewPlus.CalendarConfirmEvent) {
|
||||
console.log("handleConfirm:", e);
|
||||
if (mode.value === "multiple") {
|
||||
value.value = e;
|
||||
} else {
|
||||
value.value = e[0];
|
||||
}
|
||||
|
||||
hideCalendar();
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
hideCalendar();
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
show,
|
||||
text,
|
||||
showCalendar,
|
||||
hideCalendar,
|
||||
handleConfirm,
|
||||
handleClose,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { toRef, computed } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
interface UseDateTimePickerOptions {
|
||||
value?: string | Ref<string>;
|
||||
show?: boolean | Ref<boolean>;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 uview-plus 的 datetime-picker
|
||||
* uview-plus版本:~3.4.51
|
||||
* @param options 选项
|
||||
* @returns
|
||||
*/
|
||||
export function useDateTimePicker(options: UseDateTimePickerOptions) {
|
||||
const value = toRef(options.value ?? null);
|
||||
const show = toRef(options.show ?? false);
|
||||
const placeholder = toRef(options.placeholder ?? "请选择");
|
||||
|
||||
const text = computed(() => {
|
||||
return value.value ?? placeholder.value;
|
||||
});
|
||||
|
||||
function showPicker() {
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
function hidePicker() {
|
||||
show.value = false;
|
||||
}
|
||||
|
||||
function handleConfirm(e: UViewPlus.PickerConfirmEvent) {
|
||||
console.log("handleConfirm:", e);
|
||||
hidePicker();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
hidePicker();
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
show,
|
||||
text,
|
||||
showPicker,
|
||||
hidePicker,
|
||||
handleConfirm,
|
||||
handleCancel,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./picker-single";
|
||||
export * from "./picker";
|
||||
export * from "./datetime-picker";
|
||||
export * from "./calendar";
|
||||
@@ -0,0 +1,139 @@
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { toRef, computed, watch, nextTick, ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
interface UsePickerSingleOptions {
|
||||
value?: unknown | Ref<unknown>;
|
||||
show?: boolean | Ref<boolean>;
|
||||
indexes?: Array<number | null> | Ref<Array<number | null>>;
|
||||
list?: UViewPlus.PickerColumns[0] | Ref<UViewPlus.PickerColumns[0]>;
|
||||
textName?: string;
|
||||
valueName?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单列 picker
|
||||
* @param options 选项
|
||||
* @returns
|
||||
*/
|
||||
export function usePickerSingle(options: UsePickerSingleOptions) {
|
||||
const value = toRef(options.value ?? null);
|
||||
const show = toRef(options.show ?? false);
|
||||
const indexes = toRef(options.indexes ?? [null]);
|
||||
const list = toRef(options.list ?? []);
|
||||
const textName = toRef(options.textName ?? "text");
|
||||
const valueName = toRef(options.valueName ?? "value");
|
||||
const placeholder = toRef(options.placeholder ?? "请选择");
|
||||
|
||||
const defaultIndex = ref(cloneDeep(indexes.value));
|
||||
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return [value.value];
|
||||
},
|
||||
set(v) {
|
||||
value.value = v[0];
|
||||
},
|
||||
});
|
||||
|
||||
// 将单列数据转换为 columns 格式以兼容 picker 组件
|
||||
const columns = computed(() => [list.value]);
|
||||
|
||||
// 获取当前选中项的文本
|
||||
const text = computed(() => {
|
||||
const index = indexes.value[0];
|
||||
if (index == null) return placeholder.value;
|
||||
|
||||
const item = list.value[index];
|
||||
return item?.[textName.value] ?? placeholder.value;
|
||||
});
|
||||
|
||||
// 监听 indexes 变化,更新 value
|
||||
watch(
|
||||
[indexes, list],
|
||||
([newIndexes, newList]) => {
|
||||
console.log("indexes changed:", newIndexes, newList);
|
||||
const index = newIndexes[0];
|
||||
if (index == null) return;
|
||||
|
||||
const item = newList[index];
|
||||
const newValue = item?.[valueName.value];
|
||||
|
||||
if (newValue !== value.value) {
|
||||
value.value = newValue;
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
// 监听 value 变化,更新 indexes
|
||||
watch(
|
||||
[value, list],
|
||||
([newValue, newList]) => {
|
||||
console.log("value changed:", newValue, newList);
|
||||
const newIndex = newList.findIndex(
|
||||
(item) => item[valueName.value] === newValue,
|
||||
);
|
||||
const finalIndex = newIndex >= 0 ? newIndex : null;
|
||||
|
||||
if (finalIndex !== indexes.value[0]) {
|
||||
indexes.value = [finalIndex];
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function handleConfirm(e: UViewPlus.PickerConfirmEvent) {
|
||||
console.log("handleConfirm:", e);
|
||||
const indexs = e.indexs;
|
||||
|
||||
// 确认后必须选择值,如果未选择,则设置成第1个
|
||||
if (indexs[0] == null) {
|
||||
indexs[0] = 0;
|
||||
}
|
||||
|
||||
indexes.value = indexs;
|
||||
hidePicker();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
hidePicker();
|
||||
}
|
||||
|
||||
function showPicker() {
|
||||
const oIndexes = cloneDeep(indexes.value);
|
||||
defaultIndex.value = [0];
|
||||
show.value = true;
|
||||
nextTick(() => {
|
||||
// TODO 刷新默认选项,待优化
|
||||
defaultIndex.value = oIndexes;
|
||||
});
|
||||
}
|
||||
|
||||
function hidePicker() {
|
||||
show.value = false;
|
||||
}
|
||||
|
||||
function valueToText(v: unknown) {
|
||||
const currentItem = list.value.find((item) => item[valueName.value] === v);
|
||||
return currentItem?.[textName.value] ?? placeholder.value;
|
||||
}
|
||||
|
||||
return {
|
||||
text,
|
||||
value,
|
||||
modelValue,
|
||||
indexes,
|
||||
defaultIndex,
|
||||
columns,
|
||||
textName,
|
||||
valueName,
|
||||
show,
|
||||
handleConfirm,
|
||||
valueToText,
|
||||
showPicker,
|
||||
hidePicker,
|
||||
handleCancel,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { isEqual } from "lodash-es";
|
||||
import { toRef, computed, watch } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
interface UsePickerOptions {
|
||||
/** 选中的值 */
|
||||
value?: unknown[] | Ref<unknown[]>;
|
||||
show?: boolean | Ref<boolean>;
|
||||
/** 选中的值 */
|
||||
indexes?: Array<number | null> | Ref<Array<number | null>>;
|
||||
columns?: UViewPlus.PickerColumns | Ref<UViewPlus.PickerColumns>;
|
||||
textName?: string;
|
||||
valueName?: string;
|
||||
placeholder?: string;
|
||||
separator?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多列 picker
|
||||
* @param options 选项
|
||||
* @returns
|
||||
*/
|
||||
export function usePicker(options: UsePickerOptions) {
|
||||
const value = toRef(options.value ?? []);
|
||||
const show = toRef(options.show ?? false);
|
||||
const indexes = toRef(options.indexes ?? []);
|
||||
const columns = toRef(options.columns ?? [[]]);
|
||||
const textName = toRef(options.textName ?? "text");
|
||||
const valueName = toRef(options.valueName ?? "value");
|
||||
const placeholder = toRef(options.placeholder ?? "请选择");
|
||||
const separator = toRef(options.separator ?? " ");
|
||||
|
||||
const text = computed(() => {
|
||||
const textArray = indexes.value.map((i, index) => {
|
||||
if (i == null) return null;
|
||||
|
||||
const item = columns.value[index][i];
|
||||
return item?.[textName.value] ?? null;
|
||||
});
|
||||
|
||||
if (textArray.some((v) => v == null)) {
|
||||
return placeholder.value;
|
||||
}
|
||||
return textArray.join(separator.value);
|
||||
});
|
||||
|
||||
// 监听 indexes 变化,更新 value
|
||||
watch(
|
||||
[indexes, columns],
|
||||
([newIndexes, newColumns]) => {
|
||||
console.log("indexes changed:", newIndexes, newColumns);
|
||||
if (!newIndexes.length) return;
|
||||
const newValue = newIndexes.map((index, columnIndex) => {
|
||||
if (index == null) return null;
|
||||
const item = newColumns[columnIndex]?.[index];
|
||||
return item?.[valueName.value] ?? null;
|
||||
});
|
||||
|
||||
if (!isEqual(newValue, value.value)) {
|
||||
value.value = newValue;
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
// 监听 value 变化,更新 indexes
|
||||
watch(
|
||||
[value, columns],
|
||||
([newValue, newColumns]) => {
|
||||
console.log("value changed:", newValue, newColumns);
|
||||
const newIndexes = newColumns.map((column, columnIndex) => {
|
||||
const index = column.findIndex(
|
||||
(item) => item[valueName.value] === newValue?.[columnIndex],
|
||||
);
|
||||
return index >= 0 ? index : null;
|
||||
});
|
||||
|
||||
if (!isEqual(newIndexes, indexes.value)) {
|
||||
indexes.value = newIndexes;
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
function handleConfirm(e: UViewPlus.PickerConfirmEvent) {
|
||||
console.log("handleConfirm:", e);
|
||||
indexes.value = e.indexs;
|
||||
}
|
||||
|
||||
function columnValueToText(v: unknown, colIndex = 0) {
|
||||
const currentItem = columns.value[colIndex].find(
|
||||
(item) => item[valueName.value] === v,
|
||||
);
|
||||
return currentItem?.[textName.value];
|
||||
}
|
||||
|
||||
function valueToText(v: unknown[]) {
|
||||
return v.map((item, index) => columnValueToText(item, index));
|
||||
}
|
||||
|
||||
function showPicker() {
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
function hidePicker() {
|
||||
show.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
text,
|
||||
value,
|
||||
indexes,
|
||||
columns,
|
||||
handleConfirm,
|
||||
valueToText,
|
||||
columnValueToText,
|
||||
textName,
|
||||
valueName,
|
||||
show,
|
||||
showPicker,
|
||||
hidePicker,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { effectScope } from "vue";
|
||||
import { useCalendar } from "../src/calendar";
|
||||
|
||||
describe("useCalendar", () => {
|
||||
test("默认值", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, show, text } = useCalendar({});
|
||||
expect(value.value).toBeNull();
|
||||
expect(show.value).toBe(false);
|
||||
expect(text.value).toBe("请选择");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("单选模式:text 显示 value,无值时显示 placeholder", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, text } = useCalendar({ mode: "single", placeholder: "请选日期" });
|
||||
expect(text.value).toBe("请选日期");
|
||||
value.value = "2024-01-15";
|
||||
expect(text.value).toBe("2024-01-15");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("多选模式:text 用 separator 拼接", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, text } = useCalendar({ mode: "multiple", separator: "-" });
|
||||
expect(text.value).toBe("请选择");
|
||||
value.value = ["2024-01-15", "2024-01-16"];
|
||||
expect(text.value).toBe("2024-01-15-2024-01-16");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("showCalendar / hideCalendar", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { show, showCalendar, hideCalendar } = useCalendar({});
|
||||
expect(show.value).toBe(false);
|
||||
showCalendar();
|
||||
expect(show.value).toBe(true);
|
||||
hideCalendar();
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleConfirm 单选模式:设置 value 并关闭", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, show, handleConfirm } = useCalendar({ mode: "single" });
|
||||
show.value = true;
|
||||
handleConfirm(["2024-06-01", "2024-06-02"]);
|
||||
expect(value.value).toBe("2024-06-01");
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleConfirm 多选模式:设置 value 数组并关闭", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, show, handleConfirm } = useCalendar({ mode: "multiple" });
|
||||
show.value = true;
|
||||
handleConfirm(["2024-06-01", "2024-06-02"]);
|
||||
expect(value.value).toEqual(["2024-06-01", "2024-06-02"]);
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleClose 关闭弹窗", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { show, handleClose } = useCalendar({});
|
||||
show.value = true;
|
||||
handleClose();
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("接受初始 value", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { text } = useCalendar({ value: "2024-03-10", mode: "single" });
|
||||
expect(text.value).toBe("2024-03-10");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { effectScope } from "vue";
|
||||
import { useDateTimePicker } from "../src/datetime-picker";
|
||||
|
||||
describe("useDateTimePicker", () => {
|
||||
test("默认值", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, show, text } = useDateTimePicker({});
|
||||
expect(value.value).toBeNull();
|
||||
expect(show.value).toBe(false);
|
||||
expect(text.value).toBe("请选择");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("自定义 placeholder", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { text } = useDateTimePicker({ placeholder: "选择时间" });
|
||||
expect(text.value).toBe("选择时间");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("有 value 时 text 显示 value", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, text } = useDateTimePicker({ value: "2024-01-15 10:00" });
|
||||
expect(text.value).toBe("2024-01-15 10:00");
|
||||
value.value = "2024-06-01 08:30";
|
||||
expect(text.value).toBe("2024-06-01 08:30");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("showPicker / hidePicker", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { show, showPicker, hidePicker } = useDateTimePicker({});
|
||||
expect(show.value).toBe(false);
|
||||
showPicker();
|
||||
expect(show.value).toBe(true);
|
||||
hidePicker();
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleConfirm 关闭弹窗(不更新 value)", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, show, handleConfirm } = useDateTimePicker({ value: "2024-01-01" });
|
||||
show.value = true;
|
||||
const event = { indexs: [], value: [], values: [] } as any;
|
||||
handleConfirm(event);
|
||||
expect(show.value).toBe(false);
|
||||
expect(value.value).toBe("2024-01-01");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleCancel 关闭弹窗", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { show, handleCancel } = useDateTimePicker({});
|
||||
show.value = true;
|
||||
handleCancel();
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { effectScope, nextTick } from "vue";
|
||||
import { usePickerSingle } from "../src/picker-single";
|
||||
|
||||
const list = [
|
||||
{ text: "选项A", value: "a" },
|
||||
{ text: "选项B", value: "b" },
|
||||
{ text: "选项C", value: "c" },
|
||||
];
|
||||
|
||||
describe("usePickerSingle", () => {
|
||||
test("默认值", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { value, show, text, indexes } = usePickerSingle({});
|
||||
expect(value.value).toBeNull();
|
||||
expect(show.value).toBe(false);
|
||||
expect(text.value).toBe("请选择");
|
||||
expect(indexes.value).toEqual([null]);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("columns 是 [list]", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { columns } = usePickerSingle({ list });
|
||||
expect(columns.value).toEqual([list]);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("初始 value 匹配 list 时设置正确 indexes 和 text", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { text, indexes } = usePickerSingle({ value: "b", list });
|
||||
await nextTick();
|
||||
expect(indexes.value).toEqual([1]);
|
||||
expect(text.value).toBe("选项B");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("初始 indexes 匹配 list 时设置正确 value 和 text", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { value, text } = usePickerSingle({ indexes: [2], list });
|
||||
await nextTick();
|
||||
expect(value.value).toBe("c");
|
||||
expect(text.value).toBe("选项C");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("valueToText:根据 value 查找 text", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { valueToText } = usePickerSingle({ list });
|
||||
expect(valueToText("a")).toBe("选项A");
|
||||
expect(valueToText("b")).toBe("选项B");
|
||||
expect(valueToText("unknown")).toBe("请选择");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("showPicker / hidePicker", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { show, showPicker, hidePicker } = usePickerSingle({});
|
||||
expect(show.value).toBe(false);
|
||||
showPicker();
|
||||
expect(show.value).toBe(true);
|
||||
hidePicker();
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleConfirm:更新 indexes 并关闭", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { value, show, indexes, handleConfirm } = usePickerSingle({ list });
|
||||
show.value = true;
|
||||
handleConfirm({ indexs: [1], value: [list[1]], values: [list] } as any);
|
||||
expect(indexes.value).toEqual([1]);
|
||||
expect(show.value).toBe(false);
|
||||
await nextTick();
|
||||
expect(value.value).toBe("b");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleCancel:关闭弹窗", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { show, handleCancel } = usePickerSingle({});
|
||||
show.value = true;
|
||||
handleCancel();
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("modelValue:get/set 代理 value", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { value, modelValue } = usePickerSingle({ list });
|
||||
expect(modelValue.value).toEqual([null]);
|
||||
modelValue.value = "c";
|
||||
expect(value.value).toBe("c");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("自定义 textName 和 valueName", () => {
|
||||
const customList = [
|
||||
{ label: "甲", id: 1 },
|
||||
{ label: "乙", id: 2 },
|
||||
];
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { valueToText } = usePickerSingle({
|
||||
list: customList as any,
|
||||
textName: "label",
|
||||
valueName: "id",
|
||||
});
|
||||
expect(valueToText(1)).toBe("甲");
|
||||
expect(valueToText(2)).toBe("乙");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { effectScope, nextTick } from "vue";
|
||||
import { usePicker } from "../src/picker";
|
||||
|
||||
const columns = [
|
||||
[
|
||||
{ text: "省A", value: "pa" },
|
||||
{ text: "省B", value: "pb" },
|
||||
],
|
||||
[
|
||||
{ text: "市A", value: "ca" },
|
||||
{ text: "市B", value: "cb" },
|
||||
],
|
||||
];
|
||||
|
||||
describe("usePicker", () => {
|
||||
test("默认值", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { value, show, text, indexes } = usePicker({});
|
||||
await nextTick();
|
||||
expect(value.value).toEqual([null]);
|
||||
expect(show.value).toBe(false);
|
||||
expect(text.value).toBe("请选择");
|
||||
expect(indexes.value).toEqual([null]);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("初始 indexes 同步更新 value", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { value } = usePicker({ indexes: [0, 1], columns });
|
||||
await nextTick();
|
||||
expect(value.value).toEqual(["pa", "cb"]);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("初始 value 同步更新 indexes", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { indexes } = usePicker({ value: ["pb", "ca"], columns });
|
||||
await nextTick();
|
||||
expect(indexes.value).toEqual([1, 0]);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("text 由 indexes 和 columns 计算得出", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { text } = usePicker({ indexes: [0, 1], columns, separator: "/" });
|
||||
await nextTick();
|
||||
expect(text.value).toBe("省A/市B");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("indexes 含 null 时 text 显示 placeholder", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { text } = usePicker({ indexes: [null, 1], columns, placeholder: "请选择地区" });
|
||||
await nextTick();
|
||||
expect(text.value).toBe("请选择地区");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("handleConfirm:更新 indexes", async () => {
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { indexes, value, handleConfirm } = usePicker({ columns });
|
||||
handleConfirm({ indexs: [1, 0], value: ["pb", "ca"], values: columns } as any);
|
||||
expect(indexes.value).toEqual([1, 0]);
|
||||
await nextTick();
|
||||
expect(value.value).toEqual(["pb", "ca"]);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("showPicker / hidePicker", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { show, showPicker, hidePicker } = usePicker({});
|
||||
showPicker();
|
||||
expect(show.value).toBe(true);
|
||||
hidePicker();
|
||||
expect(show.value).toBe(false);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("columnValueToText:根据列索引和值查找 text", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { columnValueToText } = usePicker({ columns });
|
||||
expect(columnValueToText("pa", 0)).toBe("省A");
|
||||
expect(columnValueToText("cb", 1)).toBe("市B");
|
||||
expect(columnValueToText("xx", 0)).toBeUndefined();
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("valueToText:返回各列 text 数组", () => {
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
const { valueToText } = usePicker({ columns });
|
||||
expect(valueToText(["pa", "cb"])).toEqual(["省A", "市B"]);
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
test("自定义 textName 和 valueName", async () => {
|
||||
const customColumns = [
|
||||
[
|
||||
{ label: "甲", id: 1 },
|
||||
{ label: "乙", id: 2 },
|
||||
],
|
||||
];
|
||||
const scope = effectScope();
|
||||
await scope.run(async () => {
|
||||
const { text } = usePicker({
|
||||
columns: customColumns as any,
|
||||
indexes: [0],
|
||||
textName: "label",
|
||||
valueName: "id",
|
||||
});
|
||||
await nextTick();
|
||||
expect(text.value).toBe("甲");
|
||||
});
|
||||
scope.stop();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "types/**/*.d.ts"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
/// <reference types="uview-plus/types" />
|
||||
|
||||
declare namespace UViewPlus {
|
||||
type PickerColumns = never[][];
|
||||
type PickerValue<T extends PickerColumns = PickerColumns> = T[number][number][];
|
||||
|
||||
type PickerConfirmEvent<T extends PickerColumns = PickerColumns> = {
|
||||
indexs: number[];
|
||||
value: PickerValue<T>;
|
||||
values: T;
|
||||
};
|
||||
|
||||
type CalendarConfirmEvent = string[];
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`,
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['vue', 'lodash-es'],
|
||||
},
|
||||
sourcemap: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
include: ['src', 'types'],
|
||||
outDir: 'dist',
|
||||
}),
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user