chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -18,7 +18,7 @@ import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { passport } from '@coze-studio/api-schema';
|
||||
import { passportApi } from '../index';
|
||||
|
||||
// 模拟 passport API
|
||||
// Simulated passport API
|
||||
vi.mock('@coze-studio/api-schema/passport', () => ({}));
|
||||
vi.mock('@coze-studio/api-schema', () => ({
|
||||
passport: {
|
||||
|
||||
@@ -42,7 +42,7 @@ export const passportApi = {
|
||||
|
||||
updatePassword: async (params: { password: string; email: string }) => {
|
||||
await passport.PassportWebEmailPasswordResetGet({ ...params, code: '' });
|
||||
// 更新密码后,当前登录态失效,重置 store
|
||||
// After updating the password, the current login state is invalid, reset the store
|
||||
resetUserStore();
|
||||
},
|
||||
|
||||
|
||||
@@ -40,5 +40,5 @@ export const checkLoginImpl = async () => {
|
||||
|
||||
export const checkLogin = () => checkLoginBase(checkLoginImpl);
|
||||
|
||||
// 开源版本不支持渠道授权,暂无实现
|
||||
// The open-source version does not support channel authorization and has not been implemented yet.
|
||||
export const connector2Redirect: Connector2Redirect = () => undefined;
|
||||
|
||||
@@ -43,13 +43,13 @@ describe('useSyncLocalStorageUid', () => {
|
||||
initialProps: {},
|
||||
});
|
||||
|
||||
// 初始状态:未登录
|
||||
// Initial status: not logged in
|
||||
(useLoginStatus as Mock).mockReturnValue('not_login');
|
||||
(useUserInfo as Mock).mockReturnValue(null);
|
||||
rerender();
|
||||
expect(localStorageService.setUserId).toHaveBeenCalledWith();
|
||||
|
||||
// 切换到登录状态
|
||||
// Switch to login status
|
||||
(useLoginStatus as Mock).mockReturnValue('logined');
|
||||
(useUserInfo as Mock).mockReturnValue(mockUserInfo);
|
||||
rerender();
|
||||
|
||||
@@ -28,11 +28,11 @@ import { type UserInfo } from '../types';
|
||||
import { useUserStore } from '../store/user';
|
||||
|
||||
/**
|
||||
* 用于页面初始化时,检查登录状态,并监听登录态失效的接口报错
|
||||
* 在登录态失效时,会重定向到登录页
|
||||
* @param needLogin 是否需要登录
|
||||
* @param checkLogin 检查登录状态的具体实现
|
||||
* @param goLogin 重定向到登录页的具体实现
|
||||
* It is used to check the login status when the page is initialized, and listen for the interface error if the login status is invalid.
|
||||
* When the login status fails, it will be redirected to the login page
|
||||
* @param needLogin is required
|
||||
* @Param checkLogin Check the specific implementation of login status
|
||||
* @Param goLogin Redirect to login page concrete implementation
|
||||
*/
|
||||
export const useCheckLoginBase = (
|
||||
needLogin: boolean,
|
||||
@@ -54,7 +54,7 @@ export const useCheckLoginBase = (
|
||||
|
||||
useEffect(() => {
|
||||
const isLogined = !!useUserStore.getState().userInfo?.user_id_str;
|
||||
// 当前页面要求登录,登录检查结果为未登录时,重定向回登录页
|
||||
// The current page requires login. If the login check result is not logged in, redirect back to the login page.
|
||||
if (needLogin && isSettled && !isLogined) {
|
||||
memoizedGoLogin();
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export const useCheckLoginBase = (
|
||||
}
|
||||
}
|
||||
};
|
||||
// ajax 请求后端接口出现未 授权/登录 时,触发该函数
|
||||
// This function is triggered when the Ajax request backend interface appears not authorized/logged in
|
||||
handleAPIErrorEvent(APIErrorEvent.UNAUTHORIZED, handleUnauthorized);
|
||||
return () => {
|
||||
removeAPIErrorEvent(APIErrorEvent.UNAUTHORIZED, handleUnauthorized);
|
||||
|
||||
@@ -22,8 +22,8 @@ import { type LoginStatus } from '../types';
|
||||
import { useUserStore } from '../store/user';
|
||||
|
||||
/**
|
||||
* @description 用于获取用户登录状态
|
||||
* @returns 登录状态
|
||||
* @Description is used to obtain user login status
|
||||
* @returns login status
|
||||
*/
|
||||
export const useLoginStatus = (): LoginStatus =>
|
||||
useUserStore(state => {
|
||||
@@ -34,21 +34,21 @@ export const useLoginStatus = (): LoginStatus =>
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 用于获取用户信息
|
||||
* @returns 用户信息
|
||||
* @Description is used to obtain user information
|
||||
* @returns user information
|
||||
*/
|
||||
export const useUserInfo = () => useUserStore(state => state.userInfo);
|
||||
|
||||
/**
|
||||
* @description 当前是否为错误状态
|
||||
* @returns 是否为错误状态
|
||||
* @Description Whether it is currently in an error state
|
||||
* @Returns whether it is an error
|
||||
*/
|
||||
export const useHasError = () => useUserStore(state => state.hasError);
|
||||
|
||||
const currentUidLSKey = 'coze_current_uid';
|
||||
/**
|
||||
* 用于打开多页签情况下,探测其它页签下发生的登出事件并在当前触发提示
|
||||
* @param alert 触发提示的具体实现
|
||||
* It is used to detect logout events that occur under other tabs when multiple tabs are opened and trigger a prompt at the current time
|
||||
* @Param alert trigger prompt specific implementation
|
||||
*/
|
||||
export const useAlterOnLogout = (alert: () => void) => {
|
||||
const visibility = useDocumentVisibility();
|
||||
@@ -62,7 +62,7 @@ export const useAlterOnLogout = (alert: () => void) => {
|
||||
useEffect(() => {
|
||||
if (visibility === 'hidden' && isLogined) {
|
||||
const lastUserId = useUserStore.getState().userInfo?.user_id_str;
|
||||
// 登录态下,每次页面从后台回到前台,重新检查一次登录用户是否发生了变化
|
||||
// In the login state, each time the page returns to the foreground from the background, re-check whether the logged in user has changed.
|
||||
return () => {
|
||||
const latestUserId = localStorage.getItem(currentUidLSKey);
|
||||
if (lastUserId !== latestUserId) {
|
||||
@@ -72,7 +72,7 @@ export const useAlterOnLogout = (alert: () => void) => {
|
||||
}
|
||||
}, [visibility, isLogined]);
|
||||
|
||||
// 在登录态变化后,更新本地缓存状态
|
||||
// Update local cache status after login status changes
|
||||
useEffect(() => {
|
||||
if (loginStatus !== 'settling') {
|
||||
localStorage.setItem(
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 当前登录账号的用户信息
|
||||
* User information of the currently logged in account
|
||||
*/
|
||||
export interface UserInfo {
|
||||
app_id: number;
|
||||
/**
|
||||
* @deprecated 会因为溢出丢失精度,使用 user_id_str
|
||||
* @Deprecated will lose precision due to overflow, use user_id_str
|
||||
*/
|
||||
user_id: number;
|
||||
user_id_str: string;
|
||||
@@ -96,7 +96,7 @@ export interface UserInfo {
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}; // Record<string, unknown>;
|
||||
// int值。1审核中,2审核通过,3审核不通过
|
||||
// int value. 1 During the review, 2 passed the review, and 3 failed the review.
|
||||
audit_status: 1 | 2 | 3;
|
||||
details: Record<string, unknown>;
|
||||
is_auditing: boolean;
|
||||
@@ -106,9 +106,9 @@ export interface UserInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录状态
|
||||
* - settling: 登录状态检测中,一般用于首屏,会有一定的延迟
|
||||
* - not_login: 未登录
|
||||
* - logined: 已登录
|
||||
* login status
|
||||
* - settling: In the login status detection, it is generally used for the first screen, and there will be a certain delay.
|
||||
* - not_login: not logged in
|
||||
* - logined: logged in
|
||||
*/
|
||||
export type LoginStatus = 'settling' | 'not_login' | 'logined';
|
||||
|
||||
@@ -18,25 +18,25 @@ export type OAuth2StateType = 'login' | 'delete_account' | 'oauth';
|
||||
|
||||
export interface OAuth2RedirectConfig {
|
||||
/**
|
||||
* 最终的OAuth2鉴权信息将作为路由参数跳转,这个参数指定目标路由地址,注意在目标路由上使用
|
||||
* useAuthLoginDataRouteFromOAuth2来提取路由参数,并转换成用户中台三方登陆服务(authLogin)的参数;
|
||||
* 默认值为当前路径名称,即不传navigatePath参数时,当前路由一定要注册useAuthLoginDataRouteFromOAuth2才有效
|
||||
* The final OAuth2 authentication information will be redirected as a route parameter, which specifies the target route address. Be careful to use it on the target route
|
||||
* useAuthLoginDataRouteFromOAuth2 to extract the routing parameters and convert them into the parameters of the user's mid-platform three-party login service (authLogin);
|
||||
* The default value is the current path name, that is, when the navigatePath parameter is not passed, the current route must be registered useAuthLoginDataRouteFromOAuth2 to be valid
|
||||
*/
|
||||
navigatePath?: string;
|
||||
/**
|
||||
* OAuth2回调后拿到的鉴权信息的使用场景,用于在一些路由组件中区分,不符合对应场景的不能用于消费
|
||||
* The usage scenario of the authentication information obtained after the OAuth2 callback is used to distinguish among some routing components. Those that do not meet the corresponding scenario cannot be used for consumption
|
||||
*/
|
||||
type: OAuth2StateType;
|
||||
/**
|
||||
* 传递给OAuth2服务器的state字段,会在回调时传回,用于恢复网页状态
|
||||
* The state field passed to the OAuth2 server is returned during the callback to restore the state of the webpage
|
||||
*/
|
||||
|
||||
extra?: {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
origin?: string;
|
||||
[x: string]: string; // 用于安全监测
|
||||
[x: string]: string; // For safety monitoring
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
encrypt_state?: string; //加密state,bind_type 为 4时使用
|
||||
encrypt_state?: string; //Encrypted state, used when bind_type 4
|
||||
};
|
||||
scope?: string;
|
||||
optionalScope?: string;
|
||||
|
||||
@@ -20,8 +20,8 @@ import { type UserInfo } from '../types';
|
||||
import { useUserStore } from '../store/user';
|
||||
|
||||
/**
|
||||
* 主动触发刷新用户信息
|
||||
* @param checkLogin 登录检查函数
|
||||
* Actively trigger to refresh user information
|
||||
* @param checkLogin check function
|
||||
*/
|
||||
export const refreshUserInfoBase = async (
|
||||
checkLogin: () => Promise<UserInfo>,
|
||||
|
||||
@@ -21,14 +21,14 @@ import { type UserInfo, type LoginStatus } from '../types';
|
||||
import { useUserStore } from '../store/user';
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo 用户信息
|
||||
* Acquire user information
|
||||
* @returns UserInfo
|
||||
*/
|
||||
export const getUserInfo = () => useUserStore.getState().userInfo;
|
||||
|
||||
/**
|
||||
* 获取登录状态
|
||||
* @returns LoginStatus 登录状态
|
||||
* Get login status
|
||||
* @returns LoginStatus
|
||||
*/
|
||||
export const getLoginStatus = (): LoginStatus => {
|
||||
const state = useUserStore.getState();
|
||||
|
||||
@@ -43,7 +43,7 @@ const Mask: FC<PropsWithChildren> = ({ children }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
// 在需要时渲染错误状态 & loading
|
||||
// Rendering error states when needed & loading
|
||||
const LoginCheckMask: FC<{ needLogin: boolean; loginOptional: boolean }> = ({
|
||||
needLogin,
|
||||
loginOptional,
|
||||
|
||||
@@ -44,7 +44,7 @@ import { UserInfoField, type UserInfoFieldProps } from './user-info-field';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
// 用户输入 username 自动检查的时间
|
||||
// The time when the user enters the username to automatically check
|
||||
export const CHECK_USER_NAME_DEBOUNCE_TIME = 1000;
|
||||
|
||||
const WrappedInputWithCount: React.FC<
|
||||
@@ -213,7 +213,7 @@ export const UserInfoPanel = () => {
|
||||
});
|
||||
localStorage.setItem('i18next', newLang === 'en-US' ? 'en' : newLang);
|
||||
updateProfileEvent.success();
|
||||
// 更新语言设置需要刷新页面才能生效
|
||||
// Updating the language settings requires a page refresh to take effect
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
@@ -310,7 +310,7 @@ export const UserInfoPanel = () => {
|
||||
setAvatar(userInfo?.avatar_url ?? '');
|
||||
}, [userInfo]);
|
||||
|
||||
// 在进入和离开时均刷新一次用户信息
|
||||
// Refresh user information once upon entry and exit
|
||||
useEffect(() => {
|
||||
refreshUserInfo();
|
||||
return () => {
|
||||
|
||||
@@ -23,7 +23,7 @@ import s from './index.module.less';
|
||||
export const USER_NAME_MAX_LEN = 20;
|
||||
|
||||
interface InputWithCountProps extends InputProps {
|
||||
// 设置字数限制并显示字数统计
|
||||
// Set word limits and display word count
|
||||
getValueLength?: (value?: InputProps['value'] | string) => number;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useLogout = (): UseLogoutReturnType => {
|
||||
onOk={async () => {
|
||||
await logout();
|
||||
setVisible(false);
|
||||
// 跳转到根路径
|
||||
// Jump to root path
|
||||
navigate('/');
|
||||
}}
|
||||
onCancel={() => {
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 当浏览器窗口的高度大于等于760px时, 高度固定 */
|
||||
/* When the height of the browser window is greater than or equal to 760px, the height is fixed */
|
||||
@media screen and (min-height: 760px) {
|
||||
:global {
|
||||
.semi-modal {
|
||||
@@ -135,7 +135,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 当浏览器窗口的高度小于760px时, 高度 100vh - 160px */
|
||||
/* When the height of the browser window is less than 760px, the height is 100vh - 160px. */
|
||||
@media screen and (max-height: 759px) {
|
||||
:global {
|
||||
.semi-modal {
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface TabItem {
|
||||
id: string;
|
||||
tabName: string;
|
||||
/**
|
||||
* @param close 关闭setting弹窗
|
||||
* @param close settings pop-up
|
||||
* @returns ReactElement
|
||||
*/
|
||||
content: (close?: () => void) => ReactElement;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.flex-helper {
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
display: -webkit-flex; /* 新版本语法: Chrome 21+ */
|
||||
display: flex; /* 新版本语法: Opera 12.1, Firefox 22+ */
|
||||
display: -webkit-box; /* 老版本语法: Safari, iOS, Android browser, older WebKit browsers. */
|
||||
display: -moz-box; /* 老版本语法: Firefox (buggy) */
|
||||
display: -webkit-flex; /* New version syntax: Chrome 21 + */
|
||||
display: flex; /* New version syntax: Opera 12.1, Firefox 22 + */
|
||||
display: -webkit-box; /* Old syntax: Safari, iOS, Android browser, older WebKit browsers. */
|
||||
display: -moz-box; /* Old version syntax: Firefox (buggy) */
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
display: -ms-flexbox; /* 混合版本语法: IE 10 */
|
||||
display: -ms-flexbox; /* Mixed version syntax: IE 10 */
|
||||
}
|
||||
|
||||
.flex-1-helper {
|
||||
@@ -35,7 +35,7 @@
|
||||
.flex-items-center {
|
||||
/* stylelint-disable-next-line property-no-vendor-prefix */
|
||||
-webkit-align-items: center; /* Chrome 21+, Safari 6.1+, Opera 15+ */
|
||||
align-items: center; /* 新语法 */
|
||||
align-items: center; /* new grammar */
|
||||
|
||||
-ms-flex-align: center; /* IE 10 */
|
||||
}
|
||||
@@ -43,10 +43,10 @@
|
||||
.flex-justify-center {
|
||||
/* stylelint-disable-next-line property-no-vendor-prefix */
|
||||
-webkit-justify-content: center; /* Chrome 21+, Safari 6.1+ */
|
||||
justify-content: center; /* 新版浏览器 */
|
||||
justify-content: center; /* New browser */
|
||||
|
||||
-webkit-box-pack: center; /* iOS 6-, Safari 3.1-6 */
|
||||
-moz-box-pack: center; /* 早期版本的 Firefox */
|
||||
-moz-box-pack: center; /* Early versions of Firefox */
|
||||
-ms-flex-pack: center; /* IE 10 */
|
||||
}
|
||||
|
||||
|
||||
@@ -15,20 +15,20 @@
|
||||
*/
|
||||
|
||||
export function compareVersion(version1: string, version2: string): number {
|
||||
// 将版本号字符串分割成数字数组,这里使用map(Number)确保转换为数字类型
|
||||
// Split the version number string into an array of numbers, here use map (Number) to ensure conversion to numeric type
|
||||
const parts1 = version1.split('.').map(Number);
|
||||
const parts2 = version2.split('.').map(Number);
|
||||
|
||||
// 计算出最长的版本号长度
|
||||
// Calculate the longest version length
|
||||
const maxLength = Math.max(parts1.length, parts2.length);
|
||||
|
||||
// 逐个比较版本号中的每个部分
|
||||
// Compare each part of the version number one by one
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
// 如果某个版本号在这个位置没有对应的数字,则视为0
|
||||
// If a version number does not have a corresponding number at this position, it is treated as 0.
|
||||
const part1 = i < parts1.length ? parts1[i] : 0;
|
||||
const part2 = i < parts2.length ? parts2[i] : 0;
|
||||
|
||||
// 比较两个版本号的当前部分
|
||||
// Compare the current parts of two version numbers
|
||||
if (part1 > part2) {
|
||||
return 1;
|
||||
}
|
||||
@@ -37,6 +37,6 @@ export function compareVersion(version1: string, version2: string): number {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有部分都相等,则版本号相等
|
||||
// If all parts are equal, then the version numbers are equal
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ const INTERNATIONAL_BROWSER_DOWNLOAD_CONFIG: DownloadConfig = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 目前看起来 移动端 / PC 版本一致无需区分,后期如果区分,在这里通过条件区分
|
||||
* At present, it seems that the mobile end/PC version is the same without distinction. If it is distinguished later, it will be distinguished by conditions here.
|
||||
*/
|
||||
export const testLowVersionBrowse = () => testPCVersion();
|
||||
|
||||
@@ -81,7 +81,7 @@ const testPCVersion = () => {
|
||||
|
||||
const { name, version } = browserInfo;
|
||||
|
||||
// 显示的判断,用 includes 类型推断不正确
|
||||
// The displayed judgment, incorrectly inferred with the includes type
|
||||
if (name === 'bot' || name === 'react-native' || name === 'node') {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
export const isMobileFromUA = () => {
|
||||
const { userAgent } = navigator;
|
||||
// 检查是否为移动设备
|
||||
// Check if it is a mobile device
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
userAgent,
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂时不提供企业管理功能,本文件中导出的方法用于未来拓展使用。
|
||||
* The @file open-source version does not provide enterprise management functions for the time being. The methods exported in this file are for future expansion.
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂时不提供企业管理功能,本文件中导出的方法用于未来拓展使用。
|
||||
* The @file open-source version does not provide enterprise management functions for the time being. The methods exported in this file are for future expansion.
|
||||
*/
|
||||
import { type GetEnterpriseResponseData } from '@coze-arch/bot-api/pat_permission_api';
|
||||
|
||||
@@ -25,44 +25,44 @@ export interface CurrentEnterpriseInfoProps extends GetEnterpriseResponseData {
|
||||
organization_id: string | undefined;
|
||||
}
|
||||
/**
|
||||
* 获取当前企业信息。
|
||||
* 如果当前企业为个人版,则返回null。
|
||||
* 否则,返回当前企业信息,包括企业信息和组织ID。
|
||||
* Acquire current corporate information.
|
||||
* If the current enterprise is a personal edition, null is returned.
|
||||
* Otherwise, return current enterprise information, including enterprise information and organization ID.
|
||||
* @example
|
||||
* const { organization_id, enterprise_id } = useCurrentEnterpriseInfo();
|
||||
* @returns {(GetEnterpriseResponseData & { organization_id: string | undefined }) | null} 当前企业信息或null
|
||||
* @Returns { (GetEnterpriseResponseData & {organization_id: string | undefined}) | null} current enterprise information or null
|
||||
*/
|
||||
export const useCurrentEnterpriseInfo: () => CurrentEnterpriseInfoProps | null =
|
||||
() => null;
|
||||
|
||||
/**
|
||||
* 获取当前企业ID。
|
||||
* 如果当前企业类型为个人版,则返回约定的字符串。
|
||||
* 否则,返回当前企业的ID。
|
||||
* @returns {string} 当前企业ID
|
||||
* Obtain the current enterprise ID.
|
||||
* If the current enterprise type is Personal Edition, the agreed string is returned.
|
||||
* Otherwise, return the ID of the current enterprise.
|
||||
* @Returns {string} current enterprise ID
|
||||
*/
|
||||
export const useCurrentEnterpriseId = () =>
|
||||
useEnterpriseStore(store => store.enterpriseId);
|
||||
|
||||
/**
|
||||
* 检查当前企业是否为个人版。
|
||||
* @returns {boolean} 如果当前企业为个人版,则返回true,否则返回false。
|
||||
* Check whether the current enterprise is a personal version.
|
||||
* @Returns {boolean} True if the current enterprise is a personal edition, false otherwise.
|
||||
*/
|
||||
export const useIsCurrentPersonalEnterprise = () => true;
|
||||
|
||||
/**
|
||||
* 获取当前企业的角色列表。
|
||||
* 如果当前企业类型为个人版,则返回空数组。
|
||||
* 否则,返回当前企业的角色类型列表,如果列表不存在,则返回空数组。
|
||||
* @returns {Array} 当前企业的角色列表
|
||||
* Get a list of roles for the current enterprise.
|
||||
* If the current enterprise type is Personal, an empty array is returned.
|
||||
* Otherwise, a list of the current enterprise's role types is returned, or an empty array if the list does not exist.
|
||||
* @Returns {Array} list of roles for the current enterprise
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const useCurrentEnterpriseRoles = (): any[] => [];
|
||||
|
||||
/** 是否是企业版 */
|
||||
/** Is it the enterprise version? */
|
||||
export const useIsEnterpriseLevel = () => false;
|
||||
|
||||
/** 是否是团队版 */
|
||||
/** Is it the team version? */
|
||||
export const useIsTeamLevel = () => false;
|
||||
|
||||
export const useIsCurrentEnterpriseInit = () =>
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂时不提供企业管理功能,本文件中导出的方法用于未来拓展使用。
|
||||
* The @file open-source version does not provide enterprise management functions for the time being. The methods exported in this file are for future expansion.
|
||||
*/
|
||||
|
||||
import { useEnterpriseStore } from '../stores/enterprise';
|
||||
/**
|
||||
* 获取企业列表的hook。
|
||||
* 从企业store中获取企业列表,并返回企业信息列表。
|
||||
* @returns {Array} 企业信息列表
|
||||
* Hook to get the business list.
|
||||
* Get the business list from the business store and return the list of business information.
|
||||
* @Returns {Array} Enterprise Information List
|
||||
*/
|
||||
export const useEnterpriseList = () => {
|
||||
const list = useEnterpriseStore(store => store.enterpriseList);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂时不提供企业管理功能,本文件中导出的方法用于未来拓展使用。
|
||||
* The @file open-source version does not provide enterprise management functions for the time being. The methods exported in this file are for future expansion.
|
||||
*/
|
||||
|
||||
export { PERSONAL_ENTERPRISE_ID } from './constants';
|
||||
@@ -34,6 +34,6 @@ export {
|
||||
CurrentEnterpriseInfoProps,
|
||||
} from './hooks/use-current-enterprise-info';
|
||||
|
||||
// 工具方法
|
||||
// tool method
|
||||
export { switchEnterprise } from './utils/switch-enterprise';
|
||||
export { isPersonalEnterprise } from './utils/personal';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂时不提供企业管理功能,本文件中导出的方法用于未来拓展使用。
|
||||
* The @file open-source version does not provide enterprise management functions for the time being. The methods exported in this file are for future expansion.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { devtools } from 'zustand/middleware';
|
||||
@@ -74,7 +74,7 @@ export const useEnterpriseStore = create<
|
||||
setIsEnterpriseListInit: (_: boolean) => {},
|
||||
setEnterpriseList: (_: ListEnterpriseResponseData) => {},
|
||||
setIsEnterpriseExist: (_: boolean) => {},
|
||||
// 获取企业信息,可连续调用,不存在异步竞争问题。
|
||||
// Obtaining enterprise information can be continuously invoked without asynchronous competition.
|
||||
fetchEnterprise: (_: string) => {},
|
||||
}),
|
||||
{
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂时不提供企业管理功能,本文件中导出的方法用于未来拓展使用。
|
||||
* The @file open-source version does not provide enterprise management functions for the time being. The methods exported in this file are for future expansion.
|
||||
*/
|
||||
|
||||
import { PERSONAL_ENTERPRISE_ID } from '../constants';
|
||||
|
||||
// 检查企业是否为个人版
|
||||
// Check if the business is a personal version
|
||||
export const isPersonalEnterprise = (enterpriseId?: string) =>
|
||||
enterpriseId === PERSONAL_ENTERPRISE_ID;
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂时不提供企业管理功能,本文件中导出的方法用于未来拓展使用。
|
||||
* The @file open-source version does not provide enterprise management functions for the time being. The methods exported in this file are for future expansion.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 切换企业
|
||||
* @param {string} enterpriseId - 企业ID
|
||||
* Switch Enterprise
|
||||
* @param {string} enterpriseId - Enterprise ID
|
||||
*/
|
||||
export const switchEnterprise = (_: string) => Promise.resolve();
|
||||
|
||||
@@ -41,21 +41,21 @@ import {
|
||||
subscribeUserAuthInfos as subscribeUserAuthInfosImpl,
|
||||
} from '@coze-foundation/account-adapter';
|
||||
|
||||
/** @deprecated 使用 getLoginStatus */
|
||||
/** @deprecated using getLoginStatus */
|
||||
export const getIsSettled = (() =>
|
||||
getLoginStatus() !== 'settling') satisfies typeof getIsSettledOfSdk;
|
||||
/** @deprecated 使用 getLoginStatus */
|
||||
/** @deprecated using getLoginStatus */
|
||||
export const getIsLogined = (() =>
|
||||
getLoginStatus() === 'logined') satisfies typeof getIsLoginedOfSdk;
|
||||
export const getUserInfo = getUserInfoImpl satisfies typeof getUserInfoOfSdk;
|
||||
export const getUserAuthInfos =
|
||||
getUserAuthInfosImpl satisfies typeof getUserAuthInfosOfSdk;
|
||||
/** @deprecated 使用 useLoginStatus */
|
||||
/** @deprecated useLoginStatus */
|
||||
export const useIsSettled = (() => {
|
||||
const status = useLoginStatus();
|
||||
return status !== 'settling';
|
||||
}) satisfies typeof useIsSettledOfSdk;
|
||||
/** @deprecated 使用 useLoginStatus */
|
||||
/** @deprecated useLoginStatus */
|
||||
export const useIsLogined = (() => {
|
||||
const status = useLoginStatus();
|
||||
return status === 'logined';
|
||||
|
||||
@@ -26,14 +26,14 @@ export const useHasSider = () => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const pageMode = queryParams.get('page_mode');
|
||||
|
||||
// 优先使用 page_mode 参数判断是否为全屏模式
|
||||
// Priority is given to using page_mode parameters to determine whether it is full screen mode
|
||||
if (config.pageModeByQuery && pageMode === 'modal') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const notCheckLoginPage =
|
||||
(config.requireAuth && config.requireAuthOptional) || !config.requireAuth;
|
||||
// 未登录时也可访问的页面
|
||||
// Pages that can be accessed without logging in
|
||||
if (config.hasSider && notCheckLoginPage && !isLogined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const GlobalLayout: FC = () => {
|
||||
const update = useUpdate();
|
||||
const currentLocale = userInfo?.locale ?? navigator.language ?? 'en-US';
|
||||
|
||||
// 历史原因,en-US 需要被转换为 en
|
||||
// For historical reasons, en-US needs to be converted to en.
|
||||
const transformedCurrentLocale =
|
||||
currentLocale === 'en-US' ? 'en' : currentLocale;
|
||||
|
||||
@@ -46,7 +46,7 @@ export const GlobalLayout: FC = () => {
|
||||
if (userInfo && I18n.language !== transformedCurrentLocale) {
|
||||
localStorage.setItem('i18next', transformedCurrentLocale);
|
||||
I18n.setLang(transformedCurrentLocale);
|
||||
// 强制更新,否则切换语言不生效
|
||||
// Force an update, otherwise the language switch will not take effect
|
||||
update();
|
||||
}
|
||||
}, [userInfo, transformedCurrentLocale, update]);
|
||||
|
||||
@@ -31,8 +31,8 @@ import { useResetStoreOnLogout } from './use-reset-store-on-logout';
|
||||
import { useInitCommonConfig } from './use-init-common-config';
|
||||
|
||||
/**
|
||||
* 所有初始化的逻辑收敛到这里
|
||||
* 注意登录态需要自行处理
|
||||
* All initialization logic converges here
|
||||
* Note that the login status needs to be handled by yourself.
|
||||
*/
|
||||
export const useAppInit = () => {
|
||||
const { requireAuth, requireAuthOptional, loginFallbackPath } =
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 开源版暂不支持后台配置,用于未来拓展
|
||||
* @File open source version does not support background configuration for future expansion
|
||||
*/
|
||||
import { useEffect } from 'react';
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export const useCreateBotAction = ({
|
||||
urlSearch?: string;
|
||||
currentSpaceId?: string;
|
||||
}) => {
|
||||
// 创建 bot 功能
|
||||
// Create bot function
|
||||
const newWindowRef = useRef<Window | null>(null);
|
||||
const openWindow = () => {
|
||||
newWindowRef.current = window.open();
|
||||
|
||||
@@ -16,20 +16,20 @@
|
||||
|
||||
export function removeGlobalLoading() {
|
||||
const spin = document.querySelector('#global-spin-wrapper');
|
||||
// 对于不支持MutationObserver的浏览器直接隐藏 loading,避免影响正常页面的展示
|
||||
// Hide loading directly for browsers that do not support MutationObserver to avoid affecting the display of normal pages
|
||||
if (!window.MutationObserver && spin) {
|
||||
(spin as HTMLElement).style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const targetNode = document.querySelector('#root');
|
||||
const observerOptions = {
|
||||
childList: true, // 观察目标子节点的变化,是否有添加或者删除
|
||||
attributes: true, // 观察属性变动
|
||||
subtree: true, // 观察后代节点,默认为 false
|
||||
childList: true, // Observe the changes of the target sub-node and see if any are added or deleted
|
||||
attributes: true, // Observe attribute changes
|
||||
subtree: true, // Observe the descendant nodes, the default is false
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(function callback(mutationList) {
|
||||
// root 节点有任何变化就取消 loading,并取消观测
|
||||
// Cancel loading if there is any change in the root node and cancel the observation
|
||||
mutationList.forEach(mutation => {
|
||||
if (spin) {
|
||||
(spin as HTMLElement).style.display = 'none';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* stylelint-disable declaration-no-important -- 历史代码,为避免引入新BUG暂不修复 */
|
||||
/* Stylelint-disable declaration-no-important -- historical code, not fixed to avoid introducing new bugs */
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useSpaceStore, useSpaceApp } from '@coze-foundation/space-store';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
// i18n 的配置,对齐 starling 文案后再替换
|
||||
// The configuration of i18n, align the starling copy and then replace it.
|
||||
export const GlobalError: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const spaceApp = useSpaceApp();
|
||||
@@ -87,7 +87,7 @@ export const GlobalError: FC = () => {
|
||||
const spaceId =
|
||||
id ??
|
||||
getPersonalSpaceID() ??
|
||||
// 企业下无个人空间,缺省跳转到第一个空间
|
||||
// There is no personal space under the enterprise, so jump to the first space by default.
|
||||
useSpaceStore.getState().spaceList[0]?.id;
|
||||
url = spaceId ? `/space/${spaceId}/${spaceApp}` : '/space';
|
||||
} else if (base && base in BaseEnum) {
|
||||
|
||||
@@ -65,7 +65,7 @@ export const GlobalLayoutActionBtn: FC<LayoutButtonItem> = ({
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
);
|
||||
// 如果 tooltip 为空,则不显示 tooltip
|
||||
// If tooltip is empty, tooltip is not displayed
|
||||
return (
|
||||
<>
|
||||
{tooltip ? (
|
||||
|
||||
@@ -41,7 +41,7 @@ export const GLobalLayoutMenuItem: FC<LayoutMenuItem> = ({
|
||||
|
||||
let isActive = false;
|
||||
let newPath = '';
|
||||
// 如果 path 是数组,则取第一个匹配的路径
|
||||
// If path is an array, take the first matching path
|
||||
if (Array.isArray(path)) {
|
||||
isActive = path.some(p => location.pathname.startsWith(p));
|
||||
newPath = path.find(p => location.pathname.startsWith(p)) || path[0];
|
||||
|
||||
@@ -53,7 +53,7 @@ export const GlobalLayoutSider: FC<Omit<LayoutProps, 'hasSider'>> = ({
|
||||
return (
|
||||
<div className="pl-8px py-8px h-full">
|
||||
<div className={siderStyle}>
|
||||
{/* 主导航 */}
|
||||
{/* main navigation */}
|
||||
<div
|
||||
className={classNames(
|
||||
mainMenuStyle,
|
||||
@@ -83,7 +83,7 @@ export const GlobalLayoutSider: FC<Omit<LayoutProps, 'hasSider'>> = ({
|
||||
{footer}
|
||||
</Space>
|
||||
</div>
|
||||
{/* 二级导航 */}
|
||||
{/* secondary navigation */}
|
||||
<SubMenu />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@ export const useLayoutResponsive = () => {
|
||||
useEffect(() => {
|
||||
if (config.showMobileTips) {
|
||||
if (!mobileTips && isMobile()) {
|
||||
openMobileTipsModal(); // 不适配移动端弹窗提示
|
||||
openMobileTipsModal(); // Not suitable for mobile end pop-up window prompt
|
||||
setMobileTips(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IconSideFoldOutlined } from '@coze-arch/bot-icons';
|
||||
|
||||
import { useOpenGlobalLayoutSideSheet } from './global-layout/hooks';
|
||||
|
||||
// 用于在移动端模式开启侧边栏
|
||||
// Use to open sidebar in mobile end mode
|
||||
export const SideSheetMenu = () => {
|
||||
const open = useOpenGlobalLayoutSideSheet();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** 布局框架 */
|
||||
/** layout framework */
|
||||
export { SideSheetMenu } from './components/side-sheet-menu';
|
||||
export { GlobalError } from './components/global-error';
|
||||
export { BackButton } from './components/back-button';
|
||||
|
||||
@@ -18,7 +18,7 @@ import { devtools } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface SignMobileStore {
|
||||
/** 标识有没有弹出过提示 */
|
||||
/** Has the logo ever popped up? */
|
||||
mobileTips: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ const LOCAL_STORAGE_KEY = '__coz_biz_cache__';
|
||||
|
||||
describe('LocalStorageService', () => {
|
||||
beforeEach(() => {
|
||||
// 清除 localStorage
|
||||
// Clear localStorage
|
||||
localStorage.clear();
|
||||
// 重置 userId
|
||||
// Reset userId
|
||||
localStorageService.setUserId(undefined);
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('LocalStorageService', () => {
|
||||
|
||||
localStorageService.setValue(permanentKey, value);
|
||||
|
||||
// 等待 throttle
|
||||
// Waiting for throttle
|
||||
await new Promise(resolve => setTimeout(resolve, 400));
|
||||
|
||||
const storedData = JSON.parse(
|
||||
@@ -87,15 +87,15 @@ describe('LocalStorageService', () => {
|
||||
const value2 = 'user2-value';
|
||||
const userId2 = 'test-user-id-2';
|
||||
|
||||
// 第一个用户的数据
|
||||
// The first user's data
|
||||
localStorageService.setValue(userBindKey, value1);
|
||||
|
||||
// 切换到第二个用户
|
||||
// Switch to the second user
|
||||
localStorageService.setUserId(userId2);
|
||||
localStorageService.setValue(userBindKey, value2);
|
||||
expect(localStorageService.getValue(userBindKey)).toBe(value2);
|
||||
|
||||
// 切回第一个用户
|
||||
// Switch back to the first user
|
||||
localStorageService.setUserId(userId);
|
||||
expect(localStorageService.getValue(userBindKey)).toBe(value1);
|
||||
});
|
||||
@@ -111,7 +111,7 @@ describe('LocalStorageService', () => {
|
||||
localStorageService.on('change', changeHandler);
|
||||
localStorageService.setValue(permanentKey, value);
|
||||
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled();
|
||||
@@ -147,15 +147,15 @@ describe('LocalStorageService', () => {
|
||||
const userId = 'test-user-id';
|
||||
const value = 'test-value';
|
||||
|
||||
// 先设置值
|
||||
// Set the value first
|
||||
localStorageService.setUserId(userId);
|
||||
localStorageService.setValue('coachmark', value);
|
||||
localStorageService.setUserId(undefined);
|
||||
|
||||
// 异步获取值
|
||||
// Get value asynchronously
|
||||
const valuePromise = localStorageService.getValueSync('coachmark');
|
||||
|
||||
// 设置 userId
|
||||
// Set userId
|
||||
setTimeout(() => {
|
||||
localStorageService.setUserId(userId);
|
||||
}, 0);
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('useValue', () => {
|
||||
const value = 'test-value';
|
||||
localStorageService.setValue(permanentKey, value);
|
||||
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
const { result } = renderHook(() => useValue(permanentKey));
|
||||
@@ -42,12 +42,12 @@ describe('useValue', () => {
|
||||
it('当值改变时应该更新', async () => {
|
||||
const { result } = renderHook(() => useValue(permanentKey));
|
||||
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
await act(async () => {
|
||||
localStorageService.setValue(permanentKey, 'new-value');
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
@@ -57,14 +57,14 @@ describe('useValue', () => {
|
||||
it('当值被删除时应该返回 undefined', async () => {
|
||||
localStorageService.setValue(permanentKey, 'test-value');
|
||||
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
const { result } = renderHook(() => useValue(permanentKey));
|
||||
|
||||
await act(async () => {
|
||||
localStorageService.setValue(permanentKey, undefined);
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
@@ -74,18 +74,18 @@ describe('useValue', () => {
|
||||
it('卸载时应该清理事件监听', async () => {
|
||||
const { unmount } = renderHook(() => useValue(permanentKey));
|
||||
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
unmount();
|
||||
|
||||
// 确保不会触发已卸载组件的状态更新
|
||||
// Make sure that status updates for uninstalled components are not triggered
|
||||
localStorageService.setValue(permanentKey, 'new-value');
|
||||
// 如果事件监听没有被清理,这里会抛出 React 警告
|
||||
// If the event listener is not cleaned up, a React warning will be thrown here
|
||||
});
|
||||
|
||||
describe('用户相关的值', () => {
|
||||
// 使用一个确定绑定了用户的 key
|
||||
// Use a key that confirms the user is bound.
|
||||
const userBindKey = 'coachmark' as const;
|
||||
const userId = 'test-user-id';
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('useValue', () => {
|
||||
it('应该返回当前用户的值', async () => {
|
||||
localStorageService.setValue(userBindKey, 'user-value');
|
||||
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
const { result } = renderHook(() => useValue(userBindKey));
|
||||
@@ -107,7 +107,7 @@ describe('useValue', () => {
|
||||
const userId2 = 'test-user-id-2';
|
||||
localStorageService.setValue(userBindKey, 'user1-value');
|
||||
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
const { result } = renderHook(() => useValue(userBindKey));
|
||||
@@ -116,7 +116,7 @@ describe('useValue', () => {
|
||||
await act(async () => {
|
||||
localStorageService.setUserId(userId2);
|
||||
localStorageService.setValue(userBindKey, 'user2-value');
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ describe('useValue', () => {
|
||||
|
||||
await act(async () => {
|
||||
localStorageService.setUserId(userId);
|
||||
// 等待事件触发
|
||||
// Wait for the event to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('解析工具函数', () => {
|
||||
it('应该返回空对象当永久缓存数据格式无效', () => {
|
||||
const data = {
|
||||
permanent: {
|
||||
key: 123, // 应该是字符串
|
||||
key: 123, // Should be string.
|
||||
},
|
||||
};
|
||||
expect(paseLocalStorageValue(JSON.stringify(data))).toEqual({});
|
||||
@@ -82,7 +82,7 @@ describe('解析工具函数', () => {
|
||||
const data = {
|
||||
userRelated: {
|
||||
'user-1': {
|
||||
key: 123, // 应该是字符串
|
||||
key: 123, // Should be string.
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { type LocalStorageCacheConfig } from './types';
|
||||
|
||||
// 统一维护 key 定义避免出现冲突
|
||||
// Maintain key definitions uniformly to avoid conflicts
|
||||
export const LOCAL_STORAGE_CACHE_KEYS = [
|
||||
'coachmark',
|
||||
'workspace-spaceId',
|
||||
|
||||
@@ -37,8 +37,8 @@ class LocalStorageService extends EventEmitter {
|
||||
}, throttleWait);
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
/**
|
||||
* 页签进入后台后,通过操作其它页签,可能导致 #state 状态不是最新的
|
||||
* 所以页签重新激活后需要同步一次 localStorage 的数据
|
||||
* After the tab enters the background, by operating other tabs, the #state status may not be the latest
|
||||
* So after the tab is reactivated, the data of localStorage needs to be synchronized once.
|
||||
*/
|
||||
if (document.visibilityState === 'visible') {
|
||||
this.#initState();
|
||||
|
||||
@@ -27,7 +27,7 @@ const isValidDataItem = (data: unknown): data is CacheDataItems => {
|
||||
const isObject = (value: unknown): value is object =>
|
||||
!!value && typeof value === 'object' && value !== null;
|
||||
|
||||
// 判断本地缓存中的值是否与 LocalStorageCacheData 类型定义匹配
|
||||
// Determines if a value in the local cache matches the LocalStorageCacheData type definition
|
||||
const isValidCacheData = (value: unknown): value is LocalStorageCacheData => {
|
||||
if (!isObject(value)) {
|
||||
return false;
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
// eslint-disable-next-line @coze-arch/no-batch-import-or-export, @typescript-eslint/consistent-type-imports
|
||||
import * as zustand from 'zustand';
|
||||
import { act } from '@testing-library/react';
|
||||
|
||||
const { create: actualCreate, createStore: actualCreateStore } =
|
||||
// @ts-expect-error -- UT 忽略
|
||||
// @ts-expect-error -- UT ignored
|
||||
await vi.importActual<typeof zustand>('zustand');
|
||||
|
||||
// a variable to hold reset functions for all stores declared in the app
|
||||
|
||||
@@ -27,7 +27,7 @@ vi.mock('@coze-arch/bot-error', () => ({
|
||||
CustomError: vi.fn(),
|
||||
}));
|
||||
|
||||
// FIXME 改为按需 mock
|
||||
// FIXME changed to mock on demand
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
DeveloperApi: {
|
||||
GetUserAuthList: vi
|
||||
@@ -50,7 +50,7 @@ vi.mock('@coze-arch/bot-api', () => ({
|
||||
// .mockResolvedValueOnce({ code: 1 })
|
||||
// .mockResolvedValueOnce({ code: 0 })
|
||||
// .mockResolvedValueOnce({ code: 0 })
|
||||
// mock 缺失 personal store && 轮询失败
|
||||
// Mock missing personal store & & poll failed
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
.mockResolvedValueOnce({ code: 0 })
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
export enum ReportEventNames {
|
||||
EmptySpaceList = 'empty_space_List', // 空间列表为空
|
||||
PollingSpaceList = 'polling_space_list', // 轮训空间列表
|
||||
EmptySpaceList = 'empty_space_List', // Space list is empty
|
||||
PollingSpaceList = 'polling_space_list', // Rotation space list
|
||||
}
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ interface SpaceStoreState {
|
||||
recentlyUsedSpaceList: BotSpace[];
|
||||
loading: false | Promise<SpaceInfo | undefined>;
|
||||
inited?: boolean;
|
||||
createdTeamSpaceNum: number; // 个人创建的团队空间计数
|
||||
createdTeamSpaceNum: number; // Count of team spaces created by individuals
|
||||
maxTeamSpaceNum: number;
|
||||
/** @deprecated 使用 spaceList & maxTeamSpaceNum */
|
||||
/** @deprecated spaceList & maxTeamSpaceNum */
|
||||
spaces: {
|
||||
bot_space_list: BotSpace[];
|
||||
has_personal_space: boolean;
|
||||
@@ -56,7 +56,7 @@ interface SpaceStoreAction {
|
||||
getSpaceId: () => string;
|
||||
getPersonalSpaceID: () => string | undefined;
|
||||
checkSpaceID: (spaceID: string) => boolean;
|
||||
/** @deprecated 通过 id 索引 */
|
||||
/** @deprecated by id index */
|
||||
setSpace: (spaceId?: string, isBotDetailIframe?: boolean) => void | never;
|
||||
createSpace: (
|
||||
request: SaveSpaceV2Request,
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
// eslint-disable-next-line @coze-arch/no-batch-import-or-export, @typescript-eslint/consistent-type-imports
|
||||
import * as zustand from 'zustand';
|
||||
import { act } from '@testing-library/react';
|
||||
|
||||
const { create: actualCreate, createStore: actualCreateStore } =
|
||||
// @ts-expect-error -- UT 忽略
|
||||
// @ts-expect-error -- UT ignored
|
||||
await vi.importActual<typeof zustand>('zustand');
|
||||
|
||||
// a variable to hold reset functions for all stores declared in the app
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* 从URL上获取工作空间子模块
|
||||
* Get the workspace submodule from the URL
|
||||
* @param pathname
|
||||
* @returns 工作子模块字符串,如果不匹配则返回 undefined
|
||||
* @Returns the working submodule string, or undefined if it doesn't match
|
||||
*/
|
||||
const getSpaceApp = (pathname: string): string | undefined => {
|
||||
// 以 /space/ 开头,后面跟 spaceId,再跟子模块(只允许字母、数字、-、_)
|
||||
// Start with /space/, followed by spaceId, followed by submodules (only letters, numbers, -, _ allowed)
|
||||
const match = pathname.match(/^\/space\/[^/]+\/([A-Za-z0-9_-]+)/);
|
||||
return match ? match[1] : undefined;
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ import { type BotSpace } from '@coze-arch/bot-api/developer_api';
|
||||
export const useRefreshSpaces = (refresh?: boolean) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const enterpriseInfo = useCurrentEnterpriseInfo();
|
||||
// 企业发生变化,重新获取空间列表
|
||||
// Businesses change, regain the list of spaces
|
||||
useEffect(() => {
|
||||
if (refresh || !useSpaceStore.getState().inited) {
|
||||
setLoading(true);
|
||||
|
||||
@@ -20,13 +20,13 @@ import { useDestorySpace } from '@coze-common/auth';
|
||||
import { useInitSpaceRole } from '@coze-common/auth-adapter';
|
||||
|
||||
const SpaceIdContainer = ({ spaceId }: { spaceId: string }) => {
|
||||
// 空间组件销毁时,清空对应space数据
|
||||
// When the space component is destroyed, empty the corresponding space data
|
||||
useDestorySpace(spaceId);
|
||||
|
||||
// 初始化空间权限数据
|
||||
// Initialize spatial permission data
|
||||
const isCompleted = useInitSpaceRole(spaceId);
|
||||
|
||||
// isCompleted 的 判断条件很重要,确保了在Space空间内能够获取到空间的权限数据。
|
||||
// isCompleted, the judgment condition is very important to ensure that the permission data of the space can be obtained in the Space space.
|
||||
return isCompleted ? <Outlet /> : null;
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const getSubPath = (type: IntelligenceType | undefined) => {
|
||||
return 'project-ide';
|
||||
}
|
||||
if (type === IntelligenceType.Bot) {
|
||||
//跳转至 Bot编辑页,后续会改成新的URL/space/:spaceId/agent/:agentId
|
||||
//Jump to the Bot edit page, which will be changed to a new URL/space/: spaceId/agent/: agentId later.
|
||||
return 'bot';
|
||||
}
|
||||
return '';
|
||||
@@ -63,7 +63,7 @@ export const FavoritesListItem: FC<IntelligenceData> = ({
|
||||
basic_info = {},
|
||||
type,
|
||||
}) => {
|
||||
// 取消收藏
|
||||
// Cancel Favorite
|
||||
const clickToUnfavorite = async () => {
|
||||
try {
|
||||
const res: FavoriteProductResponse =
|
||||
@@ -76,7 +76,7 @@ export const FavoritesListItem: FC<IntelligenceData> = ({
|
||||
entity_id: id,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
// 取消收藏成功,刷新收藏列表
|
||||
// Cancel the collection successfully, refresh the collection list
|
||||
cozeMitt.emit('refreshFavList', {
|
||||
id,
|
||||
numDelta: -1,
|
||||
@@ -113,7 +113,7 @@ export const FavoritesListItem: FC<IntelligenceData> = ({
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
//跳转至 Bot编辑页,后续会改成新的URL/space/:spaceId/agent/:agentId
|
||||
//Jump to the Bot edit page, which will be changed to a new URL/space/: spaceId/agent/: agentId later.
|
||||
window.open(getIntelligenceNavigateUrl({ basic_info, type }), '_blank');
|
||||
}}
|
||||
data-testid="workspace.favorites.list.item"
|
||||
|
||||
@@ -101,7 +101,7 @@ const getFavoritesList = async ({
|
||||
};
|
||||
|
||||
/**
|
||||
* ahooks 的 useInfiniteScroll 返回的对象引用会变,本方法返回一个引用不变的对象,仅此而已,不用关注其声明和实现
|
||||
* The object reference returned by the useInfiniteScroll of ahooks will change. This method returns an object with unchanged references, nothing more, regardless of its declaration and implementation
|
||||
*/
|
||||
const useInfiniteScrollRef: typeof useInfiniteScroll = <
|
||||
T extends { list: unknown[] },
|
||||
@@ -123,8 +123,8 @@ export const FavoritesList: FC = () => {
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 用一个引用不变的 req,便于 effect 中的 handler 拿到最新的 loading 状态
|
||||
// (将 loading 放进 effect 的 deps 中并不能解决问题,因为上一次的闭包中 getFavoritesList 前后的 loading 状态已经固定不会变了,导致上一次执行出错)
|
||||
// Use an invariant req to make it easier for the handler in the effect to get the latest loading status
|
||||
// (Putting loading into the deps of effect doesn't solve the problem, because the loading state before and after getFavoritesList in the last closure has been fixed and will not change, resulting in an error in the last execution)
|
||||
const req = useInfiniteScrollRef<FEIntelligenceListData>(
|
||||
async dataSource =>
|
||||
await getFavoritesList({
|
||||
@@ -143,7 +143,7 @@ export const FavoritesList: FC = () => {
|
||||
useEffect(() => {
|
||||
const handler = async (refreshFavListParams: RefreshFavListParams) => {
|
||||
if (req.loading || req.loadingMore) {
|
||||
// 处理竞态问题,优先保证列表滚动加载,下同
|
||||
// Deal with the race problem, give priority to ensuring the rolling loading of the list, the same as below
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,14 +151,14 @@ export const FavoritesList: FC = () => {
|
||||
const mutateData = await getFavoritesList({
|
||||
spaceId,
|
||||
spaceType,
|
||||
// Q:为什么要专门设置 pageSize
|
||||
// A:useInfiniteScroll 有个 bug/feature,mutate 后不会立即触发高度检测
|
||||
// 这要从它的 loadmore 触发逻辑讲起,正常是监听 scroll 动作,检测高度,从而判断是否需要 loadmore
|
||||
// 但假如首次请求回来的数据就不足一屏高度,没有 overflow 无法触发 scroll 动作,怎么办?
|
||||
// 因此 useInfiniteScroll 会在 run、reload 之类的行为完成后立即做一次高度检测来判断是否要继续 loadmore。
|
||||
// 但是!它却唯独不会在 mutate 后做高度检测,导致 mutate 出来的数据不足一屏,就再也无法 loadmore 了
|
||||
// 因此这里手动计算一下需要 mutate 的数据量。
|
||||
// 如果后续 pageSize 太大有问题,那还可以继续改造一下 useInfiniteScrollRef,使 mutate 动作实际执行 reload,但拦截其 loading 属性返回 false
|
||||
// Q: Why set the pageSize specifically?
|
||||
// A: useInfiniteScroll has a bug/feature that does not trigger height detection immediately after mutating
|
||||
// This starts with its loadmore trigger logic. Normally, it monitors the scroll action and detects the height to determine whether loadmore is required.
|
||||
// But what if the data requested for the first time is less than one screen height, and the scroll action cannot be triggered without overflow?
|
||||
// Therefore, useInfiniteScroll will perform a height check immediately after running, reloading, etc. to determine whether to continue loadmore.
|
||||
// However! It will not do height detection after mutating, resulting in less than one screen of mutated data, and it can no longer loadmore
|
||||
// So here we manually calculate the amount of data that needs to be mutated.
|
||||
// If there is a problem with the subsequent pageSize being too large, you can continue to modify the useInfiniteScrollRef so that the mutate action actually executes reload, but intercepts its loading property and returns false.
|
||||
pageSize: Math.max(
|
||||
currLength
|
||||
? currLength + refreshFavListParams.numDelta
|
||||
@@ -169,7 +169,7 @@ export const FavoritesList: FC = () => {
|
||||
if (req.loading || req.loadingMore) {
|
||||
return;
|
||||
}
|
||||
// 使用 mutate 静默加载,直接更新视图,不展示 loading 效果
|
||||
// Use mutate silent loading to update the view directly without displaying the loading effect
|
||||
req.mutate(mutateData);
|
||||
};
|
||||
cozeMitt.on('refreshFavList', handler);
|
||||
@@ -177,7 +177,7 @@ export const FavoritesList: FC = () => {
|
||||
}, [spaceId, spaceType]);
|
||||
|
||||
return (
|
||||
// 这里有一个很坑,滚动蒙层如果直接挂在滚动画布元素上会一起滚动,所以这里需要单独包一层(往上方一层)
|
||||
// There is a very pit here. If the scrolling overlay is directly hung on the scrolling canvas element, it will scroll together, so it needs to be wrapped in a separate layer (one layer above).
|
||||
<div className={classNames('w-full h-full flex flex-col')}>
|
||||
<>
|
||||
<Space
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 定义滚动条默认隐藏样式 styled-scrollbar-hidden */
|
||||
/* Define the scrollbar default hidden style styled-scrollbar-hidden */
|
||||
.styled-scrollbar-hidden {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
.list-bottom-mask::after,
|
||||
.list-top-mask::before {
|
||||
pointer-events: none; /* 确保伪元素不阻挡用户交互 */
|
||||
pointer-events: none; /* Make sure that pseudo-elements do not block user interaction */
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
|
||||
@@ -77,38 +77,38 @@ export const useInitSpace = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果未指定spaceId,则跳转到兜底的space下的项目开发子路由
|
||||
// If spaceId is not specified, jump to the project development subroute under the space of the backseat
|
||||
if (!spaceId) {
|
||||
// 拉取空间列表
|
||||
// Pull space list
|
||||
await useSpaceStore.getState().fetchSpaces(true);
|
||||
// 获取个人空间Id
|
||||
// Get Personal Space Id
|
||||
const personalSpaceID = useSpaceStore.getState().getPersonalSpaceID();
|
||||
// 空间列表的第一个空间
|
||||
// The first space in the space list
|
||||
const firstSpaceID = useSpaceStore.getState().spaceList[0]?.id;
|
||||
// 未指定spaceId的兜底空间
|
||||
// SpaceId not specified
|
||||
const fallbackSpaceID = personalSpaceID ?? firstSpaceID ?? '';
|
||||
// 检查指定的spaceId是否可以访问
|
||||
// Checks if the specified spaceId is accessible
|
||||
const { checkSpaceID } = useSpaceStore.getState();
|
||||
|
||||
// 无工作空间,提示创建
|
||||
// No workspace, prompt to create
|
||||
if (!fallbackSpaceID) {
|
||||
Toast.warning(I18n.t('enterprise_workspace_default_tips2_toast'));
|
||||
} else {
|
||||
// 获取兜底的跳转URL
|
||||
// Get the jump URL of the back cover.
|
||||
const targetURL = await getFallbackWorkspaceURL(
|
||||
fallbackSpaceID,
|
||||
'develop',
|
||||
checkSpaceID,
|
||||
);
|
||||
// 跳转
|
||||
// jump
|
||||
navigate(targetURL);
|
||||
}
|
||||
} else {
|
||||
// 拉取空间列表
|
||||
// Pull space list
|
||||
await fetchSpacesWithSpaceId?.(spaceId);
|
||||
|
||||
if (!useSpaceStore.getState().checkSpaceID(spaceId)) {
|
||||
// 当 space id 在space 列表找不到时,抛出错误
|
||||
// Throws an error when the space id cannot be found in the space list
|
||||
capture(
|
||||
new CustomError(ReportEventNames.errorPath, 'space id error', {
|
||||
customGlobalErrorConfig: {
|
||||
@@ -119,7 +119,7 @@ export const useInitSpace = ({
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// 更新space store的spaceId
|
||||
// Update space store spaceId
|
||||
useSpaceStore.getState().setSpace(spaceId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user