feat: 本次提交更新内容如下

保存项目构建完成
This commit is contained in:
笔记本里的永平
2025-07-24 21:37:22 +08:00
parent 3830c272bf
commit f5b80f616e
4 changed files with 624 additions and 569 deletions

View File

@@ -357,7 +357,9 @@ export default function NewMomentsSyncTask() {
> >
<ChevronLeft className="h-6 w-6" /> <ChevronLeft className="h-6 w-6" />
</Button> </Button>
<h1 className="ml-2 text-lg font-medium"></h1> <h1 className="ml-2 text-lg font-medium">
{isEditMode ? "编辑朋友圈同步" : "新建朋友圈同步"}
</h1>
</div> </div>
</header> </header>
@@ -436,7 +438,7 @@ export default function NewMomentsSyncTask() {
loading={loading} loading={loading}
className="flex-1 h-12 bg-blue-500 hover:bg-blue-600 rounded-lg text-white" className="flex-1 h-12 bg-blue-500 hover:bg-blue-600 rounded-lg text-white"
> >
{loading ? "创建中..." : "完成"} {loading ? (isEditMode ? "保存中..." : "创建中...") : "完成"}
</Button> </Button>
</div> </div>

View File

@@ -1,4 +1,6 @@
# 基础环境变量示例 # 基础环境变量示例
VITE_API_BASE_URL=http://www.yishi.com # VITE_API_BASE_URL=http://www.yishi.com
VITE_API_BASE_URL=https://ckbapi.quwanzhi.com
VITE_APP_TITLE=Nkebao Base VITE_APP_TITLE=Nkebao Base

View File

@@ -1,257 +1,308 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Input as AntdInput, Switch } from "antd"; import { Input as AntdInput, Switch } from "antd";
import { Button, Collapse, Toast, DatePicker, Tabs } from "antd-mobile"; import { Button, Collapse, Toast, DatePicker, Tabs } from "antd-mobile";
import NavCommon from "@/components/NavCommon"; import NavCommon from "@/components/NavCommon";
import FriendSelection from "@/components/FriendSelection"; import FriendSelection from "@/components/FriendSelection";
import GroupSelection from "@/components/GroupSelection"; import GroupSelection from "@/components/GroupSelection";
import Layout from "@/components/Layout/Layout"; import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss"; import style from "./index.module.scss";
import request from "@/api/request"; import request from "@/api/request";
import { getContentLibraryDetail, updateContentLibrary } from "./api";
const { TextArea } = AntdInput;
const { TextArea } = AntdInput;
function formatDate(date: Date | null) {
if (!date) return ""; function formatDate(date: Date | null) {
// 格式化为 YYYY-MM-DD if (!date) return "";
const y = date.getFullYear(); // 格式化为 YYYY-MM-DD
const m = (date.getMonth() + 1).toString().padStart(2, "0"); const y = date.getFullYear();
const d = date.getDate().toString().padStart(2, "0"); const m = (date.getMonth() + 1).toString().padStart(2, "0");
return `${y}-${m}-${d}`; const d = date.getDate().toString().padStart(2, "0");
} return `${y}-${m}-${d}`;
}
export default function ContentForm() {
const navigate = useNavigate(); export default function ContentForm() {
const [sourceType, setSourceType] = useState<"friends" | "groups">("friends"); const navigate = useNavigate();
const [name, setName] = useState(""); const { id } = useParams<{ id?: string }>();
const [selectedFriends, setSelectedFriends] = useState<string[]>([]); const isEdit = !!id;
const [selectedGroups, setSelectedGroups] = useState<string[]>([]); const [sourceType, setSourceType] = useState<"friends" | "groups">("friends");
const [useAI, setUseAI] = useState(false); const [name, setName] = useState("");
const [aiPrompt, setAIPrompt] = useState(""); const [selectedFriends, setSelectedFriends] = useState<string[]>([]);
const [enabled, setEnabled] = useState(true); const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
const [dateRange, setDateRange] = useState<[Date | null, Date | null]>([ const [useAI, setUseAI] = useState(false);
null, const [aiPrompt, setAIPrompt] = useState("");
null, const [enabled, setEnabled] = useState(true);
]); const [dateRange, setDateRange] = useState<[Date | null, Date | null]>([
const [showStartPicker, setShowStartPicker] = useState(false); null,
const [showEndPicker, setShowEndPicker] = useState(false); null,
const [keywordsInclude, setKeywordsInclude] = useState(""); ]);
const [keywordsExclude, setKeywordsExclude] = useState(""); const [showStartPicker, setShowStartPicker] = useState(false);
const [submitting, setSubmitting] = useState(false); const [showEndPicker, setShowEndPicker] = useState(false);
const [keywordsInclude, setKeywordsInclude] = useState("");
const handleSubmit = async (e?: React.FormEvent) => { const [keywordsExclude, setKeywordsExclude] = useState("");
if (e) e.preventDefault(); const [submitting, setSubmitting] = useState(false);
if (!name.trim()) { const [loading, setLoading] = useState(false);
Toast.show({ content: "请输入内容库名称", position: "top" });
return; // 编辑模式下拉详情并回填
} useEffect(() => {
setSubmitting(true); if (isEdit && id) {
try { setLoading(true);
const payload = { getContentLibraryDetail(id)
name, .then((data) => {
sourceType: sourceType === "friends" ? 1 : 2, setName(data.name || "");
friends: selectedFriends, setSourceType(data.sourceType === 1 ? "friends" : "groups");
groups: selectedGroups, setSelectedFriends(data.sourceFriends || []);
groupMembers: {}, setSelectedGroups(data.sourceGroups || []);
keywordInclude: keywordsInclude setKeywordsInclude((data.keywordInclude || []).join(","));
.split(/,||\n|\s+/) setKeywordsExclude((data.keywordExclude || []).join(","));
.map((s) => s.trim()) setAIPrompt(data.aiPrompt || "");
.filter(Boolean), setUseAI(!!data.aiPrompt);
keywordExclude: keywordsExclude setEnabled(data.status === 1);
.split(/,||\n|\s+/) // 时间范围
.map((s) => s.trim()) let start = data.timeStart || data.startTime;
.filter(Boolean), let end = data.timeEnd || data.endTime;
aiPrompt, setDateRange([
timeEnabled: dateRange[0] || dateRange[1] ? 1 : 0, start ? new Date(start) : null,
startTime: dateRange[0] ? formatDate(dateRange[0]) : "", end ? new Date(end) : null,
endTime: dateRange[1] ? formatDate(dateRange[1]) : "", ]);
status: enabled ? 1 : 0, })
}; .catch((e) => {
await request("/v1/content/library/create", payload, "POST"); Toast.show({
Toast.show({ content: "创建成功", position: "top" }); content: e?.message || "获取详情失败",
navigate("/content"); position: "top",
} catch (e: any) { });
Toast.show({ content: e?.message || "创建失败", position: "top" }); })
} finally { .finally(() => setLoading(false));
setSubmitting(false); }
} }, [isEdit, id]);
};
const handleSubmit = async (e?: React.FormEvent) => {
return ( if (e) e.preventDefault();
<Layout if (!name.trim()) {
header={<NavCommon title="新建内容库" />} Toast.show({ content: "请输入内容库名称", position: "top" });
footer={ return;
<div style={{ padding: "16px", backgroundColor: "#fff" }}> }
<Button setSubmitting(true);
block try {
color="primary" const payload = {
loading={submitting} name,
disabled={submitting} sourceType: sourceType === "friends" ? 1 : 2,
onClick={handleSubmit} friends: selectedFriends,
> groups: selectedGroups,
groupMembers: {},
</Button> keywordInclude: keywordsInclude
</div> .split(/,||\n|\s+/)
} .map((s) => s.trim())
> .filter(Boolean),
<div className={style["form-page"]}> keywordExclude: keywordsExclude
<form .split(/,||\n|\s+/)
className={style["form-main"]} .map((s) => s.trim())
onSubmit={(e) => e.preventDefault()} .filter(Boolean),
autoComplete="off" aiPrompt,
> timeEnabled: dateRange[0] || dateRange[1] ? 1 : 0,
<div className={style["form-section"]}> startTime: dateRange[0] ? formatDate(dateRange[0]) : "",
<label className={style["form-label"]}> endTime: dateRange[1] ? formatDate(dateRange[1]) : "",
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span> status: enabled ? 1 : 0,
};
</label> if (isEdit && id) {
<AntdInput await updateContentLibrary({ id, ...payload });
placeholder="请输入内容库名称" Toast.show({ content: "保存成功", position: "top" });
value={name} } else {
onChange={(e) => setName(e.target.value)} await request("/v1/content/library/create", payload, "POST");
className={style["input"]} Toast.show({ content: "创建成功", position: "top" });
/> }
</div> navigate("/content");
} catch (e: any) {
<div className={style["section-title"]}></div> Toast.show({
<div className={style["form-section"]}> content: e?.message || (isEdit ? "保存失败" : "创建失败"),
<Tabs position: "top",
activeKey={sourceType} });
onChange={(key) => setSourceType(key as "friends" | "groups")} } finally {
className={style["tabs-bar"]} setSubmitting(false);
> }
<Tabs.Tab title="选择微信好友" key="friends"> };
<FriendSelection
selectedFriends={selectedFriends} return (
onSelect={setSelectedFriends} <Layout
placeholder="选择微信好友" header={<NavCommon title={isEdit ? "编辑内容库" : "新建内容库"} />}
/> footer={
</Tabs.Tab> <div style={{ padding: "16px", backgroundColor: "#fff" }}>
<Tabs.Tab title="选择聊天群" key="groups"> <Button
<GroupSelection block
selectedGroups={selectedGroups} color="primary"
onSelect={setSelectedGroups} loading={submitting || loading}
placeholder="选择聊天群" disabled={submitting || loading}
/> onClick={handleSubmit}
</Tabs.Tab> >
</Tabs> {isEdit
</div> ? submitting
? "保存中..."
<Collapse : "保存内容库"
defaultActiveKey={["keywords"]} : submitting
className={style["collapse"]} ? "创建中..."
> : "创建内容库"}
<Collapse.Panel </Button>
key="keywords" </div>
title={<span className={style["form-label"]}></span>} }
> >
<div className={style["form-section"]}> <div className={style["form-page"]}>
<label className={style["form-label"]}></label> <form
<TextArea className={style["form-main"]}
placeholder="多个关键词用逗号分隔" onSubmit={(e) => e.preventDefault()}
value={keywordsInclude} autoComplete="off"
onChange={(e) => setKeywordsInclude(e.target.value)} >
className={style["input"]} <div className={style["form-section"]}>
autoSize={{ minRows: 2, maxRows: 4 }} <label className={style["form-label"]}>
/> <span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
</div>
<div className={style["form-section"]}> </label>
<label className={style["form-label"]}></label> <AntdInput
<TextArea placeholder="请输入内容库名称"
placeholder="多个关键词用逗号分隔" value={name}
value={keywordsExclude} onChange={(e) => setName(e.target.value)}
onChange={(e) => setKeywordsExclude(e.target.value)} className={style["input"]}
className={style["input"]} />
autoSize={{ minRows: 2, maxRows: 4 }} </div>
/>
</div> <div className={style["section-title"]}></div>
</Collapse.Panel> <div className={style["form-section"]}>
</Collapse> <Tabs
activeKey={sourceType}
<div className={style["section-title"]}>AI</div> onChange={(key) => setSourceType(key as "friends" | "groups")}
<div className={style["tabs-bar"]}
className={style["form-section"]} >
style={{ display: "flex", alignItems: "center", gap: 12 }} <Tabs.Tab title="选择微信好友" key="friends">
> <FriendSelection
<Switch checked={useAI} onChange={setUseAI} /> selectedFriends={selectedFriends}
<span className={style["ai-desc"]}> onSelect={setSelectedFriends}
AI后AI生成 placeholder="选择微信好友"
</span> />
</div> </Tabs.Tab>
{useAI && ( <Tabs.Tab title="选择聊天群" key="groups">
<div className={style["form-section"]}> <GroupSelection
<label className={style["form-label"]}>AI提示词</label> selectedGroups={selectedGroups}
<AntdInput onSelect={setSelectedGroups}
placeholder="请输入AI提示词" placeholder="选择聊天群"
value={aiPrompt} />
onChange={(e) => setAIPrompt(e.target.value)} </Tabs.Tab>
className={style["input"]} </Tabs>
/> </div>
</div>
)} <Collapse
defaultActiveKey={["keywords"]}
<div className={style["section-title"]}></div> className={style["collapse"]}
<div >
className={style["form-section"]} <Collapse.Panel
style={{ display: "flex", gap: 12 }} key="keywords"
> title={<span className={style["form-label"]}></span>}
<label></label> >
<div style={{ flex: 1 }}> <div className={style["form-section"]}>
<AntdInput <label className={style["form-label"]}></label>
readOnly <TextArea
value={dateRange[0] ? dateRange[0].toLocaleDateString() : ""} placeholder="多个关键词用逗号分隔"
placeholder="年/月/日" value={keywordsInclude}
className={style["input"]} onChange={(e) => setKeywordsInclude(e.target.value)}
onClick={() => setShowStartPicker(true)} className={style["input"]}
/> autoSize={{ minRows: 2, maxRows: 4 }}
<DatePicker />
visible={showStartPicker} </div>
title="开始时间" <div className={style["form-section"]}>
value={dateRange[0]} <label className={style["form-label"]}></label>
onClose={() => setShowStartPicker(false)} <TextArea
onConfirm={(val) => { placeholder="多个关键词用逗号分隔"
setDateRange([val, dateRange[1]]); value={keywordsExclude}
setShowStartPicker(false); onChange={(e) => setKeywordsExclude(e.target.value)}
}} className={style["input"]}
/> autoSize={{ minRows: 2, maxRows: 4 }}
</div> />
<label></label> </div>
<div style={{ flex: 1 }}> </Collapse.Panel>
<AntdInput </Collapse>
readOnly
value={dateRange[1] ? dateRange[1].toLocaleDateString() : ""} <div className={style["section-title"]}>AI</div>
placeholder="年/月/日" <div
className={style["input"]} className={style["form-section"]}
onClick={() => setShowEndPicker(true)} style={{ display: "flex", alignItems: "center", gap: 12 }}
/> >
<DatePicker <Switch checked={useAI} onChange={setUseAI} />
visible={showEndPicker} <span className={style["ai-desc"]}>
title="结束时间" AI后AI生成
value={dateRange[1]} </span>
onClose={() => setShowEndPicker(false)} </div>
onConfirm={(val) => { {useAI && (
setDateRange([dateRange[0], val]); <div className={style["form-section"]}>
setShowEndPicker(false); <label className={style["form-label"]}>AI提示词</label>
}} <AntdInput
/> placeholder="请输入AI提示词"
</div> value={aiPrompt}
</div> onChange={(e) => setAIPrompt(e.target.value)}
className={style["input"]}
<div />
className={style["section-title"]} </div>
style={{ )}
marginTop: 24,
marginBottom: 8, <div className={style["section-title"]}></div>
display: "flex", <div
alignItems: "center", className={style["form-section"]}
justifyContent: "space-between", style={{ display: "flex", gap: 12 }}
}} >
> <label></label>
<span></span> <div style={{ flex: 1 }}>
<Switch checked={enabled} onChange={setEnabled} /> <AntdInput
</div> readOnly
</form> value={dateRange[0] ? dateRange[0].toLocaleDateString() : ""}
</div> placeholder="年/月/日"
</Layout> className={style["input"]}
); onClick={() => setShowStartPicker(true)}
} />
<DatePicker
visible={showStartPicker}
title="开始时间"
value={dateRange[0]}
onClose={() => setShowStartPicker(false)}
onConfirm={(val) => {
setDateRange([val, dateRange[1]]);
setShowStartPicker(false);
}}
/>
</div>
<label></label>
<div style={{ flex: 1 }}>
<AntdInput
readOnly
value={dateRange[1] ? dateRange[1].toLocaleDateString() : ""}
placeholder="年/月/日"
className={style["input"]}
onClick={() => setShowEndPicker(true)}
/>
<DatePicker
visible={showEndPicker}
title="结束时间"
value={dateRange[1]}
onClose={() => setShowEndPicker(false)}
onConfirm={(val) => {
setDateRange([dateRange[0], val]);
setShowEndPicker(false);
}}
/>
</div>
</div>
<div
className={style["section-title"]}
style={{
marginTop: 24,
marginBottom: 8,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<span></span>
<Switch checked={enabled} onChange={setEnabled} />
</div>
</form>
</div>
</Layout>
);
}

View File

@@ -1,309 +1,309 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { import {
Button, Button,
Input, Input,
Card, Card,
Badge, Badge,
Avatar, Avatar,
Skeleton, Skeleton,
message, message,
Spin, Spin,
Divider, Divider,
Pagination, Pagination,
} from "antd"; } from "antd";
import { import {
LikeOutlined, LikeOutlined,
ReloadOutlined, ReloadOutlined,
SearchOutlined, SearchOutlined,
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import styles from "./record.module.scss"; import styles from "./record.module.scss";
import NavCommon from "@/components/NavCommon"; import NavCommon from "@/components/NavCommon";
import { fetchLikeRecords } from "./api"; import { fetchLikeRecords } from "./api";
import Layout from "@/components/Layout/Layout"; import Layout from "@/components/Layout/Layout";
// 格式化日期 // 格式化日期
const formatDate = (dateString: string) => { const formatDate = (dateString: string) => {
try { try {
const date = new Date(dateString); const date = new Date(dateString);
return date.toLocaleString("zh-CN", { return date.toLocaleString("zh-CN", {
year: "numeric", year: "numeric",
month: "2-digit", month: "2-digit",
day: "2-digit", day: "2-digit",
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
}); });
} catch (error) { } catch (error) {
return dateString; return dateString;
} }
}; };
export default function AutoLikeRecord() { export default function AutoLikeRecord() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [records, setRecords] = useState<any[]>([]); const [records, setRecords] = useState<any[]>([]);
const [recordsLoading, setRecordsLoading] = useState(false); const [recordsLoading, setRecordsLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const pageSize = 10; const pageSize = 10;
useEffect(() => { useEffect(() => {
if (!id) return; if (!id) return;
setRecordsLoading(true); setRecordsLoading(true);
fetchLikeRecords(id, 1, pageSize) fetchLikeRecords(id, 1, pageSize)
.then((response: any) => { .then((response: any) => {
setRecords(response.list || []); setRecords(response.list || []);
setTotal(response.total || 0); setTotal(response.total || 0);
setCurrentPage(1); setCurrentPage(1);
}) })
.catch(() => { .catch(() => {
message.error("获取点赞记录失败,请稍后重试"); message.error("获取点赞记录失败,请稍后重试");
}) })
.finally(() => setRecordsLoading(false)); .finally(() => setRecordsLoading(false));
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]); }, [id]);
const handleSearch = () => { const handleSearch = () => {
setCurrentPage(1); setCurrentPage(1);
fetchLikeRecords(id!, 1, pageSize, searchTerm) fetchLikeRecords(id!, 1, pageSize, searchTerm)
.then((response: any) => { .then((response: any) => {
setRecords(response.list || []); setRecords(response.list || []);
setTotal(response.total || 0); setTotal(response.total || 0);
setCurrentPage(1); setCurrentPage(1);
}) })
.catch(() => { .catch(() => {
message.error("获取点赞记录失败,请稍后重试"); message.error("获取点赞记录失败,请稍后重试");
}); });
}; };
const handleRefresh = () => { const handleRefresh = () => {
fetchLikeRecords(id!, currentPage, pageSize, searchTerm) fetchLikeRecords(id!, currentPage, pageSize, searchTerm)
.then((response: any) => { .then((response: any) => {
setRecords(response.list || []); setRecords(response.list || []);
setTotal(response.total || 0); setTotal(response.total || 0);
}) })
.catch(() => { .catch(() => {
message.error("获取点赞记录失败,请稍后重试"); message.error("获取点赞记录失败,请稍后重试");
}); });
}; };
const handlePageChange = (newPage: number) => { const handlePageChange = (newPage: number) => {
fetchLikeRecords(id!, newPage, pageSize, searchTerm) fetchLikeRecords(id!, newPage, pageSize, searchTerm)
.then((response: any) => { .then((response: any) => {
setRecords(response.list || []); setRecords(response.list || []);
setTotal(response.total || 0); setTotal(response.total || 0);
setCurrentPage(newPage); setCurrentPage(newPage);
}) })
.catch(() => { .catch(() => {
message.error("获取点赞记录失败,请稍后重试"); message.error("获取点赞记录失败,请稍后重试");
}); });
}; };
return ( return (
<Layout <Layout
header={ header={
<> <>
<NavCommon title="点赞记录" /> <NavCommon title="点赞记录" />
<div className={styles.headerSearchBar}> <div className={styles.headerSearchBar}>
<div className={styles.headerSearchInputWrap}> <div className={styles.headerSearchInputWrap}>
<Input <Input
prefix={<SearchOutlined className={styles.headerSearchIcon} />} prefix={<SearchOutlined className={styles.headerSearchIcon} />}
placeholder="搜索好友昵称或内容" placeholder="搜索好友昵称或内容"
className={styles.headerSearchInput} className={styles.headerSearchInput}
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
onPressEnter={handleSearch} onPressEnter={handleSearch}
allowClear allowClear
/> />
</div> </div>
<Button <Button
icon={<ReloadOutlined spin={recordsLoading} />} icon={<ReloadOutlined spin={recordsLoading} />}
onClick={handleRefresh} onClick={handleRefresh}
loading={recordsLoading} loading={recordsLoading}
type="default" type="default"
shape="circle" shape="circle"
/> />
</div> </div>
</> </>
} }
footer={ footer={
<> <>
<div className={styles.footerPagination}> <div className={styles.footerPagination}>
<Pagination <Pagination
current={currentPage} current={currentPage}
total={total} total={total}
pageSize={pageSize} pageSize={pageSize}
onChange={handlePageChange} onChange={handlePageChange}
showSizeChanger={false} showSizeChanger={false}
showQuickJumper showQuickJumper
showTotal={(total, range) => showTotal={(total, range) =>
`${range[0]}-${range[1]} 条,共 ${total}` `${range[0]}-${range[1]} 条,共 ${total}`
} }
size="default" size="default"
className={styles.pagination} className={styles.pagination}
/> />
</div> </div>
</> </>
} }
> >
<div className={styles.bgWrap}> <div className={styles.bgWrap}>
<div className={styles.contentWrap}> <div className={styles.contentWrap}>
{recordsLoading ? ( {recordsLoading ? (
<div className={styles.skeletonWrap}> <div className={styles.skeletonWrap}>
{Array.from({ length: 3 }).map((_, index) => ( {Array.from({ length: 3 }).map((_, index) => (
<div key={index} className={styles.skeletonCard}> <div key={index} className={styles.skeletonCard}>
<div className={styles.skeletonCardHeader}> <div className={styles.skeletonCardHeader}>
<Skeleton.Avatar <Skeleton.Avatar
active active
size={40} size={40}
className={styles.skeletonAvatar} className={styles.skeletonAvatar}
/> />
<div className={styles.skeletonNameWrap}> <div className={styles.skeletonNameWrap}>
<Skeleton.Input <Skeleton.Input
active active
size="small" size="small"
className={styles.skeletonName} className={styles.skeletonName}
style={{ width: 96 }} style={{ width: 96 }}
/> />
<Skeleton.Input <Skeleton.Input
active active
size="small" size="small"
className={styles.skeletonSub} className={styles.skeletonSub}
style={{ width: 64 }} style={{ width: 64 }}
/> />
</div> </div>
</div> </div>
<Divider className={styles.skeletonSep} /> <Divider className={styles.skeletonSep} />
<div className={styles.skeletonContentWrap}> <div className={styles.skeletonContentWrap}>
<Skeleton.Input <Skeleton.Input
active active
size="small" size="small"
className={styles.skeletonContent1} className={styles.skeletonContent1}
style={{ width: "100%" }} style={{ width: "100%" }}
/> />
<Skeleton.Input <Skeleton.Input
active active
size="small" size="small"
className={styles.skeletonContent2} className={styles.skeletonContent2}
style={{ width: "75%" }} style={{ width: "75%" }}
/> />
<div className={styles.skeletonImgWrap}> <div className={styles.skeletonImgWrap}>
<Skeleton.Image <Skeleton.Image
active active
className={styles.skeletonImg} className={styles.skeletonImg}
style={{ width: 80, height: 80 }} style={{ width: 80, height: 80 }}
/> />
<Skeleton.Image <Skeleton.Image
active active
className={styles.skeletonImg} className={styles.skeletonImg}
style={{ width: 80, height: 80 }} style={{ width: 80, height: 80 }}
/> />
</div> </div>
</div> </div>
</div> </div>
))} ))}
</div> </div>
) : records.length === 0 ? ( ) : records.length === 0 ? (
<div className={styles.emptyWrap}> <div className={styles.emptyWrap}>
<LikeOutlined className={styles.emptyIcon} /> <LikeOutlined className={styles.emptyIcon} />
<p className={styles.emptyText}></p> <p className={styles.emptyText}></p>
</div> </div>
) : ( ) : (
<> <>
{records.map((record) => ( {records.map((record) => (
<div key={record.id} className={styles.recordCard}> <div key={record.id} className={styles.recordCard}>
<div className={styles.recordCardHeader}> <div className={styles.recordCardHeader}>
<div className={styles.recordCardHeaderLeft}> <div className={styles.recordCardHeaderLeft}>
<Avatar <Avatar
src={record.friendAvatar || undefined} src={record.friendAvatar || undefined}
icon={<UserOutlined />} icon={<UserOutlined />}
size={40} size={40}
className={styles.avatarImg} className={styles.avatarImg}
/> />
<div className={styles.friendInfo}> <div className={styles.friendInfo}>
<div <div
className={styles.friendName} className={styles.friendName}
title={record.friendName} title={record.friendName}
> >
{record.friendName} {record.friendName}
</div> </div>
<div className={styles.friendSub}></div> <div className={styles.friendSub}></div>
</div> </div>
</div> </div>
<Badge <Badge
className={styles.timeBadge} className={styles.timeBadge}
count={formatDate(record.momentTime || record.likeTime)} count={formatDate(record.momentTime || record.likeTime)}
style={{ style={{
background: "#e8f0fe", background: "#e8f0fe",
color: "#333", color: "#333",
fontWeight: 400, fontWeight: 400,
}} }}
/> />
</div> </div>
<Divider className={styles.cardSep} /> <Divider className={styles.cardSep} />
<div className={styles.cardContent}> <div className={styles.cardContent}>
{record.content && ( {record.content && (
<p className={styles.contentText}>{record.content}</p> <p className={styles.contentText}>{record.content}</p>
)} )}
{Array.isArray(record.resUrls) && {Array.isArray(record.resUrls) &&
record.resUrls.length > 0 && ( record.resUrls.length > 0 && (
<div <div
className={ className={
`${styles.imgGrid} ` + `${styles.imgGrid} ` +
(record.resUrls.length === 1 (record.resUrls.length === 1
? styles.grid1 ? styles.grid1
: record.resUrls.length === 2 : record.resUrls.length === 2
? styles.grid2 ? styles.grid2
: record.resUrls.length <= 3 : record.resUrls.length <= 3
? styles.grid3 ? styles.grid3
: record.resUrls.length <= 6 : record.resUrls.length <= 6
? styles.grid6 ? styles.grid6
: styles.grid9) : styles.grid9)
} }
> >
{record.resUrls {record.resUrls
.slice(0, 9) .slice(0, 9)
.map((image: string, idx: number) => ( .map((image: string, idx: number) => (
<div key={idx} className={styles.imgItem}> <div key={idx} className={styles.imgItem}>
<img <img
src={image} src={image}
alt={`内容图片 ${idx + 1}`} alt={`内容图片 ${idx + 1}`}
className={styles.img} className={styles.img}
/> />
</div> </div>
))} ))}
</div> </div>
)} )}
</div> </div>
<div className={styles.operatorWrap}> <div className={styles.operatorWrap}>
<Avatar <Avatar
src={record.operatorAvatar || undefined} src={record.operatorAvatar || undefined}
icon={<UserOutlined />} icon={<UserOutlined />}
size={32} size={32}
className={styles.operatorAvatar} className={styles.operatorAvatar}
/> />
<div className={styles.operatorInfo}> <div className={styles.operatorInfo}>
<span <span
className={styles.operatorName} className={styles.operatorName}
title={record.operatorName} title={record.operatorName}
> >
{record.operatorName} {record.operatorName}
</span> </span>
<span className={styles.operatorAction}> <span className={styles.operatorAction}>
<LikeOutlined <LikeOutlined
style={{ color: "red", marginRight: 4 }} style={{ color: "red", marginRight: 4 }}
/> />
</span> </span>
</div> </div>
</div> </div>
</div> </div>
))} ))}
</> </>
)} )}
</div> </div>
</div> </div>
</Layout> </Layout>
); );
} }