feat: 完善后端并新增 Vue3 WebUI

后端:
- 修复登录校验、DB 并发(单例+WAL+纯Go sqlite)、会话密钥持久化等核心缺陷
- 新增 Timer CRUD/签到/手动触发、执行日志、仪表盘、驱动元数据等 API
- 统一驱动接口: 带 context 的执行器、真实错误返回、敏感字段掩码、连通性测试
- 补全钉钉执行器; Timer 触发/提醒去重; 配置支持默认值与环境变量覆盖

前端:
- Vite+Vue3+TS+Pinia+Element Plus, 由驱动元数据动态渲染配置表单
- 登录/仪表盘(倒计时环)/定时器/规则/账号/日志/设置页面, 支持暗色模式
- 主题色 #66ccff, 信号波纹 logo, go:embed 单二进制部署

构建: Dockerfile 增加前端构建阶段; 补充各驱动配置文档

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mei
2026-07-26 22:19:41 +08:00
co-authored by Claude Fable 5
parent 89e8b7fd66
commit 79774070fc
110 changed files with 8936 additions and 849 deletions
+82
View File
@@ -0,0 +1,82 @@
import { api } from './client'
import type {
Account,
AccountRequest,
DashboardOverview,
ExecutionLog,
LogPage,
Providers,
Rule,
RuleRequest,
SiteInfo,
Timer,
TimerRequest,
UserInfo,
} from './types'
export const siteApi = {
info: () => api.get<SiteInfo>('/site'),
}
export const userApi = {
register: (username: string, password: string) =>
api.post<string>('/user/registry', { username, password }),
login: (username: string, password: string) =>
api.post<string>('/user/login', { username, password }),
logout: () => api.post<string>('/user/logout'),
info: () => api.get<UserInfo>('/user/info'),
changePassword: (oldPassword: string, newPassword: string) =>
api.post<string>('/user/password', {
old_password: oldPassword,
new_password: newPassword,
}),
updateNotify: (dingtalkConfig: string | null) =>
api.put<string>('/user/notify', { dingtalk_config: dingtalkConfig }),
}
export const dashboardApi = {
overview: () => api.get<DashboardOverview>('/dashboard'),
}
export const providerApi = {
all: () => api.get<Providers>('/providers'),
}
export const timerApi = {
list: () => api.get<Timer[]>('/timers/'),
get: (id: number) => api.get<{ timer: Timer; rule_count: number }>(`/timers/${id}`),
create: (body: TimerRequest) => api.post<Timer>('/timers/', body),
update: (id: number, body: TimerRequest) => api.put<Timer>(`/timers/${id}`, body),
sign: (id: number) => api.post<Timer>(`/timers/${id}/sign`),
signAll: () => api.post<{ signed: number; last_sign: number }>('/timers/sign'),
trigger: (id: number) => api.post<{ total: number; failed: string[] }>(`/timers/${id}/trigger`),
checkDelete: (id: number) => api.get<Rule[]>(`/timers/${id}/check`),
remove: (id: number) => api.delete<string>(`/timers/${id}`),
}
export const ruleApi = {
list: (timerId?: number) =>
api.get<Rule[]>('/rules/', timerId ? { timer_id: timerId } : undefined),
create: (body: RuleRequest) => api.post<Rule>('/rules/', body),
update: (id: number, body: RuleRequest) => api.put<Rule>(`/rules/${id}`, body),
test: (id: number) => api.post<string>(`/rules/${id}/test`),
remove: (id: number) => api.delete<string>(`/rules/${id}`),
}
export const accountApi = {
list: (type?: string) =>
api.get<Account[]>('/accounts/', type ? { type } : undefined),
create: (body: AccountRequest) => api.post<Account>('/accounts/', body),
update: (id: number, body: AccountRequest) => api.put<Account>(`/accounts/${id}`, body),
test: (id: number) => api.post<string>(`/accounts/${id}/test`),
checkDelete: (id: number) => api.get<Rule[]>(`/accounts/${id}/check`),
remove: (id: number) => api.delete<string>(`/accounts/${id}`),
}
export const logApi = {
list: (params: { page?: number; page_size?: number; rule_id?: number; success?: string }) =>
api.get<LogPage>('/logs/', params),
clear: () => api.delete<string>('/logs/'),
}
export type { ExecutionLog }