@composy/store-vue 0.0.1 → 0.0.2
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/CHANGELOG.md +59 -0
- package/README.md +169 -154
- package/dist/index.js +151 -53
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +242 -170
- package/dist/index.min.js.map +1 -1
- package/es/.dts-output-manifest.json +1 -0
- package/es/.dts.tsbuildinfo +1 -0
- package/es/composables/defineSetupStore.d.ts +0 -1
- package/es/composables/defineSetupStore.d.ts.map +1 -1
- package/es/composables/defineSetupStore.js +1 -1
- package/es/composables/defineSetupStore.js.map +1 -1
- package/es/composables/index.d.ts +8 -9
- package/es/composables/index.d.ts.map +1 -1
- package/es/composables/index.js +1 -1
- package/es/composables/useFormStore.d.ts +0 -1
- package/es/composables/useFormStore.d.ts.map +1 -1
- package/es/composables/useFormStore.js +1 -1
- package/es/composables/useFormStore.js.map +1 -1
- package/es/composables/useOptimistic.d.ts +1 -52
- package/es/composables/useOptimistic.d.ts.map +1 -1
- package/es/composables/useOptimistic.js +35 -20
- package/es/composables/useOptimistic.js.map +1 -1
- package/es/composables/usePersist.d.ts +0 -1
- package/es/composables/usePersist.d.ts.map +1 -1
- package/es/composables/usePersist.js +16 -7
- package/es/composables/usePersist.js.map +1 -1
- package/es/composables/usePersistedState.d.ts +0 -1
- package/es/composables/usePersistedState.d.ts.map +1 -1
- package/es/composables/usePersistedState.js +16 -3
- package/es/composables/usePersistedState.js.map +1 -1
- package/es/composables/useSharedState.d.ts +0 -1
- package/es/composables/useSharedState.d.ts.map +1 -1
- package/es/composables/useSharedState.js +25 -8
- package/es/composables/useSharedState.js.map +1 -1
- package/es/composables/useStore.d.ts +0 -1
- package/es/composables/useStore.js +1 -1
- package/es/composables/useStore.js.map +1 -1
- package/es/composables/useStoreDevtools.d.ts +0 -1
- package/es/composables/useStoreDevtools.d.ts.map +1 -1
- package/es/composables/useStoreDevtools.js +1 -1
- package/es/composables/useStoreDevtools.js.map +1 -1
- package/es/composables/useStoreSnapshot.d.ts +0 -1
- package/es/composables/useStoreSnapshot.d.ts.map +1 -1
- package/es/composables/useStoreSnapshot.js +18 -8
- package/es/composables/useStoreSnapshot.js.map +1 -1
- package/es/composables/useSubscribe.d.ts +0 -1
- package/es/composables/useSubscribe.d.ts.map +1 -1
- package/es/composables/useSubscribe.js +1 -1
- package/es/composables/useSubscribe.js.map +1 -1
- package/es/context/injection-keys.d.ts +0 -1
- package/es/context/injection-keys.d.ts.map +1 -1
- package/es/context/injection-keys.js +1 -1
- package/es/context/injection-keys.js.map +1 -1
- package/es/index.d.ts +1 -2
- package/es/index.js +2 -2
- package/es/index.js.map +1 -1
- package/es/plugins/engine-plugin.d.ts +1 -2
- package/es/plugins/engine-plugin.d.ts.map +1 -1
- package/es/plugins/engine-plugin.js +2 -2
- package/es/plugins/engine-plugin.js.map +1 -1
- package/es/plugins/enhanced-persist-plugin.d.ts +0 -1
- package/es/plugins/enhanced-persist-plugin.js +1 -1
- package/es/plugins/index.d.ts +0 -1
- package/es/plugins/index.js +1 -1
- package/es/plugins/pinia-plugin.d.ts +0 -1
- package/es/plugins/pinia-plugin.d.ts.map +1 -1
- package/es/plugins/pinia-plugin.js +24 -6
- package/es/plugins/pinia-plugin.js.map +1 -1
- package/es/plugins/registry-plugin.d.ts +0 -1
- package/es/plugins/registry-plugin.js +1 -1
- package/es/plugins/vue-plugin.d.ts +0 -1
- package/es/plugins/vue-plugin.d.ts.map +1 -1
- package/es/plugins/vue-plugin.js +1 -1
- package/es/plugins/vue-plugin.js.map +1 -1
- package/es/runtime/install-store-app.d.ts +0 -1
- package/es/runtime/install-store-app.js +1 -1
- package/es/runtime/install-store-app.js.map +1 -1
- package/es/runtime/store-plugin-options.d.ts +0 -1
- package/es/runtime/store-plugin-options.js +1 -1
- package/es/runtime/store-plugin-options.js.map +1 -1
- package/es/runtime/store-registry.d.ts +0 -1
- package/es/runtime/store-registry.d.ts.map +1 -1
- package/es/runtime/store-registry.js +14 -4
- package/es/runtime/store-registry.js.map +1 -1
- package/es/runtime/store-runtime.d.ts +1 -2
- package/es/runtime/store-runtime.d.ts.map +1 -1
- package/es/runtime/store-runtime.js +8 -2
- package/es/runtime/store-runtime.js.map +1 -1
- package/es/types.d.ts +2 -1
- package/es/types.d.ts.map +1 -1
- package/es/types.js +1 -1
- package/esm/composables/defineSetupStore.d.ts.map +1 -1
- package/esm/composables/defineSetupStore.js +1 -1
- package/esm/composables/defineSetupStore.js.map +1 -1
- package/esm/composables/index.d.ts +8 -8
- package/esm/composables/index.d.ts.map +1 -1
- package/esm/composables/index.js +1 -1
- package/esm/composables/useFormStore.d.ts.map +1 -1
- package/esm/composables/useFormStore.js +1 -1
- package/esm/composables/useFormStore.js.map +1 -1
- package/esm/composables/useOptimistic.d.ts +1 -51
- package/esm/composables/useOptimistic.d.ts.map +1 -1
- package/esm/composables/useOptimistic.js +35 -20
- package/esm/composables/useOptimistic.js.map +1 -1
- package/esm/composables/usePersist.d.ts.map +1 -1
- package/esm/composables/usePersist.js +16 -7
- package/esm/composables/usePersist.js.map +1 -1
- package/esm/composables/usePersistedState.d.ts.map +1 -1
- package/esm/composables/usePersistedState.js +16 -3
- package/esm/composables/usePersistedState.js.map +1 -1
- package/esm/composables/useSharedState.d.ts.map +1 -1
- package/esm/composables/useSharedState.js +25 -8
- package/esm/composables/useSharedState.js.map +1 -1
- package/esm/composables/useStore.js +1 -1
- package/esm/composables/useStore.js.map +1 -1
- package/esm/composables/useStoreDevtools.d.ts.map +1 -1
- package/esm/composables/useStoreDevtools.js +1 -1
- package/esm/composables/useStoreDevtools.js.map +1 -1
- package/esm/composables/useStoreSnapshot.d.ts.map +1 -1
- package/esm/composables/useStoreSnapshot.js +18 -8
- package/esm/composables/useStoreSnapshot.js.map +1 -1
- package/esm/composables/useSubscribe.d.ts.map +1 -1
- package/esm/composables/useSubscribe.js +1 -1
- package/esm/composables/useSubscribe.js.map +1 -1
- package/esm/context/injection-keys.d.ts.map +1 -1
- package/esm/context/injection-keys.js +1 -1
- package/esm/context/injection-keys.js.map +1 -1
- package/esm/index.d.ts +1 -1
- package/esm/index.js +2 -2
- package/esm/index.js.map +1 -1
- package/esm/plugins/engine-plugin.d.ts +1 -1
- package/esm/plugins/engine-plugin.d.ts.map +1 -1
- package/esm/plugins/engine-plugin.js +2 -2
- package/esm/plugins/engine-plugin.js.map +1 -1
- package/esm/plugins/enhanced-persist-plugin.js +1 -1
- package/esm/plugins/index.js +1 -1
- package/esm/plugins/pinia-plugin.d.ts.map +1 -1
- package/esm/plugins/pinia-plugin.js +24 -6
- package/esm/plugins/pinia-plugin.js.map +1 -1
- package/esm/plugins/registry-plugin.js +1 -1
- package/esm/plugins/vue-plugin.d.ts.map +1 -1
- package/esm/plugins/vue-plugin.js +1 -1
- package/esm/plugins/vue-plugin.js.map +1 -1
- package/esm/runtime/install-store-app.js +1 -1
- package/esm/runtime/install-store-app.js.map +1 -1
- package/esm/runtime/store-plugin-options.js +1 -1
- package/esm/runtime/store-plugin-options.js.map +1 -1
- package/esm/runtime/store-registry.d.ts.map +1 -1
- package/esm/runtime/store-registry.js +14 -4
- package/esm/runtime/store-registry.js.map +1 -1
- package/esm/runtime/store-runtime.d.ts +1 -1
- package/esm/runtime/store-runtime.d.ts.map +1 -1
- package/esm/runtime/store-runtime.js +8 -2
- package/esm/runtime/store-runtime.js.map +1 -1
- package/esm/types.d.ts +2 -0
- package/esm/types.d.ts.map +1 -1
- package/esm/types.js +1 -1
- package/lib/composables/defineSetupStore.cjs +1 -1
- package/lib/composables/defineSetupStore.cjs.map +1 -1
- package/lib/composables/{defineSetupStore.d.ts → defineSetupStore.d.cts} +0 -1
- package/lib/composables/defineSetupStore.d.cts.map +1 -0
- package/lib/composables/index.cjs +1 -1
- package/lib/composables/{index.d.ts → index.d.cts} +8 -9
- package/lib/composables/index.d.cts.map +1 -0
- package/lib/composables/useFormStore.cjs +1 -1
- package/lib/composables/useFormStore.cjs.map +1 -1
- package/lib/composables/{useFormStore.d.ts → useFormStore.d.cts} +0 -1
- package/lib/composables/useFormStore.d.cts.map +1 -0
- package/lib/composables/useOptimistic.cjs +35 -20
- package/lib/composables/useOptimistic.cjs.map +1 -1
- package/lib/composables/useOptimistic.d.cts +16 -0
- package/lib/composables/useOptimistic.d.cts.map +1 -0
- package/lib/composables/usePersist.cjs +16 -7
- package/lib/composables/usePersist.cjs.map +1 -1
- package/lib/composables/{usePersist.d.ts → usePersist.d.cts} +0 -1
- package/lib/composables/usePersist.d.cts.map +1 -0
- package/lib/composables/usePersistedState.cjs +16 -3
- package/lib/composables/usePersistedState.cjs.map +1 -1
- package/lib/composables/{usePersistedState.d.ts → usePersistedState.d.cts} +0 -1
- package/lib/composables/usePersistedState.d.cts.map +1 -0
- package/lib/composables/useSharedState.cjs +24 -7
- package/lib/composables/useSharedState.cjs.map +1 -1
- package/lib/composables/{useSharedState.d.ts → useSharedState.d.cts} +0 -1
- package/lib/composables/useSharedState.d.cts.map +1 -0
- package/lib/composables/useStore.cjs +1 -1
- package/lib/composables/useStore.cjs.map +1 -1
- package/lib/composables/{useStore.d.ts → useStore.d.cts} +0 -1
- package/lib/composables/useStore.d.cts.map +1 -0
- package/lib/composables/useStoreDevtools.cjs +1 -1
- package/lib/composables/useStoreDevtools.cjs.map +1 -1
- package/lib/composables/{useStoreDevtools.d.ts → useStoreDevtools.d.cts} +0 -1
- package/lib/composables/useStoreDevtools.d.cts.map +1 -0
- package/lib/composables/useStoreSnapshot.cjs +18 -8
- package/lib/composables/useStoreSnapshot.cjs.map +1 -1
- package/lib/composables/{useStoreSnapshot.d.ts → useStoreSnapshot.d.cts} +0 -1
- package/lib/composables/useStoreSnapshot.d.cts.map +1 -0
- package/lib/composables/useSubscribe.cjs +1 -1
- package/lib/composables/useSubscribe.cjs.map +1 -1
- package/lib/composables/{useSubscribe.d.ts → useSubscribe.d.cts} +0 -1
- package/lib/composables/useSubscribe.d.cts.map +1 -0
- package/lib/context/injection-keys.cjs +1 -1
- package/lib/context/injection-keys.cjs.map +1 -1
- package/lib/context/{injection-keys.d.ts → injection-keys.d.cts} +0 -1
- package/lib/context/injection-keys.d.cts.map +1 -0
- package/lib/index.cjs +2 -2
- package/lib/index.cjs.map +1 -1
- package/lib/{index.d.ts → index.d.cts} +1 -2
- package/lib/index.d.cts.map +1 -0
- package/lib/plugins/engine-plugin.cjs +2 -2
- package/lib/plugins/engine-plugin.cjs.map +1 -1
- package/lib/plugins/{engine-plugin.d.ts → engine-plugin.d.cts} +1 -2
- package/lib/plugins/engine-plugin.d.cts.map +1 -0
- package/lib/plugins/enhanced-persist-plugin.cjs +1 -1
- package/lib/plugins/{enhanced-persist-plugin.d.ts → enhanced-persist-plugin.d.cts} +0 -1
- package/lib/plugins/enhanced-persist-plugin.d.cts.map +1 -0
- package/lib/plugins/index.cjs +1 -1
- package/lib/plugins/{index.d.ts → index.d.cts} +0 -1
- package/lib/plugins/index.d.cts.map +1 -0
- package/lib/plugins/pinia-plugin.cjs +24 -6
- package/lib/plugins/pinia-plugin.cjs.map +1 -1
- package/lib/plugins/{pinia-plugin.d.ts → pinia-plugin.d.cts} +0 -1
- package/lib/plugins/pinia-plugin.d.cts.map +1 -0
- package/lib/plugins/registry-plugin.cjs +1 -1
- package/lib/plugins/{registry-plugin.d.ts → registry-plugin.d.cts} +0 -1
- package/lib/plugins/registry-plugin.d.cts.map +1 -0
- package/lib/plugins/vue-plugin.cjs +1 -1
- package/lib/plugins/vue-plugin.cjs.map +1 -1
- package/lib/plugins/{vue-plugin.d.ts → vue-plugin.d.cts} +0 -1
- package/lib/plugins/vue-plugin.d.cts.map +1 -0
- package/lib/runtime/install-store-app.cjs +1 -1
- package/lib/runtime/install-store-app.cjs.map +1 -1
- package/lib/runtime/{install-store-app.d.ts → install-store-app.d.cts} +0 -1
- package/lib/runtime/install-store-app.d.cts.map +1 -0
- package/lib/runtime/store-plugin-options.cjs +1 -1
- package/lib/runtime/store-plugin-options.cjs.map +1 -1
- package/lib/runtime/{store-plugin-options.d.ts → store-plugin-options.d.cts} +0 -1
- package/lib/runtime/store-plugin-options.d.cts.map +1 -0
- package/lib/runtime/store-registry.cjs +14 -4
- package/lib/runtime/store-registry.cjs.map +1 -1
- package/lib/runtime/{store-registry.d.ts → store-registry.d.cts} +0 -1
- package/lib/runtime/store-registry.d.cts.map +1 -0
- package/lib/runtime/store-runtime.cjs +8 -2
- package/lib/runtime/store-runtime.cjs.map +1 -1
- package/lib/runtime/{store-runtime.d.ts → store-runtime.d.cts} +1 -2
- package/lib/runtime/store-runtime.d.cts.map +1 -0
- package/lib/types.cjs +1 -1
- package/lib/{types.d.ts → types.d.cts} +2 -1
- package/lib/types.d.cts.map +1 -0
- package/package.json +19 -16
- package/lib/composables/useOptimistic.d.ts +0 -67
package/esm/composables/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFormStore.d.ts","sourceRoot":"","sources":["../../src/composables/useFormStore.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"useFormStore.d.ts","sourceRoot":"","sources":["../../src/composables/useFormStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFormStore.js","names":[],"sources":["../../src/composables/useFormStore.ts"],"sourcesContent":["/* ============================================================\n * useFormStore.ts\n * 表单状态管理 Composable\n * 提供表单值、字段状态、校验、提交、重置等完整表单生命周期管理\n * ============================================================ */\n\n/* 从 Vue 导入类型,用于声明响应式引用 */\nimport type { ComputedRef, Ref } from 'vue'\n/* 从 store-core 工具库导入深拷贝和深比较函数 */\nimport { deepClone, deepEqual } from '@composy/store-core'\n/* 从 Vue 导入响应式 API */\nimport { computed, reactive, ref, watch } from 'vue'\n\n/* ── 默认校验提示信息 ── */\n/* 必填项校验失败时的默认提示 */\nconst DEFAULT_REQUIRED_MESSAGE = '此字段为必填项'\n/* 正则匹配校验失败时的默认提示 */\nconst DEFAULT_PATTERN_MESSAGE = '格式不正确'\n/* 邮箱格式校验失败时的默认提示 */\nconst DEFAULT_EMAIL_MESSAGE = '邮箱格式不正确'\n/* 邮箱格式正则表达式 */\nconst EMAIL_PATTERN = /^[^\\s@]+@[^@][^.@]*\\.[^\\s@]+$/\n\n/* ── 类型定义 ── */\n\n/* 从泛型 T 中提取 string 类型的键名,用作字段标识 */\ntype FieldKey<T> = Extract<keyof T, string>\n\n/**\n * 表单校验器函数类型\n * @param value - 当前字段值\n * @param allValues - 表单所有字段的值(用于跨字段校验)\n * @returns 校验错误信息或 null(无错误),支持异步校验\n */\nexport type FormValidator<T = unknown> = (\n value: T,\n allValues: Record<string, unknown>,\n) => string | null | Promise<string | null>\n\n/**\n * 字段校验规则接口\n * @property required - 是否必填,可传 string 自定义提示\n * @property rules - 自定义校验器数组,按顺序依次执行\n */\nexport interface FormFieldRules<T = unknown> {\n required?: boolean | string\n rules?: FormValidator<T>[]\n}\n\n/**\n * useFormStore 的配置选项接口\n * @property initialValues - 表单初始值(必须提供完整对象)\n * @property validationRules - 各字段的校验规则映射\n * @property onSubmit - 表单提交回调(校验通过后调用)\n * @property validateOn - 触发校验的时机:'blur' | 'change' | 'submit'\n */\nexport interface UseFormStoreOptions<T extends Record<string, unknown>> {\n initialValues: T\n validationRules?: {\n [K in keyof T]?: FormFieldRules<T[K]>\n }\n onSubmit?: (values: T) => void | Promise<void>\n validateOn?: 'blur' | 'change' | 'submit'\n}\n\n/**\n * 单个字段的状态接口\n * @property touched - 字段是否被触碰过(获得过焦点或提交过)\n * @property dirty - 字段值是否与初始值不同(被修改过)\n * @property error - 当前校验错误信息,null 表示无错误\n * @property validating - 字段是否正在异步校验中\n */\nexport interface FormFieldState {\n touched: boolean\n dirty: boolean\n error: string | null\n validating: boolean\n}\n\n/**\n * useFormStore 返回值接口\n * @property values - 表单当前值(reactive 对象)\n * @property fields - 各字段的状态映射\n * @property isDirty - 是否有任意字段被修改过(计算属性)\n * @property isValid - 是否所有字段都无校验错误(计算属性)\n * @property isSubmitting - 表单是否正在提交中\n * @property submitError - 提交失败的错误信息\n * @property submitCount - 累计提交次数\n * @property setFieldValue - 设置指定字段的值\n * @property setFieldTouched - 标记字段为已触碰\n * @property validateField - 校验单个字段(异步)\n * @property validateAll - 校验所有字段(异步)\n * @property handleSubmit - 提交表单\n * @property reset - 重置整个表单(可传新的初始值)\n * @property resetField - 重置单个字段\n * @property errors - 所有字段错误信息的计算属性\n */\nexport interface UseFormStoreReturn<T extends Record<string, unknown>> {\n values: T\n fields: Record<string, FormFieldState>\n isDirty: ComputedRef<boolean>\n isValid: ComputedRef<boolean>\n isSubmitting: Ref<boolean>\n submitError: Ref<string | null>\n submitCount: Ref<number>\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void\n setFieldTouched: (field: keyof T) => void\n validateField: (field: keyof T) => Promise<string | null>\n validateAll: () => Promise<boolean>\n handleSubmit: () => Promise<void>\n reset: (newInitialValues?: Partial<T>) => void\n resetField: (field: keyof T) => void\n errors: ComputedRef<Record<string, string | null>>\n}\n\n/**\n * 表单状态管理 Composable\n * 提供完整的表单生命周期管理能力,包括值管理、字段状态追踪、校验、提交和重置\n *\n * @param options - 表单配置选项\n * @returns 包含表单状态和操作方法的对象\n */\nexport function useFormStore<T extends Record<string, unknown>>(\n options: UseFormStoreOptions<T>,\n): UseFormStoreReturn<T> {\n /* 解构配置选项,设置默认值 */\n const {\n initialValues, /* 表单初始值 */\n validationRules = {} as NonNullable<UseFormStoreOptions<T>['validationRules']>, /* 校验规则,默认空对象 */\n onSubmit, /* 提交回调 */\n validateOn = 'blur', /* 校验时机,默认 blur 时触发 */\n } = options\n\n /* 深拷贝初始值并保存,用于后续重置和脏值比较 */\n const storedInitialValues = deepClone(initialValues) as T\n /* 创建响应式表单值对象(从初始值的深拷贝开始) */\n const values = reactive(deepClone(storedInitialValues)) as T\n /* 将 values 转为通用 Record 类型,便于动态属性访问 */\n const valueRecord = values as Record<string, unknown>\n /* 将初始值也转为 Record 类型,用于脏值比较 */\n const initialValueRecord = storedInitialValues as Record<string, unknown>\n /* 获取所有字段名列表 */\n const fieldKeys = Object.keys(storedInitialValues) as Array<FieldKey<T>>\n\n /* 创建每个字段的响应式状态对象(touched/dirty/error/validating) */\n const fields = reactive(\n fieldKeys.reduce<Record<string, FormFieldState>>((acc, key) => {\n acc[key] = {\n touched: false, /* 字段未被触碰 */\n dirty: false, /* 字段值未被修改 */\n error: null, /* 无校验错误 */\n validating: false, /* 未在校验中 */\n }\n return acc\n }, {}),\n )\n\n /* ── 全局状态 ── */\n /* 是否正在提交中 */\n const isSubmitting = ref(false)\n /* 提交错误信息 */\n const submitError = ref<string | null>(null)\n /* 累计提交次数 */\n const submitCount = ref(0)\n\n /* ── 计算属性 ── */\n /* 是否有任意字段被修改过 */\n const isDirty = computed(() => fieldKeys.some(key => fields[key]?.dirty))\n /* 是否所有字段都无错误 */\n const isValid = computed(() => fieldKeys.every(key => !fields[key]?.error))\n\n /* 所有字段错误信息的映射 */\n const errors = computed(() => {\n return fieldKeys.reduce<Record<string, string | null>>((acc, key) => {\n acc[key] = fields[key]?.error ?? null\n return acc\n }, {})\n })\n\n /* ── 值变化监听 ── */\n /* 为每个字段创建 watch,当值变化时更新脏状态并按需触发校验 */\n fieldKeys.forEach((key) => {\n watch(\n () => valueRecord[key], /* 监听字段值 */\n (newValue) => {\n const field = fields[key]\n if (field) {\n /* 比较当前值与初始值,判断是否为脏值 */\n field.dirty = !deepEqual(newValue, initialValueRecord[key])\n }\n\n /* 如果校验时机为 'change' 且字段已被触碰,则触发校验 */\n if (validateOn === 'change' && field?.touched) {\n void validateField(key)\n }\n },\n { deep: true }, /* 深度监听,支持嵌套对象变化 */\n )\n })\n\n /* ── 操作方法 ── */\n\n /**\n * 设置指定字段的值\n * @param field - 字段名\n * @param value - 新值\n */\n function setFieldValue<K extends keyof T>(field: K, value: T[K]): void {\n valueRecord[field as string] = value\n }\n\n /**\n * 标记字段为已触碰状态\n * 如果校验时机为 'blur',同时触发该字段的校验\n * @param field - 字段名\n */\n function setFieldTouched(field: keyof T): void {\n const fieldState = fields[field as string]\n if (fieldState) {\n fieldState.touched = true\n }\n\n /* blur 时自动触发校验 */\n if (validateOn === 'blur') {\n void validateField(field)\n }\n }\n\n /**\n * 校验单个字段\n * 按顺序执行:必填校验 → 自定义规则校验\n * @param field - 字段名\n * @returns 错误信息或 null(无错误)\n */\n async function validateField(field: keyof T): Promise<string | null> {\n const fieldKey = field as string\n const fieldState = fields[fieldKey]\n\n /* 字段状态不存在则跳过 */\n if (!fieldState) {\n return null\n }\n\n /* 获取该字段的校验规则 */\n const fieldRules = validationRules[field]\n /* 无规则则清除错误并返回 */\n if (!fieldRules) {\n fieldState.error = null\n return null\n }\n\n /* 标记开始校验 */\n fieldState.validating = true\n\n try {\n const value = values[field]\n\n /* 第一步:必填校验 */\n if (fieldRules.required) {\n const isEmpty = value === '' || value === null || value === undefined\n if (isEmpty) {\n /* 支持自定义必填提示信息 */\n const message = typeof fieldRules.required === 'string'\n ? fieldRules.required\n : DEFAULT_REQUIRED_MESSAGE\n fieldState.error = message\n return message\n }\n }\n\n /* 第二步:自定义规则校验(按顺序执行,遇到第一个错误即停止) */\n if (fieldRules.rules) {\n for (const rule of fieldRules.rules) {\n const error = await rule(\n value as T[keyof T],\n valueRecord, /* 传入所有表单值,支持跨字段校验 */\n )\n\n if (error) {\n fieldState.error = error\n return error\n }\n }\n }\n\n /* 所有校验通过,清除错误 */\n fieldState.error = null\n return null\n }\n finally {\n /* 无论成功失败,标记校验结束 */\n fieldState.validating = false\n }\n }\n\n /**\n * 校验所有字段\n * @returns 是否所有字段都校验通过\n */\n async function validateAll(): Promise<boolean> {\n const results = await Promise.all(fieldKeys.map(key => validateField(key)))\n return results.every(result => result === null)\n }\n\n /**\n * 提交流程\n * 1. 递增提交计数\n * 2. 标记所有字段为已触碰\n * 3. 执行全量校验\n * 4. 校验通过且有 onSubmit 回调时,调用提交\n */\n async function handleSubmit(): Promise<void> {\n submitCount.value++\n submitError.value = null\n\n /* 标记所有字段为已触碰 */\n fieldKeys.forEach((key) => {\n const field = fields[key]\n if (field) {\n field.touched = true\n }\n })\n\n /* 执行全量校验 */\n const valid = await validateAll()\n /* 校验未通过或没有提交回调则退出 */\n if (!valid || !onSubmit) {\n return\n }\n\n /* 开始提交 */\n isSubmitting.value = true\n try {\n /* 深拷贝 values 后传给 onSubmit,避免外部修改响应式数据 */\n await onSubmit(deepClone(values) as T)\n }\n catch (error) {\n /* 捕获提交异常,保存错误信息 */\n submitError.value = error instanceof Error ? error.message : String(error)\n }\n finally {\n /* 无论成功失败,标记提交结束 */\n isSubmitting.value = false\n }\n }\n\n /**\n * 重置整个表单\n * 可选传入新的初始值(部分更新)\n * @param newInitialValues - 新的初始值(可选,会与现有初始值合并)\n */\n function reset(newInitialValues?: Partial<T>): void {\n /* 如果传入了新初始值,合并到存储的初始值中 */\n if (newInitialValues) {\n Object.assign(initialValueRecord, deepClone(newInitialValues))\n }\n\n /* 恢复每个字段的值和状态 */\n fieldKeys.forEach((key) => {\n /* 恢复为初始值的深拷贝 */\n valueRecord[key] = deepClone(initialValueRecord[key])\n\n /* 重置字段状态 */\n const field = fields[key]\n if (field) {\n field.touched = false /* 清除触碰状态 */\n field.dirty = false /* 清除脏值标记 */\n field.error = null /* 清除校验错误 */\n field.validating = false /* 清除校验中标记 */\n }\n })\n\n /* 清除提交错误 */\n submitError.value = null\n }\n\n /**\n * 重置单个字段\n * @param field - 要重置的字段名\n */\n function resetField(field: keyof T): void {\n const fieldKey = field as string\n /* 恢复字段值为初始值的深拷贝 */\n valueRecord[fieldKey] = deepClone(initialValueRecord[fieldKey])\n\n /* 重置字段状态 */\n const fieldState = fields[fieldKey]\n if (fieldState) {\n fieldState.touched = false\n fieldState.dirty = false\n fieldState.error = null\n fieldState.validating = false\n }\n }\n\n /* ── 返回所有状态和方法 ── */\n return {\n values, /* 表单当前值(响应式) */\n fields, /* 字段状态映射(响应式) */\n isDirty, /* 是否有脏值 */\n isValid, /* 是否全部有效 */\n isSubmitting, /* 是否正在提交 */\n submitError, /* 提交错误信息 */\n submitCount, /* 提交次数 */\n setFieldValue, /* 设置字段值 */\n setFieldTouched, /* 标记字段已触碰 */\n validateField, /* 校验单个字段 */\n validateAll, /* 校验所有字段 */\n handleSubmit, /* 提交表单 */\n reset, /* 重置表单 */\n resetField, /* 重置单个字段 */\n errors, /* 错误信息映射 */\n }\n}\n\n/* ============================================================\n * 预置校验器工厂函数\n * 提供常用的表单校验规则创建函数\n * ============================================================ */\n\n/**\n * 创建必填校验器\n * @param message - 自定义错误提示,默认为 '此字段为必填项'\n * @returns 校验器函数\n */\nexport function required(message = DEFAULT_REQUIRED_MESSAGE): FormValidator {\n return (value) => {\n /* 空字符串、null、undefined 均视为\"空\" */\n if (value === '' || value === null || value === undefined) {\n return message\n }\n\n return null /* 通过校验 */\n }\n}\n\n/**\n * 创建最小长度校验器\n * @param min - 最小字符数\n * @param message - 自定义错误提示\n * @returns 字符串校验器函数\n */\nexport function minLength(min: number, message?: string): FormValidator<string> {\n return (value) => {\n if (typeof value === 'string' && value.length < min) {\n return message ?? `至少需要 ${min} 个字符`\n }\n\n return null\n }\n}\n\n/**\n * 创建最大长度校验器\n * @param max - 最大字符数\n * @param message - 自定义错误提示\n * @returns 字符串校验器函数\n */\nexport function maxLength(max: number, message?: string): FormValidator<string> {\n return (value) => {\n if (typeof value === 'string' && value.length > max) {\n return message ?? `不能超过 ${max} 个字符`\n }\n\n return null\n }\n}\n\n/**\n * 创建正则匹配校验器\n * @param regex - 正则表达式\n * @param message - 自定义错误提示,默认为 '格式不正确'\n * @returns 字符串校验器函数\n */\nexport function pattern(\n regex: RegExp,\n message = DEFAULT_PATTERN_MESSAGE,\n): FormValidator<string> {\n return (value) => {\n if (typeof value === 'string' && !regex.test(value)) {\n return message\n }\n\n return null\n }\n}\n\n/**\n * 创建邮箱格式校验器\n * 基于内置的 EMAIL_PATTERN 正则\n * @param message - 自定义错误提示,默认为 '邮箱格式不正确'\n * @returns 字符串校验器函数\n */\nexport function email(message = DEFAULT_EMAIL_MESSAGE): FormValidator<string> {\n return pattern(EMAIL_PATTERN, message)\n}\n\n/**\n * 创建数值范围校验器\n * @param min - 最小值\n * @param max - 最大值\n * @param message - 自定义错误提示\n * @returns 数字校验器函数\n */\nexport function numberRange(\n min: number,\n max: number,\n message?: string,\n): FormValidator<number> {\n return (value) => {\n if (typeof value === 'number' && (value < min || value > max)) {\n return message ?? `必须在 ${min} 到 ${max} 之间`\n }\n\n return null\n }\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAM,2BAA2B;AAEjC,MAAM,0BAA0B;AAEhC,MAAM,wBAAwB;AAE9B,MAAM,gBAAgB;AAqGf,SAAS,aACd,SACuB;CAEvB,MAAM,EACJ,eACA,kBAAkB,CAAC,GACnB,UACA,aAAa,WACX;CAGJ,MAAM,sBAAsB,UAAU,aAAa;CAEnD,MAAM,SAAS,SAAS,UAAU,mBAAmB,CAAC;CAEtD,MAAM,cAAc;CAEpB,MAAM,qBAAqB;CAE3B,MAAM,YAAY,OAAO,KAAK,mBAAmB;CAGjD,MAAM,SAAS,SACb,UAAU,QAAwC,KAAK,QAAQ;EAC7D,IAAI,OAAO;GACT,SAAS;GACT,OAAO;GACP,OAAO;GACP,YAAY;EACd;EACA,OAAO;CACT,GAAG,CAAC,CAAC,CACP;CAIA,MAAM,eAAe,IAAI,KAAK;CAE9B,MAAM,cAAc,IAAmB,IAAI;CAE3C,MAAM,cAAc,IAAI,CAAC;CAIzB,MAAM,UAAU,eAAe,UAAU,MAAK,QAAO,OAAO,IAAG,EAAG,KAAK,CAAC;CAExE,MAAM,UAAU,eAAe,UAAU,OAAM,QAAO,CAAC,OAAO,IAAG,EAAG,KAAK,CAAC;CAG1E,MAAM,SAAS,eAAe;EAC5B,OAAO,UAAU,QAAuC,KAAK,QAAQ;GACnE,IAAI,OAAO,OAAO,IAAG,EAAG,SAAS;GACjC,OAAO;EACT,GAAG,CAAC,CAAC;CACP,CAAC;CAID,UAAU,SAAS,QAAQ;EACzB,YACQ,YAAY,OACjB,aAAa;GACZ,MAAM,QAAQ,OAAO;GACrB,IAAI,OAEF,MAAM,QAAQ,CAAC,UAAU,UAAU,mBAAmB,IAAI;GAI5D,IAAI,eAAe,YAAY,OAAO,SACpC,cAAmB,GAAG;EAE1B,GACA,EAAE,MAAM,KAAK,CACf;CACF,CAAC;CASD,SAAS,cAAiC,OAAU,OAAmB;EACrE,YAAY,SAAmB;CACjC;CAOA,SAAS,gBAAgB,OAAsB;EAC7C,MAAM,aAAa,OAAO;EAC1B,IAAI,YACF,WAAW,UAAU;EAIvB,IAAI,eAAe,QACjB,cAAmB,KAAK;CAE5B;CAQA,eAAe,cAAc,OAAwC;EAEnE,MAAM,aAAa,OAAO;EAG1B,IAAI,CAAC,YACH,OAAO;EAIT,MAAM,aAAa,gBAAgB;EAEnC,IAAI,CAAC,YAAY;GACf,WAAW,QAAQ;GACnB,OAAO;EACT;EAGA,WAAW,aAAa;EAExB,IAAI;GACF,MAAM,QAAQ,OAAO;GAGrB,IAAI,WAAW;QACG,UAAU,MAAM,UAAU,QAAQ,UAAU,KAAA,GAC/C;KAEX,MAAM,UAAU,OAAO,WAAW,aAAa,WAC3C,WAAW,WACX;KACJ,WAAW,QAAQ;KACnB,OAAO;IACT;;GAIF,IAAI,WAAW,OACb,KAAA,MAAW,QAAQ,WAAW,OAAO;IACnC,MAAM,QAAQ,MAAM,KAClB,OACA,WACF;IAEA,IAAI,OAAO;KACT,WAAW,QAAQ;KACnB,OAAO;IACT;GACF;GAIF,WAAW,QAAQ;GACnB,OAAO;EACT,UACA;GAEE,WAAW,aAAa;EAC1B;CACF;CAMA,eAAe,cAAgC;EAE7C,QAAO,MADe,QAAQ,IAAI,UAAU,KAAI,QAAO,cAAc,GAAG,CAAC,CAAC,EAAA,CAC3D,OAAM,WAAU,WAAW,IAAI;CAChD;CASA,eAAe,eAA8B;EAC3C,YAAY;EACZ,YAAY,QAAQ;EAGpB,UAAU,SAAS,QAAQ;GACzB,MAAM,QAAQ,OAAO;GACrB,IAAI,OACF,MAAM,UAAU;EAEpB,CAAC;EAKD,IAAI,CAAC,MAFe,YAAY,KAElB,CAAC,UACb;EAIF,aAAa,QAAQ;EACrB,IAAI;GAEF,MAAM,SAAS,UAAU,MAAM,CAAM;EACvC,SACO,OAAO;GAEZ,YAAY,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC3E,UACA;GAEE,aAAa,QAAQ;EACvB;CACF;CAOA,SAAS,MAAM,kBAAqC;EAElD,IAAI,kBACF,OAAO,OAAO,oBAAoB,UAAU,gBAAgB,CAAC;EAI/D,UAAU,SAAS,QAAQ;GAEzB,YAAY,OAAO,UAAU,mBAAmB,IAAI;GAGpD,MAAM,QAAQ,OAAO;GACrB,IAAI,OAAO;IACT,MAAM,UAAU;IAChB,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,MAAM,aAAa;GACrB;EACF,CAAC;EAGD,YAAY,QAAQ;CACtB;CAMA,SAAS,WAAW,OAAsB;EACxC,MAAM,WAAW;EAEjB,YAAY,YAAY,UAAU,mBAAmB,SAAS;EAG9D,MAAM,aAAa,OAAO;EAC1B,IAAI,YAAY;GACd,WAAW,UAAU;GACrB,WAAW,QAAQ;GACnB,WAAW,QAAQ;GACnB,WAAW,aAAa;EAC1B;CACF;CAGA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;AAYO,SAAS,SAAS,UAAU,0BAAyC;CAC1E,QAAQ,UAAU;EAEhB,IAAI,UAAU,MAAM,UAAU,QAAQ,UAAU,KAAA,GAC9C,OAAO;EAGT,OAAO;CACT;AACF;AAQO,SAAS,UAAU,KAAa,SAAyC;CAC9E,QAAQ,UAAU;EAChB,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,KAC9C,OAAO,WAAW,4BAAQ,IAAG;EAG/B,OAAO;CACT;AACF;AAQO,SAAS,UAAU,KAAa,SAAyC;CAC9E,QAAQ,UAAU;EAChB,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,KAC9C,OAAO,WAAW,4BAAQ,IAAG;EAG/B,OAAO;CACT;AACF;AAQO,SAAS,QACd,OACA,UAAU,yBACa;CACvB,QAAQ,UAAU;EAChB,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,KAAK,GAChD,OAAO;EAGT,OAAO;CACT;AACF;AAQO,SAAS,MAAM,UAAU,uBAA8C;CAC5E,OAAO,QAAQ,eAAe,OAAO;AACvC;AASO,SAAS,YACd,KACA,KACA,SACuB;CACvB,QAAQ,UAAU;EAChB,IAAI,OAAO,UAAU,aAAa,QAAQ,OAAO,QAAQ,MACvD,OAAO,WAAW,sBAAO,IAAG,UAAM,IAAG;EAGvC,OAAO;CACT;AACF"}
|
|
1
|
+
{"version":3,"file":"useFormStore.js","names":[],"sources":["../../src/composables/useFormStore.ts"],"sourcesContent":["/* ============================================================\r\n * useFormStore.ts\r\n * 表单状态管理 Composable\r\n * 提供表单值、字段状态、校验、提交、重置等完整表单生命周期管理\r\n * ============================================================ */\r\n\r\n/* 从 Vue 导入类型,用于声明响应式引用 */\r\nimport type { ComputedRef, Ref } from 'vue'\r\n/* 从 store-core 工具库导入深拷贝和深比较函数 */\r\nimport { deepClone, deepEqual } from '@composy/store-core'\r\n/* 从 Vue 导入响应式 API */\r\nimport { computed, reactive, ref, watch } from 'vue'\r\n\r\n/* ── 默认校验提示信息 ── */\r\n/* 必填项校验失败时的默认提示 */\r\nconst DEFAULT_REQUIRED_MESSAGE = '此字段为必填项'\r\n/* 正则匹配校验失败时的默认提示 */\r\nconst DEFAULT_PATTERN_MESSAGE = '格式不正确'\r\n/* 邮箱格式校验失败时的默认提示 */\r\nconst DEFAULT_EMAIL_MESSAGE = '邮箱格式不正确'\r\n/* 邮箱格式正则表达式 */\r\nconst EMAIL_PATTERN = /^[^\\s@]+@[^@][^.@]*\\.[^\\s@]+$/\r\n\r\n/* ── 类型定义 ── */\r\n\r\n/* 从泛型 T 中提取 string 类型的键名,用作字段标识 */\r\ntype FieldKey<T> = Extract<keyof T, string>\r\n\r\n/**\r\n * 表单校验器函数类型\r\n * @param value - 当前字段值\r\n * @param allValues - 表单所有字段的值(用于跨字段校验)\r\n * @returns 校验错误信息或 null(无错误),支持异步校验\r\n */\r\nexport type FormValidator<T = unknown> = (\r\n value: T,\r\n allValues: Record<string, unknown>\r\n) => string | null | Promise<string | null>\r\n\r\n/**\r\n * 字段校验规则接口\r\n * @property required - 是否必填,可传 string 自定义提示\r\n * @property rules - 自定义校验器数组,按顺序依次执行\r\n */\r\nexport interface FormFieldRules<T = unknown> {\r\n required?: boolean | string\r\n rules?: FormValidator<T>[]\r\n}\r\n\r\n/**\r\n * useFormStore 的配置选项接口\r\n * @property initialValues - 表单初始值(必须提供完整对象)\r\n * @property validationRules - 各字段的校验规则映射\r\n * @property onSubmit - 表单提交回调(校验通过后调用)\r\n * @property validateOn - 触发校验的时机:'blur' | 'change' | 'submit'\r\n */\r\nexport interface UseFormStoreOptions<T extends Record<string, unknown>> {\r\n initialValues: T\r\n validationRules?: {\r\n [K in keyof T]?: FormFieldRules<T[K]>\r\n }\r\n onSubmit?: (values: T) => void | Promise<void>\r\n validateOn?: 'blur' | 'change' | 'submit'\r\n}\r\n\r\n/**\r\n * 单个字段的状态接口\r\n * @property touched - 字段是否被触碰过(获得过焦点或提交过)\r\n * @property dirty - 字段值是否与初始值不同(被修改过)\r\n * @property error - 当前校验错误信息,null 表示无错误\r\n * @property validating - 字段是否正在异步校验中\r\n */\r\nexport interface FormFieldState {\r\n touched: boolean\r\n dirty: boolean\r\n error: string | null\r\n validating: boolean\r\n}\r\n\r\n/**\r\n * useFormStore 返回值接口\r\n * @property values - 表单当前值(reactive 对象)\r\n * @property fields - 各字段的状态映射\r\n * @property isDirty - 是否有任意字段被修改过(计算属性)\r\n * @property isValid - 是否所有字段都无校验错误(计算属性)\r\n * @property isSubmitting - 表单是否正在提交中\r\n * @property submitError - 提交失败的错误信息\r\n * @property submitCount - 累计提交次数\r\n * @property setFieldValue - 设置指定字段的值\r\n * @property setFieldTouched - 标记字段为已触碰\r\n * @property validateField - 校验单个字段(异步)\r\n * @property validateAll - 校验所有字段(异步)\r\n * @property handleSubmit - 提交表单\r\n * @property reset - 重置整个表单(可传新的初始值)\r\n * @property resetField - 重置单个字段\r\n * @property errors - 所有字段错误信息的计算属性\r\n */\r\nexport interface UseFormStoreReturn<T extends Record<string, unknown>> {\r\n values: T\r\n fields: Record<string, FormFieldState>\r\n isDirty: ComputedRef<boolean>\r\n isValid: ComputedRef<boolean>\r\n isSubmitting: Ref<boolean>\r\n submitError: Ref<string | null>\r\n submitCount: Ref<number>\r\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void\r\n setFieldTouched: (field: keyof T) => void\r\n validateField: (field: keyof T) => Promise<string | null>\r\n validateAll: () => Promise<boolean>\r\n handleSubmit: () => Promise<void>\r\n reset: (newInitialValues?: Partial<T>) => void\r\n resetField: (field: keyof T) => void\r\n errors: ComputedRef<Record<string, string | null>>\r\n}\r\n\r\n/**\r\n * 表单状态管理 Composable\r\n * 提供完整的表单生命周期管理能力,包括值管理、字段状态追踪、校验、提交和重置\r\n *\r\n * @param options - 表单配置选项\r\n * @returns 包含表单状态和操作方法的对象\r\n */\r\nexport function useFormStore<T extends Record<string, unknown>>(\r\n options: UseFormStoreOptions<T>\r\n): UseFormStoreReturn<T> {\r\n /* 解构配置选项,设置默认值 */\r\n const {\r\n initialValues /* 表单初始值 */,\r\n validationRules = {} as NonNullable<\r\n UseFormStoreOptions<T>['validationRules']\r\n > /* 校验规则,默认空对象 */,\r\n onSubmit /* 提交回调 */,\r\n validateOn = 'blur' /* 校验时机,默认 blur 时触发 */,\r\n } = options\r\n\r\n /* 深拷贝初始值并保存,用于后续重置和脏值比较 */\r\n const storedInitialValues = deepClone(initialValues) as T\r\n /* 创建响应式表单值对象(从初始值的深拷贝开始) */\r\n const values = reactive(deepClone(storedInitialValues)) as T\r\n /* 将 values 转为通用 Record 类型,便于动态属性访问 */\r\n const valueRecord = values as Record<string, unknown>\r\n /* 将初始值也转为 Record 类型,用于脏值比较 */\r\n const initialValueRecord = storedInitialValues as Record<string, unknown>\r\n /* 获取所有字段名列表 */\r\n const fieldKeys = Object.keys(storedInitialValues) as Array<FieldKey<T>>\r\n\r\n /* 创建每个字段的响应式状态对象(touched/dirty/error/validating) */\r\n const fields = reactive(\r\n fieldKeys.reduce<Record<string, FormFieldState>>((acc, key) => {\r\n acc[key] = {\r\n touched: false /* 字段未被触碰 */,\r\n dirty: false /* 字段值未被修改 */,\r\n error: null /* 无校验错误 */,\r\n validating: false /* 未在校验中 */,\r\n }\r\n return acc\r\n }, {})\r\n )\r\n\r\n /* ── 全局状态 ── */\r\n /* 是否正在提交中 */\r\n const isSubmitting = ref(false)\r\n /* 提交错误信息 */\r\n const submitError = ref<string | null>(null)\r\n /* 累计提交次数 */\r\n const submitCount = ref(0)\r\n\r\n /* ── 计算属性 ── */\r\n /* 是否有任意字段被修改过 */\r\n const isDirty = computed(() => fieldKeys.some(key => fields[key]?.dirty))\r\n /* 是否所有字段都无错误 */\r\n const isValid = computed(() => fieldKeys.every(key => !fields[key]?.error))\r\n\r\n /* 所有字段错误信息的映射 */\r\n const errors = computed(() => {\r\n return fieldKeys.reduce<Record<string, string | null>>((acc, key) => {\r\n acc[key] = fields[key]?.error ?? null\r\n return acc\r\n }, {})\r\n })\r\n\r\n /* ── 值变化监听 ── */\r\n /* 为每个字段创建 watch,当值变化时更新脏状态并按需触发校验 */\r\n fieldKeys.forEach(key => {\r\n watch(\r\n () => valueRecord[key] /* 监听字段值 */,\r\n newValue => {\r\n const field = fields[key]\r\n if (field) {\r\n /* 比较当前值与初始值,判断是否为脏值 */\r\n field.dirty = !deepEqual(newValue, initialValueRecord[key])\r\n }\r\n\r\n /* 如果校验时机为 'change' 且字段已被触碰,则触发校验 */\r\n if (validateOn === 'change' && field?.touched) {\r\n void validateField(key)\r\n }\r\n },\r\n { deep: true } /* 深度监听,支持嵌套对象变化 */\r\n )\r\n })\r\n\r\n /* ── 操作方法 ── */\r\n\r\n /**\r\n * 设置指定字段的值\r\n * @param field - 字段名\r\n * @param value - 新值\r\n */\r\n function setFieldValue<K extends keyof T>(field: K, value: T[K]): void {\r\n valueRecord[field as string] = value\r\n }\r\n\r\n /**\r\n * 标记字段为已触碰状态\r\n * 如果校验时机为 'blur',同时触发该字段的校验\r\n * @param field - 字段名\r\n */\r\n function setFieldTouched(field: keyof T): void {\r\n const fieldState = fields[field as string]\r\n if (fieldState) {\r\n fieldState.touched = true\r\n }\r\n\r\n /* blur 时自动触发校验 */\r\n if (validateOn === 'blur') {\r\n void validateField(field)\r\n }\r\n }\r\n\r\n /**\r\n * 校验单个字段\r\n * 按顺序执行:必填校验 → 自定义规则校验\r\n * @param field - 字段名\r\n * @returns 错误信息或 null(无错误)\r\n */\r\n async function validateField(field: keyof T): Promise<string | null> {\r\n const fieldKey = field as string\r\n const fieldState = fields[fieldKey]\r\n\r\n /* 字段状态不存在则跳过 */\r\n if (!fieldState) {\r\n return null\r\n }\r\n\r\n /* 获取该字段的校验规则 */\r\n const fieldRules = validationRules[field]\r\n /* 无规则则清除错误并返回 */\r\n if (!fieldRules) {\r\n fieldState.error = null\r\n return null\r\n }\r\n\r\n /* 标记开始校验 */\r\n fieldState.validating = true\r\n\r\n try {\r\n const value = values[field]\r\n\r\n /* 第一步:必填校验 */\r\n if (fieldRules.required) {\r\n const isEmpty = value === '' || value === null || value === undefined\r\n if (isEmpty) {\r\n /* 支持自定义必填提示信息 */\r\n const message =\r\n typeof fieldRules.required === 'string'\r\n ? fieldRules.required\r\n : DEFAULT_REQUIRED_MESSAGE\r\n fieldState.error = message\r\n return message\r\n }\r\n }\r\n\r\n /* 第二步:自定义规则校验(按顺序执行,遇到第一个错误即停止) */\r\n if (fieldRules.rules) {\r\n for (const rule of fieldRules.rules) {\r\n const error = await rule(\r\n value as T[keyof T],\r\n valueRecord /* 传入所有表单值,支持跨字段校验 */\r\n )\r\n\r\n if (error) {\r\n fieldState.error = error\r\n return error\r\n }\r\n }\r\n }\r\n\r\n /* 所有校验通过,清除错误 */\r\n fieldState.error = null\r\n return null\r\n } finally {\r\n /* 无论成功失败,标记校验结束 */\r\n fieldState.validating = false\r\n }\r\n }\r\n\r\n /**\r\n * 校验所有字段\r\n * @returns 是否所有字段都校验通过\r\n */\r\n async function validateAll(): Promise<boolean> {\r\n const results = await Promise.all(fieldKeys.map(key => validateField(key)))\r\n return results.every(result => result === null)\r\n }\r\n\r\n /**\r\n * 提交流程\r\n * 1. 递增提交计数\r\n * 2. 标记所有字段为已触碰\r\n * 3. 执行全量校验\r\n * 4. 校验通过且有 onSubmit 回调时,调用提交\r\n */\r\n async function handleSubmit(): Promise<void> {\r\n submitCount.value++\r\n submitError.value = null\r\n\r\n /* 标记所有字段为已触碰 */\r\n fieldKeys.forEach(key => {\r\n const field = fields[key]\r\n if (field) {\r\n field.touched = true\r\n }\r\n })\r\n\r\n /* 执行全量校验 */\r\n const valid = await validateAll()\r\n /* 校验未通过或没有提交回调则退出 */\r\n if (!valid || !onSubmit) {\r\n return\r\n }\r\n\r\n /* 开始提交 */\r\n isSubmitting.value = true\r\n try {\r\n /* 深拷贝 values 后传给 onSubmit,避免外部修改响应式数据 */\r\n await onSubmit(deepClone(values) as T)\r\n } catch (error) {\r\n /* 捕获提交异常,保存错误信息 */\r\n submitError.value = error instanceof Error ? error.message : String(error)\r\n } finally {\r\n /* 无论成功失败,标记提交结束 */\r\n isSubmitting.value = false\r\n }\r\n }\r\n\r\n /**\r\n * 重置整个表单\r\n * 可选传入新的初始值(部分更新)\r\n * @param newInitialValues - 新的初始值(可选,会与现有初始值合并)\r\n */\r\n function reset(newInitialValues?: Partial<T>): void {\r\n /* 如果传入了新初始值,合并到存储的初始值中 */\r\n if (newInitialValues) {\r\n Object.assign(initialValueRecord, deepClone(newInitialValues))\r\n }\r\n\r\n /* 恢复每个字段的值和状态 */\r\n fieldKeys.forEach(key => {\r\n /* 恢复为初始值的深拷贝 */\r\n valueRecord[key] = deepClone(initialValueRecord[key])\r\n\r\n /* 重置字段状态 */\r\n const field = fields[key]\r\n if (field) {\r\n field.touched = false /* 清除触碰状态 */\r\n field.dirty = false /* 清除脏值标记 */\r\n field.error = null /* 清除校验错误 */\r\n field.validating = false /* 清除校验中标记 */\r\n }\r\n })\r\n\r\n /* 清除提交错误 */\r\n submitError.value = null\r\n }\r\n\r\n /**\r\n * 重置单个字段\r\n * @param field - 要重置的字段名\r\n */\r\n function resetField(field: keyof T): void {\r\n const fieldKey = field as string\r\n /* 恢复字段值为初始值的深拷贝 */\r\n valueRecord[fieldKey] = deepClone(initialValueRecord[fieldKey])\r\n\r\n /* 重置字段状态 */\r\n const fieldState = fields[fieldKey]\r\n if (fieldState) {\r\n fieldState.touched = false\r\n fieldState.dirty = false\r\n fieldState.error = null\r\n fieldState.validating = false\r\n }\r\n }\r\n\r\n /* ── 返回所有状态和方法 ── */\r\n return {\r\n values /* 表单当前值(响应式) */,\r\n fields /* 字段状态映射(响应式) */,\r\n isDirty /* 是否有脏值 */,\r\n isValid /* 是否全部有效 */,\r\n isSubmitting /* 是否正在提交 */,\r\n submitError /* 提交错误信息 */,\r\n submitCount /* 提交次数 */,\r\n setFieldValue /* 设置字段值 */,\r\n setFieldTouched /* 标记字段已触碰 */,\r\n validateField /* 校验单个字段 */,\r\n validateAll /* 校验所有字段 */,\r\n handleSubmit /* 提交表单 */,\r\n reset /* 重置表单 */,\r\n resetField /* 重置单个字段 */,\r\n errors /* 错误信息映射 */,\r\n }\r\n}\r\n\r\n/* ============================================================\r\n * 预置校验器工厂函数\r\n * 提供常用的表单校验规则创建函数\r\n * ============================================================ */\r\n\r\n/**\r\n * 创建必填校验器\r\n * @param message - 自定义错误提示,默认为 '此字段为必填项'\r\n * @returns 校验器函数\r\n */\r\nexport function required(message = DEFAULT_REQUIRED_MESSAGE): FormValidator {\r\n return value => {\r\n /* 空字符串、null、undefined 均视为\"空\" */\r\n if (value === '' || value === null || value === undefined) {\r\n return message\r\n }\r\n\r\n return null /* 通过校验 */\r\n }\r\n}\r\n\r\n/**\r\n * 创建最小长度校验器\r\n * @param min - 最小字符数\r\n * @param message - 自定义错误提示\r\n * @returns 字符串校验器函数\r\n */\r\nexport function minLength(\r\n min: number,\r\n message?: string\r\n): FormValidator<string> {\r\n return value => {\r\n if (typeof value === 'string' && value.length < min) {\r\n return message ?? `至少需要 ${min} 个字符`\r\n }\r\n\r\n return null\r\n }\r\n}\r\n\r\n/**\r\n * 创建最大长度校验器\r\n * @param max - 最大字符数\r\n * @param message - 自定义错误提示\r\n * @returns 字符串校验器函数\r\n */\r\nexport function maxLength(\r\n max: number,\r\n message?: string\r\n): FormValidator<string> {\r\n return value => {\r\n if (typeof value === 'string' && value.length > max) {\r\n return message ?? `不能超过 ${max} 个字符`\r\n }\r\n\r\n return null\r\n }\r\n}\r\n\r\n/**\r\n * 创建正则匹配校验器\r\n * @param regex - 正则表达式\r\n * @param message - 自定义错误提示,默认为 '格式不正确'\r\n * @returns 字符串校验器函数\r\n */\r\nexport function pattern(\r\n regex: RegExp,\r\n message = DEFAULT_PATTERN_MESSAGE\r\n): FormValidator<string> {\r\n return value => {\r\n if (typeof value === 'string' && !regex.test(value)) {\r\n return message\r\n }\r\n\r\n return null\r\n }\r\n}\r\n\r\n/**\r\n * 创建邮箱格式校验器\r\n * 基于内置的 EMAIL_PATTERN 正则\r\n * @param message - 自定义错误提示,默认为 '邮箱格式不正确'\r\n * @returns 字符串校验器函数\r\n */\r\nexport function email(message = DEFAULT_EMAIL_MESSAGE): FormValidator<string> {\r\n return pattern(EMAIL_PATTERN, message)\r\n}\r\n\r\n/**\r\n * 创建数值范围校验器\r\n * @param min - 最小值\r\n * @param max - 最大值\r\n * @param message - 自定义错误提示\r\n * @returns 数字校验器函数\r\n */\r\nexport function numberRange(\r\n min: number,\r\n max: number,\r\n message?: string\r\n): FormValidator<number> {\r\n return value => {\r\n if (typeof value === 'number' && (value < min || value > max)) {\r\n return message ?? `必须在 ${min} 到 ${max} 之间`\r\n }\r\n\r\n return null\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1,67 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useOptimistic - 乐观更新 Hook
|
|
3
|
-
*
|
|
4
|
-
* 提供乐观更新模式:先立即更新 UI,再异步确认,失败时自动回滚
|
|
5
|
-
* @module composables/useOptimistic
|
|
6
|
-
*/
|
|
1
|
+
/** Optimistic state helper with an operation log for overlapping mutations. */
|
|
7
2
|
import type { Ref } from 'vue';
|
|
8
|
-
/**
|
|
9
|
-
* 乐观更新操作记录
|
|
10
|
-
*/
|
|
11
3
|
export interface OptimisticEntry<T> {
|
|
12
|
-
/** 操作 ID */
|
|
13
4
|
id: string;
|
|
14
|
-
/** 提交前的快照 */
|
|
15
5
|
snapshot: T;
|
|
16
|
-
/** 操作状态 */
|
|
17
6
|
status: 'pending' | 'confirmed' | 'rolled-back';
|
|
18
|
-
/** 时间戳 */
|
|
19
7
|
timestamp: number;
|
|
20
8
|
}
|
|
21
|
-
/**
|
|
22
|
-
* useOptimistic 返回值
|
|
23
|
-
*/
|
|
24
9
|
export interface UseOptimisticReturn<T> {
|
|
25
|
-
/** 响应式状态 */
|
|
26
10
|
state: Ref<T>;
|
|
27
|
-
/** 是否有挂起的操作 */
|
|
28
11
|
isPending: Ref<boolean>;
|
|
29
|
-
/** 挂起的操作记录 */
|
|
30
12
|
pending: Ref<OptimisticEntry<T>[]>;
|
|
31
|
-
/** 最近一次操作是否已回滚 */
|
|
32
13
|
isRolledBack: Ref<boolean>;
|
|
33
|
-
/** 执行乐观更新: 先 apply 新值,然后 confirm 异步确认 */
|
|
34
14
|
mutate: (optimisticValue: T | ((prev: T) => T), confirm: () => Promise<void>) => Promise<void>;
|
|
35
15
|
}
|
|
36
|
-
/**
|
|
37
|
-
* 乐观更新 Hook
|
|
38
|
-
*
|
|
39
|
-
* 先立即更新 UI(乐观),再执行异步确认操作。
|
|
40
|
-
* 若确认失败则自动回滚到更新前的状态。
|
|
41
|
-
*
|
|
42
|
-
* @template T - 状态类型
|
|
43
|
-
* @param initialState - 初始状态
|
|
44
|
-
* @returns 乐观更新 API
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```typescript
|
|
48
|
-
* const { state, isPending, isRolledBack, mutate } = useOptimistic({
|
|
49
|
-
* items: ['Apple', 'Banana'],
|
|
50
|
-
* })
|
|
51
|
-
*
|
|
52
|
-
* // 乐观添加
|
|
53
|
-
* await mutate(
|
|
54
|
-
* (prev) => ({ ...prev, items: [...prev.items, 'Cherry'] }),
|
|
55
|
-
* async () => {
|
|
56
|
-
* await fetch('/api/items', {
|
|
57
|
-
* method: 'POST',
|
|
58
|
-
* body: JSON.stringify({ name: 'Cherry' }),
|
|
59
|
-
* })
|
|
60
|
-
* }
|
|
61
|
-
* )
|
|
62
|
-
* // 成功: state 保持新值
|
|
63
|
-
* // 失败: state 自动回滚到添加之前
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
16
|
export declare function useOptimistic<T>(initialState: T): UseOptimisticReturn<T>;
|
|
67
17
|
//# sourceMappingURL=useOptimistic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOptimistic.d.ts","sourceRoot":"","sources":["../../src/composables/useOptimistic.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useOptimistic.d.ts","sourceRoot":"","sources":["../../src/composables/useOptimistic.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAI9B,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,CAAC,CAAA;IACX,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,CAAA;IAC/C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IACb,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,OAAO,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAClC,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAC1B,MAAM,EAAE,CACN,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EACrC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KACzB,OAAO,CAAC,IAAI,CAAC,CAAA;CACnB;AAUD,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CA4ExE"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ***********************************
|
|
3
3
|
* @composy/store-vue v0.0.1 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
5
|
+
* Build time: 2024-10-12 17:40:04 *
|
|
6
6
|
* Build mode: production *
|
|
7
7
|
* Minified: No *
|
|
8
8
|
* ***********************************
|
|
@@ -15,39 +15,54 @@ function uid() {
|
|
|
15
15
|
}
|
|
16
16
|
function useOptimistic(initialState) {
|
|
17
17
|
const state = ref(deepClone(initialState));
|
|
18
|
+
let committedState = deepClone(initialState);
|
|
19
|
+
const operations = [];
|
|
18
20
|
const isPending = ref(false);
|
|
19
21
|
const isRolledBack = ref(false);
|
|
20
22
|
const pending = ref([]);
|
|
23
|
+
const refresh = () => {
|
|
24
|
+
let nextState = deepClone(committedState);
|
|
25
|
+
operations.forEach((operation) => {
|
|
26
|
+
if (operation.status !== "rolled-back") nextState = operation.apply(nextState);
|
|
27
|
+
});
|
|
28
|
+
state.value = deepClone(nextState);
|
|
29
|
+
pending.value = operations.filter((operation) => operation.status === "pending").map((operation) => ({
|
|
30
|
+
id: operation.id,
|
|
31
|
+
snapshot: deepClone(operation.snapshot),
|
|
32
|
+
status: operation.status,
|
|
33
|
+
timestamp: operation.timestamp
|
|
34
|
+
}));
|
|
35
|
+
isPending.value = pending.value.length > 0;
|
|
36
|
+
};
|
|
37
|
+
const reconcile = () => {
|
|
38
|
+
while (operations[0] && operations[0].status !== "pending") {
|
|
39
|
+
const operation = operations.shift();
|
|
40
|
+
if (!operation || operation.status === "rolled-back") continue;
|
|
41
|
+
committedState = operation.apply(deepClone(committedState));
|
|
42
|
+
}
|
|
43
|
+
refresh();
|
|
44
|
+
};
|
|
21
45
|
async function mutate(optimisticValue, confirm) {
|
|
22
46
|
const snapshot = deepClone(state.value);
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
id:
|
|
47
|
+
const apply = typeof optimisticValue === "function" ? (previous) => deepClone(optimisticValue(deepClone(previous))) : () => deepClone(optimisticValue);
|
|
48
|
+
const operation = {
|
|
49
|
+
id: uid(),
|
|
26
50
|
snapshot,
|
|
27
51
|
status: "pending",
|
|
28
|
-
timestamp: Date.now()
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
apply
|
|
29
54
|
};
|
|
30
|
-
|
|
31
|
-
isPending.value = true;
|
|
55
|
+
operations.push(operation);
|
|
32
56
|
isRolledBack.value = false;
|
|
33
|
-
|
|
34
|
-
else state.value = optimisticValue;
|
|
57
|
+
refresh();
|
|
35
58
|
try {
|
|
36
59
|
await confirm();
|
|
37
|
-
|
|
38
|
-
...e,
|
|
39
|
-
status: "confirmed"
|
|
40
|
-
} : e);
|
|
60
|
+
operation.status = "confirmed";
|
|
41
61
|
} catch {
|
|
42
|
-
|
|
62
|
+
operation.status = "rolled-back";
|
|
43
63
|
isRolledBack.value = true;
|
|
44
|
-
pending.value = pending.value.map((e) => e.id === entryId ? {
|
|
45
|
-
...e,
|
|
46
|
-
status: "rolled-back"
|
|
47
|
-
} : e);
|
|
48
64
|
} finally {
|
|
49
|
-
|
|
50
|
-
isPending.value = pending.value.length > 0;
|
|
65
|
+
reconcile();
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOptimistic.js","names":[],"sources":["../../src/composables/useOptimistic.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"useOptimistic.js","names":[],"sources":["../../src/composables/useOptimistic.ts"],"sourcesContent":["/** Optimistic state helper with an operation log for overlapping mutations. */\n\nimport type { Ref } from 'vue'\nimport { deepClone } from '@composy/store-core'\nimport { ref } from 'vue'\n\nexport interface OptimisticEntry<T> {\n id: string\n snapshot: T\n status: 'pending' | 'confirmed' | 'rolled-back'\n timestamp: number\n}\n\nexport interface UseOptimisticReturn<T> {\n state: Ref<T>\n isPending: Ref<boolean>\n pending: Ref<OptimisticEntry<T>[]>\n isRolledBack: Ref<boolean>\n mutate: (\n optimisticValue: T | ((prev: T) => T),\n confirm: () => Promise<void>\n ) => Promise<void>\n}\n\ninterface Operation<T> extends OptimisticEntry<T> {\n apply: (previous: T) => T\n}\n\nfunction uid(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nexport function useOptimistic<T>(initialState: T): UseOptimisticReturn<T> {\n const state = ref<T>(deepClone(initialState)) as Ref<T>\n let committedState = deepClone(initialState)\n const operations: Operation<T>[] = []\n const isPending = ref(false)\n const isRolledBack = ref(false)\n const pending = ref<OptimisticEntry<T>[]>([]) as Ref<OptimisticEntry<T>[]>\n\n const refresh = (): void => {\n let nextState = deepClone(committedState)\n operations.forEach(operation => {\n if (operation.status !== 'rolled-back') {\n nextState = operation.apply(nextState)\n }\n })\n state.value = deepClone(nextState)\n pending.value = operations\n .filter(operation => operation.status === 'pending')\n .map(operation => ({\n id: operation.id,\n snapshot: deepClone(operation.snapshot),\n status: operation.status,\n timestamp: operation.timestamp,\n }))\n isPending.value = pending.value.length > 0\n }\n\n const reconcile = (): void => {\n while (operations[0] && operations[0].status !== 'pending') {\n const operation = operations.shift()\n if (!operation || operation.status === 'rolled-back') continue\n committedState = operation.apply(deepClone(committedState))\n }\n refresh()\n }\n\n async function mutate(\n optimisticValue: T | ((prev: T) => T),\n confirm: () => Promise<void>\n ): Promise<void> {\n const snapshot = deepClone(state.value)\n const apply =\n typeof optimisticValue === 'function'\n ? (previous: T): T =>\n deepClone((optimisticValue as (prev: T) => T)(deepClone(previous)))\n : (): T => deepClone(optimisticValue)\n const operation: Operation<T> = {\n id: uid(),\n snapshot,\n status: 'pending',\n timestamp: Date.now(),\n apply,\n }\n\n operations.push(operation)\n isRolledBack.value = false\n refresh()\n\n try {\n await confirm()\n operation.status = 'confirmed'\n } catch {\n operation.status = 'rolled-back'\n isRolledBack.value = true\n } finally {\n reconcile()\n }\n }\n\n return {\n state,\n isPending,\n pending,\n isRolledBack,\n mutate,\n }\n}\n"],"mappings":";;;;;;;;;;;;AA4BA,SAAS,MAAc;CACrB,OAAO,GAAG,KAAK,IAAI,EAAC,GAAI,KAAK,OAAO,CAAA,CAAE,SAAS,EAAE,CAAA,CAAE,MAAM,GAAG,CAAC;AAC/D;AAEO,SAAS,cAAiB,cAAyC;CACxE,MAAM,QAAQ,IAAO,UAAU,YAAY,CAAC;CAC5C,IAAI,iBAAiB,UAAU,YAAY;CAC3C,MAAM,aAA6B,CAAC;CACpC,MAAM,YAAY,IAAI,KAAK;CAC3B,MAAM,eAAe,IAAI,KAAK;CAC9B,MAAM,UAAU,IAA0B,CAAC,CAAC;CAE5C,MAAM,gBAAsB;EAC1B,IAAI,YAAY,UAAU,cAAc;EACxC,WAAW,SAAQ,cAAa;GAC9B,IAAI,UAAU,WAAW,eACvB,YAAY,UAAU,MAAM,SAAS;EAEzC,CAAC;EACD,MAAM,QAAQ,UAAU,SAAS;EACjC,QAAQ,QAAQ,WACb,QAAO,cAAa,UAAU,WAAW,SAAS,CAAA,CAClD,KAAI,eAAc;GACjB,IAAI,UAAU;GACd,UAAU,UAAU,UAAU,QAAQ;GACtC,QAAQ,UAAU;GAClB,WAAW,UAAU;EACvB,EAAE;EACJ,UAAU,QAAQ,QAAQ,MAAM,SAAS;CAC3C;CAEA,MAAM,kBAAwB;EAC5B,OAAO,WAAW,MAAM,WAAW,EAAC,CAAE,WAAW,WAAW;GAC1D,MAAM,YAAY,WAAW,MAAM;GACnC,IAAI,CAAC,aAAa,UAAU,WAAW,eAAe;GACtD,iBAAiB,UAAU,MAAM,UAAU,cAAc,CAAC;EAC5D;EACA,QAAQ;CACV;CAEA,eAAe,OACb,iBACA,SACe;EACf,MAAM,WAAW,UAAU,MAAM,KAAK;EACtC,MAAM,QACJ,OAAO,oBAAoB,cACtB,aACC,UAAW,gBAAmC,UAAU,QAAQ,CAAC,CAAC,UAC3D,UAAU,eAAe;EACxC,MAAM,YAA0B;GAC9B,IAAI,IAAI;GACR;GACA,QAAQ;GACR,WAAW,KAAK,IAAI;GACpB;EACF;EAEA,WAAW,KAAK,SAAS;EACzB,aAAa,QAAQ;EACrB,QAAQ;EAER,IAAI;GACF,MAAM,QAAQ;GACd,UAAU,SAAS;EACrB,QAAQ;GACN,UAAU,SAAS;GACnB,aAAa,QAAQ;EACvB,UAAE;GACA,UAAU;EACZ;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePersist.d.ts","sourceRoot":"","sources":["../../src/composables/usePersist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"usePersist.d.ts","sourceRoot":"","sources":["../../src/composables/usePersist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAiBnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAAC,CAAC,CAAC,CA8E7E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,EACf,OAAO,GAAE,cAAc,GAAG,gBAAiC,GAC1D,GAAG,CAAC,CAAC,CAAC,CAOR;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,EACf,GAAG,EAAE,MAAM,GACV,GAAG,CAAC,CAAC,CAAC,CA0CR"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ***********************************
|
|
3
3
|
* @composy/store-vue v0.0.1 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
5
|
+
* Build time: 2024-10-12 17:40:04 *
|
|
6
6
|
* Build mode: production *
|
|
7
7
|
* Minified: No *
|
|
8
8
|
* ***********************************
|
|
@@ -11,10 +11,14 @@ import { onMounted, ref, watch } from "vue";
|
|
|
11
11
|
//#region src/composables/usePersist.ts
|
|
12
12
|
function getStorage(type) {
|
|
13
13
|
if (typeof window === "undefined") return null;
|
|
14
|
-
|
|
14
|
+
try {
|
|
15
|
+
return type === "localStorage" ? window.localStorage : window.sessionStorage;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
function usePersist(options) {
|
|
17
|
-
const { key, storage: storageType = "localStorage", defaultValue, serialize = JSON.stringify, deserialize = JSON.parse } = options;
|
|
21
|
+
const { key, storage: storageType = "localStorage", defaultValue, serialize = JSON.stringify, deserialize = JSON.parse, restoreOnMounted = true } = options;
|
|
18
22
|
const storage = getStorage(storageType);
|
|
19
23
|
const value = ref(defaultValue);
|
|
20
24
|
const restore = () => {
|
|
@@ -43,7 +47,7 @@ function usePersist(options) {
|
|
|
43
47
|
console.error(`[usePersist] \u6E05\u9664\u5931\u8D25 (${key}):`, error);
|
|
44
48
|
}
|
|
45
49
|
};
|
|
46
|
-
onMounted(() => {
|
|
50
|
+
if (restoreOnMounted) onMounted(() => {
|
|
47
51
|
restore();
|
|
48
52
|
});
|
|
49
53
|
watch(value, () => {
|
|
@@ -65,12 +69,13 @@ function usePersistedRef(key, defaultValue, storage = "localStorage") {
|
|
|
65
69
|
return value;
|
|
66
70
|
}
|
|
67
71
|
function usePersistedRefWithTTL(key, defaultValue, ttl) {
|
|
68
|
-
const
|
|
72
|
+
const persisted = usePersist({
|
|
69
73
|
key,
|
|
70
74
|
defaultValue: {
|
|
71
75
|
value: defaultValue,
|
|
72
76
|
expiry: 0
|
|
73
77
|
},
|
|
78
|
+
restoreOnMounted: false,
|
|
74
79
|
serialize: (data) => JSON.stringify(data),
|
|
75
80
|
deserialize: (str) => {
|
|
76
81
|
const data = JSON.parse(str);
|
|
@@ -81,13 +86,17 @@ function usePersistedRefWithTTL(key, defaultValue, ttl) {
|
|
|
81
86
|
return data;
|
|
82
87
|
}
|
|
83
88
|
});
|
|
84
|
-
|
|
89
|
+
persisted.restore();
|
|
90
|
+
const wrappedRef = ref(persisted.value.value.value);
|
|
85
91
|
watch(wrappedRef, (newValue) => {
|
|
86
|
-
value.value = {
|
|
92
|
+
persisted.value.value = {
|
|
87
93
|
value: newValue,
|
|
88
94
|
expiry: Date.now() + ttl
|
|
89
95
|
};
|
|
90
96
|
});
|
|
97
|
+
watch(persisted.value, (stored) => {
|
|
98
|
+
if (!Object.is(wrappedRef.value, stored.value)) wrappedRef.value = stored.value;
|
|
99
|
+
}, { deep: true });
|
|
91
100
|
return wrappedRef;
|
|
92
101
|
}
|
|
93
102
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePersist.js","names":[],"sources":["../../src/composables/usePersist.ts"],"sourcesContent":["/**\n * usePersist Composable\n *\n * 提供状态持久化功能\n * @module composables/usePersist\n */\n\nimport type { Ref } from 'vue'\nimport type { UsePersistOptions, UsePersistReturn } from '../types'\nimport { onMounted, ref, watch } from 'vue'\n\n/**\n * 获取存储对象\n */\nfunction getStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {\n if (typeof window === 'undefined') {\n return null\n }\n return type === 'localStorage' ? localStorage : sessionStorage\n}\n\n/**\n * 状态持久化 Hook\n *\n * 将状态自动保存到 localStorage 或 sessionStorage\n *\n * @template T - 值类型\n * @param options - 持久化选项\n * @returns 持久化操作对象\n *\n * @example\n * ```typescript\n * const { value, save, restore, clear } = usePersist<User>({\n * key: 'user-info',\n * storage: 'localStorage',\n * defaultValue: { name: '', age: 0 }\n * })\n *\n * // 更新值会自动保存\n * value.value = { name: 'John', age: 25 }\n *\n * // 手动保存\n * save()\n *\n * // 从存储恢复\n * restore()\n *\n * // 清除存储\n * clear()\n * ```\n */\nexport function usePersist<T>(options: UsePersistOptions): UsePersistReturn<T> {\n const {\n key,\n storage: storageType = 'localStorage',\n defaultValue,\n serialize = JSON.stringify,\n deserialize = JSON.parse,\n } = options\n\n const storage = getStorage(storageType)\n const value = ref<T>(defaultValue as T) as Ref<T>\n\n /**\n * 从存储恢复\n */\n const restore = (): void => {\n if (!storage) return\n\n try {\n const stored = storage.getItem(key)\n if (stored !== null) {\n value.value = deserialize(stored) as T\n }\n }
|
|
1
|
+
{"version":3,"file":"usePersist.js","names":[],"sources":["../../src/composables/usePersist.ts"],"sourcesContent":["/**\r\n * usePersist Composable\r\n *\r\n * 提供状态持久化功能\r\n * @module composables/usePersist\r\n */\r\n\r\nimport type { Ref } from 'vue'\r\nimport type { UsePersistOptions, UsePersistReturn } from '../types'\r\nimport { onMounted, ref, watch } from 'vue'\r\n\r\n/**\r\n * 获取存储对象\r\n */\r\nfunction getStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {\r\n if (typeof window === 'undefined') {\r\n return null\r\n }\r\n try {\r\n return type === 'localStorage' ? window.localStorage : window.sessionStorage\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\n/**\r\n * 状态持久化 Hook\r\n *\r\n * 将状态自动保存到 localStorage 或 sessionStorage\r\n *\r\n * @template T - 值类型\r\n * @param options - 持久化选项\r\n * @returns 持久化操作对象\r\n *\r\n * @example\r\n * ```typescript\r\n * const { value, save, restore, clear } = usePersist<User>({\r\n * key: 'user-info',\r\n * storage: 'localStorage',\r\n * defaultValue: { name: '', age: 0 }\r\n * })\r\n *\r\n * // 更新值会自动保存\r\n * value.value = { name: 'John', age: 25 }\r\n *\r\n * // 手动保存\r\n * save()\r\n *\r\n * // 从存储恢复\r\n * restore()\r\n *\r\n * // 清除存储\r\n * clear()\r\n * ```\r\n */\r\nexport function usePersist<T>(options: UsePersistOptions): UsePersistReturn<T> {\r\n const {\r\n key,\r\n storage: storageType = 'localStorage',\r\n defaultValue,\r\n serialize = JSON.stringify,\r\n deserialize = JSON.parse,\r\n restoreOnMounted = true,\r\n } = options\r\n\r\n const storage = getStorage(storageType)\r\n const value = ref<T>(defaultValue as T) as Ref<T>\r\n\r\n /**\r\n * 从存储恢复\r\n */\r\n const restore = (): void => {\r\n if (!storage) return\r\n\r\n try {\r\n const stored = storage.getItem(key)\r\n if (stored !== null) {\r\n value.value = deserialize(stored) as T\r\n }\r\n } catch (error) {\r\n console.error(`[usePersist] 恢复失败 (${key}):`, error)\r\n }\r\n }\r\n\r\n /**\r\n * 保存到存储\r\n */\r\n const save = (): void => {\r\n if (!storage) return\r\n\r\n try {\r\n storage.setItem(key, serialize(value.value))\r\n } catch (error) {\r\n console.error(`[usePersist] 保存失败 (${key}):`, error)\r\n }\r\n }\r\n\r\n /**\r\n * 清除存储\r\n */\r\n const clear = (): void => {\r\n if (!storage) return\r\n\r\n try {\r\n storage.removeItem(key)\r\n value.value = defaultValue as T\r\n } catch (error) {\r\n console.error(`[usePersist] 清除失败 (${key}):`, error)\r\n }\r\n }\r\n\r\n // 组件挂载时恢复\r\n if (restoreOnMounted) {\r\n onMounted(() => {\r\n restore()\r\n })\r\n }\r\n\r\n // 监听变化自动保存\r\n watch(\r\n value,\r\n () => {\r\n save()\r\n },\r\n { deep: true }\r\n )\r\n\r\n return {\r\n value,\r\n save,\r\n restore,\r\n clear,\r\n }\r\n}\r\n\r\n/**\r\n * 创建持久化的 ref\r\n *\r\n * @template T - 值类型\r\n * @param key - 存储键名\r\n * @param defaultValue - 默认值\r\n * @param storage - 存储类型\r\n * @returns 持久化的 ref\r\n *\r\n * @example\r\n * ```typescript\r\n * const count = usePersistedRef('count', 0)\r\n *\r\n * // 更新会自动保存\r\n * count.value++\r\n * ```\r\n */\r\nexport function usePersistedRef<T>(\r\n key: string,\r\n defaultValue: T,\r\n storage: 'localStorage' | 'sessionStorage' = 'localStorage'\r\n): Ref<T> {\r\n const { value } = usePersist<T>({\r\n key,\r\n storage,\r\n defaultValue,\r\n })\r\n return value\r\n}\r\n\r\n/**\r\n * 创建带过期时间的持久化 ref\r\n *\r\n * @template T - 值类型\r\n * @param key - 存储键名\r\n * @param defaultValue - 默认值\r\n * @param ttl - 过期时间(毫秒)\r\n * @returns 持久化的 ref\r\n */\r\nexport function usePersistedRefWithTTL<T>(\r\n key: string,\r\n defaultValue: T,\r\n ttl: number\r\n): Ref<T> {\r\n interface StoredData {\r\n value: T\r\n expiry: number\r\n }\r\n\r\n const persisted = usePersist<StoredData>({\r\n key,\r\n defaultValue: { value: defaultValue, expiry: 0 },\r\n restoreOnMounted: false,\r\n serialize: (data: unknown) => JSON.stringify(data),\r\n deserialize: (str: string) => {\r\n const data = JSON.parse(str) as StoredData\r\n if (data.expiry && Date.now() > data.expiry) {\r\n return { value: defaultValue, expiry: 0 }\r\n }\r\n return data\r\n },\r\n })\r\n\r\n // 包装 ref 以自动设置过期时间\r\n persisted.restore()\r\n const wrappedRef = ref(persisted.value.value.value) as Ref<T>\r\n\r\n watch(wrappedRef, newValue => {\r\n persisted.value.value = {\r\n value: newValue,\r\n expiry: Date.now() + ttl,\r\n }\r\n })\r\n\r\n watch(\r\n persisted.value,\r\n stored => {\r\n if (!Object.is(wrappedRef.value, stored.value)) {\r\n wrappedRef.value = stored.value\r\n }\r\n },\r\n { deep: true }\r\n )\r\n\r\n return wrappedRef\r\n}\r\n"],"mappings":";;;;;;;;;;;AAcA,SAAS,WAAW,MAAyD;CAC3E,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,IAAI;EACF,OAAO,SAAS,iBAAiB,OAAO,eAAe,OAAO;CAChE,QAAQ;EACN,OAAO;CACT;AACF;AAgCO,SAAS,WAAc,SAAiD;CAC7E,MAAM,EACJ,KACA,SAAS,cAAc,gBACvB,cACA,YAAY,KAAK,WACjB,cAAc,KAAK,OACnB,mBAAmB,SACjB;CAEJ,MAAM,UAAU,WAAW,WAAW;CACtC,MAAM,QAAQ,IAAO,YAAiB;CAKtC,MAAM,gBAAsB;EAC1B,IAAI,CAAC,SAAS;EAEd,IAAI;GACF,MAAM,SAAS,QAAQ,QAAQ,GAAG;GAClC,IAAI,WAAW,MACb,MAAM,QAAQ,YAAY,MAAM;EAEpC,SAAS,OAAO;GACd,QAAQ,MAAM,0CAAsB,IAAG,KAAM,KAAK;EACpD;CACF;CAKA,MAAM,aAAmB;EACvB,IAAI,CAAC,SAAS;EAEd,IAAI;GACF,QAAQ,QAAQ,KAAK,UAAU,MAAM,KAAK,CAAC;EAC7C,SAAS,OAAO;GACd,QAAQ,MAAM,0CAAsB,IAAG,KAAM,KAAK;EACpD;CACF;CAKA,MAAM,cAAoB;EACxB,IAAI,CAAC,SAAS;EAEd,IAAI;GACF,QAAQ,WAAW,GAAG;GACtB,MAAM,QAAQ;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,0CAAsB,IAAG,KAAM,KAAK;EACpD;CACF;CAGA,IAAI,kBACF,gBAAgB;EACd,QAAQ;CACV,CAAC;CAIH,MACE,aACM;EACJ,KAAK;CACP,GACA,EAAE,MAAM,KAAK,CACf;CAEA,OAAO;EACL;EACA;EACA;EACA;CACF;AACF;AAmBO,SAAS,gBACd,KACA,cACA,UAA6C,gBACrC;CACR,MAAM,EAAE,UAAU,WAAc;EAC9B;EACA;EACA;CACF,CAAC;CACD,OAAO;AACT;AAWO,SAAS,uBACd,KACA,cACA,KACQ;CAMR,MAAM,YAAY,WAAuB;EACvC;EACA,cAAc;GAAE,OAAO;GAAc,QAAQ;EAAE;EAC/C,kBAAkB;EAClB,YAAY,SAAkB,KAAK,UAAU,IAAI;EACjD,cAAc,QAAgB;GAC5B,MAAM,OAAO,KAAK,MAAM,GAAG;GAC3B,IAAI,KAAK,UAAU,KAAK,IAAI,IAAI,KAAK,QACnC,OAAO;IAAE,OAAO;IAAc,QAAQ;GAAE;GAE1C,OAAO;EACT;CACF,CAAC;CAGD,UAAU,QAAQ;CAClB,MAAM,aAAa,IAAI,UAAU,MAAM,MAAM,KAAK;CAElD,MAAM,aAAY,aAAY;EAC5B,UAAU,MAAM,QAAQ;GACtB,OAAO;GACP,QAAQ,KAAK,IAAI,IAAI;EACvB;CACF,CAAC;CAED,MACE,UAAU,QACV,WAAU;EACR,IAAI,CAAC,OAAO,GAAG,WAAW,OAAO,OAAO,KAAK,GAC3C,WAAW,QAAQ,OAAO;CAE9B,GACA,EAAE,MAAM,KAAK,CACf;CAEA,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePersistedState.d.ts","sourceRoot":"","sources":["../../src/composables/usePersistedState.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"usePersistedState.d.ts","sourceRoot":"","sources":["../../src/composables/usePersistedState.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAM9B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,cAAc,GAAG,gBAAgB,CAAA;IAC3C,YAAY,CAAC,EAAE,CAAC,CAAA;IAChB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAA;IACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAA;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,OAAO,GAAG,IAAI,CAAA;IAC7D,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAA;IAClD,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;IACjC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;CACpC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IACb,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,SAAS,EAAE,MAAM,OAAO,CAAA;CACzB;AAmDD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAChC,oBAAoB,CAAC,CAAC,CAAC,CA2OzB"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ***********************************
|
|
3
3
|
* @composy/store-vue v0.0.1 *
|
|
4
4
|
* Built with rollup *
|
|
5
|
-
* Build time: 2024-
|
|
5
|
+
* Build time: 2024-10-12 17:40:04 *
|
|
6
6
|
* Build mode: production *
|
|
7
7
|
* Minified: No *
|
|
8
8
|
* ***********************************
|
|
@@ -29,6 +29,7 @@ function usePersistedState(options) {
|
|
|
29
29
|
const storage = getStorage(storageType);
|
|
30
30
|
const createDefaultValue = () => deepClone(defaultValue);
|
|
31
31
|
const state = ref(createDefaultValue());
|
|
32
|
+
let skipNextSave = false;
|
|
32
33
|
const save = () => {
|
|
33
34
|
if (!storage) return;
|
|
34
35
|
try {
|
|
@@ -42,6 +43,7 @@ function usePersistedState(options) {
|
|
|
42
43
|
console.error(`[usePersistedState] \u4FDD\u5B58\u5931\u8D25 (${key}):`, error);
|
|
43
44
|
}
|
|
44
45
|
};
|
|
46
|
+
const debouncedSave = debounceDelay > 0 ? debounce(save, debounceDelay) : null;
|
|
45
47
|
const clear = () => {
|
|
46
48
|
if (!storage) return;
|
|
47
49
|
try {
|
|
@@ -69,6 +71,8 @@ function usePersistedState(options) {
|
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
73
|
const reset = () => {
|
|
74
|
+
debouncedSave?.cancel();
|
|
75
|
+
skipNextSave = true;
|
|
72
76
|
state.value = createDefaultValue();
|
|
73
77
|
clear();
|
|
74
78
|
};
|
|
@@ -83,9 +87,14 @@ function usePersistedState(options) {
|
|
|
83
87
|
return false;
|
|
84
88
|
}
|
|
85
89
|
};
|
|
86
|
-
const debouncedSave = debounceDelay > 0 ? debounce(save, debounceDelay) : null;
|
|
87
90
|
const handleStorageEvent = (event) => {
|
|
88
|
-
if (event.key !== key
|
|
91
|
+
if (event.key !== key) return;
|
|
92
|
+
if (!event.newValue) {
|
|
93
|
+
debouncedSave?.cancel();
|
|
94
|
+
skipNextSave = true;
|
|
95
|
+
state.value = createDefaultValue();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
89
98
|
try {
|
|
90
99
|
const item = JSON.parse(event.newValue);
|
|
91
100
|
if (item.expiresAt && Date.now() > item.expiresAt) return;
|
|
@@ -96,6 +105,10 @@ function usePersistedState(options) {
|
|
|
96
105
|
};
|
|
97
106
|
restore();
|
|
98
107
|
watch(state, (newValue, oldValue) => {
|
|
108
|
+
if (skipNextSave) {
|
|
109
|
+
skipNextSave = false;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
99
112
|
if (onBeforeChange?.(newValue, oldValue) === false) return;
|
|
100
113
|
if (debouncedSave) debouncedSave();
|
|
101
114
|
else save();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePersistedState.js","names":[],"sources":["../../src/composables/usePersistedState.ts"],"sourcesContent":["/* ============================================================\n * usePersistedState.ts\n * 持久化状态 Composable\n * 提供将 Vue 响应式状态自动持久化到 localStorage/sessionStorage 的能力\n * 支持 TTL 过期、跨标签页同步、防抖保存、序列化自定义等特性\n * ============================================================ */\n\n/* 从 Vue 导入 Ref 类型 */\nimport type {Ref} from 'vue';\n/* 从 store-core 导入防抖和深拷贝工具函数 */\nimport { debounce, deepClone } from '@composy/store-core'\n/* 从 Vue 导入生命周期钩子和响应式 API */\nimport { onUnmounted, ref, watch } from 'vue'\n\n/**\n * 持久化状态配置选项接口\n * @property key - 存储键名\n * @property storage - 存储类型,默认 'localStorage'\n * @property defaultValue - 默认值(当存储中无数据或过期时使用)\n * @serializer - 自定义序列化函数(默认 JSON.stringify)\n * @deserializer - 自定义反序列化函数(默认 JSON.parse)\n * @property syncTabs - 是否同步其他标签页的变化\n * @property debounce - 防抖延迟(毫秒),0 表示不防抖\n * @property deep - 是否深度监听状态变化\n * @property ttl - 存活时间(毫秒),过期后自动清除\n * @property onBeforeChange - 值变化前的钩子,返回 false 可阻止保存\n * @property onAfterChange - 值变化后的钩子\n * @property onBeforeRestore - 恢复前的钩子,可转换恢复的值\n * @property onAfterRestore - 恢复后的钩子\n */\nexport interface PersistedStateOptions<T> {\n key: string\n storage?: 'localStorage' | 'sessionStorage'\n defaultValue?: T\n serializer?: (value: T) => string\n deserializer?: (value: string) => T\n syncTabs?: boolean\n debounce?: number\n deep?: boolean\n ttl?: number\n onBeforeChange?: (newValue: T, oldValue: T) => boolean | void\n onAfterChange?: (newValue: T, oldValue: T) => void\n onBeforeRestore?: (value: T) => T\n onAfterRestore?: (value: T) => void\n}\n\n/**\n * 持久化状态返回值接口\n * @property state - 响应式状态引用\n * @property save - 手动保存到存储\n * @property restore - 从存储手动恢复\n * @property clear - 清除存储中的数据\n * @property reset - 重置为默认值并清除存储\n * @property isExpired - 检查存储数据是否已过期\n */\nexport interface PersistedStateReturn<T> {\n state: Ref<T>\n save: () => void\n restore: () => void\n clear: () => void\n reset: () => void\n isExpired: () => boolean\n}\n\n/**\n * 存储项的内部格式接口\n * 包装序列化后的值,附加时间戳和过期时间\n * @property value - 序列化后的字符串值\n * @property timestamp - 保存时间戳\n * @property expiresAt - 过期时间戳(可选,无则永不过期)\n */\ninterface StorageItem {\n value: string\n timestamp: number\n expiresAt?: number\n}\n\n/**\n * 默认序列化函数\n * 使用 JSON.stringify 将值转为 JSON 字符串\n */\nfunction defaultSerializer<T>(value: T): string {\n return JSON.stringify(value)\n}\n\n/**\n * 默认反序列化函数\n * 使用 JSON.parse 解析 JSON 字符串,解析失败则原样返回\n */\nfunction defaultDeserializer<T>(value: string): T {\n try {\n return JSON.parse(value)\n }\n catch {\n /* 解析失败(数据损坏),原样返回 */\n return value as T\n }\n}\n\n/**\n * 获取浏览器存储对象\n * 在非浏览器环境(如 SSR)返回 null\n * @param type - 存储类型\n * @returns Storage 对象或 null\n */\nfunction getStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {\n /* SSR 环境下无 window 对象 */\n if (typeof window === 'undefined') {\n return null\n }\n\n return type === 'localStorage' ? window.localStorage : window.sessionStorage\n}\n\n/**\n * 持久化状态 Composable\n * 将 Vue 响应式状态自动持久化到浏览器存储\n * 支持 TTL 过期、跨标签页同步、防抖、生命周期钩子\n *\n * @param options - 配置选项\n * @returns 包含状态和操作方法的对象\n */\nexport function usePersistedState<T>(\n options: PersistedStateOptions<T>,\n): PersistedStateReturn<T> {\n /* 解构配置选项,设置默认值 */\n const {\n key, /* 存储键名 */\n storage: storageType = 'localStorage', /* 存储类型,默认 localStorage */\n defaultValue, /* 默认值 */\n serializer = defaultSerializer, /* 序列化函数 */\n deserializer = defaultDeserializer, /* 反序列化函数 */\n syncTabs = false, /* 是否跨标签页同步 */\n debounce: debounceDelay = 0, /* 防抖延迟 */\n deep = true, /* 是否深度监听 */\n ttl, /* 过期时间 */\n onBeforeChange, /* 变化前钩子 */\n onAfterChange, /* 变化后钩子 */\n onBeforeRestore, /* 恢复前钩子 */\n onAfterRestore, /* 恢复后钩子 */\n } = options\n\n /* 获取实际的存储对象 */\n const storage = getStorage(storageType)\n /* 创建默认值的深拷贝工厂函数(避免共享引用) */\n const createDefaultValue = (): T => deepClone(defaultValue as T)\n /* 创建响应式状态,初始值为默认值的深拷贝 */\n const state = ref(createDefaultValue()) as Ref<T>\n\n /**\n * 将当前状态保存到存储\n * 将状态序列化并包装为 StorageItem 格式(含时间戳和过期时间)\n */\n const save = (): void => {\n if (!storage) {\n return /* 存储不可用(SSR 环境) */\n }\n\n try {\n /* 构建存储项:包含序列化值、时间戳和可选的过期时间 */\n const item: StorageItem = {\n value: serializer(state.value), /* 序列化当前状态 */\n timestamp: Date.now(), /* 保存时间 */\n expiresAt: ttl ? Date.now() + ttl : undefined, /* 过期时间 */\n }\n\n /* 将存储项 JSON 序列化后写入存储 */\n storage.setItem(key, JSON.stringify(item))\n }\n catch (error) {\n /* 存储可能已满或被禁用 */\n console.error(`[usePersistedState] 保存失败 (${key}):`, error)\n }\n }\n\n /**\n * 清除存储中的数据\n */\n const clear = (): void => {\n if (!storage) {\n return\n }\n\n try {\n storage.removeItem(key)\n }\n catch (error) {\n console.error(`[usePersistedState] 清除失败 (${key}):`, error)\n }\n }\n\n /**\n * 从存储恢复状态\n * 读取存储项,检查过期,执行恢复前钩子,更新响应式状态\n */\n const restore = (): void => {\n if (!storage) {\n return\n }\n\n try {\n /* 读取存储项 */\n const stored = storage.getItem(key)\n if (!stored) {\n return /* 存储中无数据 */\n }\n\n /* 解析存储项 */\n const item = JSON.parse(stored) as StorageItem\n /* 检查是否过期,过期则清除并退出 */\n if (item.expiresAt && Date.now() > item.expiresAt) {\n clear()\n return\n }\n\n /* 反序列化存储的值 */\n let value = deserializer(item.value)\n /* 执行恢复前钩子,允许转换恢复的值 */\n if (onBeforeRestore) {\n value = onBeforeRestore(value)\n }\n\n /* 更新响应式状态 */\n state.value = value\n /* 执行恢复后钩子 */\n onAfterRestore?.(value)\n }\n catch (error) {\n console.error(`[usePersistedState] 恢复失败 (${key}):`, error)\n }\n }\n\n /**\n * 重置为默认值并清除存储\n */\n const reset = (): void => {\n state.value = createDefaultValue() /* 恢复为默认值 */\n clear() /* 清除存储 */\n }\n\n /**\n * 检查存储数据是否已过期\n * @returns 是否已过期\n */\n const isExpired = (): boolean => {\n /* 无存储或无 TTL 则不可能过期 */\n if (!storage || !ttl) {\n return false\n }\n\n try {\n const stored = storage.getItem(key)\n if (!stored) {\n return false /* 无数据,视为未过期 */\n }\n\n const item = JSON.parse(stored) as StorageItem\n /* 比较当前时间与过期时间 */\n return item.expiresAt ? Date.now() > item.expiresAt : false\n }\n catch {\n return false\n }\n }\n\n /* 如果设置了防抖延迟,创建防抖版的 save 函数 */\n const debouncedSave = debounceDelay > 0 ? debounce(save, debounceDelay) : null\n\n /**\n * 处理跨标签页存储事件\n * 当其他标签页修改了同一 key 的存储时触发\n */\n const handleStorageEvent = (event: StorageEvent): void => {\n /* 只关注目标 key 且有新值的事件 */\n if (event.key !== key || !event.newValue) {\n return\n }\n\n try {\n const item = JSON.parse(event.newValue) as StorageItem\n /* 跳过已过期的数据 */\n if (item.expiresAt && Date.now() > item.expiresAt) {\n return\n }\n\n /* 反序列化并更新本地状态 */\n state.value = deserializer(item.value)\n }\n catch (error) {\n console.error(`[usePersistedState] 同步失败 (${key}):`, error)\n }\n }\n\n /* ── 初始化 ── */\n /* 组件挂载时立即尝试从存储恢复状态 */\n restore()\n\n /* ── 状态变化监听 ── */\n /* 监听 state 变化,自动保存到存储 */\n watch(\n state,\n (newValue, oldValue) => {\n /* 执行变化前钩子,返回 false 可阻止保存 */\n const canContinue = onBeforeChange?.(newValue, oldValue)\n if (canContinue === false) {\n return /* 阻止保存 */\n }\n\n /* 使用防抖保存或立即保存 */\n if (debouncedSave) {\n debouncedSave()\n }\n else {\n save()\n }\n\n /* 执行变化后钩子 */\n onAfterChange?.(newValue, oldValue)\n },\n { deep }, /* 深度监听选项 */\n )\n\n /* ── 跨标签页同步 ── */\n /* 如果启用了跨标签页同步,监听 storage 事件 */\n if (syncTabs && typeof window !== 'undefined') {\n window.addEventListener('storage', handleStorageEvent)\n }\n\n /* ── 清理 ── */\n /* 组件卸载时取消防抖和移除事件监听 */\n onUnmounted(() => {\n /* 取消待执行的防抖保存 */\n debouncedSave?.cancel()\n /* 移除跨标签页同步的事件监听 */\n if (syncTabs && typeof window !== 'undefined') {\n window.removeEventListener('storage', handleStorageEvent)\n }\n })\n\n /* ── 返回状态和方法 ── */\n return {\n state, /* 响应式状态 */\n save, /* 手动保存 */\n restore, /* 手动恢复 */\n clear, /* 清除存储 */\n reset, /* 重置为默认值 */\n isExpired, /* 检查是否过期 */\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"usePersistedState.js","names":[],"sources":["../../src/composables/usePersistedState.ts"],"sourcesContent":["/* ============================================================\r\n * usePersistedState.ts\r\n * 持久化状态 Composable\r\n * 提供将 Vue 响应式状态自动持久化到 localStorage/sessionStorage 的能力\r\n * 支持 TTL 过期、跨标签页同步、防抖保存、序列化自定义等特性\r\n * ============================================================ */\r\n\r\n/* 从 Vue 导入 Ref 类型 */\r\nimport type { Ref } from 'vue'\r\n/* 从 store-core 导入防抖和深拷贝工具函数 */\r\nimport { debounce, deepClone } from '@composy/store-core'\r\n/* 从 Vue 导入生命周期钩子和响应式 API */\r\nimport { onUnmounted, ref, watch } from 'vue'\r\n\r\n/**\r\n * 持久化状态配置选项接口\r\n * @property key - 存储键名\r\n * @property storage - 存储类型,默认 'localStorage'\r\n * @property defaultValue - 默认值(当存储中无数据或过期时使用)\r\n * @serializer - 自定义序列化函数(默认 JSON.stringify)\r\n * @deserializer - 自定义反序列化函数(默认 JSON.parse)\r\n * @property syncTabs - 是否同步其他标签页的变化\r\n * @property debounce - 防抖延迟(毫秒),0 表示不防抖\r\n * @property deep - 是否深度监听状态变化\r\n * @property ttl - 存活时间(毫秒),过期后自动清除\r\n * @property onBeforeChange - 值变化前的钩子,返回 false 可阻止保存\r\n * @property onAfterChange - 值变化后的钩子\r\n * @property onBeforeRestore - 恢复前的钩子,可转换恢复的值\r\n * @property onAfterRestore - 恢复后的钩子\r\n */\r\nexport interface PersistedStateOptions<T> {\r\n key: string\r\n storage?: 'localStorage' | 'sessionStorage'\r\n defaultValue?: T\r\n serializer?: (value: T) => string\r\n deserializer?: (value: string) => T\r\n syncTabs?: boolean\r\n debounce?: number\r\n deep?: boolean\r\n ttl?: number\r\n onBeforeChange?: (newValue: T, oldValue: T) => boolean | void\r\n onAfterChange?: (newValue: T, oldValue: T) => void\r\n onBeforeRestore?: (value: T) => T\r\n onAfterRestore?: (value: T) => void\r\n}\r\n\r\n/**\r\n * 持久化状态返回值接口\r\n * @property state - 响应式状态引用\r\n * @property save - 手动保存到存储\r\n * @property restore - 从存储手动恢复\r\n * @property clear - 清除存储中的数据\r\n * @property reset - 重置为默认值并清除存储\r\n * @property isExpired - 检查存储数据是否已过期\r\n */\r\nexport interface PersistedStateReturn<T> {\r\n state: Ref<T>\r\n save: () => void\r\n restore: () => void\r\n clear: () => void\r\n reset: () => void\r\n isExpired: () => boolean\r\n}\r\n\r\n/**\r\n * 存储项的内部格式接口\r\n * 包装序列化后的值,附加时间戳和过期时间\r\n * @property value - 序列化后的字符串值\r\n * @property timestamp - 保存时间戳\r\n * @property expiresAt - 过期时间戳(可选,无则永不过期)\r\n */\r\ninterface StorageItem {\r\n value: string\r\n timestamp: number\r\n expiresAt?: number\r\n}\r\n\r\n/**\r\n * 默认序列化函数\r\n * 使用 JSON.stringify 将值转为 JSON 字符串\r\n */\r\nfunction defaultSerializer<T>(value: T): string {\r\n return JSON.stringify(value)\r\n}\r\n\r\n/**\r\n * 默认反序列化函数\r\n * 使用 JSON.parse 解析 JSON 字符串,解析失败则原样返回\r\n */\r\nfunction defaultDeserializer<T>(value: string): T {\r\n try {\r\n return JSON.parse(value)\r\n } catch {\r\n /* 解析失败(数据损坏),原样返回 */\r\n return value as T\r\n }\r\n}\r\n\r\n/**\r\n * 获取浏览器存储对象\r\n * 在非浏览器环境(如 SSR)返回 null\r\n * @param type - 存储类型\r\n * @returns Storage 对象或 null\r\n */\r\nfunction getStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {\r\n /* SSR 环境下无 window 对象 */\r\n if (typeof window === 'undefined') {\r\n return null\r\n }\r\n\r\n return type === 'localStorage' ? window.localStorage : window.sessionStorage\r\n}\r\n\r\n/**\r\n * 持久化状态 Composable\r\n * 将 Vue 响应式状态自动持久化到浏览器存储\r\n * 支持 TTL 过期、跨标签页同步、防抖、生命周期钩子\r\n *\r\n * @param options - 配置选项\r\n * @returns 包含状态和操作方法的对象\r\n */\r\nexport function usePersistedState<T>(\r\n options: PersistedStateOptions<T>\r\n): PersistedStateReturn<T> {\r\n /* 解构配置选项,设置默认值 */\r\n const {\r\n key /* 存储键名 */,\r\n storage: storageType = 'localStorage' /* 存储类型,默认 localStorage */,\r\n defaultValue /* 默认值 */,\r\n serializer = defaultSerializer /* 序列化函数 */,\r\n deserializer = defaultDeserializer /* 反序列化函数 */,\r\n syncTabs = false /* 是否跨标签页同步 */,\r\n debounce: debounceDelay = 0 /* 防抖延迟 */,\r\n deep = true /* 是否深度监听 */,\r\n ttl /* 过期时间 */,\r\n onBeforeChange /* 变化前钩子 */,\r\n onAfterChange /* 变化后钩子 */,\r\n onBeforeRestore /* 恢复前钩子 */,\r\n onAfterRestore /* 恢复后钩子 */,\r\n } = options\r\n\r\n /* 获取实际的存储对象 */\r\n const storage = getStorage(storageType)\r\n /* 创建默认值的深拷贝工厂函数(避免共享引用) */\r\n const createDefaultValue = (): T => deepClone(defaultValue as T)\r\n /* 创建响应式状态,初始值为默认值的深拷贝 */\r\n const state = ref(createDefaultValue()) as Ref<T>\r\n /* 标记一次不应回写存储的内部状态同步 */\r\n let skipNextSave = false\r\n\r\n /**\r\n * 将当前状态保存到存储\r\n * 将状态序列化并包装为 StorageItem 格式(含时间戳和过期时间)\r\n */\r\n const save = (): void => {\r\n if (!storage) {\r\n return /* 存储不可用(SSR 环境) */\r\n }\r\n\r\n try {\r\n /* 构建存储项:包含序列化值、时间戳和可选的过期时间 */\r\n const item: StorageItem = {\r\n value: serializer(state.value) /* 序列化当前状态 */,\r\n timestamp: Date.now() /* 保存时间 */,\r\n expiresAt: ttl ? Date.now() + ttl : undefined /* 过期时间 */,\r\n }\r\n\r\n /* 将存储项 JSON 序列化后写入存储 */\r\n storage.setItem(key, JSON.stringify(item))\r\n } catch (error) {\r\n /* 存储可能已满或被禁用 */\r\n console.error(`[usePersistedState] 保存失败 (${key}):`, error)\r\n }\r\n }\r\n\r\n /* 如果设置了防抖延迟,创建防抖版的 save 函数 */\r\n const debouncedSave = debounceDelay > 0 ? debounce(save, debounceDelay) : null\r\n\r\n /**\r\n * 清除存储中的数据\r\n */\r\n const clear = (): void => {\r\n if (!storage) {\r\n return\r\n }\r\n\r\n try {\r\n storage.removeItem(key)\r\n } catch (error) {\r\n console.error(`[usePersistedState] 清除失败 (${key}):`, error)\r\n }\r\n }\r\n\r\n /**\r\n * 从存储恢复状态\r\n * 读取存储项,检查过期,执行恢复前钩子,更新响应式状态\r\n */\r\n const restore = (): void => {\r\n if (!storage) {\r\n return\r\n }\r\n\r\n try {\r\n /* 读取存储项 */\r\n const stored = storage.getItem(key)\r\n if (!stored) {\r\n return /* 存储中无数据 */\r\n }\r\n\r\n /* 解析存储项 */\r\n const item = JSON.parse(stored) as StorageItem\r\n /* 检查是否过期,过期则清除并退出 */\r\n if (item.expiresAt && Date.now() > item.expiresAt) {\r\n clear()\r\n return\r\n }\r\n\r\n /* 反序列化存储的值 */\r\n let value = deserializer(item.value)\r\n /* 执行恢复前钩子,允许转换恢复的值 */\r\n if (onBeforeRestore) {\r\n value = onBeforeRestore(value)\r\n }\r\n\r\n /* 更新响应式状态 */\r\n state.value = value\r\n /* 执行恢复后钩子 */\r\n onAfterRestore?.(value)\r\n } catch (error) {\r\n console.error(`[usePersistedState] 恢复失败 (${key}):`, error)\r\n }\r\n }\r\n\r\n /**\r\n * 重置为默认值并清除存储\r\n */\r\n const reset = (): void => {\r\n debouncedSave?.cancel()\r\n skipNextSave = true\r\n state.value = createDefaultValue() /* 恢复为默认值 */\r\n clear() /* 清除存储 */\r\n }\r\n\r\n /**\r\n * 检查存储数据是否已过期\r\n * @returns 是否已过期\r\n */\r\n const isExpired = (): boolean => {\r\n /* 无存储或无 TTL 则不可能过期 */\r\n if (!storage || !ttl) {\r\n return false\r\n }\r\n\r\n try {\r\n const stored = storage.getItem(key)\r\n if (!stored) {\r\n return false /* 无数据,视为未过期 */\r\n }\r\n\r\n const item = JSON.parse(stored) as StorageItem\r\n /* 比较当前时间与过期时间 */\r\n return item.expiresAt ? Date.now() > item.expiresAt : false\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /**\r\n * 处理跨标签页存储事件\r\n * 当其他标签页修改了同一 key 的存储时触发\r\n */\r\n const handleStorageEvent = (event: StorageEvent): void => {\r\n /* 只关注目标 key */\r\n if (event.key !== key) {\r\n return\r\n }\r\n\r\n /* 其他标签页删除 key 时同步恢复默认值,且不把默认值重新写回去 */\r\n if (!event.newValue) {\r\n debouncedSave?.cancel()\r\n skipNextSave = true\r\n state.value = createDefaultValue()\r\n return\r\n }\r\n\r\n try {\r\n const item = JSON.parse(event.newValue) as StorageItem\r\n /* 跳过已过期的数据 */\r\n if (item.expiresAt && Date.now() > item.expiresAt) {\r\n return\r\n }\r\n\r\n /* 反序列化并更新本地状态 */\r\n state.value = deserializer(item.value)\r\n } catch (error) {\r\n console.error(`[usePersistedState] 同步失败 (${key}):`, error)\r\n }\r\n }\r\n\r\n /* ── 初始化 ── */\r\n /* 组件挂载时立即尝试从存储恢复状态 */\r\n restore()\r\n\r\n /* ── 状态变化监听 ── */\r\n /* 监听 state 变化,自动保存到存储 */\r\n watch(\r\n state,\r\n (newValue, oldValue) => {\r\n if (skipNextSave) {\r\n skipNextSave = false\r\n return\r\n }\r\n\r\n /* 执行变化前钩子,返回 false 可阻止保存 */\r\n const canContinue = onBeforeChange?.(newValue, oldValue)\r\n if (canContinue === false) {\r\n return /* 阻止保存 */\r\n }\r\n\r\n /* 使用防抖保存或立即保存 */\r\n if (debouncedSave) {\r\n debouncedSave()\r\n } else {\r\n save()\r\n }\r\n\r\n /* 执行变化后钩子 */\r\n onAfterChange?.(newValue, oldValue)\r\n },\r\n { deep } /* 深度监听选项 */\r\n )\r\n\r\n /* ── 跨标签页同步 ── */\r\n /* 如果启用了跨标签页同步,监听 storage 事件 */\r\n if (syncTabs && typeof window !== 'undefined') {\r\n window.addEventListener('storage', handleStorageEvent)\r\n }\r\n\r\n /* ── 清理 ── */\r\n /* 组件卸载时取消防抖和移除事件监听 */\r\n onUnmounted(() => {\r\n /* 取消待执行的防抖保存 */\r\n debouncedSave?.cancel()\r\n /* 移除跨标签页同步的事件监听 */\r\n if (syncTabs && typeof window !== 'undefined') {\r\n window.removeEventListener('storage', handleStorageEvent)\r\n }\r\n })\r\n\r\n /* ── 返回状态和方法 ── */\r\n return {\r\n state /* 响应式状态 */,\r\n save /* 手动保存 */,\r\n restore /* 手动恢复 */,\r\n clear /* 清除存储 */,\r\n reset /* 重置为默认值 */,\r\n isExpired /* 检查是否过期 */,\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSharedState.d.ts","sourceRoot":"","sources":["../../src/composables/useSharedState.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"useSharedState.d.ts","sourceRoot":"","sources":["../../src/composables/useSharedState.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAI9B;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,uBAAuB;IACvB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,eAAe;IACf,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,mBAAmB;IACnB,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IACb,qBAAqB;IACrB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,qBAAqB;IACrB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,aAAa;IACb,UAAU,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC9B,WAAW;IACX,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,CAAC,EACf,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAAC,CAAC,CAAC,CAiIzB"}
|