mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-04 05:10:19 +08:00
Add chat page
This commit is contained in:
@ -30,6 +30,7 @@ type Chat struct {
|
|||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
|
Category string `xorm:"varchar(100)" json:"category"`
|
||||||
User1 string `xorm:"varchar(100)" json:"user1"`
|
User1 string `xorm:"varchar(100)" json:"user1"`
|
||||||
User2 string `xorm:"varchar(100)" json:"user2"`
|
User2 string `xorm:"varchar(100)" json:"user2"`
|
||||||
Users []string `xorm:"varchar(100)" json:"users"`
|
Users []string `xorm:"varchar(100)" json:"users"`
|
||||||
|
@ -17,7 +17,7 @@ import "./App.less";
|
|||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||||
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||||
import OrganizationListPage from "./OrganizationListPage";
|
import OrganizationListPage from "./OrganizationListPage";
|
||||||
@ -44,8 +44,9 @@ import SyncerListPage from "./SyncerListPage";
|
|||||||
import SyncerEditPage from "./SyncerEditPage";
|
import SyncerEditPage from "./SyncerEditPage";
|
||||||
import CertListPage from "./CertListPage";
|
import CertListPage from "./CertListPage";
|
||||||
import CertEditPage from "./CertEditPage";
|
import CertEditPage from "./CertEditPage";
|
||||||
import ChatEditPage from "./ChatEditPage";
|
|
||||||
import ChatListPage from "./ChatListPage";
|
import ChatListPage from "./ChatListPage";
|
||||||
|
import ChatEditPage from "./ChatEditPage";
|
||||||
|
import ChatPage from "./ChatPage";
|
||||||
import MessageEditPage from "./MessageEditPage";
|
import MessageEditPage from "./MessageEditPage";
|
||||||
import MessageListPage from "./MessageListPage";
|
import MessageListPage from "./MessageListPage";
|
||||||
import ProductListPage from "./ProductListPage";
|
import ProductListPage from "./ProductListPage";
|
||||||
@ -325,12 +326,17 @@ class App extends Component {
|
|||||||
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
||||||
"/account"
|
"/account"
|
||||||
));
|
));
|
||||||
|
items.push(Setting.getItem(<><CommentOutlined /> {i18next.t("account:Chats & Messages")}</>,
|
||||||
|
"/chat"
|
||||||
|
));
|
||||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||||
"/logout"));
|
"/logout"));
|
||||||
|
|
||||||
const onClick = (e) => {
|
const onClick = (e) => {
|
||||||
if (e.key === "/account") {
|
if (e.key === "/account") {
|
||||||
this.props.history.push("/account");
|
this.props.history.push("/account");
|
||||||
|
} else if (e.key === "/chat") {
|
||||||
|
this.props.history.push("/chat");
|
||||||
} else if (e.key === "/logout") {
|
} else if (e.key === "/logout") {
|
||||||
this.logout();
|
this.logout();
|
||||||
}
|
}
|
||||||
@ -547,6 +553,7 @@ class App extends Component {
|
|||||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||||
|
@ -140,6 +140,16 @@ class ChatEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.chat.category} onChange={e => {
|
||||||
|
this.updateChatField("category", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("chat:User1 - Tooltip"))} :
|
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("chat:User1 - Tooltip"))} :
|
||||||
|
@ -33,6 +33,7 @@ class ChatListPage extends BaseListPage {
|
|||||||
organization: this.props.account.owner,
|
organization: this.props.account.owner,
|
||||||
displayName: `New Chat - ${randomName}`,
|
displayName: `New Chat - ${randomName}`,
|
||||||
type: "Single",
|
type: "Single",
|
||||||
|
category: "Chat Category - 1",
|
||||||
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
||||||
user2: "",
|
user2: "",
|
||||||
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||||
@ -151,6 +152,14 @@ class ChatListPage extends BaseListPage {
|
|||||||
return i18next.t(`chat:${text}`);
|
return i18next.t(`chat:${text}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Category"),
|
||||||
|
dataIndex: "category",
|
||||||
|
key: "category",
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("category"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("chat:User1"),
|
title: i18next.t("chat:User1"),
|
||||||
dataIndex: "user1",
|
dataIndex: "user1",
|
||||||
|
78
web/src/ChatMenu.js
Normal file
78
web/src/ChatMenu.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Menu} from "antd";
|
||||||
|
import {MailOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
class ChatMenu extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
openKeys: ["0"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
chatsToItems(chats) {
|
||||||
|
const categories = {};
|
||||||
|
chats.forEach((chat) => {
|
||||||
|
if (!categories[chat.category]) {
|
||||||
|
categories[chat.category] = [];
|
||||||
|
}
|
||||||
|
categories[chat.category].push(chat);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.keys(categories).map((category, index) => {
|
||||||
|
return {
|
||||||
|
key: `${index}`,
|
||||||
|
icon: <MailOutlined />,
|
||||||
|
label: category,
|
||||||
|
children: categories[category].map((chat) => ({
|
||||||
|
key: chat.id,
|
||||||
|
label: chat.displayName,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理菜单展开事件
|
||||||
|
onOpenChange = (keys) => {
|
||||||
|
const rootSubmenuKeys = this.props.chats.map((_, index) => `${index}`);
|
||||||
|
const latestOpenKey = keys.find((key) => this.state.openKeys.indexOf(key) === -1);
|
||||||
|
|
||||||
|
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
|
||||||
|
this.setState({openKeys: keys});
|
||||||
|
} else {
|
||||||
|
this.setState({openKeys: latestOpenKey ? [latestOpenKey] : []});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const items = this.chatsToItems(this.props.chats);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
mode="inline"
|
||||||
|
openKeys={this.state.openKeys}
|
||||||
|
onOpenChange={this.onOpenChange}
|
||||||
|
style={{
|
||||||
|
width: 256,
|
||||||
|
}}
|
||||||
|
items={items}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatMenu;
|
127
web/src/ChatPage.js
Normal file
127
web/src/ChatPage.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import {Col, Row} from "antd";
|
||||||
|
// import React from "react";
|
||||||
|
import moment from "moment";
|
||||||
|
import React from "react";
|
||||||
|
import ChatMenu from "./ChatMenu";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as ChatBackend from "./backend/ChatBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class ChatListPage extends BaseListPage {
|
||||||
|
newChat() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "admin", // this.props.account.applicationName,
|
||||||
|
name: `chat_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
updatedTime: moment().format(),
|
||||||
|
organization: this.props.account.owner,
|
||||||
|
displayName: `New Chat - ${randomName}`,
|
||||||
|
type: "Single",
|
||||||
|
category: "Chat Category - 1",
|
||||||
|
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
||||||
|
user2: "",
|
||||||
|
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||||
|
messageCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addChat() {
|
||||||
|
const newChat = this.newChat();
|
||||||
|
ChatBackend.addChat(newChat)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChat(i) {
|
||||||
|
ChatBackend.deleteChat(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(chats) {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
<ChatMenu chats={this.state.data} />
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
222
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.category !== undefined && params.category !== null) {
|
||||||
|
field = "category";
|
||||||
|
value = params.category;
|
||||||
|
} else if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
isAuthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatListPage;
|
Reference in New Issue
Block a user