@yh-ui/hooks 0.1.16 → 0.1.21

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.
@@ -0,0 +1,30 @@
1
+ export interface SkuSpec {
2
+ id: string | number;
3
+ name: string;
4
+ values: SkuSpecValue[];
5
+ }
6
+ export interface SkuSpecValue {
7
+ id: string | number;
8
+ name: string;
9
+ [key: string]: unknown;
10
+ }
11
+ export interface SkuItem {
12
+ id: string | number;
13
+ specValueIds: Array<string | number>;
14
+ price: number;
15
+ stock: number;
16
+ [key: string]: unknown;
17
+ }
18
+ /**
19
+ * SKU 核心选择逻辑 Hook
20
+ * @description 基于幂集 (Power Set) 算法实现规格项状态联动和库存检查
21
+ * @param specs 规格列表
22
+ * @param skus SKU 列表
23
+ * @param initialSelection 初始选中的 ID
24
+ */
25
+ export declare function useSKU(specs: SkuSpec[], skus: SkuItem[], initialSelection?: Array<string | number>): {
26
+ selectedValueIds: import("vue").Ref<(string | number)[], (string | number)[]>;
27
+ isValueSelectable: (specIndex: number, valueId: string | number) => boolean;
28
+ selectedSku: import("vue").ComputedRef<SkuItem | null>;
29
+ toggleValue: (specIndex: number, valueId: string | number) => void;
30
+ };
@@ -0,0 +1,58 @@
1
+ import { ref, computed } from "vue";
2
+ export function useSKU(specs, skus, initialSelection = []) {
3
+ const selectedValueIds = ref(initialSelection);
4
+ const pathDict = computed(() => {
5
+ const dict = {};
6
+ skus.forEach((sku) => {
7
+ if (sku.stock <= 0) return;
8
+ const powerSet = getPowerSet(sku.specValueIds);
9
+ powerSet.forEach((path) => {
10
+ const key = path.join(",");
11
+ dict[key] = (dict[key] || 0) + sku.stock;
12
+ });
13
+ });
14
+ return dict;
15
+ });
16
+ const isValueSelectable = (specIndex, valueId) => {
17
+ const tempSelected = [...selectedValueIds.value];
18
+ if (tempSelected[specIndex] === valueId) {
19
+ tempSelected[specIndex] = "";
20
+ } else {
21
+ tempSelected[specIndex] = valueId;
22
+ }
23
+ const query = tempSelected.filter((v) => !!v).sort((a, b) => String(a).localeCompare(String(b))).join(",");
24
+ if (!query) return true;
25
+ return !!pathDict.value[query];
26
+ };
27
+ const toggleValue = (specIndex, valueId) => {
28
+ if (selectedValueIds.value[specIndex] === valueId) {
29
+ selectedValueIds.value[specIndex] = "";
30
+ } else {
31
+ selectedValueIds.value[specIndex] = valueId;
32
+ }
33
+ };
34
+ const selectedSku = computed(() => {
35
+ const completeSelection = selectedValueIds.value.every((v) => !!v);
36
+ if (!completeSelection || selectedValueIds.value.length < specs.length) return null;
37
+ const targetKey = [...selectedValueIds.value].sort((a, b) => String(a).localeCompare(String(b))).join(",");
38
+ return skus.find(
39
+ (sku) => [...sku.specValueIds].sort((a, b) => String(a).localeCompare(String(b))).join(",") === targetKey
40
+ ) || null;
41
+ });
42
+ function getPowerSet(arr) {
43
+ const result = [[]];
44
+ for (const item of arr) {
45
+ const size = result.length;
46
+ for (let i = 0; i < size; i++) {
47
+ result.push([...result[i], item]);
48
+ }
49
+ }
50
+ return result.filter((v) => v.length > 0).map((v) => [...v].sort((a, b) => String(a).localeCompare(String(b))));
51
+ }
52
+ return {
53
+ selectedValueIds,
54
+ isValueSelectable,
55
+ selectedSku,
56
+ toggleValue
57
+ };
58
+ }
@@ -7,45 +7,36 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.useVirtualScroll = useVirtualScroll;
8
8
  var _vue = require("vue");
9
9
  function useVirtualScroll(options) {
10
- const {
11
- itemHeight,
12
- containerHeight,
13
- overscan = 3
14
- } = options;
15
10
  const containerRef = (0, _vue.ref)(null);
16
11
  const scrollTop = (0, _vue.ref)(0);
17
12
  const itemsRef = (0, _vue.computed)(() => {
18
- const items = options.items;
19
- return Array.isArray(items) ? items : items.value;
13
+ return Array.isArray(options.items) ? options.items : options.items.value;
20
14
  });
21
- const totalHeight = (0, _vue.computed)(() => itemsRef.value.length * itemHeight);
22
- const visibleCount = (0, _vue.computed)(() => Math.ceil(containerHeight / itemHeight));
15
+ const itemHeightRef = (0, _vue.computed)(() => (0, _vue.unref)(options.itemHeight));
16
+ const containerHeightRef = (0, _vue.computed)(() => (0, _vue.unref)(options.containerHeight));
17
+ const overscanRef = (0, _vue.computed)(() => (0, _vue.unref)(options.overscan) ?? 3);
18
+ const totalHeight = (0, _vue.computed)(() => itemsRef.value.length * itemHeightRef.value);
23
19
  const startIndex = (0, _vue.computed)(() => {
24
- const items = itemsRef.value;
25
- if (items.length === 0) return 0;
26
- const start = Math.floor(scrollTop.value / itemHeight);
27
- return Math.max(0, start - overscan);
20
+ if (itemsRef.value.length === 0) return 0;
21
+ const start = Math.floor(scrollTop.value / itemHeightRef.value);
22
+ return Math.max(0, start - overscanRef.value);
28
23
  });
29
24
  const endIndex = (0, _vue.computed)(() => {
30
- const items = itemsRef.value;
31
- if (items.length === 0) return 0;
32
- const start = Math.floor(scrollTop.value / itemHeight);
33
- const end = start + visibleCount.value;
34
- return Math.min(items.length, end + overscan);
25
+ if (itemsRef.value.length === 0) return 0;
26
+ const end = Math.ceil((scrollTop.value + containerHeightRef.value) / itemHeightRef.value);
27
+ return Math.min(itemsRef.value.length, end + overscanRef.value);
35
28
  });
36
29
  const visibleItems = (0, _vue.computed)(() => {
37
- const items = itemsRef.value;
38
- if (items.length === 0) return [];
39
- return items.slice(startIndex.value, endIndex.value);
30
+ return itemsRef.value.slice(startIndex.value, endIndex.value);
40
31
  });
41
- const offsetY = (0, _vue.computed)(() => startIndex.value * itemHeight);
32
+ const offsetY = (0, _vue.computed)(() => startIndex.value * itemHeightRef.value);
42
33
  const onScroll = event => {
43
34
  const target = event.target;
44
- scrollTop.value = target.scrollTop;
35
+ if (target) scrollTop.value = target.scrollTop;
45
36
  };
46
37
  const scrollToIndex = index => {
47
38
  if (containerRef.value) {
48
- const targetScrollTop = index * itemHeight;
39
+ const targetScrollTop = index * itemHeightRef.value;
49
40
  containerRef.value.scrollTop = targetScrollTop;
50
41
  scrollTop.value = targetScrollTop;
51
42
  }
@@ -54,8 +45,8 @@ function useVirtualScroll(options) {
54
45
  visibleItems,
55
46
  totalHeight,
56
47
  offsetY,
57
- startIndex: (0, _vue.computed)(() => startIndex.value),
58
- endIndex: (0, _vue.computed)(() => endIndex.value),
48
+ startIndex,
49
+ endIndex,
59
50
  onScroll,
60
51
  scrollToIndex,
61
52
  containerRef
@@ -1,17 +1,13 @@
1
- /**
2
- * useVirtualScroll - 虚拟滚动 Hook
3
- * @description 用于大数据量列表的虚拟滚动渲染
4
- */
5
1
  import { type Ref, type ComputedRef } from 'vue';
6
2
  export interface VirtualScrollOptions<T = unknown> {
7
3
  /** 每项高度 */
8
- itemHeight: number;
4
+ itemHeight: number | Ref<number>;
9
5
  /** 容器高度 */
10
- containerHeight: number;
6
+ containerHeight: number | Ref<number>;
11
7
  /** 数据列表 */
12
8
  items: Ref<T[]> | T[];
13
9
  /** 上下额外渲染数量 */
14
- overscan?: number;
10
+ overscan?: number | Ref<number>;
15
11
  }
16
12
  export interface VirtualScrollReturn<T = unknown> {
17
13
  /** 可见项列表 */
@@ -21,9 +17,9 @@ export interface VirtualScrollReturn<T = unknown> {
21
17
  /** Y轴偏移量 */
22
18
  offsetY: ComputedRef<number>;
23
19
  /** 起始索引 */
24
- startIndex: Ref<number>;
20
+ startIndex: ComputedRef<number>;
25
21
  /** 结束索引 */
26
- endIndex: Ref<number>;
22
+ endIndex: ComputedRef<number>;
27
23
  /** 滚动事件处理 */
28
24
  onScroll: (event: Event) => void;
29
25
  /** 滚动到指定索引 */
@@ -1,40 +1,35 @@
1
- import { ref, computed } from "vue";
1
+ import { ref, computed, unref } from "vue";
2
2
  export function useVirtualScroll(options) {
3
- const { itemHeight, containerHeight, overscan = 3 } = options;
4
3
  const containerRef = ref(null);
5
4
  const scrollTop = ref(0);
6
5
  const itemsRef = computed(() => {
7
- const items = options.items;
8
- return Array.isArray(items) ? items : items.value;
6
+ return Array.isArray(options.items) ? options.items : options.items.value;
9
7
  });
10
- const totalHeight = computed(() => itemsRef.value.length * itemHeight);
11
- const visibleCount = computed(() => Math.ceil(containerHeight / itemHeight));
8
+ const itemHeightRef = computed(() => unref(options.itemHeight));
9
+ const containerHeightRef = computed(() => unref(options.containerHeight));
10
+ const overscanRef = computed(() => unref(options.overscan) ?? 3);
11
+ const totalHeight = computed(() => itemsRef.value.length * itemHeightRef.value);
12
12
  const startIndex = computed(() => {
13
- const items = itemsRef.value;
14
- if (items.length === 0) return 0;
15
- const start = Math.floor(scrollTop.value / itemHeight);
16
- return Math.max(0, start - overscan);
13
+ if (itemsRef.value.length === 0) return 0;
14
+ const start = Math.floor(scrollTop.value / itemHeightRef.value);
15
+ return Math.max(0, start - overscanRef.value);
17
16
  });
18
17
  const endIndex = computed(() => {
19
- const items = itemsRef.value;
20
- if (items.length === 0) return 0;
21
- const start = Math.floor(scrollTop.value / itemHeight);
22
- const end = start + visibleCount.value;
23
- return Math.min(items.length, end + overscan);
18
+ if (itemsRef.value.length === 0) return 0;
19
+ const end = Math.ceil((scrollTop.value + containerHeightRef.value) / itemHeightRef.value);
20
+ return Math.min(itemsRef.value.length, end + overscanRef.value);
24
21
  });
25
22
  const visibleItems = computed(() => {
26
- const items = itemsRef.value;
27
- if (items.length === 0) return [];
28
- return items.slice(startIndex.value, endIndex.value);
23
+ return itemsRef.value.slice(startIndex.value, endIndex.value);
29
24
  });
30
- const offsetY = computed(() => startIndex.value * itemHeight);
25
+ const offsetY = computed(() => startIndex.value * itemHeightRef.value);
31
26
  const onScroll = (event) => {
32
27
  const target = event.target;
33
- scrollTop.value = target.scrollTop;
28
+ if (target) scrollTop.value = target.scrollTop;
34
29
  };
35
30
  const scrollToIndex = (index) => {
36
31
  if (containerRef.value) {
37
- const targetScrollTop = index * itemHeight;
32
+ const targetScrollTop = index * itemHeightRef.value;
38
33
  containerRef.value.scrollTop = targetScrollTop;
39
34
  scrollTop.value = targetScrollTop;
40
35
  }
@@ -43,8 +38,8 @@ export function useVirtualScroll(options) {
43
38
  visibleItems,
44
39
  totalHeight,
45
40
  offsetY,
46
- startIndex: computed(() => startIndex.value),
47
- endIndex: computed(() => endIndex.value),
41
+ startIndex,
42
+ endIndex,
48
43
  onScroll,
49
44
  scrollToIndex,
50
45
  containerRef
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yh-ui/hooks",
3
- "version": "0.1.16",
3
+ "version": "0.1.21",
4
4
  "description": "YH-UI composition hooks",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -24,8 +24,8 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "dayjs": "^1.11.19",
27
- "@yh-ui/locale": "0.1.16",
28
- "@yh-ui/utils": "0.1.16"
27
+ "@yh-ui/locale": "0.1.21",
28
+ "@yh-ui/utils": "0.1.21"
29
29
  },
30
30
  "devDependencies": {
31
31
  "vue": "^3.5.27",
@@ -52,6 +52,8 @@
52
52
  "license": "MIT",
53
53
  "scripts": {
54
54
  "build": "unbuild",
55
- "dev": "unbuild --stub"
55
+ "dev": "unbuild --stub",
56
+ "typecheck": "vue-tsc --noEmit",
57
+ "lint": "eslint ."
56
58
  }
57
59
  }