feat(admin): add system notify

pull/100/head
moonrailgun 2 years ago
parent fd9787f77b
commit 449845315e

@ -574,7 +574,7 @@ importers:
version: 0.32.11
zustand:
specifier: ^4.3.6
version: 4.3.6(immer@9.0.21)(react@18.2.0)
version: 4.3.6(immer@9.0.15)(react@18.2.0)
devDependencies:
'@types/crc':
specifier: ^3.4.0
@ -1556,8 +1556,8 @@ importers:
specifier: workspace:^
version: link:../packages/sdk
tushan:
specifier: ^0.2.28
version: 0.2.28(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1)
specifier: ^0.2.32
version: 0.2.32(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1)
vite-express:
specifier: 0.8.0
version: 0.8.0(patch_hash=u6touqej4dt3zxnslnszarl7vq)(express@4.18.2)(vite@4.2.0)
@ -1875,7 +1875,7 @@ importers:
version: 5.3.6(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
zustand:
specifier: ^4.3.6
version: 4.3.6(immer@9.0.21)(react@18.2.0)
version: 4.3.6(immer@9.0.15)(react@18.2.0)
server/plugins/com.msgbyte.getui:
dependencies:
@ -2053,7 +2053,7 @@ importers:
version: 5.3.6(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
zustand:
specifier: ^4.3.6
version: 4.3.6(immer@9.0.21)(react@18.2.0)
version: 4.3.6(immer@9.0.15)(react@18.2.0)
server/plugins/com.msgbyte.welcome:
dependencies:
@ -21749,10 +21749,10 @@ packages:
/immer@9.0.15:
resolution: {integrity: sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==}
dev: false
/immer@9.0.21:
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
dev: false
/import-fresh@2.0.0:
resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==}
@ -34259,8 +34259,8 @@ packages:
domino: 2.1.6
dev: false
/tushan@0.2.28(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1):
resolution: {integrity: sha512-8lYhCgOqvayRHkC6oT44WRzGWIjf0Eh565sjMLmyS5+9K10s/Wb0W1z8mPdx8N+PLDMhA2sBchc9Q3cLjJtA6g==}
/tushan@0.2.32(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.41.5)(ts-node@10.9.1):
resolution: {integrity: sha512-yTv1TTUrCSJqzaYxJ1T21pjbxlQnyiEx2bAjB4VDixhrDFMwRugBpy+bZ58imKq1NKv2SZLi8NQlIUW8x432Eg==}
dependencies:
'@arco-design/web-react': 2.49.2(@types/react@18.0.20)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query': 4.29.3(react-dom@18.2.0)(react@18.2.0)
@ -36412,7 +36412,6 @@ packages:
immer: 9.0.15
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
/zustand@4.3.6(immer@9.0.21)(react@18.2.0):
resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==}
@ -36429,6 +36428,7 @@ packages:
immer: 9.0.21
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
/zwitch@1.0.5:
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}

@ -29,7 +29,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailchat-server-sdk": "workspace:^",
"tushan": "^0.2.28",
"tushan": "^0.2.32",
"vite-express": "0.8.0"
},
"devDependencies": {

@ -11,6 +11,7 @@ import {
IconEmail,
IconFile,
IconMessage,
IconNotification,
IconSettings,
IconStorage,
IconUser,
@ -27,6 +28,7 @@ import { CacheManager } from './routes/cache';
import { TailchatNetwork } from './routes/network';
import { SocketIOAdmin } from './routes/socketio';
import { SystemConfig } from './routes/system';
import { SystemNotify } from './routes/system/notify';
const dataProvider = jsonServerProvider('/admin/api', httpClient);
@ -111,6 +113,10 @@ function App() {
<CacheManager />
</CustomRoute>
<CustomRoute name="system-notify" icon={<IconNotification />}>
<SystemNotify />
</CustomRoute>
<CustomRoute name="system" icon={<IconSettings />}>
<SystemConfig />
</CustomRoute>

@ -61,6 +61,18 @@ export const i18n: TushanContextProps['i18n'] = {
'Please be cautious in the production environment, clearing the cache may lead to increased pressure on the database in a short period of time',
cleanBtn: 'Clean Cache',
},
'system-notify': {
create: 'Create System Notify',
title: 'Title',
content: 'Content',
scope: 'Notify Scope',
allUser: 'All User',
allUserTip:
'All users excluding temporary users. Also, if there are many users, it may not be possible to notify all users at once',
specifiedUser: 'Specified User',
notifySuccess:
'Sent successfully, sent to ${data.userIds.length} users',
},
},
},
},
@ -153,6 +165,9 @@ export const i18n: TushanContextProps['i18n'] = {
cache: {
name: '缓存管理',
},
'system-notify': {
name: '系统通知',
},
},
custom: {
action: {
@ -204,6 +219,17 @@ export const i18n: TushanContextProps['i18n'] = {
'生产环境请谨慎操作, 清理缓存可能会导致短时间内数据库压力增加',
cleanBtn: '清理缓存',
},
'system-notify': {
create: '创建系统通知',
title: '标题',
content: '内容',
scope: '通知范围',
allUser: '所有用户',
allUserTip:
'所有用户不包含临时用户。另外,如果用户很多,可能会无法立即通知所有用户',
specifiedUser: '指定用户',
notifySuccess: '发送成功,已发送给 ${count} 名用户',
},
},
},
},

@ -0,0 +1,141 @@
import React from 'react';
import {
Button,
Input,
Form,
useTranslation,
Typography,
Card,
Radio,
ReferenceFieldEdit,
useAsyncRequest,
Tooltip,
Message,
} from 'tushan';
import { IconExclamationCircle } from 'tushan/icon';
import { MarkdownEditor } from '../../components/MarkdownEditor';
import { request } from '../../request';
/**
* Tailchat
*
* markdown
*/
export const SystemNotify: React.FC = React.memo(() => {
const { t } = useTranslation();
const [form] = Form.useForm();
const scope: 'all' | 'specified' = Form.useWatch('scope', form);
const [{ loading }, handleSubmit] = useAsyncRequest(async (values) => {
const { data } = await request.post('/users/system/notify', {
scope: values.scope,
specifiedUser: values.specifiedUser,
title: values.title,
content: values.content,
});
Message.success(
t('custom.system-notify.notifySuccess', { count: data.userIds.length })
);
});
return (
<Card>
<Typography.Title heading={3} style={{ textAlign: 'center' }}>
{t('custom.system-notify.create')}
</Typography.Title>
<Form form={form} onSubmit={handleSubmit}>
<Form.Item label={t('custom.system-notify.title')} field="title">
<Input name="title" />
</Form.Item>
<Form.Item
label={t('custom.system-notify.content')}
field="content"
rules={[{ required: true }]}
>
<MarkdownFormInput />
</Form.Item>
<Form.Item
label={t('custom.system-notify.scope')}
field="scope"
rules={[{ required: true }]}
initialValue="all"
>
<Radio.Group>
<Radio value="all">
{t('custom.system-notify.allUser')}
<Tooltip content={t('custom.system-notify.allUserTip')}>
<IconExclamationCircle
style={{ margin: '0 8px', color: 'rgb(var(--arcoblue-6))' }}
/>
</Tooltip>
</Radio>
<Radio value="specified">
{t('custom.system-notify.specifiedUser')}
</Radio>
</Radio.Group>
</Form.Item>
{scope === 'specified' && (
<Form.Item
label={t('custom.system-notify.specifiedUser')}
field="specifiedUser"
>
<UserSelectedFormInput />
</Form.Item>
)}
<Form.Item label={' '}>
<Button htmlType="submit" loading={loading}>
{t('tushan.common.submit')}
</Button>
</Form.Item>
</Form>
</Card>
);
});
SystemNotify.displayName = 'SystemNotify';
export const MarkdownFormInput: React.FC<{
value?: string;
onChange?: (val: string) => void;
}> = React.memo((props) => {
const value = props.value || '';
const handleChange = (newValue) => {
props.onChange && props.onChange(newValue);
};
return <MarkdownEditor value={value} onChange={handleChange} />;
});
MarkdownFormInput.displayName = 'MarkdownFormInput';
export const UserSelectedFormInput: React.FC<{
value?: string;
onChange?: (val: string) => void;
}> = React.memo((props) => {
const value = props.value || '';
const handleChange = (newValue) => {
props.onChange && props.onChange(newValue);
};
/**
* Wait for ReferenceMany
*/
return (
<ReferenceFieldEdit
value={value}
onChange={handleChange}
options={{
reference: 'users',
displayField: 'nickname',
}}
/>
);
});
UserSelectedFormInput.displayName = 'UserSelectedFormInput';

@ -121,6 +121,40 @@ router.post('/user/unban', auth(), async (req, res) => {
ret,
});
});
router.post('/users/system/notify', auth(), async (req, res) => {
const { scope, specifiedUser, title, content } = req.body;
let userIds = [];
if (scope === 'all') {
const users = await userModel.find(
{
// false 或 null(正式用户或者老的用户)
temporary: {
$ne: true,
},
},
{
_id: 1,
}
);
userIds = users.map((u) => u._id);
} else if (scope === 'specified') {
userIds = Array.isArray(specifiedUser) ? specifiedUser : [specifiedUser];
}
broker.call('chat.inbox.batchAppend', {
userIds,
type: 'markdown',
payload: {
title,
content,
},
});
res.json({ userIds });
});
router.use(
'/users',
auth(),

Loading…
Cancel
Save