项目从akmon迁入到mall

This commit is contained in:
comlibmb
2026-01-21 12:12:22 +08:00
parent cf8236e175
commit d7f95f7fa5
165 changed files with 69160 additions and 0 deletions

View File

@@ -0,0 +1,416 @@
import { AkReqUploadOptions, AkReqOptions, AkReqResponse, AkReqError } from './interface.uts';
// token 持久化 key
const ACCESS_TOKEN_KEY = 'akreq_access_token';
const REFRESH_TOKEN_KEY = 'akreq_refresh_token';
const EXPIRES_AT_KEY = 'akreq_expires_at';
// 优化:用静态变量缓存 token只有 set/clear 时同步 storage
let _accessToken : string | null = null;
let _refreshToken : string | null = null;
let _expiresAt : number | null = null;
export class AkReq {
static setToken(token : string, refreshToken : string, expiresAt : number) {
_accessToken = token;
_refreshToken = refreshToken;
_expiresAt = expiresAt;
uni.setStorageSync(ACCESS_TOKEN_KEY, token);
uni.setStorageSync(REFRESH_TOKEN_KEY, refreshToken);
uni.setStorageSync(EXPIRES_AT_KEY, expiresAt);
}
static getToken() : string | null {
if (_accessToken != null) return _accessToken;
const t = uni.getStorageSync(ACCESS_TOKEN_KEY) as string | null;
_accessToken = t;
return t;
}
static getRefreshToken() : string | null {
if (_refreshToken != null) return _refreshToken;
const t = uni.getStorageSync(REFRESH_TOKEN_KEY) as string | null;
_refreshToken = t;
return t;
} static getExpiresAt() : number | null {
const val = _expiresAt;
if (val != null) return val;
const t = uni.getStorageSync(EXPIRES_AT_KEY) as number | null;
_expiresAt = t;
return t;
}
static clearToken() {
_accessToken = null;
_refreshToken = null;
_expiresAt = null;
uni.removeStorageSync(ACCESS_TOKEN_KEY);
uni.removeStorageSync(REFRESH_TOKEN_KEY);
uni.removeStorageSync(EXPIRES_AT_KEY);
} // 判断 token 是否即将过期提前5分钟刷新
static isTokenExpiring() : boolean {
const expiresAt = this.getExpiresAt();
if (expiresAt === null || expiresAt == 0) {
return true;
}
const now = Math.floor(Date.now() / 1000);
return (expiresAt - now) < 300; // 提前5分钟刷新
}
// 自动刷新 token返回 true=已刷新false=未刷新
static async refreshTokenIfNeeded(apikey ?: string) : Promise<boolean> {
// 没有 access_token 直接返回,不刷新
const accessToken = this.getToken();
if (accessToken === null || accessToken === "") {
return false;
}
if (!this.isTokenExpiring()) {
return false;
}
const refreshToken = this.getRefreshToken();
if (refreshToken === null || refreshToken === "") {
this.clearToken();
return false;
}
// 构造 header必须带 apikey
let headers = {} as UTSJSONObject;
if (apikey !== null && apikey !== "") {
headers = Object.assign({}, headers, { 'apikey': apikey }) as UTSJSONObject;
} try {
const res = await this.request({
url: 'https://ak3.oulog.com/auth/v1/token?grant_type=refresh_token',
method: 'POST',
data: ({ refresh_token: refreshToken } as UTSJSONObject),
headers: headers,
contentType: 'application/json'
}, true); // skipRefresh=true避免递归
const data = res.data as UTSJSONObject | null;
let accessToken : string | null = null;
let refreshTokenNew : string | null = null;
let expiresAt : number | null = null;
if (data != null && typeof data.getString === 'function' && typeof data.getNumber === 'function') {
accessToken = data.getString('access_token');
refreshTokenNew = data.getString('refresh_token');
expiresAt = data.getNumber('expires_at');
}
if (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {
this.setToken(accessToken, refreshTokenNew, expiresAt);
return true;
} else {
this.clearToken();
return false;
}
} catch (e) {
this.clearToken();
return false;
}
}
// options: AkReqOptions, skipRefresh: boolean = false
static async request(options : AkReqOptions, skipRefresh ?: boolean) : Promise<AkReqResponse<any>> {
// 自动刷新 token
if (skipRefresh != true) {
let apikey : string | null = null;
const headersObj = options.headers;
if (headersObj != null && typeof headersObj.getString === 'function') {
apikey = headersObj.getString('apikey');
}
await this.refreshTokenIfNeeded(apikey);
}
// 统一 header自动带上 Authorization/Content-Type/Accept
let headers = options.headers ?? ({} as UTSJSONObject);
const token = this.getToken();
if (token != null && token != "") {
headers = Object.assign({}, headers, { Authorization: `Bearer ${token}` }) as UTSJSONObject;
}
let contentType = options.contentType ?? '';
if (headers != null && typeof headers.getString === 'function') {
const headerContentType = headers.getString('Content-Type');
if (headerContentType != null) {
contentType = headerContentType;
}
}
if (contentType != null && contentType != "") {
headers = Object.assign({}, headers, { 'Content-Type': contentType }) as UTSJSONObject;
}
// 默认 Accept
headers = Object.assign({ Accept: 'application/json' } as UTSJSONObject, headers) as UTSJSONObject;
const timeout = options.timeout ?? 10000;
const maxRetry = Math.max(0, options.retryCount ?? 0);
const baseDelay = Math.max(0, options.retryDelayMs ?? 300);
const doOnce = (): Promise<AkReqResponse<any>> => {
return new Promise<AkReqResponse<any>>((resolve) => {
uni.request({
url: options.url,
method: options.method ?? 'GET',
data: options.data,
header: headers,
timeout: timeout,
success: (res) => {
// HEAD 请求特殊处理:没有响应体,只有 headers
if (options.method == 'HEAD') {
const result = AkReq.createResponse<any>(
res.statusCode,
[] as Array<any>,
res.header as UTSJSONObject
);
resolve(result);
return;
}
// 兼容 res.data 可能为 string 或 UTSJSONObject 或 UTSArray
let data : UTSJSONObject | Array<UTSJSONObject> | null;
if (typeof res.data == 'string') {
const strData = res.data as string;
if (strData.length > 0 && /[^\s]/.test(strData)) {
try {
data = JSON.parse(strData) as UTSJSONObject;
} catch (e) {
data = null;
}
} else {
data = null;
}
} else if (Array.isArray(res.data)) {
data = res.data as UTSJSONObject[];
} else {
const objData = res.data as UTSJSONObject | null;
data = objData;
if (objData != null) {
const accessToken = objData.getString('access_token');
const refreshTokenNew = objData.getString('refresh_token');
const expiresAt = objData.getNumber('expires_at');
if (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {
AkReq.setToken(accessToken, refreshTokenNew, expiresAt);
}
}
}
const result = AkReq.createResponse<any>(
res.statusCode,
data ?? {},
res.header as UTSJSONObject
);
resolve(result);
},
fail: (err) => {
const result = AkReq.createResponse<any>(
err.errCode,
err.data ?? {},
{} as UTSJSONObject,
new UniError('uni-request', err.errCode, err.errMsg ?? 'request fail')
);
resolve(result);
}
});
});
};
let attempt = 0;
let lastRes: AkReqResponse<any> | null = null;
while (attempt <= maxRetry) {
const res = await doOnce();
lastRes = res;
// 仅网络失败/超时errCode 非 0 且 status 非 2xx/3xx时重试
const status = res.status ?? 0;
const isOk = status >= 200 && status < 400;
if (isOk) return res;
if (attempt === maxRetry) break;
// 简单退避
const delay = baseDelay * Math.pow(2, attempt);
await new Promise<void>((r) => { setTimeout(() => { r(); }, delay); });
attempt++;
}
return lastRes!!;
}
// 新增 upload 方法,支持 uni.uploadFile自动带 token/apikey
static async upload(options : AkReqUploadOptions) : Promise<AkReqResponse<any>> {
// 上传前尝试刷新 token若即将过期。优先从 options.headers 或 apikey 字段获取 apikey
let apikey: string | null = null;
const hdr = options.headers;
if (hdr != null && typeof hdr.getString === 'function') {
apikey = hdr.getString('apikey');
}
if (apikey == null && options.apikey != null) apikey = options.apikey;
await this.refreshTokenIfNeeded(apikey != null ? apikey : null);
let headers = options.headers ?? ({} as UTSJSONObject);
const token = this.getToken();
if (token != null && token !== "") {
headers = Object.assign({}, headers, { Authorization: `Bearer ${token}` }) as UTSJSONObject;
}
if (apikey != null && apikey !== "") {
headers = Object.assign({}, headers, { apikey: apikey }) as UTSJSONObject;
}
// 默认 Accept
headers = Object.assign({ Accept: 'application/json' } as UTSJSONObject, headers) as UTSJSONObject;
const timeout = options.timeout ?? 10000;
const maxRetry = Math.max(0, options.retryCount ?? 0);
const baseDelay = Math.max(0, options.retryDelayMs ?? 300);
const doOnce = (): Promise<AkReqResponse<any>> => {
return new Promise<AkReqResponse<any>>((resolve) => {
const task = uni.uploadFile({
url: options.url,
filePath: options.filePath,
name: options.name,
formData: options.formData ?? {},
header: headers,
timeout: timeout,
success: (res : UploadFileSuccess) => {
let parsed: UTSJSONObject | null = null;
try {
parsed = JSON.parse(res.data) as UTSJSONObject;
} catch (e) {
parsed = null;
}
if (parsed != null) {
const accessToken = parsed.getString('access_token');
const refreshTokenNew = parsed.getString('refresh_token');
const expiresAt = parsed.getNumber('expires_at');
if (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {
AkReq.setToken(accessToken, refreshTokenNew, expiresAt);
}
}
const result = AkReq.createResponse<any>(
res.statusCode,
parsed ?? {},
headers
);
resolve(result);
},
fail: (err) => {
const result = AkReq.createResponse<any>(
err.errCode,
err.data ?? {},
{} as UTSJSONObject,
new UniError('uni-upload', err.errCode, err.errMsg ?? 'upload fail')
);
resolve(result);
}
});
if (options.onProgress != null && task != null) {
const progressCallback = (res: OnProgressUpdateResult) => {
const percent = res.progress as number; // 0-100
const sent = res.totalBytesSent as number | null;
const expected = res.totalBytesExpectedToSend as number | null;
if (options.onProgress != null) {
options.onProgress(percent, sent, expected);
}
};
task.onProgressUpdate(progressCallback);
}
});
};
let attempt = 0;
let lastRes: AkReqResponse<any> | null = null;
while (attempt <= maxRetry) {
const res = await doOnce();
lastRes = res;
const status = res.status ?? 0;
const isOk = status >= 200 && status < 400;
if (isOk) return res;
if (attempt === maxRetry) break;
const delay = baseDelay * Math.pow(2, attempt);
await new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, delay);
});
attempt++;
}
return lastRes!!;
}
// 辅助方法:创建 AkReqResponse 对象,避免类型推断问题
static createResponse<T>(
status: number,
data: T | Array<T> ,
headers: UTSJSONObject,
error: UniError | null = null,
total: number | null = null,
page: number | null = null,
limit: number | null = null,
hasmore: boolean | null = null,
origin: any | null = null
): AkReqResponse<T> {
return {
status,
data,
headers,
error,
total,
page,
limit,
hasmore,
origin
};
}
// 新增:支持类型转换的请求方法
static async requestAs<T = any>(options : AkReqOptions, skipRefresh ?: boolean) : Promise<AkReqResponse<T|Array<T>>> {
const response = await this.request(options, skipRefresh);
// 如果原始 data 是 null直接返回 null
// if (response.data == null) {
// return {
// status: response.status,
// data: null,
// headers: response.headers,
// error: response.error,
// total: response.total,
// page: response.page,
// limit: response.limit,
// hasmore: response.hasmore,
// origin: response.origin
// } as AkReqResponse<T|Array<T>>;
// }
// 尝试类型转换
let convertedData: T | null = null;
try {
// #ifdef APP-ANDROID
if (response.data instanceof UTSJSONObject) {
convertedData = response.data.parse<T>();
} else if (Array.isArray(response.data)) {
const convertedArray: Array<any> = [];
const dataArray = response.data;
for (let i = 0; i < dataArray.length; i++) {
const item = dataArray[i];
if (item instanceof UTSJSONObject) {
const parsed = item.parse<T>();
if (parsed != null) {
convertedArray.push(parsed);
}
} else {
convertedArray.push(item);
}
}
convertedData = convertedArray as T;
}
// #endif
// #ifndef APP-ANDROID
convertedData = response.data as T;
// #endif
} catch (e) {
console.warn('类型转换失败,使用原始 UTSJSONObject:', e);
// 转换失败时,返回原始 UTSJSONObject
convertedData = response.data as T;
}
const aaa = {
status: response.status,
data: convertedData!!,
headers: response.headers,
error: response.error,
total: response.total,
page: response.page,
limit: response.limit,
hasmore: response.hasmore,
origin: response.origin
} ;
return aaa
}
}
export default AkReq;

View File

@@ -0,0 +1,2 @@
export * from './interface.uts';
export * from './ak-req.uts';

View File

@@ -0,0 +1,48 @@
// ak-req 类型定义
export type AkReqOptions = {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' |'HEAD';
data?: UTSJSONObject | Array<UTSJSONObject>;
headers?: UTSJSONObject;
timeout?: number;
contentType?: string; // 新增,支持顶级 contentType
// 可选:重试设置(仅网络错误/超时触发)。默认重试 0 次
retryCount?: number; // 最大重试次数,默认 0
retryDelayMs?: number; // 首次重试延迟,默认 300ms指数退避
};
// 上传参数类型定义
export type AkReqUploadOptions = {
url: string,
filePath: string,
name: string,
formData?: UTSJSONObject,
headers?: UTSJSONObject,
apikey?: string,
timeout?: number,
// 进度回调0-100注意H5/APP 平台支持不同)
onProgress?: (progress: number, transferredBytes?: number, totalBytes?: number) => void,
// 可选:重试设置(仅网络错误/超时触发)。默认 0
retryCount?: number,
retryDelayMs?: number
};
export type AkReqResponse<T = any> = {
status: number;
data: T | Array<T> | null; // 支持 null
headers: UTSJSONObject;
error: UniError | null;
total:number |null;
page: number |null;
limit: number |null;
hasmore:boolean |null;
origin: any | null;
};
export class AkReqError extends Error {
code: number;
constructor(message: string, code: number = 0) {
super(message);
this.code = code;
this.name = 'AkReqError';
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "ak-req",
"version": "0.0.1",
"main": "ak-req.uts",
"types": "interface.uts",
"uni_modules": {
"uni_modules": true
}
}

View File

@@ -0,0 +1,6 @@
## 0.0.32024-05-29
- feat: `SetClipboardDataOption``showToast`为默认弹出
## 0.0.22024-05-29
- feat: `SetClipboardDataOption`增加`showToast`对齐web
## 0.0.12024-04-12
- init

View File

@@ -0,0 +1,32 @@
<template>
<view>
<button @click="setClipboard">设置</button>
<button @click="getClipboard">获取</button>
</view>
</template>
<script setup>
import {setClipboardData, getClipboardData, SetClipboardDataOption, GetClipboardDataOption, GetClipboardDataSuccessCallbackOption} from '@/uni_modules/lime-clipboard'
const setClipboard = ()=>{
setClipboardData({
data: '這里是內容',
showToast: true,
success(res){
console.log('res', res.errMsg)
}
} as SetClipboardDataOption)
}
const getClipboard = () =>{
getClipboardData({
success(res: GetClipboardDataSuccessCallbackOption){
console.log('res', res)
}
} as GetClipboardDataOption)
}
</script>
<style>
</style>

View File

@@ -0,0 +1,86 @@
{
"id": "lime-clipboard",
"displayName": "lime-clipboard 剪贴板",
"version": "0.0.3",
"description": "lime-clipboard 系参考小程序setClipboardData和getClipboardData实现的UTS API支持uniappX(web,ios,安卓)",
"keywords": [
"lime-clipboard",
"setClipboardData",
"getClipboardData",
"clipboard",
"剪贴板"
],
"repository": "",
"engines": {
"HBuilderX": "^4.11"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "y"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "y"
},
"App": {
"app-android": "y",
"app-ios": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
# lime-clipboard
- 参考小程序`setClipboardData``getClipboardData`实现的UTS API支持uniappX(web,ios,安卓)
## 安装
插件市场导入即可
## 使用
使用方法跟小程序的一样
```ts
import {setClipboardData, getClipboardData, SetClipboardDataOption, GetClipboardDataOption, GetClipboardDataSuccessCallbackOption} from '@/uni_modules/lime-clipboard'
setClipboardData({
data: '这里是內容',
success(res){
console.log('res', res.errMsg)
}
} as SetClipboardDataOption)
getClipboardData({
success(res: GetClipboardDataSuccessCallbackOption){
console.log('res', res)
}
} as GetClipboardDataOption)
```
## API
因为直接参照小程序`setClipboardData``getClipboardData`API所以可以直接按[clipboard](https://uniapp.dcloud.net.cn/api/system/clipboard.html)文档来

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.limeui.clipboard">
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
<uses-permission android:name="android.permission.WRITE_CLIPBOARD_IN_BACKGROUND" />
</manifest>

View File

@@ -0,0 +1,3 @@
{
"minSdkVersion": "21"
}

View File

@@ -0,0 +1,80 @@
import ClipData from "android.content.ClipData";
import ClipboardManager from "android.content.ClipboardManager";
import Context from "android.content.Context";
import { UTSAndroid } from "io.dcloud.uts";
import { SetClipboardDataOption, GetClipboardDataOption, GetClipboardDataSuccessCallbackOption } from '../interface.uts';
import { GeneralCallbackResultImpl } from '../unierror.uts';
export function setClipboardData(options : SetClipboardDataOption) {
const handleClipboardOperationFailure = () => {
const res = new GeneralCallbackResultImpl(9010002)
options.fail?.(res)
options.complete?.(res)
}
try {
const context = UTSAndroid.getAppContext();
if (context != null) {
const clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager;
const clip = ClipData.newPlainText('label', options.data);
clipboard.setPrimaryClip(clip);
const res = new GeneralCallbackResultImpl(9010001)
if(options.showToast != false){
uni.showToast({
icon: 'success',
title: '内容已复制'
})
}
options.success?.(res)
options.complete?.(res)
} else {
handleClipboardOperationFailure()
}
} catch (e) {
handleClipboardOperationFailure()
}
}
export function getClipboardData(options : GetClipboardDataOption) {
const handleClipboardOperationFailure = () => {
const res = new GeneralCallbackResultImpl(9010002, 'get')
options.fail?.(res)
options.complete?.(res)
}
try {
const context = UTSAndroid.getAppContext();
if (context != null) {
const clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager;
const clip = clipboard.getPrimaryClip();
if (clip != null && clip.getItemCount() > 0) {
const text = clip.getItemAt(0).getText();
if (text != null) {
options.success?.({
data: text.toString(),
errMsg: '成功'
} as GetClipboardDataSuccessCallbackOption)
} else {
// 如果剪贴板没有文本数据,调用失败的处理函数
handleClipboardOperationFailure();
}
} else {
// 如果剪贴板没有内容,调用失败的处理函数
handleClipboardOperationFailure();
}
} else {
// 如果无法获取应用上下文,调用失败的处理函数
handleClipboardOperationFailure();
}
} catch (e) {
handleClipboardOperationFailure()
}
}

View File

@@ -0,0 +1,3 @@
{
"deploymentTarget": "9"
}

View File

@@ -0,0 +1,33 @@
import { UIPasteboard } from "UIKit"
import { SetClipboardDataOption, GetClipboardDataOption, GetClipboardDataSuccessCallbackOption } from '../interface.uts';
import { GeneralCallbackResultImpl } from '../unierror.uts';
export function setClipboardData(options : SetClipboardDataOption){
let pasteboard = UIPasteboard.general
pasteboard.string = options.data
const res = new GeneralCallbackResultImpl(9010001)
if(options.showToast != false){
uni.showToast({
icon: 'success',
title: '内容已复制'
})
}
options.success?.(res)
options.complete?.(res)
}
export function getClipboardData(options : GetClipboardDataOption){
let pasteboard = UIPasteboard.general;
const res = new GeneralCallbackResultImpl(9010002, 'get')
if(pasteboard.string == null){
options.fail?.(res)
options.complete?.(res)
} else {
options.success?.({
errMsg: 'getClipboardData:ok',
data: `${pasteboard.string!}`
} as GetClipboardDataSuccessCallbackOption)
options.complete?.(res)
}
}

View File

@@ -0,0 +1,19 @@
export * from './interface'
import {SetClipboardDataOption, GetClipboardDataOption} from './interface'
/**
* 设置系统剪贴板的内容
*
* 文档: [http://uniapp.dcloud.io/api/system/clipboard?id=setclipboarddata](http://uniapp.dcloud.io/api/system/clipboard?id=setclipboarddata)
*/
export function setClipboardData(options : SetClipboardDataOption) {
uni.setClipboardData(options as UniNamespace.SetClipboardDataOptions)
}
/**
* 获得系统剪贴板的内容
*
* 文档: [http://uniapp.dcloud.io/api/system/clipboard?id=getclipboarddata](http://uniapp.dcloud.io/api/system/clipboard?id=getclipboarddata)
*/
export function getClipboardData(options : GetClipboardDataOption) {
uni.getClipboardData(options as UniNamespace.GetClipboardDataOptions)
}

View File

@@ -0,0 +1,66 @@
/**
* 错误码
* 根据uni错误码规范要求建议错误码以90开头以下是错误码示例
* - 9010001 错误信息1
* - 9010002 错误信息2
*/
export type LimeClipboardErrorCode = 9010001 | 9010002;
/**
* myApi 的错误回调参数
*/
export interface GeneralCallbackResult extends IUniError {
errCode : LimeClipboardErrorCode
};
// export interface GeneralCallbackResult {
// /** 错误信息 */
// errMsg : string
// }
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
export type SetClipboardDataCompleteCallback = (res : UniError) => void
/** 接口调用失败的回调函数 */
export type SetClipboardDataFailCallback = (res : UniError) => void
/** 接口调用成功的回调函数 */
export type SetClipboardDataSuccessCallback = (res : UniError) => void
export type SetClipboardDataOption = {
showToast?: boolean
/** 剪贴板的内容 */
data : string
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
complete ?: SetClipboardDataCompleteCallback
/** 接口调用失败的回调函数 */
fail ?: SetClipboardDataFailCallback
/** 接口调用成功的回调函数 */
success ?: SetClipboardDataSuccessCallback
}
export type GetClipboardDataSuccessCallbackOption = {
/** 剪贴板的内容 */
data : string
errMsg : string
}
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
export type GetClipboardDataCompleteCallback = (res : UniError) => void
/** 接口调用失败的回调函数 */
export type GetClipboardDataFailCallback = (res : UniError) => void
/** 接口调用成功的回调函数 */
export type GetClipboardDataSuccessCallback = (
option : GetClipboardDataSuccessCallbackOption
) => void
export type GetClipboardDataOption = {
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
complete ?: GetClipboardDataCompleteCallback
/** 接口调用失败的回调函数 */
fail ?: GetClipboardDataFailCallback
/** 接口调用成功的回调函数 */
success ?: GetClipboardDataSuccessCallback
}

View File

@@ -0,0 +1,39 @@
/* 此规范为 uni 规范,可以按照自己的需要选择是否实现 */
import { LimeClipboardErrorCode, GeneralCallbackResult } from "./interface.uts"
/**
* 错误主题
* 注意:错误主题一般为插件名称,每个组件不同,需要使用时请更改。
* [可选实现]
*/
export const UniErrorSubject = 'ClipboardData';
/**
* 错误信息
* @UniError
* [可选实现]
*/
export const UniErrors : Map<LimeClipboardErrorCode, string> = new Map([
/**
* 错误码及对应的错误信息
*/
[9010001, 'ClipboardData:ok'],
[9010002, 'ClipboardData:failed'],
]);
/**
* 错误对象实现
*/
export class GeneralCallbackResultImpl extends UniError implements GeneralCallbackResult {
/**
* 错误对象构造函数
*/
constructor(errCode : LimeClipboardErrorCode, type: string = 'set') {
super();
this.errSubject = type + UniErrorSubject;
this.errCode = errCode;
this.errMsg = type + (UniErrors[errCode] ?? "");
}
}

View File

@@ -0,0 +1,55 @@
// agent session 列表参数类型
export type AgentSessionListOptions = {
page?: number,
page_size?: number,
orderby?: string,
desc?: boolean,
id?: string,
user_id?: string,
dsl?: string
}
// rag-req 类型声明
export type RagReqOptions = {
url: string;
method?: string;
headers?: UTSJSONObject;
data?: any;
timeout?: number;
};
export type RagReqResponse<T = any> = {
status: number;
data: T;
headers: UTSJSONObject;
error?: string | null;
total?: number | null;
page?: number | null;
limit?: number | null;
hasmore?: boolean | null;
origin?: any | null;
};
export interface RagReqError {
code: number;
message: string;
}
export interface RagSessionData {
id: string;
session_name?: string;
total_messages?: number;
last_message_at?: string;
is_active?: boolean;
// 索引签名已移除,兼容 UTS
}
export interface RagMessageData {
id?: string;
role?: string;
content?: string;
created_at?: string;
answer?: string;
message?: string;
// 索引签名已移除,兼容 UTS
}

View File

@@ -0,0 +1,218 @@
import { RagReqOptions, RagReqResponse, RagReqError, RagSessionData, RagMessageData, AgentSessionListOptions } from './interface.uts';
// token/session 持久化 key
const RAG_ACCESS_TOKEN_KEY = 'ragreq_access_token';
const RAG_SESSION_ID_KEY = 'ragreq_session_id';
let _accessToken : string | null = null;
let _sessionId : string | null = null;
export type RagReqConfig {
baseUrl : string;
apiKey ?: string;
}
export class RagReq {
private baseUrl : string;
constructor(config : RagReqConfig) {
this.baseUrl = config.baseUrl.replace(/\/$/, '');
if ((config.apiKey ?? '') !== '') {
RagReq.setToken(config.apiKey!);
}
}
// 设置 token
static setToken(token : string) {
_accessToken = token;
uni.setStorageSync(RAG_ACCESS_TOKEN_KEY, token);
}
static getToken() : string | null {
if (_accessToken != null) return _accessToken;
const t = uni.getStorageSync(RAG_ACCESS_TOKEN_KEY) as string | null;
_accessToken = t;
return t;
}
static clearToken() {
_accessToken = null;
uni.removeStorageSync(RAG_ACCESS_TOKEN_KEY);
}
// sessionId 管理
static setSessionId(sessionId : string) {
_sessionId = sessionId;
uni.setStorageSync(RAG_SESSION_ID_KEY, sessionId);
}
static getSessionId() : string | null {
if (_sessionId != null) return _sessionId;
const t = uni.getStorageSync(RAG_SESSION_ID_KEY) as string | null;
_sessionId = t;
return t;
}
static clearSessionId() {
_sessionId = null;
uni.removeStorageSync(RAG_SESSION_ID_KEY);
}
// 通用 request
async request(options : RagReqOptions) : Promise<RagReqResponse<any>> {
let headers = options.headers ?? ({} as UTSJSONObject);
const token = RagReq.getToken();
if ((token ?? '') !== '') {
headers = Object.assign({}, headers, { Authorization: `Bearer ${token}` }) as UTSJSONObject;
}
const url = options.url.startsWith('http') ? options.url : this.baseUrl + options.url;
return new Promise<RagReqResponse<any>>((resolve, reject) => {
uni.request({
url,
method: options.method ?? 'POST',
data: options.data,
header: headers,
timeout: options.timeout ?? 10000,
success: (res) => {
let data : UTSJSONObject | Array<UTSJSONObject> | null;
if (typeof res.data == 'string') {
try { data = JSON.parse(res.data as string) as UTSJSONObject; } catch { data = null; }
} else if (Array.isArray(res.data)) {
data = res.data as UTSJSONObject[];
} else {
data = res.data as UTSJSONObject | null;
}
resolve({
status: res.statusCode,
data: data ?? {},
headers: res.header as UTSJSONObject,
error: null
} as RagReqResponse<any>);
},
fail: (err) => {
resolve({
status: err.errCode,
data: null as any,
headers: {} as UTSJSONObject,
error: err.errMsg ?? 'request fail'
} as RagReqResponse<any>);
}
});
});
}
// 发送消息到 RAG
async sendMessage(message : string, sessionId ?: string) : Promise<RagReqResponse<RagMessageData>> {
const sid = sessionId ?? RagReq.getSessionId();
const reqOpt : RagReqOptions = {
url: '/api/session/chat',
method: 'POST',
data: { message, session_id: sid } as UTSJSONObject
};
const res = await this.request(reqOpt);
return res;
}
// 获取指定 agent 的会话列表(新接口)
async getAgentSessionList(
agentId: string,
options?: AgentSessionListOptions
): Promise<RagReqResponse<any>> {
let url = `/api/v1/agents/${agentId}/sessions`;
const params: string[] = [];
if (options!=null) {
if (options.page !== null) params.push(`page=${options.page}`);
if (options?.page_size !== null) params.push(`page_size=${options?.page_size}`);
if (options?.orderby!=null) params.push(`orderby=${encodeURIComponent(options?.orderby??'')}`);
if (options.desc !== null) params.push(`desc=${options.desc!=null ? 'true' : 'false'}`);
if (options.id!='') params.push(`id=${encodeURIComponent(options?.id??'')}`);
if (options.user_id!='') params.push(`user_id=${encodeURIComponent(options?.user_id??'')}`);
if (options.dsl!=null) params.push(`dsl=${encodeURIComponent(options?.dsl??'')}`);
}
if (params.length > 0) {
url += '?' + params.join('&');
}
const headers = {} as UTSJSONObject;
// Authorization header will be auto-added in request()
return await this.request({
url,
method: 'GET',
headers
});
}
// 获取历史消息
async getHistory(sessionId ?: string) : Promise<RagReqResponse<Array<RagMessageData>>> {
const sid = sessionId ?? RagReq.getSessionId();
const reqOpt : RagReqOptions = {
url: `/api/session/history?session_id=${sid}`,
method: 'GET'
};
const res = await this.request(reqOpt);
return res;
}
// 新建会话
async createSession() : Promise<RagReqResponse<RagSessionData>> {
const reqOpt : RagReqOptions = {
url: '/api/session/create',
method: 'POST',
data: {} as UTSJSONObject
};
const res = await this.request(reqOpt);
return res;
}
// 创建会话(支持 json 或 form-data
async createAgentSession(
agentId : string,
params ?: UTSJSONObject,
userId ?: string,
isFormData : boolean = false
) : Promise<RagReqResponse<any>> {
let url = `/api/v1/agents/${agentId}/sessions`;
if ((userId ?? '') !== '') url += `?user_id=${encodeURIComponent(userId!)}`;
let headers = {} as UTSJSONObject;
let data : any = params ?? {};
if (isFormData) {
headers['Content-Type'] = 'multipart/form-data';
} else {
headers['Content-Type'] = 'application/json';
}
return await this.request({
url,
method: 'POST',
headers,
data
});
}
// 与 agent 对话
async converseWithAgent(
agentId : string,
body : UTSJSONObject
) : Promise<RagReqResponse<UTSJSONObject>> {
const url = `/api/v1/agents/${agentId}/completions`;
const headers = { 'Content-Type': 'application/json' } as UTSJSONObject;
return await this.request({
url,
method: 'POST',
headers,
data: body
});
}
// 删除 agent 的会话(批量)
async deleteAgentSessions(
agentId: string,
ids: string[]
): Promise<RagReqResponse<any>> {
const url = `/api/v1/agents/${agentId}/sessions`;
const headers = { 'Content-Type': 'application/json' } as UTSJSONObject;
const data = { ids } as UTSJSONObject;
return await this.request({
url,
method: 'DELETE',
headers,
data
});
}
}
export default RagReq;