364 lines
11 KiB
Plaintext
364 lines
11 KiB
Plaintext
<template>
|
||
<slot name="default" :data="data" :current="localPageCurrent" :total="total" :hasmore="hasmore" :loading="loading" :error="error">
|
||
</slot>
|
||
</template>
|
||
|
||
<script setup lang="uts"> import { ref, watch, onMounted } from 'vue';
|
||
import supa from './aksupainstance.uts';
|
||
import { AkSupaSelectOptions } from './aksupa.uts'
|
||
import { AkReqResponse } from '@/uni_modules/ak-req/index.uts';
|
||
import { toUniError } from '@/utils/utils.uts';
|
||
const props = defineProps({
|
||
collection: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
filter: {
|
||
type: UTSJSONObject,
|
||
default: () => ({}),
|
||
},
|
||
field: {
|
||
type: String,
|
||
default: '*'
|
||
},
|
||
where: Object,
|
||
orderby: String,
|
||
pageData: {
|
||
type: String,
|
||
default: 'add',
|
||
},
|
||
pageCurrent: {
|
||
type: Number,
|
||
default: 1,
|
||
},
|
||
pageSize: {
|
||
type: Number,
|
||
default: 10,
|
||
},
|
||
/*"exact" | "planned" | "estimated" */
|
||
getcount: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
getone: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
loadtime: {
|
||
type: String,
|
||
default: 'auto',
|
||
},
|
||
datafunc: Function,
|
||
// RPC 函数名,当使用 RPC 时,collection 参数可以为空
|
||
rpc: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// RPC 参数,用于传递给 RPC 函数的额外参数
|
||
params: {
|
||
type: UTSJSONObject,
|
||
default: () => ({})
|
||
}
|
||
});
|
||
const emit = defineEmits<{
|
||
(e : 'process-data', val : UTSJSONObject) : void,
|
||
(e : 'load', val : any[]) : void,
|
||
(e : 'error', val : any) : void
|
||
}>();
|
||
|
||
const data = ref<any[]>([]);
|
||
const loading = ref(false);
|
||
const error = ref<any>('');
|
||
const total = ref(0);
|
||
const hasmore = ref(true);
|
||
|
||
// Use local refs for pagination state to avoid mutating props directly
|
||
const localPageCurrent = ref(props.pageCurrent);
|
||
const localPageSize = ref(props.pageSize);
|
||
|
||
type Pagination = {
|
||
count : Number;
|
||
current : Number;
|
||
size : Number;
|
||
};
|
||
|
||
let pagination = { total: 0 };
|
||
|
||
let hasMoreData = true;
|
||
|
||
|
||
/**
|
||
* Unified data loading method
|
||
* @param {UTSJSONObject} opt
|
||
* opt.append Whether to append data
|
||
* opt.clear Whether to clear data
|
||
* opt.page Specify page number
|
||
*/ const fetchData = async (opt : UTSJSONObject) => {
|
||
loading.value = true;
|
||
error.value = '';
|
||
|
||
// 检查是否为 RPC 调用
|
||
const isRpcCall = props.rpc != null && props.rpc.length > 0;
|
||
|
||
// 只有在非 RPC 调用时才检查 collection
|
||
if (!isRpcCall && (props.collection == null || props.collection.trim() == '')) {
|
||
error.value = 'collection/table 不能为空';
|
||
loading.value = false;
|
||
return;
|
||
}
|
||
|
||
// RPC 调用时检查 rpc 参数
|
||
if (isRpcCall && (props.rpc == null || props.rpc.trim() == '')) {
|
||
error.value = 'rpc 函数名不能为空';
|
||
loading.value = false;
|
||
return;
|
||
}try {
|
||
// Platform-specific parameter extraction for UTSJSONObject compatibility
|
||
let append: boolean = false
|
||
let clear: boolean = false
|
||
let page: number = localPageCurrent.value
|
||
|
||
// #ifdef APP-ANDROID || APP-IOS
|
||
// Native platform: use UTSJSONObject methods
|
||
append = opt.getBoolean('append') ?? false
|
||
clear = opt.getBoolean('clear') ?? false
|
||
page = opt.getNumber('page') ?? localPageCurrent.value
|
||
// #endif
|
||
|
||
// #ifndef APP-ANDROID || APP-IOS
|
||
// Web platform: direct property access
|
||
append = (opt as any)['append'] as boolean ?? false
|
||
clear = (opt as any)['clear'] as boolean ?? false
|
||
page = (opt as any)['page'] as number ?? localPageCurrent.value
|
||
// #endif
|
||
|
||
// Update local pagination state
|
||
localPageCurrent.value = page;
|
||
localPageSize.value = props.pageSize;
|
||
|
||
// Build query options
|
||
let selectOptions : AkSupaSelectOptions = {
|
||
limit: localPageSize.value,
|
||
order: props.orderby,
|
||
columns: props.field,
|
||
};
|
||
if (props.getcount != null && props.getcount.length > 0) {
|
||
selectOptions['getcount'] = props.getcount;
|
||
} let result: any;
|
||
if (isRpcCall) {
|
||
// 支持rpc调用 - RPC方法只接受functionName和params两个参数
|
||
// 将filter、params和selectOptions合并为rpcParams
|
||
const rpcParams = new UTSJSONObject();
|
||
|
||
// 首先添加props.params中的参数
|
||
if (props.params != null) {
|
||
const paramsKeys = UTSJSONObject.keys(props.params);
|
||
for (let i = 0; i < paramsKeys.length; i++) {
|
||
const key = paramsKeys[i];
|
||
|
||
// #ifdef APP-ANDROID || APP-IOS
|
||
// Native platform: use UTSJSONObject methods
|
||
rpcParams.set(key, props.params.get(key));
|
||
// #endif
|
||
|
||
// #ifndef APP-ANDROID || APP-IOS
|
||
// Web platform: direct property access
|
||
rpcParams.set(key, (props.params as any)[key]);
|
||
// #endif
|
||
}
|
||
}
|
||
|
||
// 然后添加filter中的参数(可能会覆盖params中的同名参数)
|
||
if (props.filter != null) {
|
||
// Platform-specific filter handling for UTSJSONObject compatibility
|
||
const filterKeys = UTSJSONObject.keys(props.filter);
|
||
for (let i = 0; i < filterKeys.length; i++) {
|
||
const key = filterKeys[i];
|
||
|
||
// #ifdef APP-ANDROID || APP-IOS
|
||
// Native platform: use UTSJSONObject methods
|
||
rpcParams.set(key, props.filter.get(key));
|
||
// #endif
|
||
|
||
// #ifndef APP-ANDROID || APP-IOS
|
||
// Web platform: direct property access
|
||
rpcParams.set(key, (props.filter as any)[key]);
|
||
// #endif
|
||
}
|
||
}
|
||
|
||
// 添加分页和排序参数
|
||
if (selectOptions.limit != null) rpcParams.set('limit', selectOptions.limit);
|
||
if (selectOptions.order != null) rpcParams.set('order', selectOptions.order);
|
||
if (selectOptions.columns != null) rpcParams.set('columns', selectOptions.columns);
|
||
if (selectOptions.getcount != null) rpcParams.set('getcount', selectOptions.getcount);
|
||
|
||
result = await supa.rpc(props.rpc, rpcParams);
|
||
} else {
|
||
// Query data
|
||
result = await supa.select_uts(props.collection, props.filter, selectOptions);
|
||
}
|
||
// headers 判空
|
||
let countstring = '';
|
||
let headers:UTSJSONObject = result.headers != null ? result.headers : {};
|
||
if (headers != null) {
|
||
if (typeof headers.getString == 'function') {
|
||
let val = headers.getString('content-range');
|
||
if (val != null && typeof val == 'string') {
|
||
countstring = val;
|
||
}
|
||
} else if (headers['content-range'] != null) {
|
||
// 类型断言为 string,否则转为 string
|
||
countstring = `${headers['content-range']}`;
|
||
}
|
||
}
|
||
console.log(countstring)
|
||
if (countstring != null && countstring != '') {
|
||
try {
|
||
const rangeParts = countstring.split('/')
|
||
if (rangeParts.length == 2) {
|
||
// 检查第二部分是否为数字(不是 '*')
|
||
const totalPart = rangeParts[1].trim()
|
||
if (totalPart !== '*' && !isNaN(parseInt(totalPart))) {
|
||
total.value = parseInt(totalPart)
|
||
console.log('Total count from header:', total.value)
|
||
pagination.total = total.value;
|
||
|
||
const rangeValues = rangeParts[0].split('-')
|
||
if (rangeValues.length == 2) {
|
||
const end = parseInt(rangeValues[1])
|
||
hasmore.value = end < total.value - 1
|
||
hasMoreData = hasmore.value
|
||
} } else {
|
||
// 当总数未知时(返回 *),设置默认值
|
||
console.log('Total count unknown (*), using default pagination logic')
|
||
total.value = 0
|
||
pagination.total = 0
|
||
// 根据当前返回的数据量判断是否还有更多数据
|
||
hasmore.value = Array.isArray(result.data) && (result.data as any[]).length >= localPageSize.value
|
||
hasMoreData = hasmore.value
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('Failed to parse content-range header', e)
|
||
} } else {
|
||
// 当没有 content-range header 时,根据返回的数据量判断分页
|
||
console.log('No content-range header, using data length for pagination')
|
||
total.value = 0
|
||
pagination.total = 0
|
||
// 如果返回的数据量等于页面大小,可能还有更多数据
|
||
hasmore.value = Array.isArray(result.data) && (result.data as any[]).length >= localPageSize.value
|
||
hasMoreData = hasmore.value
|
||
}
|
||
// data 判空
|
||
// UTS 平台下无 UTSArray.fromAny,需兼容
|
||
let items: Array<any> = (Array.isArray(result.data)) ? result.data as Array<any> : Array<any>();
|
||
try {
|
||
// emit('process-data', items != null ? items : []);
|
||
console.log(result)
|
||
|
||
// Manually create UTSJSONObject from AkReqResponse for cross-platform compatibility
|
||
const prodata = new UTSJSONObject()
|
||
prodata.set('status', result.status)
|
||
prodata.set('data', result.data)
|
||
prodata.set('headers', result.headers)
|
||
prodata.set('error', result.error)
|
||
prodata.set('total', total.value)
|
||
|
||
emit('process-data', prodata);
|
||
} catch (e) {
|
||
console.error('emit process-data error', e);
|
||
}
|
||
|
||
if (clear) {
|
||
data.value = [];
|
||
}
|
||
if (append) {
|
||
if(Array.isArray(items)) {
|
||
data.value = ([] as any[]).concat(data.value, items);
|
||
}
|
||
} else {
|
||
data.value = items as any[];
|
||
}
|
||
pagination.total = total.value;
|
||
hasMoreData = Array.isArray(items) && items.length == localPageSize.value; } catch (err : any) {
|
||
// 使用标准化错误处理
|
||
const uniError = toUniError(err, 'An error occurred while fetching data')
|
||
|
||
try {
|
||
emit('error', uniError);
|
||
} catch (e) {
|
||
console.error('emit error event error', e);
|
||
}
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
// 页码变更统一通过 fetchData
|
||
const handlePageChange = (page : number) => {
|
||
fetchData({ page: page, clear: true, append: false } as UTSJSONObject);
|
||
};
|
||
// 主动加载数据,支持清空
|
||
const loadData = (opt : UTSJSONObject) => {
|
||
console.log('loadData')
|
||
|
||
// Platform-specific parameter extraction for UTSJSONObject compatibility
|
||
let clear: boolean = true
|
||
|
||
// #ifdef APP-ANDROID || APP-IOS
|
||
// Native platform: use UTSJSONObject methods
|
||
clear = opt.getBoolean('clear') ?? true
|
||
// #endif
|
||
|
||
// #ifndef APP-ANDROID || APP-IOS
|
||
// Web platform: direct property access
|
||
clear = (opt as any)['clear'] as boolean ?? true
|
||
// #endif
|
||
|
||
fetchData({ clear, append: false, page: 1 } as UTSJSONObject);
|
||
};
|
||
|
||
const refresh = () => {
|
||
console.log('refresh')
|
||
const clear = true;
|
||
fetchData({ clear, append: false, page: 1 } as UTSJSONObject);
|
||
};
|
||
|
||
// 加载更多,自动追加
|
||
const loadMore = () => {
|
||
if (hasMoreData) {
|
||
const nextPage = props.pageCurrent + 1;
|
||
fetchData({ append: true, clear: false, page: nextPage } as UTSJSONObject);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
if (props.loadtime === 'auto' || props.loadtime === 'onready') {
|
||
onMounted(() => {
|
||
fetchData({} as UTSJSONObject);
|
||
});
|
||
}
|
||
|
||
|
||
// watch(data, (newValue : any) => {
|
||
// emit('load', newValue);
|
||
// });
|
||
watch(error, (newValue : any) => {
|
||
if (newValue != null) {
|
||
emit('error', newValue);
|
||
}
|
||
});
|
||
// Documented exposed methods for parent components
|
||
/**
|
||
* Exposed methods:
|
||
* - loadData(opt?): Load data, optionally clearing previous data
|
||
* - loadMore(): Load next page and append
|
||
*/
|
||
defineExpose({ loadData,refresh, loadMore,hasmore,total });
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 添加您的样式 */
|
||
</style> |