feat: 添加账号支持选择学习平台

账号新增 platform 字段,登录/刷新课程/发现课程按平台分发;同时把 JS 脚本收进 script/(目录规范调整)。
This commit is contained in:
study-tool
2026-10-30 01:13:47 +08:00
parent e8634a6317
commit 1a3347f7f9
29 changed files with 449 additions and 297 deletions
+17 -4
View File
@@ -75,6 +75,7 @@ function accountPayload(account: Account) {
return {
id: account.id,
username: account.username,
platform: account.platform || 'guanyixue',
note: account.note || '',
user: account.user || '',
cookies: account.cookies || [],
@@ -93,6 +94,9 @@ export default function App() {
const [accounts, setAccounts] = useState<Account[]>([])
const accountsRef = useRef(accounts)
const [platforms, setPlatforms] = useState<Record<string, string>>({ guanyixue: '莞易学' })
const [platformList, setPlatformList] = useState<PlatformInfo[]>([
{ id: 'guanyixue', label: '莞易学' },
])
const [offline, setOffline] = useState(false)
const [toast, setToast] = useState<ToastState | null>(null)
const [pending, setPending] = useState<Record<string, string>>({})
@@ -193,9 +197,12 @@ export default function App() {
useEffect(() => {
api<{ platforms: PlatformInfo[] }>('/api/platforms')
.then((response) => {
const list = response.platforms || []
if (!list.length) return
setPlatformList(list)
const map: Record<string, string> = {}
for (const platform of response.platforms || []) map[platform.id] = platform.label
if (Object.keys(map).length) setPlatforms(map)
for (const platform of list) map[platform.id] = platform.label
setPlatforms(map)
})
.catch(() => {})
}, [])
@@ -366,7 +373,10 @@ export default function App() {
try {
let response = await api<CoursesResponse>('/api/courses', {
method: 'POST',
body: JSON.stringify({ cookies: account.cookies }),
body: JSON.stringify({
cookies: account.cookies,
platform: account.platform || 'guanyixue',
}),
})
if (!response.ok && response.code === 'session_expired') {
notify({ message: '会话已失效,正在自动重新登录…' })
@@ -578,6 +588,7 @@ export default function App() {
method: 'POST',
body: JSON.stringify({
username: data.username,
platform: data.platform,
password: data.password,
note: data.note,
cookies: [],
@@ -621,7 +632,8 @@ export default function App() {
}
function platformLabel(account: Account) {
const id = (account.courses || []).find((course) => course.platform)?.platform
const id =
account.platform || (account.courses || []).find((course) => course.platform)?.platform
return (id && platforms[id]) || '莞易学'
}
@@ -749,6 +761,7 @@ export default function App() {
<AddAccountDialog
open={dialog?.type === 'add'}
platforms={platformList}
onClose={() => setDialog(null)}
onSubmit={addAccount}
/>
+30 -2
View File
@@ -1,5 +1,7 @@
import { useEffect, useState } from 'react'
import { ChevronDown, GraduationCap } from 'lucide-react'
import type { PlatformInfo } from '../types'
import { Modal } from './ui/Modal'
import { Button } from './ui/Button'
import { inputCls } from '../lib/utils'
@@ -8,20 +10,24 @@ export interface NewAccount {
username: string
password: string
note: string
platform: string
}
export function AddAccountDialog({
open,
platforms,
onClose,
onSubmit,
}: {
open: boolean
platforms: PlatformInfo[]
onClose: () => void
onSubmit: (account: NewAccount) => Promise<void>
}) {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [note, setNote] = useState('')
const [platform, setPlatform] = useState(platforms[0]?.id || 'guanyixue')
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
@@ -30,9 +36,10 @@ export function AddAccountDialog({
setUsername('')
setPassword('')
setNote('')
setPlatform(platforms[0]?.id || 'guanyixue')
setError('')
setSaving(false)
}, [open])
}, [open, platforms])
async function submit() {
if (!username.trim() || !password) {
@@ -42,7 +49,7 @@ export function AddAccountDialog({
setSaving(true)
setError('')
try {
await onSubmit({ username: username.trim(), password, note: note.trim() })
await onSubmit({ username: username.trim(), password, note: note.trim(), platform })
onClose()
} catch (err) {
setError(err instanceof Error ? err.message : String(err))
@@ -69,6 +76,27 @@ export function AddAccountDialog({
}
>
<div className="space-y-4">
<div>
<label className="mb-1.5 block text-[12px] font-medium text-slate-600">学习平台</label>
<div className="relative">
<GraduationCap className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-slate-400" />
<select
className={`${inputCls} appearance-none pl-8 pr-8`}
value={platform}
onChange={(event) => setPlatform(event.target.value)}
>
{platforms.map((item) => (
<option key={item.id} value={item.id}>
{item.label}
</option>
))}
</select>
<ChevronDown className="pointer-events-none absolute right-2.5 top-1/2 size-3.5 -translate-y-1/2 text-slate-400" />
</div>
<p className="mt-1.5 text-[11px] leading-relaxed text-slate-400">
选「莞易学」后,后台会自动识别旧版 / 新版课程页并按对应实现学习;后续新增的平台会出现在这里。
</p>
</div>
<div>
<label className="mb-1.5 block text-[12px] font-medium text-slate-600">登录账号</label>
<input
+1
View File
@@ -47,6 +47,7 @@ export function QuizDialog({
try {
const body: Record<string, unknown> = {
cookies: account.cookies,
platform: account.platform || 'guanyixue',
plan_id: course.plan_id,
project_id: course.project_id,
name: course.name || '',
+1
View File
@@ -37,6 +37,7 @@ export interface RunState {
export interface Account {
id: string
username: string
platform?: string
note?: string
user?: string
cookies: Cookie[]