@cynnie/ui-core 0.1.0

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/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @cynnie/ui-core
2
+
3
+ SnowUI 组件库的无 UI 依赖核心层:`createApis`、通用 hooks(`useOptions` / `useDebounce` / `useThrottle` / `useAsyncLoading`)以及类型契约(`FormItem`、`TableColumn`、`DialogOptions` 等)。
4
+
5
+ `@cynnie/ui-antd`、`@cynnie/ui-element`、`@cynnie/ui-shadcn` 均依赖本包,一般无需单独安装;如果你的项目只用核心工具函数,可以只装它。
6
+
7
+ ## 安装
8
+
9
+ ```sh
10
+ pnpm add @cynnie/ui-core
11
+ ```
12
+
13
+ ## 使用
14
+
15
+ ```ts
16
+ import { createApis, useOptions, useDebounce } from "@cynnie/ui-core";
17
+ ```
18
+
19
+ peer 依赖 `vue ^3.5`,需在你的项目中自行安装。
20
+
21
+ ## 相关包
22
+
23
+ - [@cynnie/ui-antd](https://www.npmjs.com/package/@cynnie/ui-antd) — 基于 ant-design-vue
24
+ - [@cynnie/ui-element](https://www.npmjs.com/package/@cynnie/ui-element) — 基于 element-plus
25
+ - [@cynnie/ui-shadcn](https://www.npmjs.com/package/@cynnie/ui-shadcn) — 基于 shadcn-vue(reka-ui + Tailwind CSS v4)
26
+
27
+ ## License
28
+
29
+ MIT
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 防抖:间隔内重复调用只执行最后一次
3
+ */
4
+ export declare function useDebounce(fn: (...args: any[]) => void, delay?: number): (...args: any[]) => void;
5
+ /**
6
+ * 节流:间隔内最多执行一次
7
+ */
8
+ export declare function useThrottle(fn: (...args: any[]) => void, interval?: number): (...args: any[]) => void;
9
+ /**
10
+ * async 点击的 loading 状态:等待 promise 结束自动复位
11
+ */
12
+ export declare function useAsyncLoading(): {
13
+ isLoading: import('vue').Ref<boolean, boolean>;
14
+ wrap: (fn: (...args: any[]) => any, ...args: any[]) => Promise<any>;
15
+ };
@@ -0,0 +1,25 @@
1
+ import { Ref } from 'vue';
2
+ export interface DictOption {
3
+ label: string;
4
+ value: any;
5
+ disabled?: boolean;
6
+ }
7
+ export interface UseOptionsOptions {
8
+ /** 静态选项,元素支持任意结构(配合 labelField/valueField);也支持返回选项的函数或响应式值 */
9
+ options?: any;
10
+ /** 远程字典:返回选项数组的函数,会在挂载时和 deps 变化时自动调用 */
11
+ fetch?: (...args: any[]) => Promise<DictOption[]> | DictOption[];
12
+ /** 依赖值,变化时重新拉取远程字典 */
13
+ deps?: Ref<any> | Ref<any>[];
14
+ /** 挂载时是否立即拉取,默认 true */
15
+ immediate?: boolean;
16
+ }
17
+ /**
18
+ * 选项数据源:静态数组 / 函数 / 远程字典三选一。
19
+ * 供 ProSelect / ProCheckboxGroup / ProRadioGroup 共用。
20
+ */
21
+ export declare function useOptions(config?: UseOptionsOptions): {
22
+ options: Ref<any[], any[]>;
23
+ isLoading: Ref<boolean, boolean>;
24
+ reload: () => Promise<void>;
25
+ };
@@ -0,0 +1,4 @@
1
+ export { createApis } from './utils/createApis.js';
2
+ export * from './types.js';
3
+ export * from './composables/useOptions.js';
4
+ export * from './composables/hooks.js';
package/dist/index.js ADDED
@@ -0,0 +1,95 @@
1
+ import { onMounted, ref, watch } from "vue";
2
+ function createApis(baseUrl, options = {}) {
3
+ const request = options.request || ((url, init) => fetch(url, init).then((res) => res.json()));
4
+ return {
5
+ get: (params) => request(`${baseUrl}?${new URLSearchParams(params)}`, { method: "GET" }),
6
+ create: (data) => request(baseUrl, {
7
+ method: "POST",
8
+ headers: { "Content-Type": "application/json" },
9
+ body: JSON.stringify(data)
10
+ }),
11
+ update: (data) => request(baseUrl, {
12
+ method: "PUT",
13
+ headers: { "Content-Type": "application/json" },
14
+ body: JSON.stringify(data)
15
+ }),
16
+ remove: (ids) => request(baseUrl, {
17
+ method: "DELETE",
18
+ headers: { "Content-Type": "application/json" },
19
+ body: JSON.stringify(ids)
20
+ })
21
+ };
22
+ }
23
+ function useOptions(config = {}) {
24
+ const options = ref([]);
25
+ const isLoading = ref(false);
26
+ function unwrapDeps() {
27
+ const deps = config.deps;
28
+ if (deps == null) return void 0;
29
+ if (Array.isArray(deps)) return deps.map((d) => d && typeof d === "object" && "value" in d ? d.value : d);
30
+ return deps && typeof deps === "object" && "value" in deps ? deps.value : deps;
31
+ }
32
+ async function load() {
33
+ const { options: staticOptions, fetch: fetch$1 } = config;
34
+ const source = typeof staticOptions === "object" && staticOptions !== null && "value" in staticOptions ? staticOptions.value : staticOptions;
35
+ if (source) {
36
+ options.value = typeof source === "function" ? await source(unwrapDeps()) : source;
37
+ return;
38
+ }
39
+ if (fetch$1) {
40
+ isLoading.value = true;
41
+ try {
42
+ options.value = await fetch$1(unwrapDeps());
43
+ } finally {
44
+ isLoading.value = false;
45
+ }
46
+ }
47
+ }
48
+ if (config.deps) watch(Array.isArray(config.deps) ? config.deps : [config.deps], () => load());
49
+ onMounted(() => {
50
+ if (config.immediate !== false) load();
51
+ });
52
+ return {
53
+ options,
54
+ isLoading,
55
+ reload: load
56
+ };
57
+ }
58
+ function useDebounce(fn, delay = 300) {
59
+ let timer;
60
+ return (...args) => {
61
+ if (timer) clearTimeout(timer);
62
+ timer = setTimeout(() => {
63
+ fn(...args);
64
+ }, delay);
65
+ };
66
+ }
67
+ function useThrottle(fn, interval = 300) {
68
+ let last = 0;
69
+ return (...args) => {
70
+ const now = Date.now();
71
+ if (now - last >= interval) {
72
+ last = now;
73
+ fn(...args);
74
+ }
75
+ };
76
+ }
77
+ function useAsyncLoading() {
78
+ const isLoading = ref(false);
79
+ async function wrap(fn, ...args) {
80
+ if (isLoading.value) return;
81
+ isLoading.value = true;
82
+ try {
83
+ return await fn(...args);
84
+ } finally {
85
+ isLoading.value = false;
86
+ }
87
+ }
88
+ return {
89
+ isLoading,
90
+ wrap
91
+ };
92
+ }
93
+ export { createApis, useAsyncLoading, useDebounce, useOptions, useThrottle };
94
+
95
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["timer: ReturnType<typeof setTimeout> | undefined"],"sources":["../src/utils/createApis.ts","../src/composables/useOptions.ts","../src/composables/hooks.ts"],"sourcesContent":["/**\n * 根据接口地址创建 ProTable 需要的 CRUD 接口集合(基于 fetch 的默认实现)。\n * 实际项目里可换成自己的请求库,只要提供 get/create/update/remove 四个 Promise 方法即可。\n */\nexport type PromiseFn = (...args: any[]) => Promise<any>;\n\nexport interface PageApis {\n get: PromiseFn;\n create: PromiseFn;\n update: PromiseFn;\n remove: PromiseFn;\n}\n\nexport interface CreateApisOptions {\n /** 自定义请求实现,默认使用 fetch */\n request?: (url: string, init: RequestInit) => Promise<any>;\n}\n\nexport function createApis(baseUrl: string, options: CreateApisOptions = {}): PageApis {\n const request = options.request || ((url, init) => fetch(url, init).then((res) => res.json()));\n\n return {\n get: (params?: Record<string, any>) =>\n request(`${baseUrl}?${new URLSearchParams(params)}`, { method: \"GET\" }),\n create: (data: Record<string, any>) =>\n request(baseUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n }),\n update: (data: Record<string, any>) =>\n request(baseUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n }),\n remove: (ids: Array<string | number>) =>\n request(baseUrl, {\n method: \"DELETE\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(ids),\n }),\n };\n}\n","import { onMounted, ref, watch, type Ref } from \"vue\";\n\nexport interface DictOption {\n label: string;\n value: any;\n disabled?: boolean;\n}\n\nexport interface UseOptionsOptions {\n /** 静态选项,元素支持任意结构(配合 labelField/valueField);也支持返回选项的函数或响应式值 */\n options?: any\n /** 远程字典:返回选项数组的函数,会在挂载时和 deps 变化时自动调用 */\n fetch?: (...args: any[]) => Promise<DictOption[]> | DictOption[]\n /** 依赖值,变化时重新拉取远程字典 */\n deps?: Ref<any> | Ref<any>[]\n /** 挂载时是否立即拉取,默认 true */\n immediate?: boolean\n}\n\n/**\n * 选项数据源:静态数组 / 函数 / 远程字典三选一。\n * 供 ProSelect / ProCheckboxGroup / ProRadioGroup 共用。\n */\nexport function useOptions(config: UseOptionsOptions = {}) {\n const options = ref<any[]>([]);\n const isLoading = ref(false);\n\n function unwrapDeps(): any {\n const deps = config.deps;\n // deps 可能是 Ref / Ref 数组 / 普通值,取当前值给 fetch 函数用\n if (deps == null) return undefined;\n if (Array.isArray(deps)) {\n return deps.map((d) => (d && typeof d === \"object\" && \"value\" in d ? d.value : d));\n }\n return deps && typeof deps === \"object\" && \"value\" in deps ? deps.value : deps;\n }\n\n async function load() {\n const { options: staticOptions, fetch } = config;\n // options 可能是响应式包裹的值(computed),先解包\n const source = typeof staticOptions === \"object\" && staticOptions !== null && \"value\" in staticOptions ? (staticOptions as any).value : staticOptions;\n if (source) {\n options.value = typeof source === \"function\" ? await source(unwrapDeps()) : source;\n return;\n }\n if (fetch) {\n isLoading.value = true;\n try {\n options.value = await fetch(unwrapDeps());\n } finally {\n isLoading.value = false;\n }\n }\n }\n\n if (config.deps) {\n const deps = Array.isArray(config.deps) ? config.deps : [config.deps];\n watch(deps, () => load());\n }\n\n onMounted(() => {\n if (config.immediate !== false) load();\n });\n\n return { options, isLoading, reload: load };\n}\n","import { ref } from \"vue\";\n\n/**\n * 防抖:间隔内重复调用只执行最后一次\n */\nexport function useDebounce(fn: (...args: any[]) => void, delay = 300) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n return (...args: any[]) => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n fn(...args);\n }, delay);\n };\n}\n\n/**\n * 节流:间隔内最多执行一次\n */\nexport function useThrottle(fn: (...args: any[]) => void, interval = 300) {\n let last = 0;\n\n return (...args: any[]) => {\n const now = Date.now();\n if (now - last >= interval) {\n last = now;\n fn(...args);\n }\n };\n}\n\n/**\n * async 点击的 loading 状态:等待 promise 结束自动复位\n */\nexport function useAsyncLoading() {\n const isLoading = ref(false);\n\n async function wrap(fn: (...args: any[]) => any, ...args: any[]) {\n if (isLoading.value) return;\n isLoading.value = true;\n try {\n return await fn(...args);\n } finally {\n isLoading.value = false;\n }\n }\n\n return { isLoading, wrap };\n}\n"],"mappings":";AAkBA,SAAgB,WAAW,SAAiB,UAA6B,EAAE,EAAY;CACrF,MAAM,UAAU,QAAQ,aAAa,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC,MAAM,QAAQ,IAAI,MAAM,CAAC;AAE7F,QAAO;EACL,MAAM,WACJ,QAAQ,GAAG,QAAQ,GAAG,IAAI,gBAAgB,OAAO,IAAI,EAAE,QAAQ,OAAO,CAAC;EACzE,SAAS,SACP,QAAQ,SAAS;GACf,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU,KAAK;GAC3B,CAAC;EACJ,SAAS,SACP,QAAQ,SAAS;GACf,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU,KAAK;GAC3B,CAAC;EACJ,SAAS,QACP,QAAQ,SAAS;GACf,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU,IAAI;GAC1B,CAAC;EACL;;ACnBH,SAAgB,WAAW,SAA4B,EAAE,EAAE;CACzD,MAAM,UAAU,IAAW,EAAE,CAAC;CAC9B,MAAM,YAAY,IAAI,MAAM;CAE5B,SAAS,aAAkB;EACzB,MAAM,OAAO,OAAO;AAEpB,MAAI,QAAQ,KAAM,QAAO,KAAA;AACzB,MAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,KAAK,KAAK,MAAO,KAAK,OAAO,MAAM,YAAY,WAAW,IAAI,EAAE,QAAQ,EAAG;AAEpF,SAAO,QAAQ,OAAO,SAAS,YAAY,WAAW,OAAO,KAAK,QAAQ;;CAG5E,eAAe,OAAO;EACpB,MAAM,EAAE,SAAS,eAAe,OAAA,YAAU;EAE1C,MAAM,SAAS,OAAO,kBAAkB,YAAY,kBAAkB,QAAQ,WAAW,gBAAiB,cAAsB,QAAQ;AACxI,MAAI,QAAQ;AACV,WAAQ,QAAQ,OAAO,WAAW,aAAa,MAAM,OAAO,YAAY,CAAC,GAAG;AAC5E;;AAEF,MAAI,SAAO;AACT,aAAU,QAAQ;AAClB,OAAI;AACF,YAAQ,QAAQ,MAAM,QAAM,YAAY,CAAC;aACjC;AACR,cAAU,QAAQ;;;;AAKxB,KAAI,OAAO,KAET,OADa,MAAM,QAAQ,OAAO,KAAK,GAAG,OAAO,OAAO,CAAC,OAAO,KAAK,QACnD,MAAM,CAAC;AAG3B,iBAAgB;AACd,MAAI,OAAO,cAAc,MAAO,OAAM;GACtC;AAEF,QAAO;EAAE;EAAS;EAAW,QAAQ;EAAM;;AC3D7C,SAAgB,YAAY,IAA8B,QAAQ,KAAK;CACrE,IAAIA;AAEJ,SAAQ,GAAG,SAAgB;AACzB,MAAI,MAAO,cAAa,MAAM;AAC9B,UAAQ,iBAAiB;AACvB,MAAG,GAAG,KAAK;KACV,MAAM;;;AAOb,SAAgB,YAAY,IAA8B,WAAW,KAAK;CACxE,IAAI,OAAO;AAEX,SAAQ,GAAG,SAAgB;EACzB,MAAM,MAAM,KAAK,KAAK;AACtB,MAAI,MAAM,QAAQ,UAAU;AAC1B,UAAO;AACP,MAAG,GAAG,KAAK;;;;AAQjB,SAAgB,kBAAkB;CAChC,MAAM,YAAY,IAAI,MAAM;CAE5B,eAAe,KAAK,IAA6B,GAAG,MAAa;AAC/D,MAAI,UAAU,MAAO;AACrB,YAAU,QAAQ;AAClB,MAAI;AACF,UAAO,MAAM,GAAG,GAAG,KAAK;YAChB;AACR,aAAU,QAAQ;;;AAItB,QAAO;EAAE;EAAW;EAAM"}
@@ -0,0 +1,44 @@
1
+ import { Component } from 'vue';
2
+ export type PromiseFn = (...args: any[]) => Promise<any>;
3
+ export interface PageApis {
4
+ get: PromiseFn;
5
+ create: PromiseFn;
6
+ update: PromiseFn;
7
+ remove: PromiseFn;
8
+ }
9
+ export interface CreateApisOptions {
10
+ /** 自定义请求实现,默认使用 fetch */
11
+ request?: (url: string, init: RequestInit) => Promise<any>;
12
+ }
13
+ /** ProFormBuilder 的单项配置 */
14
+ export interface FormItem extends Record<string, any> {
15
+ label?: string;
16
+ key: string;
17
+ /** 内置类型:input / textarea / number / date / select / checkbox;也可直接传组件 */
18
+ type?: string | Component;
19
+ /** 隐藏该项(配合 computed 可做联动显隐) */
20
+ hidden?: boolean;
21
+ /** 栅格数(24 分制),不传用组件的 span */
22
+ span?: number;
23
+ /** 直接透传给渲染组件的 props(设置后其余顶层字段不再透传) */
24
+ props?: Record<string, any>;
25
+ /** 配置式插槽:{ 默认插槽名或函数 },传给渲染的组件 */
26
+ slots?: Record<string, any>;
27
+ }
28
+ /** ProFormBuilder 的规则配置:各 UI 库的原生校验规则结构 */
29
+ export type FormRules = Record<string, any>;
30
+ /** ProTable 的列配置:各 UI 库的原生表格列结构 */
31
+ export type TableColumn = Record<string, any>;
32
+ /** 命令式弹窗的配置:title / width / onOk / onCancel 等公共字段 */
33
+ export interface DialogOptions {
34
+ title?: any;
35
+ width?: string | number;
36
+ /** 点确定:拿到内容组件实例(或提交结果),返回 false / reject 拦截关闭 */
37
+ onOk?: (result?: any) => any;
38
+ onCancel?: (e?: any) => any;
39
+ [key: string]: any;
40
+ }
41
+ /** openDialog 的返回值 */
42
+ export interface DialogHandle {
43
+ close: () => void;
44
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 根据接口地址创建 ProTable 需要的 CRUD 接口集合(基于 fetch 的默认实现)。
3
+ * 实际项目里可换成自己的请求库,只要提供 get/create/update/remove 四个 Promise 方法即可。
4
+ */
5
+ export type PromiseFn = (...args: any[]) => Promise<any>;
6
+ export interface PageApis {
7
+ get: PromiseFn;
8
+ create: PromiseFn;
9
+ update: PromiseFn;
10
+ remove: PromiseFn;
11
+ }
12
+ export interface CreateApisOptions {
13
+ /** 自定义请求实现,默认使用 fetch */
14
+ request?: (url: string, init: RequestInit) => Promise<any>;
15
+ }
16
+ export declare function createApis(baseUrl: string, options?: CreateApisOptions): PageApis;
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@cynnie/ui-core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "SnowUI 无 UI 依赖的核心层:createApis / hooks / 类型契约",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/snowbitx/ui.git",
10
+ "directory": "packages/ui-core"
11
+ },
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "sideEffects": false,
29
+ "peerDependencies": {
30
+ "vue": "^3.5.17"
31
+ },
32
+ "devDependencies": {
33
+ "@vitejs/plugin-vue": "^6.0.0",
34
+ "@vitejs/plugin-vue-jsx": "^5.0.0",
35
+ "@vue/tsconfig": "^0.7.0",
36
+ "vite": "npm:rolldown-vite@^7.3.1",
37
+ "vite-plugin-dts": "^4.5.4",
38
+ "vue": "^3.5.17",
39
+ "vue-tsc": "^2.2.10"
40
+ },
41
+ "scripts": {
42
+ "build": "vite build",
43
+ "format": "prettier --write src/",
44
+ "type-check": "vue-tsc --noEmit -p tsconfig.json"
45
+ }
46
+ }