diff --git a/client/packages/design/components/WebMetaForm/index.tsx b/client/packages/design/components/WebMetaForm/index.tsx
index 6248014e351..6d4e562872e 100644
--- a/client/packages/design/components/WebMetaForm/index.tsx
+++ b/client/packages/design/components/WebMetaForm/index.tsx
@@ -35,6 +35,8 @@ export function setWebFastifyFormConfig(config: typeof webFastifyFormConfig) {
const WebFastifyFormContainer: FastifyFormContainerComponent = React.memo(
(props) => {
const layout = props.layout;
+ const suffixElement = props.extraProps?.suffixElement;
+
const submitButtonRender = useMemo(() => {
return (
{props.children}
+ {suffixElement}
{submitButtonRender}
);
diff --git a/client/shared/hooks/useEditValue.ts b/client/shared/hooks/useEditValue.ts
new file mode 100644
index 00000000000..6363ba3b2ec
--- /dev/null
+++ b/client/shared/hooks/useEditValue.ts
@@ -0,0 +1,15 @@
+import { useCallback, useLayoutEffect, useState } from 'react';
+
+export function useEditValue(value: T, onChange: (val: T) => void) {
+ const [inner, setInner] = useState(value);
+
+ useLayoutEffect(() => {
+ setInner(value);
+ }, [value]);
+
+ const onSave = useCallback(() => {
+ onChange(inner);
+ }, [inner, onChange]);
+
+ return [inner, setInner, onSave] as const;
+}
diff --git a/client/shared/hooks/useLazyValue.ts b/client/shared/hooks/useLazyValue.ts
new file mode 100644
index 00000000000..d21686469df
--- /dev/null
+++ b/client/shared/hooks/useLazyValue.ts
@@ -0,0 +1,17 @@
+import { useLayoutEffect, useState } from 'react';
+import { useEvent } from './useEvent';
+
+export function useLazyValue(value: T, onChange: (val: T) => void) {
+ const [inner, setInner] = useState(value);
+
+ useLayoutEffect(() => {
+ setInner(value);
+ }, [value]);
+
+ const handleChange = useEvent((val: T) => {
+ setInner(val);
+ onChange(val);
+ });
+
+ return [inner, handleChange] as const;
+}
diff --git a/client/shared/index.tsx b/client/shared/index.tsx
index 18bd97f59f3..37fafbfa72c 100644
--- a/client/shared/index.tsx
+++ b/client/shared/index.tsx
@@ -74,8 +74,10 @@ export { useAsyncFn } from './hooks/useAsyncFn';
export { useAsyncRefresh } from './hooks/useAsyncRefresh';
export { useAsyncRequest } from './hooks/useAsyncRequest';
export { useDebounce } from './hooks/useDebounce';
+export { useEditValue } from './hooks/useEditValue';
export { useEvent } from './hooks/useEvent';
export { useInterval } from './hooks/useInterval';
+export { useLazyValue } from './hooks/useLazyValue';
export { useMemoizedFn } from './hooks/useMemoizedFn';
export { useMountedState } from './hooks/useMountedState';
export { usePrevious } from './hooks/usePrevious';
diff --git a/client/web/src/components/CollapseView.tsx b/client/web/src/components/CollapseView.tsx
index 5f33e85ea12..c7d59c7308f 100644
--- a/client/web/src/components/CollapseView.tsx
+++ b/client/web/src/components/CollapseView.tsx
@@ -4,10 +4,11 @@ import React from 'react';
interface CollapseViewProps extends React.PropsWithChildren {
title: string;
className?: string;
+ style?: React.CSSProperties;
}
export const CollapseView: React.FC = React.memo((props) => {
return (
-
+
{props.children}
diff --git a/client/web/src/components/modals/GroupPanel/AdvanceGroupPanelPermission.tsx b/client/web/src/components/modals/GroupPanel/AdvanceGroupPanelPermission.tsx
new file mode 100644
index 00000000000..3f01cd64f4b
--- /dev/null
+++ b/client/web/src/components/modals/GroupPanel/AdvanceGroupPanelPermission.tsx
@@ -0,0 +1,120 @@
+import { PermissionList } from '@/components/PermissionList';
+import { Button } from 'antd';
+import clsx from 'clsx';
+import React, { PropsWithChildren, useState } from 'react';
+import {
+ ALL_PERMISSION,
+ getDefaultPermissionList,
+ t,
+ useAppSelector,
+ useEvent,
+ useLazyValue,
+} from 'tailchat-shared';
+import _isEqual from 'lodash/isEqual';
+
+interface AdvanceGroupPanelPermissionProps {
+ height?: number;
+ groupId: string;
+ panelId: string;
+ onChange: (
+ permissionMap: Record | undefined
+ ) => void;
+}
+
+export const AdvanceGroupPanelPermission: React.FC =
+ React.memo((props) => {
+ const [selectedRoleId, setSelectedRoleId] = useState<
+ typeof ALL_PERMISSION | string
+ >(ALL_PERMISSION);
+
+ const roles = useAppSelector((state) => {
+ const groupInfo = state.group.groups[props.groupId];
+ return groupInfo.roles;
+ });
+
+ const permissionMap: Record =
+ useAppSelector((state) => {
+ const groupInfo = state.group.groups[props.groupId];
+ const panelInfo = groupInfo.panels.find((p) => p.id === props.panelId);
+ if (!panelInfo) {
+ return { [ALL_PERMISSION]: getDefaultPermissionList() };
+ } else {
+ return {
+ [ALL_PERMISSION]:
+ panelInfo.fallbackPermissions ?? getDefaultPermissionList(),
+ ...panelInfo.permissionMap,
+ };
+ }
+ }, _isEqual);
+
+ const [editPermissionMap, setEditPermissionMap] = useLazyValue(
+ permissionMap,
+ props.onChange
+ );
+
+ const handleUpdatePermissionMap = useEvent((permissions: string[]) => {
+ const newMap = { ...editPermissionMap, [selectedRoleId]: permissions };
+ setEditPermissionMap(newMap);
+ });
+
+ const handleSyncWithGroup = useEvent(() => {
+ setEditPermissionMap({
+ [ALL_PERMISSION]: getDefaultPermissionList(),
+ });
+ props.onChange(undefined);
+ });
+
+ return (
+
+
+ setSelectedRoleId(ALL_PERMISSION)}
+ >
+ {t('所有人')}
+
+ {roles.map((r) => (
+ setSelectedRoleId(r._id)}
+ >
+ {r.name}
+
+ ))}
+
+
+
+ );
+ });
+AdvanceGroupPanelPermission.displayName = 'AdvanceGroupPanelPermission';
+
+const RoleItem: React.FC<
+ PropsWithChildren<{
+ active: boolean;
+ onClick?: () => void;
+ }>
+> = React.memo((props) => {
+ return (
+
+ {props.children}
+
+ );
+});
+RoleItem.displayName = 'RoleItem';
diff --git a/client/web/src/components/modals/GroupPanel/ModifyGroupPanel.tsx b/client/web/src/components/modals/GroupPanel/ModifyGroupPanel.tsx
index 8d65e9731e6..c80c834351e 100644
--- a/client/web/src/components/modals/GroupPanel/ModifyGroupPanel.tsx
+++ b/client/web/src/components/modals/GroupPanel/ModifyGroupPanel.tsx
@@ -6,12 +6,18 @@ import {
modifyGroupPanel,
showToasts,
useGroupPanelInfo,
+ useEvent,
+ ALL_PERMISSION,
+ DevContainer,
} from 'tailchat-shared';
import { ModalWrapper } from '../../Modal';
import { WebMetaForm } from 'tailchat-design';
import { buildDataFromValues, pickValuesFromGroupPanelInfo } from './helper';
import type { GroupPanelValues } from './types';
import { useGroupPanelFields } from './useGroupPanelFields';
+import { AdvanceGroupPanelPermission } from './AdvanceGroupPanelPermission';
+import { CollapseView } from '@/components/CollapseView';
+import _omit from 'lodash/omit';
/**
* 修改群组面板
@@ -22,35 +28,70 @@ export const ModalModifyGroupPanel: React.FC<{
onSuccess?: () => void;
}> = React.memo((props) => {
const groupPanelInfo = useGroupPanelInfo(props.groupId, props.groupPanelId);
- const [currentValues, setValues] = useState>({});
-
- const [, handleSubmit] = useAsyncRequest(
- async (values: GroupPanelValues) => {
- await modifyGroupPanel(
- props.groupId,
- props.groupPanelId,
- buildDataFromValues(values)
- );
- showToasts(t('修改成功'), 'success');
- typeof props.onSuccess === 'function' && props.onSuccess();
- },
- [props.groupId, props.groupPanelId, props.onSuccess]
+ const [currentValues, setValues] = useState(
+ pickValuesFromGroupPanelInfo(groupPanelInfo)
);
+ const [, handleSubmit] = useAsyncRequest(async () => {
+ await modifyGroupPanel(
+ props.groupId,
+ props.groupPanelId,
+ buildDataFromValues(currentValues)
+ );
+ showToasts(t('修改成功'), 'success');
+ typeof props.onSuccess === 'function' && props.onSuccess();
+ }, [props.groupId, props.groupPanelId, props.onSuccess, currentValues]);
+
const { fields, schema } = useGroupPanelFields(props.groupId, currentValues);
+ const handleUpdateValues = useEvent((values: Partial) => {
+ setValues((state) => ({
+ ...state,
+ ...values,
+ }));
+ });
+
if (!groupPanelInfo) {
return ;
}
return (
-
+
f.type !== 'type')} // 变更时不显示类型
initialValues={pickValuesFromGroupPanelInfo(groupPanelInfo)}
- onChange={setValues}
+ onChange={handleUpdateValues}
onSubmit={handleSubmit}
+ extraProps={{
+ suffixElement: (
+
+
+ {
+ if (permissionMap) {
+ const fallbackPermissions = permissionMap[ALL_PERMISSION];
+ const others = { ...permissionMap };
+
+ handleUpdateValues({
+ fallbackPermissions,
+ permissionMap: _omit(others, [ALL_PERMISSION]),
+ });
+ } else {
+ handleUpdateValues({
+ fallbackPermissions: undefined,
+ permissionMap: undefined,
+ });
+ }
+ }}
+ />
+
+
+ ),
+ }}
/>
);
diff --git a/client/web/src/components/modals/GroupPanel/helper.ts b/client/web/src/components/modals/GroupPanel/helper.ts
index 4da6389374c..52303104c4a 100644
--- a/client/web/src/components/modals/GroupPanel/helper.ts
+++ b/client/web/src/components/modals/GroupPanel/helper.ts
@@ -6,7 +6,7 @@ import type { GroupPanelValues } from './types';
* 根据表单数据生成需要提交的内容
*/
export function buildDataFromValues(values: GroupPanelValues) {
- const { name, type, ...meta } = values;
+ const { name, type, permissionMap, fallbackPermissions, ...meta } = values;
let panelType: number;
let provider: string | undefined = undefined;
let pluginPanelName: string | undefined = undefined;
@@ -30,6 +30,8 @@ export function buildDataFromValues(values: GroupPanelValues) {
provider,
pluginPanelName,
meta,
+ permissionMap,
+ fallbackPermissions,
};
}
@@ -37,8 +39,15 @@ export function buildDataFromValues(values: GroupPanelValues) {
* 从群组面板信息中获取面板属性修改相关信息
*/
export function pickValuesFromGroupPanelInfo(
- groupPanelInfo: GroupPanel
+ groupPanelInfo: GroupPanel | null
): GroupPanelValues {
+ if (groupPanelInfo === null) {
+ return {
+ name: '',
+ type: GroupPanelType.TEXT,
+ };
+ }
+
return {
...groupPanelInfo.meta,
name: groupPanelInfo.name,
@@ -46,5 +55,7 @@ export function pickValuesFromGroupPanelInfo(
groupPanelInfo.type === GroupPanelType.PLUGIN
? String(groupPanelInfo.pluginPanelName)
: groupPanelInfo.type,
+ permissionMap: groupPanelInfo.permissionMap,
+ fallbackPermissions: groupPanelInfo.fallbackPermissions,
};
}