Initial commit of akmon project
This commit is contained in:
364
components/supadb/supadb.uvue
Normal file
364
components/supadb/supadb.uvue
Normal file
@@ -0,0 +1,364 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user