添加聊天

This commit is contained in:
bobobobo
2025-12-30 23:28:59 +08:00
parent d0cf491201
commit 2294b3b76e
450 changed files with 37066 additions and 96 deletions

View File

@@ -0,0 +1,23 @@
const Link = {
product: {
label: '产品文档',
url: 'https://cloud.tencent.com/document/product/269/1499#.E7.BE.A4.E7.BB.84.E5.8A.9F.E8.83.BD',
},
customMessage: {
label: '自定义消息',
url: 'https://web.sdk.qcloud.com/im/doc/v3/zh-cn/SDK.html#createCustomMessage',
},
complaint: {
label: '点此投诉',
url: 'https://cloud.tencent.com/apply/p/xc3oaubi98g',
},
implement: {
label: '集成TUICallKit',
url: 'https://cloud.tencent.com/document/product/269/79861',
},
purchase: {
label: '开通腾讯实时音视频服务',
url: 'https://cloud.tencent.com/document/product/1640/79968',
},
};
export default Link;

View File

@@ -0,0 +1,8 @@
import { TUIStore, StoreName } from "@tencentcloud/chat-uikit-engine-lite";
export function enableSampleTaskStatus(taskKey: string) {
const tasks = TUIStore.getData(StoreName.APP, "tasks");
if (taskKey in tasks && !tasks[taskKey]) {
tasks[taskKey] = true;
TUIStore.update(StoreName.APP, "tasks", tasks);
}
}

17
TUIKit/utils/env.ts Normal file
View File

@@ -0,0 +1,17 @@
import { getPlatform } from '@tencentcloud/universal-api';
declare const uni: any;
export const isPC = getPlatform() === 'pc';
export const isH5 = getPlatform() === 'h5';
export const isWeChat = getPlatform() === 'wechat';
export const isApp = getPlatform() === 'app';
export const isUniFrameWork = typeof uni !== 'undefined';
// H5, mini programs, and apps are all considered mobile.
// If you need to unify the mobile UI style, you can directly use isMobile to control
export const isMobile = isH5 || isWeChat || isApp;

1
TUIKit/utils/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from "./type-check";

169
TUIKit/utils/lodash.ts Normal file
View File

@@ -0,0 +1,169 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
/** Error message constants. */
const FUNC_ERROR_TEXT = 'Expected a function';
interface IDebounceOptions {
leading?: boolean;
maxWait?: number;
trailing?: boolean;
}
type IThrottleOptions = IDebounceOptions;
function throttle(func, wait: number, options?: IThrottleOptions) {
let leading = true,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
if (options && isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
return debounce(func, wait, {
leading: leading,
maxWait: wait,
trailing: trailing,
});
}
function debounce(func, wait: number, options?: IDebounceOptions) {
let lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = wait || 0;
if (options && isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? Math.max(options.maxWait || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
const args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait);
// Invoke the leading edge.
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
timeWaiting = wait - timeSinceLastCall;
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait)
|| (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
const time = Date.now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now());
}
function debounced() {
const time = Date.now(),
isInvoking = shouldInvoke(time);
// eslint-disable-next-line prefer-rest-params
lastArgs = arguments;
// @ts-expect-error ignore this type
// eslint-disable-next-line @typescript-eslint/no-this-alias
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
clearTimeout(timerId);
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}
function isObject(value) {
const type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
export {
debounce,
throttle,
};

101
TUIKit/utils/permission.js Normal file
View File

@@ -0,0 +1,101 @@
let isIos;
// #ifdef APP-PLUS
isIos = (plus.os.name == 'iOS');
// #endif
// 判断麦克风权限是否开启iOS
function judgeIosPermissionRecord() {
let result = false;
const avaudiosession = plus.ios.import('AVAudioSession');
const avaudio = avaudiosession.sharedInstance();
const permissionStatus = avaudio.recordPermission();
console.log('[Permission] iOS permissionStatus:', permissionStatus);
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
console.log('[Permission] iOS 麦克风权限没有开启');
} else {
result = true;
console.log('[Permission] iOS 麦克风权限已经开启');
}
plus.ios.deleteObject(avaudiosession);
return result;
}
// Android 权限查询
function requestAndroidPermission(permissionID) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
(resultObj) => {
let result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
const grantedPermission = resultObj.granted[i];
console.log(`[Permission] Android 已获取的权限:${grantedPermission}`);
result = 1;
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
const deniedPresentPermission = resultObj.deniedPresent[i];
console.log(`[Permission] Android 拒绝本次申请的权限:${deniedPresentPermission}`);
result = 0;
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
const deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log(`[Permission] Android 永久拒绝申请的权限:${deniedAlwaysPermission}`);
result = -1;
}
resolve(result);
},
(error) => {
console.log(`[Permission] Android 申请权限错误:${error.code} = ${error.message}`);
resolve({
code: error.code,
message: error.message,
});
},
);
});
}
// 跳转到**应用**的权限页面
function gotoAppPermissionSetting() {
if (isIos) {
const UIApplication = plus.ios.import('UIApplication');
const application2 = UIApplication.sharedApplication();
const NSURL2 = plus.ios.import('NSURL');
const setting2 = NSURL2.URLWithString('app-settings:');
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
const Intent = plus.android.importClass('android.content.Intent');
const Settings = plus.android.importClass('android.provider.Settings');
const Uri = plus.android.importClass('android.net.Uri');
const mainActivity = plus.android.runtimeMainActivity();
const intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
const uri = Uri.fromParts('package', mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
}
function checkPermissionStatusInApp(permission) {
const status = plus.navigator.checkPermission(permission);
switch (status) {
case 'authorized':
return 1;
case 'denied':
return -1;
default:
return 0; // 未授权或状态未知
// https://www.html5plus.org/doc/zh_cn/navigator.html#plus.navigator.checkPermission
}
}
export {
judgeIosPermissionRecord,
requestAndroidPermission,
gotoAppPermissionSetting,
checkPermissionStatusInApp,
};

156
TUIKit/utils/riseInput.ts Normal file
View File

@@ -0,0 +1,156 @@
import { isIOS } from '@tencentcloud/universal-api';
const ua = navigator.userAgent;
function getScrollTypeByPlatform() {
if (isIOS) {
if (/Safari\//.test(ua) || /IOS 11_[0-3]\D/.test(ua)) {
// Safari IOS 11.0-11.3 exclude`scrollTop`/`scrolIntoView` not working
return 0;
}
// IOS: use `scrollTop`
return 1;
}
// Android: use `scrollIntoView`
return 2;
}
function createKeyboardHeightObserver(callback: (height: number) => void) {
const initialViewportHeight = window.innerHeight;
let lastKeyboardHeight = 0;
if (window.visualViewport) {
const handleViewportChange = () => {
const currentViewportHeight = window.visualViewport!.height;
const keyboardHeight = initialViewportHeight - currentViewportHeight;
if (Math.abs(keyboardHeight - lastKeyboardHeight) > 10) {
lastKeyboardHeight = keyboardHeight;
callback(keyboardHeight > 0 ? keyboardHeight : 0);
}
};
window.visualViewport.addEventListener('resize', handleViewportChange);
return () => {
window.visualViewport?.removeEventListener('resize', handleViewportChange);
};
}
const handleWindowResize = () => {
const currentHeight = window.innerHeight;
const keyboardHeight = initialViewportHeight - currentHeight;
if (Math.abs(keyboardHeight - lastKeyboardHeight) > 10) {
lastKeyboardHeight = keyboardHeight;
callback(keyboardHeight > 0 ? keyboardHeight : 0);
}
};
window.addEventListener('resize', handleWindowResize);
return () => {
window.removeEventListener('resize', handleWindowResize);
};
}
function adjustPageHeight(keyboardHeight: number) {
const possibleContainers = [
document.getElementById('app'),
document.querySelector('.app'),
document.querySelector('[data-app]'),
document.body,
];
const container = possibleContainers.find(el => el !== null) as HTMLElement;
if (!container) return;
if (keyboardHeight > 0) {
container.style.paddingBottom = '';
container.style.transition = '';
const timer = setTimeout(() => {
const activeElement = document.activeElement as HTMLElement;
if (activeElement && activeElement.scrollIntoView) {
const elementRect = activeElement.getBoundingClientRect();
const currentViewportHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight;
if (elementRect.bottom > currentViewportHeight - 50) {
activeElement.scrollIntoView({
behavior: 'smooth',
block: 'end',
inline: 'nearest',
});
}
}
clearTimeout(timer);
}, 100);
} else {
container.style.paddingBottom = '';
const timer = setTimeout(() => {
container.style.transition = '';
clearTimeout(timer);
}, 250);
}
}
export default function riseInput(input: HTMLElement, target?: HTMLElement) {
const scrollType = getScrollTypeByPlatform();
let scrollTimer: ReturnType<typeof setTimeout>;
let keyboardObserver: (() => void) | null = null;
if (!target) {
// eslint-disable-next-line no-param-reassign
target = input;
}
const scrollIntoView = () => {
if (scrollType === 0) return;
if (scrollType === 1) {
document.body.scrollTop = document.body.scrollHeight;
} else {
target?.scrollIntoView(false);
}
};
if (isIOS) {
keyboardObserver = createKeyboardHeightObserver((keyboardHeight) => {
adjustPageHeight(keyboardHeight);
if (keyboardHeight > 0 && document.activeElement === input) {
const timer = setTimeout(() => {
input.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest',
});
clearTimeout(timer);
}, 200);
}
});
}
input.addEventListener('focus', () => {
const timer = setTimeout(() => {
scrollIntoView();
clearTimeout(timer);
}, 300);
scrollTimer = setTimeout(scrollIntoView, 1000);
});
input.addEventListener('blur', () => {
clearTimeout(scrollTimer);
// Handle IOS issues about keyboard is hidden but page not refreshed
if (scrollType && isIOS) {
const timer = setTimeout(() => {
if (!keyboardObserver) {
document.body.scrollIntoView();
adjustPageHeight(0);
}
clearTimeout(timer);
}, 300);
}
});
return () => {
clearTimeout(scrollTimer);
keyboardObserver?.();
};
}

View File

@@ -0,0 +1,44 @@
const objectToString = Object.prototype.toString;
const toTypeString = (value: any) => objectToString.call(value);
export const { isArray } = Array;
export const isMap = (val: any) => toTypeString(val) === '[object Map]';
export const isSet = (val: any) => toTypeString(val) === '[object Set]';
export const isDate = (val: any) => val instanceof Date;
export const isFunction = (val: any) => typeof val === 'function';
export const isString = (val: any) => typeof val === 'string';
export const isSymbol = (val: any) => typeof val === 'symbol';
export const isObject = (val: any) => val !== null && typeof val === 'object';
export const isPromise = (val: any) =>
isObject(val) && isFunction(val.then) && isFunction(val.catch);
// Determine whether it is url
export const isUrl = (url: string) => {
return /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(
url,
);
};
// Determine if it is a JSON string
export const isJSON = (str: string) => {
// eslint-disable-next-line no-useless-escape
if (typeof str === 'string') {
try {
const data = JSON.parse(str);
if (data) {
return true;
}
return false;
} catch (error: any) {
return false;
}
}
return false;
};
// Determine if it is a JSON string
export const JSONToObject = (str: string) => {
if (!str || !isJSON(str)) {
return str;
}
return JSON.parse(str);
};

View File

@@ -0,0 +1,27 @@
export default function unifyPromiseVue2() {
try {
// eslint-disable-next-line no-inner-declarations
function isPromise(obj) {
return Boolean(obj) && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
// Unified vue2 API Promise return format to be consistent with vue3
// eslint-disable-next-line no-undef
(uni as any).addInterceptor({
returnValue(res) {
if (!isPromise(res)) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => {
if (res[0]) {
reject(res[0]);
} else {
resolve(res[1]);
}
});
});
},
});
// eslint-disable-next-line no-empty
} catch (error) { }
}