feat: add global announcement

pull/109/head
moonrailgun 2 years ago
parent 4c77d144e8
commit 798abeb1ec

@ -266,6 +266,7 @@
"kaefc1e64": "Password reset successful, now back to login page",
"kaf403ef0": "Light Mode",
"kaf51d834": "Reset to default",
"kb01f8383": "Learn More",
"kb030fbd1": "Member List",
"kb0584341": "Usage Count",
"kb07659b0": "Repeat password",
@ -357,7 +358,6 @@
"ke3d797fd": "Drop files to send into current converse",
"ke59ffe49": "Muted, there are {{remain}} left",
"ke6da074f": "The message was withdrawn successfully",
"kea977d95": "The following users are offline",
"kec46a57f": "Add members",
"kecb51e2c": "Old password",
"kecbb0e45": "System",

@ -266,6 +266,7 @@
"kaefc1e64": "密码重置成功,现在回到登录页",
"kaf403ef0": "亮色模式",
"kaf51d834": "重置为默认值",
"kb01f8383": "了解更多",
"kb030fbd1": "成员列表",
"kb0584341": "使用次数",
"kb07659b0": "重复密码",
@ -357,7 +358,6 @@
"ke3d797fd": "拖放文件以发送到当前会话",
"ke59ffe49": "禁言中, 还剩 {{remain}}",
"ke6da074f": "消息撤回成功",
"kea977d95": "以下用户已离线",
"kec46a57f": "添加成员",
"kecb51e2c": "旧密码",
"kecbb0e45": "系统",

@ -50,6 +50,14 @@ export interface GlobalConfig {
*
*/
disableAddFriend?: boolean;
announcement?:
| false
| {
id: string;
text: string;
link?: string;
};
}
export function getGlobalConfig(): GlobalConfig {

@ -27,4 +27,5 @@ export const defaultGlobalConfig: GlobalConfig = {
disableCreateGroup: false,
disablePluginStore: false,
disableAddFriend: false,
announcement: false,
};

@ -0,0 +1,43 @@
import { useLocalStorageState } from '@/hooks/useLocalStorage';
import { Button } from 'antd';
import React from 'react';
import { Icon } from 'tailchat-design';
import { t, useGlobalConfigStore } from 'tailchat-shared';
export const GlobalAnnouncementBar: React.FC = React.memo(() => {
const announcementInfo = useGlobalConfigStore((state) => state.announcement);
const [ackId, setAckId] = useLocalStorageState('ackGlobalAnnouncement');
if (!announcementInfo) {
return null;
}
if (ackId === announcementInfo.id) {
// 如果该公告已读,也不展示
return null;
}
return (
<div className="text-center bg-indigo-400 text-white relative">
{announcementInfo.text}
{announcementInfo.link && (
<Button
type="link"
className="text-indigo-700 font-bold ml-2"
size="small"
onClick={() => window.open(announcementInfo.link)}
>
{t('了解更多')}
</Button>
)}
<Icon
className="absolute top-0.5 right-1 opacity-80 hover:opacity-100 cursor-pointer text-xl"
icon="mdi:close-circle-outline"
onClick={() => setAckId(announcementInfo.id)}
/>
</div>
);
});
GlobalAnnouncementBar.displayName = 'GlobalAnnouncementBar';

@ -1,3 +1,4 @@
import { GlobalAnnouncementBar } from '@/components/GlobalAnnouncementBar';
import { GlobalTemporaryTip } from '@/components/GlobalTemporaryTip';
import { useRecordMeasure } from '@/utils/measure-helper';
import React from 'react';
@ -17,6 +18,8 @@ const MainRoute: React.FC = React.memo(() => {
<div className="flex flex-col h-full">
<GlobalTemporaryTip />
<GlobalAnnouncementBar />
<div className="flex flex-1 overflow-hidden">
<Navbar />

@ -65,6 +65,13 @@ export const i18n: TushanContextProps['i18n'] = {
allowCreateGroup: 'Allow Create Group',
serverName: 'Server Name',
serverEntryImage: 'Server Entry Page Image',
configPanel: 'Config',
announcementPanel: 'Announcement',
announcementEnable: 'Is Enable Announcement',
announcementText: 'Announcement Text',
announcementLink: 'Announcement Link',
announcementLinkTip:
'This content is optional, and it is the address to announce more content',
},
cache: {
cleanTitle: 'Are you sure you want to clear the cache?',
@ -235,6 +242,12 @@ export const i18n: TushanContextProps['i18n'] = {
allowCreateGroup: '允许创建群组',
serverName: '服务器名',
serverEntryImage: '服务器登录图',
configPanel: '配置',
announcementPanel: '公告',
announcementEnable: '是否启用公告',
announcementText: '公告文本',
announcementLink: '公告链接',
announcementLinkTip: '该内容可选,为公告更多内容的地址',
},
cache: {
cleanTitle: '确定要清理缓存么?',

@ -11,7 +11,10 @@ import {
Upload,
useTranslation,
Card,
Tabs,
Switch,
} from 'tushan';
import _get from 'lodash/get';
import { IconCheck, IconClose, IconDelete } from 'tushan/icon';
import { TailchatImage } from '../../components/TailchatImage';
@ -45,7 +48,7 @@ export const SystemConfig: React.FC = React.memo(() => {
value: val,
});
fetchConfig();
Message.success('Success');
Message.success(t('tushan.common.success'));
} catch (err) {
console.log(err);
Message.error(String(err));
@ -88,6 +91,31 @@ export const SystemConfig: React.FC = React.memo(() => {
}
);
const [{}, handleChangeAnnouncement] = useAsyncRequest(
async (values: { enable: boolean; link: string; text: string }) => {
console.log(values);
const { enable = false, link = '', text = '' } = values;
if (enable) {
await request.patch('/config/client', {
key: 'announcement',
value: {
id: Date.now(),
text,
link,
},
});
} else {
await request.patch('/config/client', {
key: 'announcement',
value: false,
});
}
Message.success(t('tushan.common.success'));
}
);
if (loading) {
return <Spin />;
}
@ -99,71 +127,118 @@ export const SystemConfig: React.FC = React.memo(() => {
return (
<Card>
<Form>
<Form.Item label={t('custom.config.uploadFileLimit')}>
{config.uploadFileLimit}
</Form.Item>
<Form.Item label={t('custom.config.emailVerification')}>
{config.emailVerification ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.allowGuestLogin')}>
{!config.disableGuestLogin ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.allowUserRegister')}>
{!config.disableUserRegister ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.allowCreateGroup')}>
{!config.disableCreateGroup ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.serverName')}>
<Input
value={serverName}
onChange={(val) => setServerName(val)}
onBlur={() => saveServerName()}
placeholder="Tailchat"
/>
</Form.Item>
<Form.Item label={t('custom.config.serverEntryImage')}>
<div>
{config?.serverEntryImage ? (
<div style={{ marginTop: 10 }}>
<div>
<TailchatImage
style={{
maxWidth: '100%',
maxHeight: 360,
overflow: 'hidden',
marginBottom: 4,
<Tabs>
<Tabs.TabPane key={0} title={t('custom.config.configPanel')}>
<Form>
<Form.Item label={t('custom.config.uploadFileLimit')}>
{config.uploadFileLimit}
</Form.Item>
<Form.Item label={t('custom.config.emailVerification')}>
{config.emailVerification ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.allowGuestLogin')}>
{!config.disableGuestLogin ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.allowUserRegister')}>
{!config.disableUserRegister ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.allowCreateGroup')}>
{!config.disableCreateGroup ? <IconCheck /> : <IconClose />}
</Form.Item>
<Form.Item label={t('custom.config.serverName')}>
<Input
value={serverName}
onChange={(val) => setServerName(val)}
onBlur={() => saveServerName()}
placeholder="Tailchat"
/>
</Form.Item>
<Form.Item label={t('custom.config.serverEntryImage')}>
<div>
{config?.serverEntryImage ? (
<div style={{ marginTop: 10 }}>
<div>
<TailchatImage
style={{
maxWidth: '100%',
maxHeight: 360,
overflow: 'hidden',
marginBottom: 4,
}}
src={config?.serverEntryImage}
/>
</div>
<Button
type="primary"
icon={<IconDelete />}
onClick={() => handleChangeServerEntryImage(null)}
>
Delete
</Button>
</div>
) : (
<Upload
onChange={(_, file) => {
handleChangeServerEntryImage(file.originFile);
}}
src={config?.serverEntryImage}
/>
</div>
<Button
type="primary"
icon={<IconDelete />}
onClick={() => handleChangeServerEntryImage(null)}
>
Delete
</Button>
)}
</div>
) : (
<Upload
onChange={(_, file) => {
handleChangeServerEntryImage(file.originFile);
}}
/>
)}
</div>
</Form.Item>
</Form>
</Form.Item>
</Form>
</Tabs.TabPane>
<Tabs.TabPane key={1} title={t('custom.config.announcementPanel')}>
<Form
initialValues={
config['announcement']
? {
enable: true,
text: _get(config, ['announcement', 'text'], ''),
link: _get(config, ['announcement', 'link'], ''),
}
: { enable: false, text: '', link: '' }
}
onSubmit={handleChangeAnnouncement}
>
<Form.Item
label={t('custom.config.announcementEnable')}
field="enable"
>
<SwitchFormInput />
</Form.Item>
<Form.Item label={t('custom.config.announcementText')} field="text">
<Input maxLength={120} />
</Form.Item>
<Form.Item
label={t('custom.config.announcementLink')}
field="link"
tooltip={t('custom.config.announcementLinkTip')}
>
<Input placeholder="https://tailchat.msgbyte.com/" />
</Form.Item>
<Form.Item label={' '}>
<Button htmlType="submit">{t('tushan.common.submit')}</Button>
</Form.Item>
</Form>
</Tabs.TabPane>
</Tabs>
</Card>
);
});
SystemConfig.displayName = 'SystemConfig';
export const SwitchFormInput: React.FC<{
value?: boolean;
onChange?: (val: boolean) => void;
}> = React.memo((props) => {
return <Switch checked={props.value} onChange={props.onChange} />;
});
SwitchFormInput.displayName = 'SwitchFormInput';

Loading…
Cancel
Save