diff --git a/src/components/StudentManager.tsx b/src/components/StudentManager.tsx
index 5060cf1..99fe008 100644
--- a/src/components/StudentManager.tsx
+++ b/src/components/StudentManager.tsx
@@ -14,7 +14,7 @@ import {
Select,
} from "antd"
import type { ColumnsType } from "antd/es/table"
-import { UploadOutlined, MoreOutlined, PlusOutlined } from "@ant-design/icons"
+import { UploadOutlined, MoreOutlined, PlusOutlined, CopyOutlined } from "@ant-design/icons"
import { useTranslation } from "react-i18next"
import { TagEditorDialog } from "./TagEditorDialog"
import { getAvatarFromExtraJson, setAvatarInExtraJson } from "../utils/studentAvatar"
@@ -720,6 +720,28 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
}
}
+ const handleAvatarPasteFromClipboard = async () => {
+ if (typeof navigator === "undefined" || !navigator.clipboard?.read) {
+ messageApi.error(t("students.avatarClipboardUnsupported"))
+ return
+ }
+ try {
+ const clipboardItems = await navigator.clipboard.read()
+ for (const item of clipboardItems) {
+ const imageType = item.types.find((type) => type.startsWith("image/"))
+ if (!imageType) continue
+ const blob = await item.getType(imageType)
+ const ext = imageType.split("/")[1] || "png"
+ const file = new File([blob], `avatar-clipboard.${ext}`, { type: imageType })
+ await handleAvatarFileChange(file)
+ return
+ }
+ messageApi.warning(t("students.avatarClipboardNoImage"))
+ } catch {
+ messageApi.error(t("students.avatarClipboardReadFailed"))
+ }
+ }
+
const handleSaveAvatar = async () => {
if (!(window as any).api || !avatarStudent) return
@@ -2197,6 +2219,13 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
>
{t("students.avatarUpload")}
+ }
+ onClick={handleAvatarPasteFromClipboard}
+ disabled={!canEdit}
+ >
+ {t("students.avatarClipboardImport")}
+
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 7e4b6ee..3a361a2 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -568,7 +568,11 @@
"avatarSaveFailed": "Failed to save avatar",
"avatarInvalidFile": "Please choose an image file",
"avatarTooLarge": "Image must be smaller than 2MB",
- "avatarReadFailed": "Failed to read image"
+ "avatarReadFailed": "Failed to read image",
+ "avatarClipboardImport": "Import from Clipboard",
+ "avatarClipboardUnsupported": "Clipboard image read is not supported",
+ "avatarClipboardNoImage": "No image found in clipboard",
+ "avatarClipboardReadFailed": "Failed to read clipboard image"
},
"score": {
"title": "Points Management",
diff --git a/src/i18n/locales/ja-JP.json b/src/i18n/locales/ja-JP.json
index 803974d..92b7750 100644
--- a/src/i18n/locales/ja-JP.json
+++ b/src/i18n/locales/ja-JP.json
@@ -575,7 +575,11 @@
"avatarSaveFailed": "アバターの保存に失敗しました",
"avatarInvalidFile": "画像ファイルを選択してください",
"avatarTooLarge": "画像は2MB以下にしてください",
- "avatarReadFailed": "画像の読み取りに失敗しました"
+ "avatarReadFailed": "画像の読み取りに失敗しました",
+ "avatarClipboardImport": "クリップボードから取り込み",
+ "avatarClipboardUnsupported": "この環境ではクリップボード画像を読み取れません",
+ "avatarClipboardNoImage": "クリップボードに画像がありません",
+ "avatarClipboardReadFailed": "クリップボード画像の読み取りに失敗しました"
},
"score": {
"title": "ポイント管理",
diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json
index 85b9938..52dc263 100644
--- a/src/i18n/locales/zh-CN.json
+++ b/src/i18n/locales/zh-CN.json
@@ -568,7 +568,11 @@
"avatarSaveFailed": "头像保存失败",
"avatarInvalidFile": "请选择图片文件",
"avatarTooLarge": "图片不能超过 2MB",
- "avatarReadFailed": "读取图片失败"
+ "avatarReadFailed": "读取图片失败",
+ "avatarClipboardImport": "从剪贴板导入",
+ "avatarClipboardUnsupported": "当前环境不支持读取剪贴板图片",
+ "avatarClipboardNoImage": "剪贴板中没有图片",
+ "avatarClipboardReadFailed": "读取剪贴板图片失败"
},
"score": {
"title": "积分管理",