fix: 修复代码审查发现的所有 bug

Critical 级别:
- zhipu.ts: 添加 API 响应边界检查
- DictionaryClient.tsx: 添加 entries 数组边界检查
- subtitleParser.ts: 修复 getNearestIndex 逻辑错误

High 级别:
- text-speaker/page.tsx: 修复非空断言和 ref 检查
- folder-repository.ts: 添加 user 关系 null 检查

Medium 级别:
- InFolder.tsx: 修复 throw result.message 为 throw new Error()
- localStorageOperators.ts: 返回类型改为 T | null,添加 schema 验证
- SaveList.tsx: 处理 data 可能为 null 的情况
This commit is contained in:
2026-03-09 19:11:49 +08:00
parent 020744b353
commit c83aefabfa
8 changed files with 75 additions and 59 deletions

View File

@@ -90,11 +90,11 @@ export function DictionaryClient({ initialFolders }: DictionaryClientProps) {
const folderSelect = document.getElementById("folder-select") as HTMLSelectElement;
const folderId = folderSelect?.value ? Number(folderSelect.value) : folders[0]?.id;
if (!searchResult) return;
if (!searchResult?.entries?.length) return;
const definition = searchResult.entries.reduce((p, e) => {
return { ...p, definition: p.definition + ' | ' + e.definition };
}).definition;
const definition = searchResult.entries
.map((e) => e.definition)
.join(" | ");
try {
await actionCreatePair({
@@ -102,7 +102,7 @@ export function DictionaryClient({ initialFolders }: DictionaryClientProps) {
text2: definition,
language1: queryLang,
language2: definitionLang,
ipa1: searchResult.entries[0].ipa,
ipa1: searchResult.entries[0]?.ipa,
folderId: folderId,
});

View File

@@ -62,13 +62,12 @@ export function getNearestIndex(
): number | null {
for (let i = 0; i < subtitles.length; i++) {
const subtitle = subtitles[i];
const isBefore = currentTime - subtitle.start >= 0;
const isAfter = currentTime - subtitle.end >= 0;
const isWithin = currentTime >= subtitle.start && currentTime <= subtitle.end;
if (!isBefore || !isAfter) return i - 1;
if (isBefore && !isAfter) return i;
if (isWithin) return i;
if (currentTime < subtitle.start) return i > 0 ? i - 1 : null;
}
return null;
return subtitles.length > 0 ? subtitles.length - 1 : null;
}
export function getCurrentSubtitle(

View File

@@ -60,11 +60,12 @@ export function SaveList({ show = false, handleUse }: SaveListProps) {
const [data, setData] = useState(getFromLocalStorage());
const handleDel = (item: z.infer<typeof TextSpeakerItemSchema>) => {
const current_data = getFromLocalStorage();
if (!current_data) return;
current_data.splice(
current_data.findIndex((v) => v.text === item.text),
1,
);
const index = current_data.findIndex((v) => v.text === item.text);
if (index === -1) return;
current_data.splice(index, 1);
setIntoLocalStorage(current_data);
refresh();
};
@@ -78,33 +79,25 @@ export function SaveList({ show = false, handleUse }: SaveListProps) {
refresh();
}
};
if (show)
if (show && data)
return (
<div
className="my-4 p-2 mx-4 md:mx-32 border border-gray-200 rounded-lg"
style={{ fontFamily: "Times New Roman, serif" }}
>
<div className="flex flex-row justify-center gap-8 items-center">
<IconClick
src={IMAGES.refresh}
alt="refresh"
onClick={refresh}
size="lg"
className=""
></IconClick>
<IconClick
src={IMAGES.delete}
alt="delete"
<div className="flex justify-between items-center mb-2">
<p className="text-sm text-gray-600">{t("saved")}</p>
<button
onClick={handleDeleteAll}
size="lg"
className=""
></IconClick>
className="text-xs text-gray-500 hover:text-gray-800"
>
{t("clearAll")}
</button>
</div>
<ul>
{data.map((v) => (
<ul className="divide-y divide-gray-100">
{data.map((item, i) => (
<TextCard
item={v}
key={crypto.randomUUID()}
key={i}
item={item}
handleUse={handleUse}
handleDel={handleDel}
></TextCard>

View File

@@ -48,8 +48,8 @@ export default function TextSpeakerPage() {
const handleEnded = () => {
if (autopause) {
setPause(true);
} else {
load(objurlRef.current!);
} else if (objurlRef.current) {
load(objurlRef.current);
play();
}
};
@@ -187,7 +187,7 @@ export default function TextSpeakerPage() {
theIPA = tmp_ipa;
}
const save = getFromLocalStorage();
const save = getFromLocalStorage() ?? [];
const oldIndex = save.findIndex((v) => v.text === textRef.current);
if (oldIndex !== -1) {
const oldItem = save[oldIndex];
@@ -293,7 +293,7 @@ export default function TextSpeakerPage() {
size="lg"
onClick={() => {
setAutopause(!autopause);
if (objurlRef) {
if (objurlRef.current) {
stop();
}
setPause(true);