mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 12:34:22 +08:00
feat: 头像设置支持从剪贴板导入图片
This commit is contained in:
@@ -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")}
|
||||
</Button>
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={handleAvatarPasteFromClipboard}
|
||||
disabled={!canEdit}
|
||||
>
|
||||
{t("students.avatarClipboardImport")}
|
||||
</Button>
|
||||
<Button onClick={() => setAvatarValue(null)} disabled={!canEdit}>
|
||||
{t("students.avatarClear")}
|
||||
</Button>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -575,7 +575,11 @@
|
||||
"avatarSaveFailed": "アバターの保存に失敗しました",
|
||||
"avatarInvalidFile": "画像ファイルを選択してください",
|
||||
"avatarTooLarge": "画像は2MB以下にしてください",
|
||||
"avatarReadFailed": "画像の読み取りに失敗しました"
|
||||
"avatarReadFailed": "画像の読み取りに失敗しました",
|
||||
"avatarClipboardImport": "クリップボードから取り込み",
|
||||
"avatarClipboardUnsupported": "この環境ではクリップボード画像を読み取れません",
|
||||
"avatarClipboardNoImage": "クリップボードに画像がありません",
|
||||
"avatarClipboardReadFailed": "クリップボード画像の読み取りに失敗しました"
|
||||
},
|
||||
"score": {
|
||||
"title": "ポイント管理",
|
||||
|
||||
@@ -568,7 +568,11 @@
|
||||
"avatarSaveFailed": "头像保存失败",
|
||||
"avatarInvalidFile": "请选择图片文件",
|
||||
"avatarTooLarge": "图片不能超过 2MB",
|
||||
"avatarReadFailed": "读取图片失败"
|
||||
"avatarReadFailed": "读取图片失败",
|
||||
"avatarClipboardImport": "从剪贴板导入",
|
||||
"avatarClipboardUnsupported": "当前环境不支持读取剪贴板图片",
|
||||
"avatarClipboardNoImage": "剪贴板中没有图片",
|
||||
"avatarClipboardReadFailed": "读取剪贴板图片失败"
|
||||
},
|
||||
"score": {
|
||||
"title": "积分管理",
|
||||
|
||||
Reference in New Issue
Block a user