Files
akmon/components/supadb/supadb.uvue
2026-01-20 08:04:15 +08:00

364 lines
11 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>