consumerm模块完成度90%,完善消费者和商家端数据库表,商品、聊天、订单数据对接好了supabase,和商家端对接了聊天功能,安卓端编译通过了css样式,剩余几个页面在处理函数规范问题
This commit is contained in:
@@ -331,7 +331,7 @@ export default {
|
||||
|
||||
.map-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@ export default {
|
||||
|
||||
.menu-item.active .menu-text {
|
||||
color: #3b82f6;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
|
||||
@@ -79,11 +79,11 @@ export class AkSupaQueryBuilder {
|
||||
like(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'like', value); }
|
||||
ilike(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'ilike', value); }
|
||||
in(field : string, value : any[]) : AkSupaQueryBuilder { return this._addCond(field, 'in', value); }
|
||||
is(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'is', value); }
|
||||
is(field : string, value : any | null) : AkSupaQueryBuilder { return this._addCond(field, 'is', value); }
|
||||
contains(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cs', value); }
|
||||
containedBy(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cd', value); }
|
||||
not(field : string, opOrValue : any, value?: any) : AkSupaQueryBuilder {
|
||||
if (value !== undefined) {
|
||||
not(field : string, opOrValue : any, value: any | null = null) : AkSupaQueryBuilder {
|
||||
if (value != null) {
|
||||
// 三元形式:field, operator, value
|
||||
// 例如 not('badge', 'is', null) -> badge=not.is.null
|
||||
const combinedOp = 'not.' + opOrValue;
|
||||
@@ -113,7 +113,7 @@ export class AkSupaQueryBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
private _addCond(afield : string, op : string, value : any) : AkSupaQueryBuilder {
|
||||
private _addCond(afield : string, op : string, value : any | null) : AkSupaQueryBuilder {
|
||||
//console.log('add cond:', op, afield, value)
|
||||
const field = encodeURIComponent(afield)!!
|
||||
// 将 null 转换为字符串 'null',避免构造对象时缺少 value 属性
|
||||
@@ -266,7 +266,7 @@ export class AkSupaQueryBuilder {
|
||||
return params.length > 0 ? params.join('&') : null;
|
||||
}
|
||||
|
||||
select(columns ?: string, opt ?: UTSJSONObject) : AkSupaQueryBuilder {
|
||||
select(columns : string = "*", opt : UTSJSONObject | null = null) : AkSupaQueryBuilder {
|
||||
this._action = 'select';
|
||||
if (columns != null) {
|
||||
this._options.columns = columns;
|
||||
@@ -692,7 +692,7 @@ export class AkSupa {
|
||||
token_type: 'bearer',
|
||||
expires_in: 0,
|
||||
raw: user
|
||||
} as any;
|
||||
} as AkSupaSignInResult;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -999,6 +999,22 @@ async delete(table : string, filter : string | null) : Promise<AkReqResponse<any
|
||||
from(tableName : string) : AkSupaQueryBuilder {
|
||||
return new AkSupaQueryBuilder(this, tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建实时订阅通道 (兼容 Supabase Realtime 接口,目前使用轮询模拟)
|
||||
* @param topic 通道名称,如 public:table
|
||||
*/
|
||||
channel(topic: string): AkSupaRealtimeChannel {
|
||||
return new AkSupaRealtimeChannel(this, topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除通道
|
||||
*/
|
||||
removeChannel(channel: AkSupaRealtimeChannel): Promise<string> {
|
||||
channel.unsubscribe();
|
||||
return Promise.resolve('ok');
|
||||
}
|
||||
// AkSupa类内新增:自动刷新session
|
||||
async refreshSession() : Promise<boolean> {
|
||||
if (this.session == null || this.session?.refresh_token == null) return false;
|
||||
@@ -1133,4 +1149,122 @@ export function createClient(url : string, key : string) : AkSupa {
|
||||
return new AkSupa(url, key);
|
||||
}
|
||||
|
||||
// 模拟 Realtime Channel 类 (Polling Fallback)
|
||||
export class AkSupaRealtimeChannel {
|
||||
private _supa: AkSupa;
|
||||
private _topic: string;
|
||||
private _timer: number = 0;
|
||||
private _callback: ((payload: any) => void) | null = null;
|
||||
private _table: string = '';
|
||||
private _lastTime: string = new Date().toISOString();
|
||||
private _isSubscribed: boolean = false;
|
||||
|
||||
constructor(supa: AkSupa, topic: string) {
|
||||
this._supa = supa;
|
||||
this._topic = topic;
|
||||
}
|
||||
|
||||
// 绑定事件 (仅支持 postgres_changes INSERT)
|
||||
on(type: string, filter: UTSJSONObject, callback: (payload: any) => void): AkSupaRealtimeChannel {
|
||||
// 解析 table
|
||||
const table = filter.getString('table');
|
||||
if (table != null) {
|
||||
this._table = table;
|
||||
}
|
||||
this._callback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 开始订阅
|
||||
subscribe(callback?: (status: string, err: any | null) => void): AkSupaRealtimeChannel {
|
||||
if (this._isSubscribed) return this;
|
||||
this._isSubscribed = true;
|
||||
|
||||
// 初始回调
|
||||
if (callback != null) {
|
||||
callback('SUBSCRIBED', null);
|
||||
}
|
||||
|
||||
// 如果没有指定 table,无法轮询
|
||||
if (this._table == '') {
|
||||
console.warn('Realtime check: No table specified for polling.');
|
||||
return this;
|
||||
}
|
||||
|
||||
// 开始轮询 (每3秒)
|
||||
this._timer = setInterval(() => {
|
||||
this._checkUpdates();
|
||||
}, 3000);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// 停止订阅
|
||||
unsubscribe() {
|
||||
if (this._timer > 0) {
|
||||
clearInterval(this._timer);
|
||||
this._timer = 0;
|
||||
}
|
||||
this._isSubscribed = false;
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
private async _checkUpdates() {
|
||||
if (!this._isSubscribed || this._table == '') return;
|
||||
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const res = await this._supa
|
||||
.from(this._table)
|
||||
.select('*')
|
||||
.gt('created_at', this._lastTime)
|
||||
.order('created_at', { ascending: true })
|
||||
.execute();
|
||||
|
||||
if (res.error == null && res.data != null) {
|
||||
let list: any[] = [];
|
||||
if (Array.isArray(res.data)) {
|
||||
list = res.data as any[];
|
||||
}
|
||||
|
||||
if (list.length > 0) {
|
||||
// 更新最后时间
|
||||
const lastItem = list[list.length - 1];
|
||||
let lastTimeStr: string | null = null;
|
||||
|
||||
if (lastItem instanceof UTSJSONObject) {
|
||||
lastTimeStr = lastItem.getString('created_at');
|
||||
} else {
|
||||
// 尝试转 json
|
||||
const j = JSON.parse(JSON.stringify(lastItem)) as UTSJSONObject;
|
||||
lastTimeStr = j.getString('created_at');
|
||||
}
|
||||
|
||||
if (lastTimeStr != null) {
|
||||
this._lastTime = lastTimeStr;
|
||||
} else {
|
||||
this._lastTime = now;
|
||||
}
|
||||
|
||||
// 触发回调
|
||||
if (this._callback != null) {
|
||||
// 模拟 Realtime payload
|
||||
list.forEach(item => {
|
||||
const payload = {
|
||||
new: item,
|
||||
eventType: 'INSERT',
|
||||
old: null
|
||||
};
|
||||
this._callback?.(payload);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Realtime polling error:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AkSupa;
|
||||
|
||||
Reference in New Issue
Block a user