添加 Android 打包工作流并实现响应式设计

- 在 GitHub Actions 中添加 Android 打包工作流,支持 ARM64、ARMv7 和 x86_64 架构
- 创建 CI 脚本用于解析提交信息和应用版本号
- 移除标题栏的旋转屏幕按钮,简化窗口控制
- 创建响应式设计 hooks (useResponsive) 用于检测屏幕尺寸
- 更新 StudentManager 组件以支持移动端响应式布局
- 在移动设备上自动调整表格列宽、按钮大小和字体大小
- 移动端隐藏标签列以节省空间
- 按钮在移动端使用垂直布局和更小的尺寸
This commit is contained in:
Fox_block
2026-03-20 21:02:52 +08:00
parent bb156f7ffd
commit 73db5778f1
8 changed files with 492 additions and 175 deletions
+147 -97
View File
@@ -5,6 +5,7 @@ import { UploadOutlined } from "@ant-design/icons"
import { useTranslation } from "react-i18next"
import { TagEditorDialog } from "./TagEditorDialog"
import { getAvatarFromExtraJson, setAvatarInExtraJson } from "../utils/studentAvatar"
import { useResponsive, useIsMobile, useIsTablet } from "../hooks/useResponsive"
const createXlsxWorker = () => {
return new Worker(new URL("../workers/xlsxWorker.ts", import.meta.url), {
@@ -48,6 +49,9 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
const xlsxWorkerRef = useRef<Worker | null>(null)
const [form] = Form.useForm()
const [messageApi, contextHolder] = message.useMessage()
const isMobile = useIsMobile()
const isTablet = useIsTablet()
const breakpoint = useResponsive()
useEffect(() => {
xlsxWorkerRef.current = createXlsxWorker()
@@ -470,107 +474,148 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
}
}
const columns: ColumnsType<student> = [
{
title: t("students.avatar"),
key: "avatar",
width: 88,
align: "center",
render: (_, row) =>
row.avatarUrl ? (
<img
src={row.avatarUrl}
alt={row.name}
const columns: ColumnsType<student> = useMemo(() => {
const baseColumns: ColumnsType<student> = [
{
title: t("students.avatar"),
key: "avatar",
width: isMobile ? 60 : 88,
align: "center",
render: (_, row) =>
row.avatarUrl ? (
<img
src={row.avatarUrl}
alt={row.name}
style={{
width: isMobile ? 24 : 32,
height: isMobile ? 24 : 32,
borderRadius: "50%",
objectFit: "cover",
border: "1px solid var(--ss-border-color)",
}}
/>
) : (
<div
style={{
width: isMobile ? 24 : 32,
height: isMobile ? 24 : 32,
borderRadius: "50%",
margin: "0 auto",
backgroundColor: "var(--ss-bg-color)",
border: "1px dashed var(--ss-border-color)",
color: "var(--ss-text-secondary)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: isMobile ? 10 : 12,
}}
>
-
</div>
),
},
{
title: t("students.name"),
dataIndex: "name",
key: "name",
width: isMobile ? 80 : 100,
ellipsis: true,
},
{
title: t("students.currentScore"),
dataIndex: "score",
key: "score",
width: isMobile ? 100 : 160,
align: "center",
render: (score: number) => (
<span
style={{
width: 32,
height: 32,
borderRadius: "50%",
objectFit: "cover",
border: "1px solid var(--ss-border-color)",
}}
/>
) : (
<div
style={{
width: 32,
height: 32,
borderRadius: "50%",
margin: "0 auto",
backgroundColor: "var(--ss-bg-color)",
border: "1px dashed var(--ss-border-color)",
color: "var(--ss-text-secondary)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 12,
fontWeight: "bold",
color:
score > 0
? "var(--ant-color-success, #52c41a)"
: score < 0
? "var(--ant-color-error, #ff4d4f)"
: "inherit",
fontSize: isMobile ? 12 : 14,
}}
>
-
</div>
{score > 0 ? `+${score}` : score}
</span>
),
},
{ title: t("students.name"), dataIndex: "name", key: "name", width: 100 },
{
title: t("students.currentScore"),
dataIndex: "score",
key: "score",
width: 160,
align: "center",
render: (score: number) => (
<span
style={{
fontWeight: "bold",
color:
score > 0
? "var(--ant-color-success, #52c41a)"
: score < 0
? "var(--ant-color-error, #ff4d4f)"
: "inherit",
}}
>
{score > 0 ? `+${score}` : score}
</span>
),
},
{
title: t("students.tags"),
dataIndex: "tags",
key: "tags",
width: 200,
render: (tags: string[] = []) => (
<Space>
{tags.length === 0 ? (
<span style={{ color: "var(--ss-text-secondary)" }}>{t("students.noTags")}</span>
) : (
tags.slice(0, 3).map((tag) => (
<Tag key={tag} color="blue">
{tag}
},
{
title: t("students.tags"),
dataIndex: "tags",
key: "tags",
width: isMobile ? 120 : 200,
render: (tags: string[] = []) => (
<Space size={isMobile ? 2 : 4}>
{tags.length === 0 ? (
<span style={{ color: "var(--ss-text-secondary)", fontSize: isMobile ? 10 : 12 }}>
{t("students.noTags")}
</span>
) : (
tags.slice(0, isMobile ? 2 : 3).map((tag) => (
<Tag key={tag} color="blue" style={{ fontSize: isMobile ? 10 : 12, margin: 0 }}>
{tag}
</Tag>
))
)}
{tags.length > (isMobile ? 2 : 3) && (
<Tag style={{ fontSize: isMobile ? 10 : 12, margin: 0 }}>
+{tags.length - (isMobile ? 2 : 3)}
</Tag>
))
)}
{tags.length > 3 && <Tag>+{tags.length - 3}</Tag>}
</Space>
),
},
{
title: t("common.operation"),
key: "operation",
width: 220,
render: (_, row) => (
<Space>
<Button type="link" disabled={!canEdit} onClick={() => handleOpenTagEditor(row)}>
{t("students.editTags")}
</Button>
<Button type="link" disabled={!canEdit} onClick={() => handleOpenAvatarEditor(row)}>
{t("students.editAvatar")}
</Button>
<Button type="link" danger disabled={!canEdit} onClick={() => handleDelete(row.id)}>
{t("common.delete")}
</Button>
</Space>
),
},
]
)}
</Space>
),
},
{
title: t("common.operation"),
key: "operation",
width: isMobile ? 150 : 220,
fixed: isMobile ? "right" : undefined,
render: (_, row) => (
<Space size={isMobile ? 4 : 8} direction={isMobile ? "vertical" : "horizontal"}>
<Button
type="link"
size={isMobile ? "small" : "middle"}
disabled={!canEdit}
onClick={() => handleOpenTagEditor(row)}
style={{ padding: isMobile ? "2px 4px" : undefined }}
>
{t("students.editTags")}
</Button>
<Button
type="link"
size={isMobile ? "small" : "middle"}
disabled={!canEdit}
onClick={() => handleOpenAvatarEditor(row)}
style={{ padding: isMobile ? "2px 4px" : undefined }}
>
{t("students.editAvatar")}
</Button>
<Button
type="link"
danger
size={isMobile ? "small" : "middle"}
disabled={!canEdit}
onClick={() => handleDelete(row.id)}
style={{ padding: isMobile ? "2px 4px" : undefined }}
>
{t("common.delete")}
</Button>
</Space>
),
},
]
if (isMobile) {
return baseColumns.filter((col) => col.key !== "tags")
}
return baseColumns
}, [t, canEdit, isMobile, isTablet, breakpoint])
const paginatedData = data.slice((currentPage - 1) * pageSize, currentPage * pageSize)
@@ -649,7 +694,12 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
placeholder={t("students.importTextPlaceholder")}
disabled={!canEdit || textImportLoading}
/>
<Button type="primary" loading={textImportLoading} disabled={!canEdit} onClick={handleImportTextNames}>
<Button
type="primary"
loading={textImportLoading}
disabled={!canEdit}
onClick={handleImportTextNames}
>
{t("students.importByText")}
</Button>
<Button