@mtn-ui-z/utils 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,307 @@
1
+ /*
2
+ * @Author: liaokt
3
+ * @Description: 表格状态管理 Hook - 仿 ahooks useAntdTable 风格
4
+ * @Date: 2025-09-25 10:20:00
5
+ * @LastEditors: liaokt
6
+ * @LastEditTime: 2025-12-26 16:56:24
7
+ */
8
+ import { ref, reactive, watch, type Ref } from 'vue'
9
+
10
+ // 数据类型定义
11
+ export interface Data {
12
+ total: number
13
+ list: any[]
14
+ }
15
+
16
+ // 支持两种函数签名:
17
+ // 1. 标准格式:(pagination, searchParams) => Promise<Data>
18
+ // 2. 简化格式:(params) => Promise<any> - 会自动转换数据格式
19
+ type ServiceFunction<TData extends Data> =
20
+ | ((
21
+ pagination: { current: number; page_size: number },
22
+ searchParams: Record<string, any>
23
+ ) => Promise<TData>)
24
+ | ((params?: Record<string, any>) => Promise<any>)
25
+
26
+ export interface UseTableOptions {
27
+ /** 默认参数 */
28
+ defaultParams?: any
29
+ /** 默认页面大小 */
30
+ defaultPageSize?: number
31
+ /** 刷新依赖 */
32
+ refreshDeps?: any[]
33
+ /** 是否立即请求 */
34
+ manual?: boolean
35
+ /** 搜索参数格式化函数 */
36
+ searchFormatter?: (params: any) => any
37
+ /** 错误处理函数 */
38
+ onError?: (error: Error) => void
39
+ /** 成功回调 */
40
+ onSuccess?: (data: any) => void
41
+ }
42
+
43
+ export interface UseTableResult<TData extends Data> {
44
+ /** 表格属性 */
45
+ tableProps: {
46
+ dataSource: TData['list']
47
+ loading: boolean
48
+ onChange: (pagination: any, filters?: any, sorter?: any, extra?: any) => void
49
+ pagination: {
50
+ current: number
51
+ pageSize: number
52
+ total: number
53
+ }
54
+ }
55
+ /** 搜索功能 */
56
+ search: {
57
+ submit: (params: Record<string, any>) => void
58
+ reset: () => void
59
+ }
60
+ /** 数据源 */
61
+ dataSource: Ref<TData['list']>
62
+ /** 加载状态 */
63
+ loading: Ref<boolean>
64
+ /** 分页状态 */
65
+ pagination: Ref<{
66
+ current: number
67
+ page_size: number
68
+ total: number
69
+ }>
70
+ /** 选中的行键 */
71
+ selectedRowKeys: Ref<string[]>
72
+ /** 选中的行数据 */
73
+ selectedRows: Ref<any[]>
74
+ /** 刷新 */
75
+ refresh: () => Promise<void>
76
+ /** 重新加载 */
77
+ reload: () => Promise<void>
78
+ /** 重置 */
79
+ reset: () => void
80
+ }
81
+
82
+ export function useTable<TData extends Data>(
83
+ service: ServiceFunction<TData>,
84
+ options: UseTableOptions = {}
85
+ ): UseTableResult<TData> {
86
+ const {
87
+ defaultParams,
88
+ defaultPageSize = 10,
89
+ refreshDeps = [],
90
+ manual = false,
91
+ searchFormatter,
92
+ onError,
93
+ onSuccess
94
+ } = options
95
+
96
+ // 状态
97
+ const dataSource = ref<any[]>([]) as Ref<TData['list']>
98
+ const loading = ref(false)
99
+ const selectedRowKeys = ref<string[]>([])
100
+ const selectedRows = ref<any[]>([])
101
+
102
+ // 分页状态
103
+ const pagination = ref({
104
+ current: 1,
105
+ page_size: defaultPageSize,
106
+ total: 0
107
+ })
108
+
109
+ // 搜索状态
110
+ const searchParams = ref<any>(defaultParams || {})
111
+
112
+ // 表格 onChange(提取为顶层函数,模板中可直接绑定)
113
+ const handleTableChange = (paginationParams: any) => {
114
+ pagination.value.current = paginationParams.current
115
+ pagination.value.page_size = paginationParams.pageSize
116
+ run()
117
+ }
118
+
119
+ // 表格属性
120
+ const tableProps = reactive({
121
+ get dataSource() {
122
+ return dataSource.value
123
+ },
124
+ get loading() {
125
+ return loading.value
126
+ },
127
+ onChange: handleTableChange,
128
+ get pagination() {
129
+ return {
130
+ current: pagination.value.current,
131
+ pageSize: pagination.value.page_size,
132
+ total: pagination.value.total,
133
+ showSizeChanger: true,
134
+ showTotal: (t: number) => `共 ${t} 条`
135
+ }
136
+ }
137
+ })
138
+
139
+ // 搜索功能
140
+ const search = {
141
+ submit: (params: Record<string, any>) => {
142
+ pagination.value.current = 1
143
+ searchParams.value = params
144
+ run()
145
+ },
146
+ reset: () => {
147
+ searchParams.value = {}
148
+ pagination.value.current = 1
149
+ run()
150
+ }
151
+ }
152
+
153
+ // 请求数据
154
+ const run = async () => {
155
+ loading.value = true
156
+ try {
157
+ // 格式化搜索参数
158
+ const formattedSearchParams = searchFormatter
159
+ ? searchFormatter(searchParams.value)
160
+ : searchParams.value
161
+
162
+ // 构建请求参数
163
+ const paginationParams = {
164
+ current: pagination.value.current,
165
+ page_size: pagination.value.page_size
166
+ }
167
+
168
+ // 检测函数签名:如果函数接受单个参数,则合并参数
169
+ const params = {
170
+ page: paginationParams.current,
171
+ page_size: paginationParams.page_size,
172
+ ...formattedSearchParams
173
+ }
174
+
175
+ // 调用服务函数
176
+ let result: any
177
+ if (service.length === 0 || service.length === 1) {
178
+ // 简化格式:接受单个参数对象
179
+ result = await (service as (params?: Record<string, any>) => Promise<any>)(params)
180
+ } else {
181
+ // 标准格式:接受两个参数
182
+ result = await (service as (pagination: any, searchParams: any) => Promise<TData>)(
183
+ paginationParams,
184
+ formattedSearchParams
185
+ )
186
+ }
187
+
188
+ // 处理返回数据格式
189
+ let finalResult: TData
190
+ if (result && typeof result === 'object') {
191
+ // 如果返回的是 { list, total } 格式,直接使用
192
+ if ('list' in result && 'total' in result) {
193
+ finalResult = result as TData
194
+ }
195
+ // 如果返回 { data: [], total } 这样的结构
196
+ else if (Array.isArray((result as any).data) && 'total' in result) {
197
+ finalResult = {
198
+ list: (result as any).data,
199
+ total: (result as any).total ?? (result as any).data.length
200
+ } as TData
201
+ }
202
+ // 如果返回的是 { code, data } 格式(API 响应格式),需要转换
203
+ else if ('data' in result && 'code' in result) {
204
+ const data = result.data
205
+ // 如果 data 是数组
206
+ if (Array.isArray(data)) {
207
+ finalResult = {
208
+ list: data,
209
+ total: data.length
210
+ } as TData
211
+ }
212
+ // 如果 data 是分页对象 { list, total }
213
+ else if (data && typeof data === 'object' && 'list' in data && 'total' in data) {
214
+ finalResult = {
215
+ list: data.list || [],
216
+ total: data.total || 0
217
+ } as TData
218
+ }
219
+ // 如果 data 是分页对象 { results, count }
220
+ else if (data && typeof data === 'object' && 'results' in data && 'count' in data) {
221
+ finalResult = {
222
+ list: (data.results as any[]) || [],
223
+ total: (data.count as number) || 0
224
+ } as TData
225
+ }
226
+ // 其他情况,默认空列表
227
+ else {
228
+ finalResult = {
229
+ list: [],
230
+ total: 0
231
+ } as unknown as TData
232
+ }
233
+ }
234
+ // 其他格式,尝试直接使用
235
+ else {
236
+ finalResult = result as TData
237
+ }
238
+ } else {
239
+ // 非对象类型,返回空列表
240
+ finalResult = {
241
+ list: [],
242
+ total: 0
243
+ } as unknown as TData
244
+ }
245
+
246
+ dataSource.value = finalResult.list as TData['list']
247
+ pagination.value.total = finalResult.total
248
+
249
+ // 成功回调
250
+ onSuccess?.(finalResult)
251
+ } catch (error) {
252
+ console.error('请求数据失败:', error)
253
+ // 错误处理回调
254
+ onError?.(error as Error)
255
+ } finally {
256
+ loading.value = false
257
+ }
258
+ }
259
+
260
+ // 刷新
261
+ const refresh = async () => {
262
+ await run()
263
+ }
264
+
265
+ // 重新加载
266
+ const reload = async () => {
267
+ pagination.value.current = 1
268
+ await run()
269
+ }
270
+
271
+ // 重置
272
+ const reset = () => {
273
+ pagination.value.current = 1
274
+ pagination.value.page_size = defaultPageSize
275
+ dataSource.value = [] as TData['list']
276
+ selectedRowKeys.value = []
277
+ selectedRows.value = []
278
+ searchParams.value = {}
279
+ }
280
+
281
+ // 监听刷新依赖
282
+ watch(
283
+ refreshDeps,
284
+ () => {
285
+ run()
286
+ },
287
+ { immediate: false }
288
+ )
289
+
290
+ // 立即请求
291
+ if (!manual) {
292
+ run()
293
+ }
294
+
295
+ return {
296
+ tableProps,
297
+ search,
298
+ dataSource,
299
+ loading,
300
+ pagination,
301
+ selectedRowKeys,
302
+ selectedRows,
303
+ refresh,
304
+ reload,
305
+ reset
306
+ }
307
+ }