|
|
@@ -8,15 +8,16 @@ import {
|
|
|
} from "@ant-design/icons";
|
|
|
import {
|
|
|
App,
|
|
|
- AutoComplete,
|
|
|
Button,
|
|
|
Card,
|
|
|
Col,
|
|
|
+ DatePicker,
|
|
|
Empty,
|
|
|
Form,
|
|
|
Image,
|
|
|
- Input,
|
|
|
+ Popover,
|
|
|
Row,
|
|
|
+ Select,
|
|
|
Space,
|
|
|
Spin,
|
|
|
Table,
|
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
Typography,
|
|
|
} from "antd";
|
|
|
import type { ColumnsType } from "antd/es/table";
|
|
|
+import type { Dayjs } from "dayjs";
|
|
|
import type React from "react";
|
|
|
import { useMemo, useRef, useState } from "react";
|
|
|
import UserBrief from "@/components/UserBrief";
|
|
|
@@ -32,6 +34,7 @@ import { getUserPage } from "@/services/user";
|
|
|
import type { ImChatInfoVo, UserChatSessionItemVo } from "@/types/api";
|
|
|
import dayjs from "@/utils/dayjs";
|
|
|
|
|
|
+const { RangePicker } = DatePicker;
|
|
|
const { Text, Paragraph } = Typography;
|
|
|
|
|
|
function formatUnixSeconds(sec?: number): string {
|
|
|
@@ -193,13 +196,11 @@ function getChatRowKey(c: ImChatInfoVo): string {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
-function getWeekRangeByPage(
|
|
|
- page: number,
|
|
|
-): [ReturnType<typeof dayjs>, ReturnType<typeof dayjs>] {
|
|
|
+function getWeekRangeByPage(page: number): [Dayjs, Dayjs] {
|
|
|
const safe = Math.max(1, Math.floor(page || 1));
|
|
|
const end = dayjs().subtract((safe - 1) * 7, "day");
|
|
|
const start = end.subtract(7, "day");
|
|
|
- return [start, end];
|
|
|
+ return [start, end] as [Dayjs, Dayjs];
|
|
|
}
|
|
|
|
|
|
const ImRecordPage: React.FC = () => {
|
|
|
@@ -231,6 +232,8 @@ const ImRecordPage: React.FC = () => {
|
|
|
const [historyLoading, setHistoryLoading] = useState(false);
|
|
|
// "Page" means week index: 1 => last 7 days, 2 => 7-14 days ago, ...
|
|
|
const [historyWeekPage, setHistoryWeekPage] = useState(1);
|
|
|
+ const [historyMode, setHistoryMode] = useState<"week" | "custom">("week");
|
|
|
+ const [customRange, setCustomRange] = useState<[Dayjs, Dayjs] | null>(null);
|
|
|
const [chatInfos, setChatInfos] = useState<ImChatInfoVo[]>([]);
|
|
|
|
|
|
const sessionsReqId = useRef(0);
|
|
|
@@ -271,6 +274,8 @@ const ImRecordPage: React.FC = () => {
|
|
|
|
|
|
const resetHistory = () => {
|
|
|
setHistoryWeekPage(1);
|
|
|
+ setHistoryMode("week");
|
|
|
+ setCustomRange(null);
|
|
|
setChatInfos([]);
|
|
|
};
|
|
|
|
|
|
@@ -384,8 +389,8 @@ const ImRecordPage: React.FC = () => {
|
|
|
await loadSessions(nextPage);
|
|
|
};
|
|
|
|
|
|
- const loadHistoryWeek = async (
|
|
|
- weekPage: number,
|
|
|
+ const loadHistoryRange = async (
|
|
|
+ range: [Dayjs, Dayjs],
|
|
|
fromAccountInput?: string,
|
|
|
toAccountInput?: string,
|
|
|
sessionTypeInput?: number,
|
|
|
@@ -403,7 +408,8 @@ const ImRecordPage: React.FC = () => {
|
|
|
const reqId = ++historyReqId.current;
|
|
|
setHistoryLoading(true);
|
|
|
try {
|
|
|
- const [start, end] = getWeekRangeByPage(weekPage);
|
|
|
+ const start = range[0];
|
|
|
+ const end = range[1];
|
|
|
const minTime = start.unix();
|
|
|
const maxTime = end.unix();
|
|
|
|
|
|
@@ -411,7 +417,7 @@ const ImRecordPage: React.FC = () => {
|
|
|
let next: string | undefined;
|
|
|
let guard = 0;
|
|
|
|
|
|
- // Pull all cursor pages within the week window.
|
|
|
+ // Pull all cursor pages within the range window.
|
|
|
while (guard < 200) {
|
|
|
guard += 1;
|
|
|
const res = await getChatHistory({
|
|
|
@@ -453,10 +459,27 @@ const ImRecordPage: React.FC = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ const loadHistoryWeek = async (
|
|
|
+ weekPage: number,
|
|
|
+ fromAccountInput?: string,
|
|
|
+ toAccountInput?: string,
|
|
|
+ sessionTypeInput?: number,
|
|
|
+ ) => {
|
|
|
+ const [start, end] = getWeekRangeByPage(weekPage);
|
|
|
+ await loadHistoryRange(
|
|
|
+ [start, end],
|
|
|
+ fromAccountInput,
|
|
|
+ toAccountInput,
|
|
|
+ sessionTypeInput,
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
const selectSession = (session: UserChatSessionItemVo | null) => {
|
|
|
setSelectedSession(session);
|
|
|
|
|
|
setHistoryWeekPage(1);
|
|
|
+ setHistoryMode("week");
|
|
|
+ setCustomRange(null);
|
|
|
setChatInfos([]);
|
|
|
|
|
|
if (!session) {
|
|
|
@@ -482,6 +505,11 @@ const ImRecordPage: React.FC = () => {
|
|
|
|
|
|
setChatInfos([]);
|
|
|
|
|
|
+ if (historyMode === "custom" && customRange) {
|
|
|
+ await loadHistoryRange(customRange);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
await loadHistoryWeek(historyWeekPage);
|
|
|
};
|
|
|
|
|
|
@@ -489,6 +517,8 @@ const ImRecordPage: React.FC = () => {
|
|
|
if (!selectedSession) return;
|
|
|
if (historyWeekPage <= 1) return;
|
|
|
const nextPage = historyWeekPage - 1;
|
|
|
+ setHistoryMode("week");
|
|
|
+ setCustomRange(null);
|
|
|
setHistoryWeekPage(nextPage);
|
|
|
setChatInfos([]);
|
|
|
await loadHistoryWeek(nextPage);
|
|
|
@@ -497,11 +527,22 @@ const ImRecordPage: React.FC = () => {
|
|
|
const handleNextWeek = async () => {
|
|
|
if (!selectedSession) return;
|
|
|
const nextPage = historyWeekPage + 1;
|
|
|
+ setHistoryMode("week");
|
|
|
+ setCustomRange(null);
|
|
|
setHistoryWeekPage(nextPage);
|
|
|
setChatInfos([]);
|
|
|
await loadHistoryWeek(nextPage);
|
|
|
};
|
|
|
|
|
|
+ const handleSelectCustomRange = async (range: [Dayjs, Dayjs] | null) => {
|
|
|
+ if (!selectedSession) return;
|
|
|
+ if (!range) return;
|
|
|
+ setHistoryMode("custom");
|
|
|
+ setCustomRange(range);
|
|
|
+ setChatInfos([]);
|
|
|
+ await loadHistoryRange(range);
|
|
|
+ };
|
|
|
+
|
|
|
const sessionColumns: ColumnsType<UserChatSessionItemVo> = [
|
|
|
{
|
|
|
title: "用户",
|
|
|
@@ -540,10 +581,16 @@ const ImRecordPage: React.FC = () => {
|
|
|
{
|
|
|
title: "消息",
|
|
|
dataIndex: "msgBody",
|
|
|
- render: (v: unknown) => {
|
|
|
+ render: (v: unknown, record) => {
|
|
|
const rawText = safeJsonStringify(v);
|
|
|
+ const from = String(record?.from_Account || "").trim();
|
|
|
+ const me = String(currentUserNo || "").trim();
|
|
|
+ const isSentByMe = !!me && from === me;
|
|
|
return (
|
|
|
<Space size={8} style={{ width: "100%" }}>
|
|
|
+ <Tag color={isSentByMe ? "blue" : "default"}>
|
|
|
+ {isSentByMe ? "发送" : "接收"}
|
|
|
+ </Tag>
|
|
|
{renderParsedMsgBody(v)}
|
|
|
<Button
|
|
|
type="link"
|
|
|
@@ -592,21 +639,31 @@ const ImRecordPage: React.FC = () => {
|
|
|
label="用户"
|
|
|
rules={[{ required: true, message: "请先搜索用户" }]}
|
|
|
>
|
|
|
- <AutoComplete
|
|
|
+ <Select
|
|
|
style={{ width: 320 }}
|
|
|
placeholder="输入用户编号 / 昵称 / 邮箱搜索"
|
|
|
+ showSearch
|
|
|
+ filterOption={false}
|
|
|
options={userOptions}
|
|
|
onSearch={handleUserNoSearch}
|
|
|
allowClear
|
|
|
notFoundContent={
|
|
|
userSearchLoading ? <Spin size="small" /> : <Empty />
|
|
|
}
|
|
|
+ optionLabelProp="label"
|
|
|
onSelect={(value) => {
|
|
|
void loadSessionsForUser(String(value || ""));
|
|
|
}}
|
|
|
- >
|
|
|
- <Input />
|
|
|
- </AutoComplete>
|
|
|
+ onClear={() => {
|
|
|
+ setCurrentUserNo("");
|
|
|
+ setSelectedSession(null);
|
|
|
+ resetHistory();
|
|
|
+ setSessionsPage(1);
|
|
|
+ setSessionsTokenByPage({ 1: undefined });
|
|
|
+ setSessionsHasNext(false);
|
|
|
+ setSessions([]);
|
|
|
+ }}
|
|
|
+ />
|
|
|
</Form.Item>
|
|
|
<Form.Item>
|
|
|
<Space>
|
|
|
@@ -684,6 +741,27 @@ const ImRecordPage: React.FC = () => {
|
|
|
title="聊天记录"
|
|
|
extra={
|
|
|
<Space>
|
|
|
+ <Popover
|
|
|
+ trigger="click"
|
|
|
+ placement="bottomRight"
|
|
|
+ content={
|
|
|
+ <RangePicker
|
|
|
+ showTime
|
|
|
+ style={{ width: 360 }}
|
|
|
+ value={customRange}
|
|
|
+ onChange={(dates) => {
|
|
|
+ const d = dates as [Dayjs, Dayjs] | null;
|
|
|
+ if (d?.[0] && d?.[1]) void handleSelectCustomRange(d);
|
|
|
+ }}
|
|
|
+ allowClear
|
|
|
+ disabled={!selectedSession || historyLoading}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Button disabled={!selectedSession} loading={historyLoading}>
|
|
|
+ 自选范围
|
|
|
+ </Button>
|
|
|
+ </Popover>
|
|
|
<Button
|
|
|
onClick={handlePrevWeek}
|
|
|
disabled={!selectedSession || historyWeekPage <= 1}
|
|
|
@@ -708,7 +786,10 @@ const ImRecordPage: React.FC = () => {
|
|
|
</Form.Item>
|
|
|
<Form.Item label="时间范围">
|
|
|
{(() => {
|
|
|
- const [start, end] = getWeekRangeByPage(historyWeekPage);
|
|
|
+ const [start, end] =
|
|
|
+ historyMode === "custom" && customRange
|
|
|
+ ? customRange
|
|
|
+ : getWeekRangeByPage(historyWeekPage);
|
|
|
return (
|
|
|
<Text type="secondary">
|
|
|
{start.format("YYYY-MM-DD")} ~{" "}
|