feat:android build

This commit is contained in:
Fox_block
2026-03-20 21:34:17 +08:00
parent 5a483f781e
commit 77acab3562
18 changed files with 353 additions and 918 deletions
+4 -5
View File
@@ -7,11 +7,10 @@
--ss-safe-area-bottom: env(safe-area-inset-bottom, 0px);
--ss-safe-area-left: env(safe-area-inset-left, 0px);
--ss-font-family:
"PingFang SC", "PingFangTC-Regular", "Hiragino Sans GB", "Hiragino Sans",
"STHeiti", "Heiti SC", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC",
"Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji",
sans-serif;
"PingFang SC", "PingFangTC-Regular", "Hiragino Sans GB", "Hiragino Sans", "STHeiti", "Heiti SC",
"Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei UI",
"Microsoft YaHei", "微软雅黑", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
}
@supports (height: 100dvh) {
+144 -33
View File
@@ -211,11 +211,15 @@ const normalizeBoards = (input: unknown): BoardConfig[] => {
const normalizedLists = lists.length > 0 ? lists : [createDefaultList()]
const fallbackLayout = createLeafForList(normalizedLists[0].id)
const layout = board?.layout && typeof board.layout === "object" ? (board.layout as LayoutNode) : fallbackLayout
const layout =
board?.layout && typeof board.layout === "object"
? (board.layout as LayoutNode)
: fallbackLayout
return syncBoardLayout({
id: typeof board?.id === "string" && board.id.trim() ? board.id : makeId(),
name: typeof board?.name === "string" && board.name.trim() ? board.name.trim() : "未命名看板",
name:
typeof board?.name === "string" && board.name.trim() ? board.name.trim() : "未命名看板",
lists: normalizedLists,
layout,
})
@@ -283,7 +287,9 @@ const toStudentCards = (rows: any[]): BoardStudentCardData[] => {
if (cards.every((item) => item.score === undefined)) return cards
return cards.sort((a, b) => (b.score ?? Number.MIN_SAFE_INTEGER) - (a.score ?? Number.MIN_SAFE_INTEGER))
return cards.sort(
(a, b) => (b.score ?? Number.MIN_SAFE_INTEGER) - (a.score ?? Number.MIN_SAFE_INTEGER)
)
}
const getAvatarText = (name: string): string => {
@@ -312,7 +318,11 @@ const getAvatarColor = (name: string): string => {
return palette[hash % palette.length]
}
const replaceLayoutNode = (node: LayoutNode, targetId: string, replacement: LayoutNode): LayoutNode => {
const replaceLayoutNode = (
node: LayoutNode,
targetId: string,
replacement: LayoutNode
): LayoutNode => {
if (node.id === targetId) return replacement
if (node.type === "leaf") return node
return {
@@ -551,8 +561,13 @@ ORDER BY reward_points DESC, score DESC`,
useEffect(() => {
const onDataUpdated = (e: Event) => {
const detail = (e as CustomEvent<{ category?: string }>).detail
if (!detail?.category || detail.category === "all") runAllInBoard(activeBoard).catch(() => void 0)
if (detail.category === "students" || detail.category === "events" || detail.category === "reasons") {
if (!detail?.category || detail.category === "all")
runAllInBoard(activeBoard).catch(() => void 0)
if (
detail.category === "students" ||
detail.category === "events" ||
detail.category === "reasons"
) {
runAllInBoard(activeBoard).catch(() => void 0)
}
}
@@ -595,7 +610,9 @@ ORDER BY reward_points DESC, score DESC`,
const updateBoardName = (boardId: string, name: string) => {
mutateBoards((prev) =>
prev.map((board) => (board.id === boardId ? { ...board, name: name || t("board.untitledBoard") } : board))
prev.map((board) =>
board.id === boardId ? { ...board, name: name || t("board.untitledBoard") } : board
)
)
}
@@ -727,7 +744,8 @@ ORDER BY reward_points DESC, score DESC`,
dataIndex: key,
key,
ellipsis: true,
render: (value: any) => (value === null || value === undefined || value === "" ? "-" : String(value)),
render: (value: any) =>
value === null || value === undefined || value === "" ? "-" : String(value),
}))
: []
@@ -738,7 +756,11 @@ ORDER BY reward_points DESC, score DESC`,
dataSource={rows}
columns={columns}
loading={Boolean(runningIds[list.id])}
locale={{ emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("common.noData")} /> }}
locale={{
emptyText: (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("common.noData")} />
),
}}
scroll={{ x: true }}
pagination={{ pageSize: 20, showSizeChanger: false }}
size="small"
@@ -777,7 +799,10 @@ ORDER BY reward_points DESC, score DESC`,
: null
return (
<div key={item.key} style={{ ...(list.viewMode === "grid" ? { aspectRatio: "1 / 1" } : null) }}>
<div
key={item.key}
style={{ ...(list.viewMode === "grid" ? { aspectRatio: "1 / 1" } : null) }}
>
<Card
style={{
width: "100%",
@@ -789,13 +814,22 @@ ORDER BY reward_points DESC, score DESC`,
}}
styles={{
body: {
padding: list.viewMode === "grid" ? "8px" : list.viewMode === "list" ? "10px 12px" : "12px 14px",
padding:
list.viewMode === "grid"
? "8px"
: list.viewMode === "list"
? "10px 12px"
: "12px 14px",
height: list.viewMode === "grid" ? "100%" : undefined,
},
}}
>
{rankBadge && (
<div style={{ position: "absolute", top: "-10px", left: "-10px", fontSize: "24px" }}>{rankBadge}</div>
<div
style={{ position: "absolute", top: "-10px", left: "-10px", fontSize: "24px" }}
>
{rankBadge}
</div>
)}
{list.viewMode === "grid" ? (
@@ -827,7 +861,15 @@ ORDER BY reward_points DESC, score DESC`,
>
{avatarText}
</div>
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 6, width: "100%" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 6,
width: "100%",
}}
>
<div
style={{
fontWeight: 600,
@@ -842,10 +884,17 @@ ORDER BY reward_points DESC, score DESC`,
</div>
{primaryMetric && (
<Tag
color={typeof primaryMetric.value === "number" && primaryMetric.value >= 0 ? "success" : "error"}
color={
typeof primaryMetric.value === "number" && primaryMetric.value >= 0
? "success"
: "error"
}
style={{ fontWeight: "bold", marginInlineEnd: 0 }}
>
{primaryMetric.label}:{primaryMetric.value > 0 ? `+${primaryMetric.value}` : primaryMetric.value}
{primaryMetric.label}:
{primaryMetric.value > 0
? `+${primaryMetric.value}`
: primaryMetric.value}
</Tag>
)}
</div>
@@ -885,7 +934,8 @@ ORDER BY reward_points DESC, score DESC`,
<div style={{ marginTop: 4, display: "flex", flexWrap: "wrap", gap: 6 }}>
{item.score !== undefined && (
<Tag color={item.score >= 0 ? "success" : "error"} style={{ margin: 0 }}>
{t("board.metrics.totalScore")}: {item.score > 0 ? `+${item.score}` : item.score}
{t("board.metrics.totalScore")}:{" "}
{item.score > 0 ? `+${item.score}` : item.score}
</Tag>
)}
{item.rewardPoints !== undefined && (
@@ -894,8 +944,12 @@ ORDER BY reward_points DESC, score DESC`,
</Tag>
)}
{item.weekChange !== undefined && (
<Tag color={item.weekChange >= 0 ? "success" : "error"} style={{ margin: 0 }}>
{t("board.metrics.weekChange")}: {item.weekChange > 0 ? `+${item.weekChange}` : item.weekChange}
<Tag
color={item.weekChange >= 0 ? "success" : "error"}
style={{ margin: 0 }}
>
{t("board.metrics.weekChange")}:{" "}
{item.weekChange > 0 ? `+${item.weekChange}` : item.weekChange}
</Tag>
)}
{item.weekDeducted !== undefined && (
@@ -934,11 +988,15 @@ ORDER BY reward_points DESC, score DESC`,
flexDirection: "column",
overflow: "hidden",
backgroundColor: "var(--ss-card-bg)",
border: isSelected ? "1px solid var(--ant-color-primary, #1677ff)" : "1px solid var(--ss-border-color)",
border: isSelected
? "1px solid var(--ant-color-primary, #1677ff)"
: "1px solid var(--ss-border-color)",
boxShadow: isSelected ? "0 8px 18px rgba(22, 119, 255, 0.14)" : undefined,
cursor: "pointer",
}}
styles={{ body: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column", padding: 12 } }}
styles={{
body: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column", padding: 12 },
}}
title={<span style={{ fontWeight: 600 }}>{list.name}</span>}
extra={
<Space size={6}>
@@ -950,15 +1008,24 @@ ORDER BY reward_points DESC, score DESC`,
onConfirm={() => removeList(board.id, list.id)}
disabled={!canManage || board.lists.length <= 1}
>
<Button size="small" danger icon={<DeleteOutlined />} disabled={!canManage || board.lists.length <= 1}>
<Button
size="small"
danger
icon={<DeleteOutlined />}
disabled={!canManage || board.lists.length <= 1}
>
{t("board.removeList")}
</Button>
</Popconfirm>
</Space>
}
>
{errorMap[list.id] && <Alert style={{ marginBottom: 12 }} type="error" message={errorMap[list.id]} showIcon />}
<div style={{ flex: 1, overflowY: "auto", overflowX: "hidden" }}>{renderStudentView(list)}</div>
{errorMap[list.id] && (
<Alert style={{ marginBottom: 12 }} type="error" message={errorMap[list.id]} showIcon />
)}
<div style={{ flex: 1, overflowY: "auto", overflowX: "hidden" }}>
{renderStudentView(list)}
</div>
</Card>
)
}
@@ -988,7 +1055,9 @@ ORDER BY reward_points DESC, score DESC`,
minHeight: 200,
}}
>
<div style={{ flex: `${node.ratio} 1 0`, minWidth: 0, minHeight: 0 }}>{renderLayoutNode(board, node.first)}</div>
<div style={{ flex: `${node.ratio} 1 0`, minWidth: 0, minHeight: 0 }}>
{renderLayoutNode(board, node.first)}
</div>
<div
onMouseDown={(event) => {
if (!canManage) return
@@ -1047,13 +1116,31 @@ ORDER BY reward_points DESC, score DESC`,
}}
>
{contextHolder}
<div style={{ display: "flex", flexDirection: "column", gap: 16, width: "100%", minHeight: 0, flex: 1 }}>
<div style={{ display: "flex", width: "100%", justifyContent: "space-between", alignItems: "center" }}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
width: "100%",
minHeight: 0,
flex: 1,
}}
>
<div
style={{
display: "flex",
width: "100%",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Space align="center" size={8}>
<Typography.Title level={2} style={{ margin: 0, color: "var(--ss-text-main)" }}>
{t("board.title")}
</Typography.Title>
<Tag color={canManage ? "success" : "default"}>{canManage ? t("board.editable") : t("board.readonly")}</Tag>
<Tag color={canManage ? "success" : "default"}>
{canManage ? t("board.editable") : t("board.readonly")}
</Tag>
</Space>
<Space>
<Button
@@ -1070,7 +1157,11 @@ ORDER BY reward_points DESC, score DESC`,
>
{t("board.splitVertical")}
</Button>
<Button icon={<EditOutlined />} onClick={openRenameModal} disabled={!canManage || !activeBoard}>
<Button
icon={<EditOutlined />}
onClick={openRenameModal}
disabled={!canManage || !activeBoard}
>
{t("board.renameBoard")}
</Button>
<Popconfirm
@@ -1100,7 +1191,16 @@ ORDER BY reward_points DESC, score DESC`,
/>
{activeBoard ? (
<div style={{ width: "100%", minHeight: 0, flex: 1, display: "flex", flexDirection: "column", gap: 8 }}>
<div
style={{
width: "100%",
minHeight: 0,
flex: 1,
display: "flex",
flexDirection: "column",
gap: 8,
}}
>
{saving && (
<Typography.Text type="secondary" style={{ flexShrink: 0 }}>
{t("board.saving")}
@@ -1123,7 +1223,11 @@ ORDER BY reward_points DESC, score DESC`,
okText={t("common.confirm")}
cancelText={t("common.cancel")}
>
<Input value={renameValue} onChange={(e) => setRenameValue(e.target.value)} placeholder={t("board.boardNamePlaceholder")} />
<Input
value={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
placeholder={t("board.boardNamePlaceholder")}
/>
</Modal>
<Modal
@@ -1147,14 +1251,19 @@ ORDER BY reward_points DESC, score DESC`,
<Select
style={{ width: 260 }}
placeholder={t("board.applyPreset")}
options={presets.map((preset) => ({ value: preset.id, label: `${preset.name} · ${preset.description}` }))}
options={presets.map((preset) => ({
value: preset.id,
label: `${preset.name} · ${preset.description}`,
}))}
onChange={(presetId) => applyPreset(activeBoard.id, editingList.id, presetId)}
disabled={!canManage}
/>
<Select
style={{ width: 160 }}
value={editingList.viewMode}
onChange={(viewMode: BoardStudentViewMode) => updateList(activeBoard.id, editingList.id, { viewMode })}
onChange={(viewMode: BoardStudentViewMode) =>
updateList(activeBoard.id, editingList.id, { viewMode })
}
options={[
{ value: "list", label: t("board.viewModes.list") },
{ value: "card", label: t("board.viewModes.card") },
@@ -1170,7 +1279,9 @@ ORDER BY reward_points DESC, score DESC`,
disabled={!canManage}
spellCheck={false}
placeholder={t("board.sqlPlaceholder")}
style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace" }}
style={{
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
}}
/>
</Space>
)}
+24 -9
View File
@@ -34,7 +34,11 @@ interface reasonItem {
const deepClone = <T,>(v: T): T => JSON.parse(JSON.stringify(v)) as T
const withTimeout = async (promise: Promise<any>, ms: number, timeoutMessage: string): Promise<any> => {
const withTimeout = async (
promise: Promise<any>,
ms: number,
timeoutMessage: string
): Promise<any> => {
let timer: ReturnType<typeof setTimeout> | undefined
try {
return await Promise.race<any>([
@@ -115,17 +119,22 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
const fileInputRef = useRef<HTMLInputElement>(null)
const steps: oobeStep[] = ["entry", "language", "theme", "password", "students", "reasons", "start"]
const steps: oobeStep[] = [
"entry",
"language",
"theme",
"password",
"students",
"reasons",
"start",
]
const stepIndex = steps.indexOf(currentStep) + 1
const totalSteps = steps.length
const primaryColor = workingTheme?.config?.tdesign?.brandColor || "#1677FF"
const isDark = workingTheme?.mode === "dark"
const showOobeMessage = (
type: "success" | "error" | "warning" | "info",
content: string
) => {
const showOobeMessage = (type: "success" | "error" | "warning" | "info", content: string) => {
messageApi.open({
type,
content,
@@ -392,7 +401,9 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
"同步预检查超时,请稍后重试"
)
if (!previewRes?.success || !previewRes?.data?.can_sync) {
throw new Error(previewRes?.message || previewRes?.data?.message || t("settings.database.uploadFailed"))
throw new Error(
previewRes?.message || previewRes?.data?.message || t("settings.database.uploadFailed")
)
}
if (previewRes?.data?.need_sync) {
@@ -403,7 +414,9 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
)
if (!syncApplyRes?.success || !syncApplyRes?.data?.success) {
throw new Error(
syncApplyRes?.data?.message || syncApplyRes?.message || t("settings.database.uploadFailed")
syncApplyRes?.data?.message ||
syncApplyRes?.message ||
t("settings.database.uploadFailed")
)
}
}
@@ -942,7 +955,9 @@ export const OOBE: React.FC<oobeProps> = ({ visible, onComplete }) => {
}}
>
<div style={{ display: "flex", gap: 8 }}>
{currentStep !== "language" && <Button onClick={handlePrev}>{t("common.prev")}</Button>}
{currentStep !== "language" && (
<Button onClick={handlePrev}>{t("common.prev")}</Button>
)}
{currentStep !== "start" && <Button onClick={handleSkip}>{t("oobe.skip")}</Button>}
</div>
+30 -7
View File
@@ -78,7 +78,10 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
}, [fetchData])
const sortedStudents = useMemo(
() => [...students].sort((a, b) => b.reward_points - a.reward_points || a.name.localeCompare(b.name, "zh-CN")),
() =>
[...students].sort(
(a, b) => b.reward_points - a.reward_points || a.name.localeCompare(b.name, "zh-CN")
),
[students]
)
@@ -128,7 +131,12 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
}
const columns: ColumnsType<RedemptionItem> = [
{ title: t("rewardExchange.recordStudent"), dataIndex: "student_name", key: "student_name", width: 120 },
{
title: t("rewardExchange.recordStudent"),
dataIndex: "student_name",
key: "student_name",
width: 120,
},
{ title: t("rewardExchange.recordReward"), dataIndex: "reward_name", key: "reward_name" },
{
title: t("rewardExchange.recordCost"),
@@ -149,7 +157,14 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
return (
<div style={{ padding: "24px" }}>
{contextHolder}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "16px",
}}
>
<h2 style={{ margin: 0, color: "var(--ss-text-main)" }}>{t("rewardExchange.title")}</h2>
<Button
type={exchangeMode ? "default" : "primary"}
@@ -187,7 +202,9 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
onClick={() => handleStudentClick(student)}
style={{
cursor: exchangeMode ? "pointer" : "default",
border: exchangeMode ? "1px solid var(--ant-color-primary, #1677ff)" : "1px solid var(--ss-border-color)",
border: exchangeMode
? "1px solid var(--ant-color-primary, #1677ff)"
: "1px solid var(--ss-border-color)",
}}
bodyStyle={{ padding: "12px" }}
>
@@ -196,7 +213,9 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
{points > 0 ? `+${points}` : points}
</Tag>
<div style={{ marginTop: 8, fontSize: 12, color: "var(--ss-text-secondary)" }}>
{exchangeMode ? t("rewardExchange.remainingRewardPoints") : t("rewardExchange.currentScore")}
{exchangeMode
? t("rewardExchange.remainingRewardPoints")
: t("rewardExchange.currentScore")}
</div>
</Card>
)
@@ -228,11 +247,15 @@ export const RewardExchange: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
{t("rewardExchange.currentRewardPoints", { points: selectedStudent.reward_points })}
</div>
{affordableRewards.length === 0 ? (
<div style={{ color: "var(--ss-text-secondary)" }}>{t("rewardExchange.noAffordableRewards")}</div>
<div style={{ color: "var(--ss-text-secondary)" }}>
{t("rewardExchange.noAffordableRewards")}
</div>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{affordableRewards
.sort((a, b) => a.cost_points - b.cost_points || a.name.localeCompare(b.name, "zh-CN"))
.sort(
(a, b) => a.cost_points - b.cost_points || a.name.localeCompare(b.name, "zh-CN")
)
.map((reward) => (
<div
key={reward.id}
+12 -3
View File
@@ -105,14 +105,19 @@ export const RewardSettings: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
: await (window as any).api.rewardSettingCreate(payload)
if (res.success) {
messageApi.success(editing ? t("rewardSettings.updateSuccess") : t("rewardSettings.createSuccess"))
messageApi.success(
editing ? t("rewardSettings.updateSuccess") : t("rewardSettings.createSuccess")
)
setVisible(false)
form.resetFields()
setEditing(null)
fetchRewards()
emitDataUpdated()
} else {
messageApi.error(res.message || (editing ? t("rewardSettings.updateFailed") : t("rewardSettings.createFailed")))
messageApi.error(
res.message ||
(editing ? t("rewardSettings.updateFailed") : t("rewardSettings.createFailed"))
)
}
}
@@ -196,7 +201,11 @@ export const RewardSettings: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
name="cost_points"
rules={[{ required: true, message: t("rewardSettings.costRequired") }]}
>
<InputNumber min={1} style={{ width: "100%" }} placeholder={t("rewardSettings.costPlaceholder")} />
<InputNumber
min={1}
style={{ width: "100%" }}
placeholder={t("rewardSettings.costPlaceholder")}
/>
</Form.Item>
</Form>
</Modal>
+3 -1
View File
@@ -296,7 +296,9 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
<Button
size="small"
onClick={handleClearSelectedStudents}
disabled={!selectedStudentNames || selectedStudentNames.length === 0 || !canEdit}
disabled={
!selectedStudentNames || selectedStudentNames.length === 0 || !canEdit
}
>
{t("score.clearSelectedStudents")}
</Button>
+17 -7
View File
@@ -27,7 +27,11 @@ type appSettings = {
auto_score_enabled?: boolean
}
const withTimeout = async (promise: Promise<any>, ms: number, timeoutMessage: string): Promise<any> => {
const withTimeout = async (
promise: Promise<any>,
ms: number,
timeoutMessage: string
): Promise<any> => {
let timer: ReturnType<typeof setTimeout> | undefined
try {
return await Promise.race<any>([
@@ -397,7 +401,11 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
if (!(window as any).api) return
try {
const statusRes = await (window as any).api.dbGetStatus()
if (!statusRes.success || !statusRes.data?.connected || statusRes.data?.type !== "postgresql") {
if (
!statusRes.success ||
!statusRes.data?.connected ||
statusRes.data?.type !== "postgresql"
) {
messageApi.warning(t("settings.database.uploadNeedRemote"))
return
}
@@ -408,7 +416,9 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
"同步预检查超时,请稍后重试"
)
if (!previewRes.success || !previewRes.data?.can_sync) {
messageApi.error(previewRes.message || previewRes.data?.message || t("settings.database.uploadFailed"))
messageApi.error(
previewRes.message || previewRes.data?.message || t("settings.database.uploadFailed")
)
return
}
if (!previewRes.data.need_sync) {
@@ -434,9 +444,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
"执行同步超时,请稍后重试"
)
if (applyRes.success && applyRes.data?.success) {
messageApi.success(
applyRes.data?.message || t("settings.database.uploadSuccess")
)
messageApi.success(applyRes.data?.message || t("settings.database.uploadSuccess"))
emitDataUpdated("all")
await loadAll()
} else {
@@ -1060,7 +1068,9 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
<div style={{ fontSize: "16px", fontWeight: 600, marginBottom: "8px" }}>
{t("settings.app.title")}
</div>
<div style={{ color: "var(--ss-text-secondary)", marginBottom: "12px", fontSize: "12px" }}>
<div
style={{ color: "var(--ss-text-secondary)", marginBottom: "12px", fontSize: "12px" }}
>
{t("settings.app.description")}
</div>
<div style={{ marginTop: "8px" }}>
-1
View File
@@ -124,7 +124,6 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre
useEffect(() => {
const api = (window as any).api
if (!api) return
;(async () => {
await loadThemes()
await loadCurrentTheme()
+8 -4
View File
@@ -103,7 +103,8 @@ const api = {
deleteReason: (id: number): Promise<{ success: boolean }> => invoke("reason_delete", { id }),
// DB - Reward
rewardSettingQuery: (): Promise<{ success: boolean; data: any[] }> => invoke("reward_setting_query"),
rewardSettingQuery: (): Promise<{ success: boolean; data: any[] }> =>
invoke("reward_setting_query"),
rewardSettingCreate: (data: {
name: string
cost_points: number
@@ -121,8 +122,9 @@ const api = {
data?: { redemption_id: number; remaining_reward_points: number }
message?: string
}> => invoke("reward_redeem", { data }),
rewardRedemptionQuery: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
invoke("reward_redemption_query", { params }),
rewardRedemptionQuery: (params?: {
limit?: number
}): Promise<{ success: boolean; data: any[] }> => invoke("reward_redemption_query", { params }),
// DB - Event
queryEvents: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
@@ -273,7 +275,9 @@ const api = {
message?: string
}
}> => invoke("db_sync_preview"),
dbSyncApply: (strategy: "keep_local" | "keep_remote"): Promise<{
dbSyncApply: (
strategy: "keep_local" | "keep_remote"
): Promise<{
success: boolean
data: { success: boolean; synced_records: number; resolved_conflicts: number; message?: string }
}> => invoke("db_sync_apply", { strategy }),