添加聊天
This commit is contained in:
23
TUIKit/utils/documentLink.ts
Normal file
23
TUIKit/utils/documentLink.ts
Normal 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;
|
||||
8
TUIKit/utils/enableSampleTaskStatus.ts
Normal file
8
TUIKit/utils/enableSampleTaskStatus.ts
Normal 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
17
TUIKit/utils/env.ts
Normal 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
1
TUIKit/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./type-check";
|
||||
169
TUIKit/utils/lodash.ts
Normal file
169
TUIKit/utils/lodash.ts
Normal 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
101
TUIKit/utils/permission.js
Normal 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
156
TUIKit/utils/riseInput.ts
Normal 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?.();
|
||||
};
|
||||
}
|
||||
44
TUIKit/utils/type-check.ts
Normal file
44
TUIKit/utils/type-check.ts
Normal 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);
|
||||
};
|
||||
27
TUIKit/utils/unifyPromiseVue2.ts
Normal file
27
TUIKit/utils/unifyPromiseVue2.ts
Normal 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) { }
|
||||
}
|
||||
Reference in New Issue
Block a user