feat(app): migrate schedules and screenshots (stage 4)

This commit is contained in:
2025-12-14 00:15:19 +08:00
parent 9798ed52c3
commit 54cf6fe538
23 changed files with 924 additions and 44 deletions

View File

@@ -0,0 +1,42 @@
import { publicApi } from './http'
export async function fetchSchedules() {
const { data } = await publicApi.get('/schedules')
return data
}
export async function createSchedule(payload) {
const { data } = await publicApi.post('/schedules', payload)
return data
}
export async function updateSchedule(scheduleId, payload) {
const { data } = await publicApi.put(`/schedules/${scheduleId}`, payload)
return data
}
export async function deleteSchedule(scheduleId) {
const { data } = await publicApi.delete(`/schedules/${scheduleId}`)
return data
}
export async function toggleSchedule(scheduleId, payload) {
const { data } = await publicApi.post(`/schedules/${scheduleId}/toggle`, payload)
return data
}
export async function runScheduleNow(scheduleId) {
const { data } = await publicApi.post(`/schedules/${scheduleId}/run`, {})
return data
}
export async function fetchScheduleLogs(scheduleId, params = {}) {
const { data } = await publicApi.get(`/schedules/${scheduleId}/logs`, { params })
return data
}
export async function clearScheduleLogs(scheduleId) {
const { data } = await publicApi.delete(`/schedules/${scheduleId}/logs`)
return data
}

View File

@@ -0,0 +1,17 @@
import { publicApi } from './http'
export async function fetchScreenshots() {
const { data } = await publicApi.get('/screenshots')
return data
}
export async function deleteScreenshot(filename) {
const { data } = await publicApi.delete(`/screenshots/${encodeURIComponent(filename)}`)
return data
}
export async function clearScreenshots() {
const { data } = await publicApi.post('/screenshots/clear', {})
return data
}

View File

@@ -1,20 +1,598 @@
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fetchAccounts } from '../api/accounts'
import {
clearScheduleLogs,
createSchedule,
deleteSchedule,
fetchScheduleLogs,
fetchSchedules,
runScheduleNow,
toggleSchedule,
updateSchedule,
} from '../api/schedules'
import { useUserStore } from '../stores/user'
const userStore = useUserStore()
const loading = ref(false)
const schedules = ref([])
const accountsLoading = ref(false)
const accountOptions = ref([])
const editorOpen = ref(false)
const editorSaving = ref(false)
const editingId = ref(null)
const logsOpen = ref(false)
const logsLoading = ref(false)
const logs = ref([])
const logsSchedule = ref(null)
const vipModalOpen = ref(false)
const form = reactive({
name: '',
schedule_time: '08:00',
weekdays: ['1', '2', '3', '4', '5'],
browse_type: '应读',
enable_screenshot: true,
account_ids: [],
})
const browseTypeOptions = [
{ label: '应读', value: '应读' },
{ label: '未读', value: '未读' },
{ label: '注册前未读', value: '注册前未读' },
]
const weekdayOptions = [
{ label: '周一', value: '1' },
{ label: '周二', value: '2' },
{ label: '周三', value: '3' },
{ label: '周四', value: '4' },
{ label: '周五', value: '5' },
{ label: '周六', value: '6' },
{ label: '周日', value: '7' },
]
const canUseSchedule = computed(() => userStore.isVip)
function normalizeTime(value) {
const match = String(value || '').match(/^(\d{1,2}):(\d{2})$/)
if (!match) return null
const hour = Number(match[1])
const minute = Number(match[2])
if (Number.isNaN(hour) || Number.isNaN(minute)) return null
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`
}
function weekdaysText(textOrArray) {
const raw = Array.isArray(textOrArray) ? textOrArray : String(textOrArray || '').split(',').filter(Boolean)
const map = Object.fromEntries(weekdayOptions.map((w) => [w.value, w.label]))
return raw.map((d) => map[String(d)] || String(d)).join(' ')
}
async function loadAccounts() {
accountsLoading.value = true
try {
const list = await fetchAccounts({ refresh: false })
accountOptions.value = (list || []).map((acc) => ({ label: acc.username, value: acc.id }))
} catch {
accountOptions.value = []
} finally {
accountsLoading.value = false
}
}
async function loadSchedules() {
loading.value = true
try {
schedules.value = await fetchSchedules()
} catch (e) {
if (e?.response?.status === 401) window.location.href = '/login'
schedules.value = []
} finally {
loading.value = false
}
}
function openCreate() {
editingId.value = null
form.name = ''
form.schedule_time = '08:00'
form.weekdays = ['1', '2', '3', '4', '5']
form.browse_type = '应读'
form.enable_screenshot = true
form.account_ids = []
editorOpen.value = true
}
function openEdit(schedule) {
editingId.value = schedule.id
form.name = schedule.name || ''
form.schedule_time = normalizeTime(schedule.schedule_time) || '08:00'
form.weekdays = String(schedule.weekdays || '')
.split(',')
.filter(Boolean)
.map((v) => String(v))
if (form.weekdays.length === 0) form.weekdays = ['1', '2', '3', '4', '5']
form.browse_type = schedule.browse_type || '应读'
form.enable_screenshot = Number(schedule.enable_screenshot ?? 1) !== 0
form.account_ids = Array.isArray(schedule.account_ids) ? schedule.account_ids.slice() : []
editorOpen.value = true
}
async function saveSchedule() {
if (!canUseSchedule.value) {
vipModalOpen.value = true
return
}
const normalizedTime = normalizeTime(form.schedule_time)
if (!normalizedTime) {
ElMessage.error('时间格式错误,请使用 HH:MM')
return
}
if (!form.weekdays || form.weekdays.length === 0) {
ElMessage.warning('请选择至少一个执行日期')
return
}
editorSaving.value = true
try {
const payload = {
name: form.name.trim() || '我的定时任务',
schedule_time: normalizedTime,
weekdays: form.weekdays.join(','),
browse_type: form.browse_type,
enable_screenshot: form.enable_screenshot ? 1 : 0,
account_ids: form.account_ids,
}
if (editingId.value) {
await updateSchedule(editingId.value, payload)
ElMessage.success('保存成功')
} else {
await createSchedule(payload)
ElMessage.success('创建成功')
}
editorOpen.value = false
await loadSchedules()
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '保存失败')
} finally {
editorSaving.value = false
}
}
async function onDelete(schedule) {
try {
await ElMessageBox.confirm(`确定要删除定时任务「${schedule.name || '未命名任务'}」吗?`, '删除任务', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await deleteSchedule(schedule.id)
if (res?.success) {
ElMessage.success('已删除')
await loadSchedules()
} else {
ElMessage.error(res?.error || '删除失败')
}
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '删除失败')
}
}
async function onToggle(schedule, enabled) {
if (!canUseSchedule.value) {
vipModalOpen.value = true
return
}
try {
const res = await toggleSchedule(schedule.id, { enabled })
if (res?.success) {
schedule.enabled = enabled ? 1 : 0
ElMessage.success(enabled ? '已启用' : '已禁用')
}
} catch {
ElMessage.error('操作失败')
}
}
async function onRunNow(schedule) {
if (!canUseSchedule.value) {
vipModalOpen.value = true
return
}
try {
const res = await runScheduleNow(schedule.id)
if (res?.success) ElMessage.success(res?.message || '已开始执行')
else ElMessage.error(res?.error || '执行失败')
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '执行失败')
}
}
async function openLogs(schedule) {
logsSchedule.value = schedule
logsOpen.value = true
logsLoading.value = true
try {
logs.value = await fetchScheduleLogs(schedule.id, { limit: 20 })
} catch {
logs.value = []
} finally {
logsLoading.value = false
}
}
async function clearLogs() {
const schedule = logsSchedule.value
if (!schedule) return
try {
await ElMessageBox.confirm('确定要清空该任务的所有执行日志吗?', '清空日志', {
confirmButtonText: '清空',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await clearScheduleLogs(schedule.id)
if (res?.success) {
ElMessage.success(`已清空 ${res?.deleted || 0} 条日志`)
logs.value = []
} else {
ElMessage.error(res?.error || '操作失败')
}
} catch {
ElMessage.error('操作失败')
}
}
function statusTagType(status) {
const text = String(status || '')
if (text === 'success' || text === 'completed') return 'success'
if (text === 'failed') return 'danger'
return 'info'
}
function formatDuration(seconds) {
const value = Number(seconds || 0)
const mins = Math.floor(value / 60)
const secs = value % 60
if (mins <= 0) return `${secs}`
return `${mins}${secs}`
}
onMounted(async () => {
if (!userStore.vipInfo) {
userStore.refreshVipInfo().catch(() => {
window.location.href = '/login'
})
}
await Promise.all([loadAccounts(), loadSchedules()])
})
</script>
<template>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<h2 class="title">定时任务</h2>
<div class="app-muted">阶段1页面壳子已就绪功能将在后续阶段迁移</div>
</el-card>
<div class="page">
<el-alert
v-if="!canUseSchedule"
type="warning"
show-icon
:closable="false"
title="定时任务为 VIP 专属功能,升级后可使用。"
class="vip-alert"
>
<template #default>
<div class="vip-actions">
<el-button type="primary" plain @click="vipModalOpen = true">了解VIP特权</el-button>
</div>
</template>
</el-alert>
<el-card shadow="never" class="panel" :body-style="{ padding: '14px' }">
<div class="panel-head">
<div class="panel-title">定时任务</div>
<div class="panel-actions">
<el-button :loading="loading" @click="loadSchedules">刷新</el-button>
<el-button type="primary" :disabled="!canUseSchedule" @click="openCreate">新建任务</el-button>
</div>
</div>
<el-skeleton v-if="loading" :rows="6" animated />
<template v-else>
<el-empty v-if="schedules.length === 0" description="暂无定时任务" />
<div v-else class="grid">
<el-card v-for="s in schedules" :key="s.id" shadow="never" class="schedule-card" :body-style="{ padding: '14px' }">
<div class="schedule-top">
<div class="schedule-main">
<div class="schedule-title">
<span class="schedule-name">{{ s.name || '未命名任务' }}</span>
<el-tag size="small" effect="light" :type="Number(s.enabled) ? 'success' : 'info'">
{{ Number(s.enabled) ? '启用' : '停用' }}
</el-tag>
</div>
<div class="schedule-meta app-muted">
<span> {{ normalizeTime(s.schedule_time) || s.schedule_time }}</span>
<span>📅 {{ weekdaysText(s.weekdays) }}</span>
</div>
<div class="schedule-meta app-muted">
<span>📋 {{ s.browse_type || '应读' }}</span>
<span>👥 {{ (s.account_ids || []).length }} 个账号</span>
<span>{{ Number(s.enable_screenshot ?? 1) !== 0 ? '📸 截图' : '📷 不截图' }}</span>
</div>
</div>
<div class="schedule-switch">
<el-switch
:model-value="Boolean(Number(s.enabled))"
:disabled="!canUseSchedule"
inline-prompt
active-text="启用"
inactive-text="停用"
@change="(val) => onToggle(s, val)"
/>
</div>
</div>
<div class="schedule-actions">
<el-button size="small" type="primary" :disabled="!canUseSchedule" @click="onRunNow(s)">立即执行</el-button>
<el-button size="small" @click="openLogs(s)">日志</el-button>
<el-button size="small" :disabled="!canUseSchedule" @click="openEdit(s)">编辑</el-button>
<el-button size="small" type="danger" text :disabled="!canUseSchedule" @click="onDelete(s)">删除</el-button>
</div>
</el-card>
</div>
</template>
</el-card>
<el-dialog v-model="editorOpen" :title="editingId ? '编辑定时任务' : '新建定时任务'" width="min(720px, 92vw)">
<el-form label-position="top">
<el-form-item label="任务名称">
<el-input v-model="form.name" placeholder="我的定时任务" :disabled="!canUseSchedule" />
</el-form-item>
<el-form-item label="执行时间HH:MM">
<el-input v-model="form.schedule_time" placeholder="08:00" :disabled="!canUseSchedule" />
</el-form-item>
<el-form-item label="执行日期">
<el-checkbox-group v-model="form.weekdays" :disabled="!canUseSchedule">
<el-checkbox v-for="w in weekdayOptions" :key="w.value" :label="w.value">{{ w.label }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="浏览类型">
<el-select v-model="form.browse_type" style="width: 160px" :disabled="!canUseSchedule">
<el-option v-for="opt in browseTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
<el-form-item label="截图">
<el-switch v-model="form.enable_screenshot" :disabled="!canUseSchedule" inline-prompt active-text="截图" inactive-text="不截图" />
</el-form-item>
<el-form-item label="参与账号">
<el-select
v-model="form.account_ids"
multiple
filterable
collapse-tags
collapse-tags-tooltip
placeholder="选择账号(可多选)"
style="width: 100%"
:loading="accountsLoading"
:disabled="!canUseSchedule"
>
<el-option v-for="opt in accountOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editorOpen = false">取消</el-button>
<el-button type="primary" :loading="editorSaving" :disabled="!canUseSchedule" @click="saveSchedule">保存</el-button>
</template>
</el-dialog>
<el-dialog v-model="logsOpen" :title="logsSchedule ? `【${logsSchedule.name || '未命名任务'}】执行日志` : '执行日志'" width="min(760px, 92vw)">
<el-skeleton v-if="logsLoading" :rows="6" animated />
<template v-else>
<el-empty v-if="logs.length === 0" description="暂无执行日志" />
<div v-else class="logs">
<el-card v-for="log in logs" :key="log.id" shadow="never" class="log-card" :body-style="{ padding: '12px' }">
<div class="log-head">
<el-tag size="small" effect="light" :type="statusTagType(log.status)">
{{ log.status === 'failed' ? '失败' : log.status === 'running' ? '进行中' : '成功' }}
</el-tag>
<span class="app-muted">{{ log.created_at || '' }}</span>
</div>
<div class="log-body">
<div>账号数{{ log.total_accounts || 0 }} </div>
<div>成功{{ log.success_count || 0 }} · 失败{{ log.failed_count || 0 }} </div>
<div>耗时{{ formatDuration(log.duration || 0) }}</div>
<div v-if="log.error_message" class="log-error">错误{{ log.error_message }}</div>
</div>
</el-card>
</div>
</template>
<template #footer>
<el-button @click="logsOpen = false">关闭</el-button>
<el-button type="danger" plain :disabled="logs.length === 0" @click="clearLogs">清空日志</el-button>
</template>
</el-dialog>
<el-dialog v-model="vipModalOpen" title="VIP 特权" width="min(560px, 92vw)">
<el-alert
type="info"
:closable="false"
title="升级 VIP 后可解锁:无限账号、优先排队、定时任务、批量操作。"
show-icon
/>
<div class="vip-body">
<div class="vip-tip app-muted">升级方式请通过反馈联系管理员开通</div>
</div>
<template #footer>
<el-button type="primary" @click="vipModalOpen = false">我知道了</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.card {
.page {
display: flex;
flex-direction: column;
gap: 12px;
}
.vip-alert {
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
}
.title {
margin: 0 0 6px;
.vip-actions {
margin-top: 10px;
}
.panel {
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
}
.panel-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.panel-title {
font-size: 16px;
font-weight: 800;
font-weight: 900;
}
.panel-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 12px;
}
.schedule-card {
border-radius: 14px;
border: 1px solid var(--app-border);
}
.schedule-top {
display: flex;
justify-content: space-between;
gap: 12px;
}
.schedule-main {
min-width: 0;
flex: 1;
}
.schedule-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.schedule-name {
font-size: 14px;
font-weight: 900;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.schedule-meta {
margin-top: 6px;
display: flex;
gap: 10px;
flex-wrap: wrap;
font-size: 12px;
}
.schedule-actions {
margin-top: 12px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.logs {
display: flex;
flex-direction: column;
gap: 10px;
}
.log-card {
border-radius: 12px;
border: 1px solid var(--app-border);
}
.log-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: 12px;
}
.log-body {
margin-top: 8px;
font-size: 13px;
line-height: 1.6;
}
.log-error {
margin-top: 6px;
color: #b91c1c;
}
.vip-body {
padding: 12px 0 0;
}
.vip-tip {
margin-top: 10px;
font-size: 13px;
line-height: 1.6;
}
@media (max-width: 480px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -1,20 +1,253 @@
<script setup>
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { clearScreenshots, deleteScreenshot, fetchScreenshots } from '../api/screenshots'
const loading = ref(false)
const screenshots = ref([])
const previewOpen = ref(false)
const previewUrl = ref('')
const previewTitle = ref('')
function buildUrl(filename) {
return `/screenshots/${encodeURIComponent(filename)}`
}
async function load() {
loading.value = true
try {
const data = await fetchScreenshots()
screenshots.value = Array.isArray(data) ? data : []
} catch (e) {
if (e?.response?.status === 401) window.location.href = '/login'
screenshots.value = []
} finally {
loading.value = false
}
}
function openPreview(item) {
previewTitle.value = item.display_name || item.filename || '截图预览'
previewUrl.value = buildUrl(item.filename)
previewOpen.value = true
}
async function onClearAll() {
try {
await ElMessageBox.confirm('确定要清空全部截图吗?', '清空截图', {
confirmButtonText: '清空',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await clearScreenshots()
if (res?.success) {
ElMessage.success(`已清空(删除 ${res?.deleted || 0} 张)`)
screenshots.value = []
previewOpen.value = false
return
}
ElMessage.error(res?.error || '操作失败')
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '操作失败')
}
}
async function onDelete(item) {
try {
await ElMessageBox.confirm(`确定要删除截图「${item.display_name || item.filename}」吗?`, '删除截图', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await deleteScreenshot(item.filename)
if (res?.success) {
screenshots.value = screenshots.value.filter((s) => s.filename !== item.filename)
if (previewUrl.value.includes(encodeURIComponent(item.filename))) previewOpen.value = false
ElMessage.success('已删除')
return
}
ElMessage.error(res?.error || '删除失败')
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '删除失败')
}
}
async function copyLink(item) {
const url = `${window.location.origin}${buildUrl(item.filename)}`
try {
await navigator.clipboard.writeText(url)
ElMessage.success('链接已复制')
} catch {
ElMessage.warning('复制失败,请手动复制链接')
}
}
async function copyImage(item) {
const url = buildUrl(item.filename)
try {
const resp = await fetch(url, { credentials: 'include' })
const blob = await resp.blob()
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })])
ElMessage.success('图片已复制')
} catch {
await copyLink(item)
}
}
function download(item) {
const link = document.createElement('a')
link.href = buildUrl(item.filename)
link.download = item.display_name || item.filename
document.body.appendChild(link)
link.click()
link.remove()
}
onMounted(load)
</script>
<template>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<h2 class="title">截图管理</h2>
<div class="app-muted">阶段1页面壳子已就绪功能将在后续阶段迁移</div>
<el-card shadow="never" class="panel" :body-style="{ padding: '14px' }">
<div class="panel-head">
<div class="panel-title">截图管理</div>
<div class="panel-actions">
<el-button :loading="loading" @click="load">刷新</el-button>
<el-button type="danger" plain :disabled="screenshots.length === 0" @click="onClearAll">清空全部</el-button>
</div>
</div>
<el-skeleton v-if="loading" :rows="6" animated />
<template v-else>
<el-empty v-if="screenshots.length === 0" description="暂无截图" />
<div v-else class="grid">
<el-card v-for="item in screenshots" :key="item.filename" shadow="never" class="shot-card" :body-style="{ padding: '0' }">
<img class="shot-img" :src="buildUrl(item.filename)" :alt="item.display_name || item.filename" loading="lazy" @click="openPreview(item)" />
<div class="shot-body">
<div class="shot-name" :title="item.display_name || item.filename">{{ item.display_name || item.filename }}</div>
<div class="shot-meta app-muted">{{ item.created || '' }}</div>
<div class="shot-actions">
<el-button size="small" text type="primary" @click="copyImage(item)">复制图片</el-button>
<el-button size="small" text @click="download(item)">下载</el-button>
<el-button size="small" text type="danger" @click="onDelete(item)">删除</el-button>
</div>
</div>
</el-card>
</div>
</template>
<el-dialog v-model="previewOpen" :title="previewTitle" width="min(920px, 94vw)">
<div class="preview">
<img :src="previewUrl" :alt="previewTitle" class="preview-img" />
</div>
<template #footer>
<el-button @click="previewOpen = false">关闭</el-button>
</template>
</el-dialog>
</el-card>
</template>
<style scoped>
.card {
.panel {
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
}
.title {
margin: 0 0 6px;
.panel-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.panel-title {
font-size: 16px;
font-weight: 900;
}
.panel-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
}
.shot-card {
border-radius: 14px;
border: 1px solid var(--app-border);
overflow: hidden;
}
.shot-img {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
cursor: pointer;
display: block;
}
.shot-body {
padding: 12px;
}
.shot-name {
font-size: 13px;
font-weight: 800;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.shot-meta {
margin-top: 4px;
font-size: 12px;
}
.shot-actions {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.preview {
display: flex;
justify-content: center;
}
.preview-img {
max-width: 100%;
max-height: 78vh;
object-fit: contain;
border-radius: 10px;
border: 1px solid var(--app-border);
background: #fff;
}
@media (max-width: 480px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -1,6 +1,13 @@
{
"_auth-yhlOdREj.js": {
"file": "assets/auth-yhlOdREj.js",
"_accounts-DI7tHfNj.js": {
"file": "assets/accounts-DI7tHfNj.js",
"name": "accounts",
"imports": [
"index.html"
]
},
"_auth-CgGRYkq1.js": {
"file": "assets/auth-CgGRYkq1.js",
"name": "auth",
"imports": [
"index.html"
@@ -11,7 +18,7 @@
"name": "password"
},
"index.html": {
"file": "assets/index-DvbGwVAp.js",
"file": "assets/index-BstQMnWL.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -29,11 +36,12 @@
]
},
"src/pages/AccountsPage.vue": {
"file": "assets/AccountsPage-C2BSK5Ns.js",
"file": "assets/AccountsPage-Bcis23qR.js",
"name": "AccountsPage",
"src": "src/pages/AccountsPage.vue",
"isDynamicEntry": true,
"imports": [
"_accounts-DI7tHfNj.js",
"index.html"
],
"css": [
@@ -41,13 +49,13 @@
]
},
"src/pages/LoginPage.vue": {
"file": "assets/LoginPage-DYohZsxn.js",
"file": "assets/LoginPage-BNb9NBzk.js",
"name": "LoginPage",
"src": "src/pages/LoginPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_auth-yhlOdREj.js",
"_auth-CgGRYkq1.js",
"_password-7ryi82gE.js"
],
"css": [
@@ -55,26 +63,26 @@
]
},
"src/pages/RegisterPage.vue": {
"file": "assets/RegisterPage-CGBzvBqd.js",
"file": "assets/RegisterPage-CFiuiL-s.js",
"name": "RegisterPage",
"src": "src/pages/RegisterPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_auth-yhlOdREj.js"
"_auth-CgGRYkq1.js"
],
"css": [
"assets/RegisterPage-CVjBOq6i.css"
]
},
"src/pages/ResetPasswordPage.vue": {
"file": "assets/ResetPasswordPage-ClLk6uyu.js",
"file": "assets/ResetPasswordPage-DeWXyaHc.js",
"name": "ResetPasswordPage",
"src": "src/pages/ResetPasswordPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_auth-yhlOdREj.js",
"_auth-CgGRYkq1.js",
"_password-7ryi82gE.js"
],
"css": [
@@ -82,19 +90,20 @@
]
},
"src/pages/SchedulesPage.vue": {
"file": "assets/SchedulesPage-DHlqgLCv.js",
"file": "assets/SchedulesPage-BXyRqadY.js",
"name": "SchedulesPage",
"src": "src/pages/SchedulesPage.vue",
"isDynamicEntry": true,
"imports": [
"_accounts-DI7tHfNj.js",
"index.html"
],
"css": [
"assets/SchedulesPage-BAj1X6GW.css"
"assets/SchedulesPage-Gu_OsDUd.css"
]
},
"src/pages/ScreenshotsPage.vue": {
"file": "assets/ScreenshotsPage-jZuEr5af.js",
"file": "assets/ScreenshotsPage-BwyU04c1.js",
"name": "ScreenshotsPage",
"src": "src/pages/ScreenshotsPage.vue",
"isDynamicEntry": true,
@@ -102,11 +111,11 @@
"index.html"
],
"css": [
"assets/ScreenshotsPage-CmPGicmh.css"
"assets/ScreenshotsPage-B66M6Olm.css"
]
},
"src/pages/VerifyResultPage.vue": {
"file": "assets/VerifyResultPage-B_i4AM-j.js",
"file": "assets/VerifyResultPage-DgCNTQ4L.js",
"name": "VerifyResultPage",
"src": "src/pages/VerifyResultPage.vue",
"isDynamicEntry": true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{_ as M,r as j,a as p,c as B,o as A,b as S,d as t,w as o,e as m,u as H,f as g,g as n,h as U,i as x,j as N,t as q,k as E,E as d}from"./index-DvbGwVAp.js";import{g as z,f as F,c as G}from"./auth-yhlOdREj.js";const J={class:"auth-wrap"},O={class:"hint app-muted"},Q={class:"captcha-row"},W=["src"],X={class:"actions"},Y={__name:"RegisterPage",setup(Z){const T=H(),a=j({username:"",password:"",confirm_password:"",email:"",captcha:""}),v=p(!1),f=p(""),b=p(""),h=p(!1),l=p(""),_=p(""),V=p(""),K=B(()=>v.value?"邮箱 *":"邮箱(可选)"),P=B(()=>v.value?"必填,用于账号验证":"选填,用于接收审核通知");async function w(){try{const u=await z();b.value=u?.session_id||"",f.value=u?.captcha_image||"",a.captcha=""}catch{b.value="",f.value=""}}async function R(){try{const u=await F();v.value=!!u?.register_verify_enabled}catch{v.value=!1}}function D(){l.value="",_.value="",V.value=""}async function k(){D();const u=a.username.trim(),e=a.password,y=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){l.value="用户名至少3个字符",d.error(l.value);return}if(e.length<6){l.value="密码至少6个字符",d.error(l.value);return}if(e!==y){l.value="两次输入的密码不一致",d.error(l.value);return}if(v.value&&!s){l.value="请填写邮箱地址用于账号验证",d.error(l.value);return}if(s&&!s.includes("@")){l.value="邮箱格式不正确",d.error(l.value);return}if(!i){l.value="请输入验证码",d.error(l.value);return}h.value=!0;try{const c=await G({username:u,password:e,email:s,captcha_session:b.value,captcha:i});_.value=c?.message||"注册成功",V.value=c?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",d.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(c){const C=c?.response?.data;l.value=C?.error||"注册失败",d.error(l.value),await w()}finally{h.value=!1}}function I(){T.push("/login")}return A(async()=>{await w(),await R()}),(u,e)=>{const y=m("el-alert"),s=m("el-input"),i=m("el-form-item"),c=m("el-button"),C=m("el-form"),L=m("el-card");return g(),S("div",J,[t(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),l.value?(g(),U(y,{key:0,type:"error",closable:!1,title:l.value,"show-icon":"",class:"alert"},null,8,["title"])):x("",!0),_.value?(g(),U(y,{key:1,type:"success",closable:!1,title:_.value,description:V.value,"show-icon":"",class:"alert"},null,8,["title","description"])):x("",!0),t(C,{"label-position":"top"},{default:o(()=>[t(i,{label:"用户名 *"},{default:o(()=>[t(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),t(i,{label:"密码 *"},{default:o(()=>[t(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少6个字符",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少6个字符",-1))]),_:1}),t(i,{label:"确认密码 *"},{default:o(()=>[t(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:N(k,["enter"])},null,8,["modelValue"])]),_:1}),t(i,{label:K.value},{default:o(()=>[t(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",O,q(P.value),1)]),_:1},8,["label"]),t(i,{label:"验证码 *"},{default:o(()=>[n("div",Q,[t(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:N(k,["enter"])},null,8,["modelValue"]),f.value?(g(),S("img",{key:0,class:"captcha-img",src:f.value,alt:"验证码",title:"点击刷新",onClick:w},null,8,W)):x("",!0),t(c,{onClick:w},{default:o(()=>[...e[7]||(e[7]=[E("刷新",-1)])]),_:1})])]),_:1})]),_:1}),t(c,{type:"primary",class:"submit-btn",loading:h.value,onClick:k},{default:o(()=>[...e[8]||(e[8]=[E("注册",-1)])]),_:1},8,["loading"]),n("div",X,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),t(c,{link:"",type:"primary",onClick:I},{default:o(()=>[...e[9]||(e[9]=[E("立即登录",-1)])]),_:1})])]),_:1})])}}},ae=M(Y,[["__scopeId","data-v-32684b4d"]]);export{ae as default};
import{_ as M,r as j,a as p,c as B,o as A,b as S,d as t,w as o,e as m,u as H,f as g,g as n,h as U,i as x,j as N,t as q,k as E,E as d}from"./index-BstQMnWL.js";import{g as z,f as F,c as G}from"./auth-CgGRYkq1.js";const J={class:"auth-wrap"},O={class:"hint app-muted"},Q={class:"captcha-row"},W=["src"],X={class:"actions"},Y={__name:"RegisterPage",setup(Z){const T=H(),a=j({username:"",password:"",confirm_password:"",email:"",captcha:""}),v=p(!1),f=p(""),b=p(""),h=p(!1),l=p(""),_=p(""),V=p(""),K=B(()=>v.value?"邮箱 *":"邮箱(可选)"),P=B(()=>v.value?"必填,用于账号验证":"选填,用于接收审核通知");async function w(){try{const u=await z();b.value=u?.session_id||"",f.value=u?.captcha_image||"",a.captcha=""}catch{b.value="",f.value=""}}async function R(){try{const u=await F();v.value=!!u?.register_verify_enabled}catch{v.value=!1}}function D(){l.value="",_.value="",V.value=""}async function k(){D();const u=a.username.trim(),e=a.password,y=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){l.value="用户名至少3个字符",d.error(l.value);return}if(e.length<6){l.value="密码至少6个字符",d.error(l.value);return}if(e!==y){l.value="两次输入的密码不一致",d.error(l.value);return}if(v.value&&!s){l.value="请填写邮箱地址用于账号验证",d.error(l.value);return}if(s&&!s.includes("@")){l.value="邮箱格式不正确",d.error(l.value);return}if(!i){l.value="请输入验证码",d.error(l.value);return}h.value=!0;try{const c=await G({username:u,password:e,email:s,captcha_session:b.value,captcha:i});_.value=c?.message||"注册成功",V.value=c?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",d.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(c){const C=c?.response?.data;l.value=C?.error||"注册失败",d.error(l.value),await w()}finally{h.value=!1}}function I(){T.push("/login")}return A(async()=>{await w(),await R()}),(u,e)=>{const y=m("el-alert"),s=m("el-input"),i=m("el-form-item"),c=m("el-button"),C=m("el-form"),L=m("el-card");return g(),S("div",J,[t(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),l.value?(g(),U(y,{key:0,type:"error",closable:!1,title:l.value,"show-icon":"",class:"alert"},null,8,["title"])):x("",!0),_.value?(g(),U(y,{key:1,type:"success",closable:!1,title:_.value,description:V.value,"show-icon":"",class:"alert"},null,8,["title","description"])):x("",!0),t(C,{"label-position":"top"},{default:o(()=>[t(i,{label:"用户名 *"},{default:o(()=>[t(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),t(i,{label:"密码 *"},{default:o(()=>[t(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少6个字符",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少6个字符",-1))]),_:1}),t(i,{label:"确认密码 *"},{default:o(()=>[t(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:N(k,["enter"])},null,8,["modelValue"])]),_:1}),t(i,{label:K.value},{default:o(()=>[t(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",O,q(P.value),1)]),_:1},8,["label"]),t(i,{label:"验证码 *"},{default:o(()=>[n("div",Q,[t(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:N(k,["enter"])},null,8,["modelValue"]),f.value?(g(),S("img",{key:0,class:"captcha-img",src:f.value,alt:"验证码",title:"点击刷新",onClick:w},null,8,W)):x("",!0),t(c,{onClick:w},{default:o(()=>[...e[7]||(e[7]=[E("刷新",-1)])]),_:1})])]),_:1})]),_:1}),t(c,{type:"primary",class:"submit-btn",loading:h.value,onClick:k},{default:o(()=>[...e[8]||(e[8]=[E("注册",-1)])]),_:1},8,["loading"]),n("div",X,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),t(c,{link:"",type:"primary",onClick:I},{default:o(()=>[...e[9]||(e[9]=[E("立即登录",-1)])]),_:1})])]),_:1})])}}},ae=M(Y,[["__scopeId","data-v-32684b4d"]]);export{ae as default};

View File

@@ -1 +1 @@
import{_ as L,a as n,l as M,r as U,c as j,o as F,m as K,b as v,d as s,w as a,e as l,u as D,f as m,g as w,F as T,k,h as q,i as x,j as z,t as G,E as y}from"./index-DvbGwVAp.js";import{d as H}from"./auth-yhlOdREj.js";import{v as J}from"./password-7ryi82gE.js";const O={class:"auth-wrap"},Q={class:"actions"},W={class:"actions"},X={key:0,class:"app-muted"},Y={__name:"ResetPasswordPage",setup(Z){const B=M(),A=D(),r=n(String(B.params.token||"")),i=n(!0),b=n(""),t=U({newPassword:"",confirmPassword:""}),g=n(!1),f=n(""),d=n(0);let u=null;function C(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=j(()=>!!(i.value&&r.value&&!f.value));function S(){A.push("/login")}function N(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=J(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}g.value=!0;try{await H({token:r.value,new_password:o}),f.value="密码重置成功3秒后跳转到登录页面...",y.success("密码重置成功"),N()}catch(p){const _=p?.response?.data;y.error(_?.error||"重置失败")}finally{g.value=!1}}return F(()=>{const o=C();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),K(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),_=l("el-input"),h=l("el-form-item"),R=l("el-form"),E=l("el-card");return m(),v("div",O,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=w("div",{class:"brand"},[w("div",{class:"brand-title"},"知识管理平台"),w("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(m(),v(T,{key:1},[f.value?(m(),q(c,{key:0,type:"success",closable:!1,title:"重置成功",description:f.value,"show-icon":"",class:"alert"},null,8,["description"])):x("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(h,{label:"新密码至少8位且包含字母和数字"},{default:a(()=>[s(_,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=P=>t.newPassword=P),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(h,{label:"确认密码"},{default:a(()=>[s(_,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=P=>t.confirmPassword=P),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:z(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:g.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),w("div",W,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(m(),v("span",X,G(d.value)+" 秒后自动跳转…",1)):x("",!0)])],64)):(m(),v(T,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),w("div",Q,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},se=L(Y,[["__scopeId","data-v-0bbb511c"]]);export{se as default};
import{_ as L,a as n,l as M,r as U,c as j,o as F,m as K,b as v,d as s,w as a,e as l,u as D,f as m,g as w,F as T,k,h as q,i as x,j as z,t as G,E as y}from"./index-BstQMnWL.js";import{d as H}from"./auth-CgGRYkq1.js";import{v as J}from"./password-7ryi82gE.js";const O={class:"auth-wrap"},Q={class:"actions"},W={class:"actions"},X={key:0,class:"app-muted"},Y={__name:"ResetPasswordPage",setup(Z){const B=M(),A=D(),r=n(String(B.params.token||"")),i=n(!0),b=n(""),t=U({newPassword:"",confirmPassword:""}),g=n(!1),f=n(""),d=n(0);let u=null;function C(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=j(()=>!!(i.value&&r.value&&!f.value));function S(){A.push("/login")}function N(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=J(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}g.value=!0;try{await H({token:r.value,new_password:o}),f.value="密码重置成功3秒后跳转到登录页面...",y.success("密码重置成功"),N()}catch(p){const _=p?.response?.data;y.error(_?.error||"重置失败")}finally{g.value=!1}}return F(()=>{const o=C();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),K(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),_=l("el-input"),h=l("el-form-item"),R=l("el-form"),E=l("el-card");return m(),v("div",O,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=w("div",{class:"brand"},[w("div",{class:"brand-title"},"知识管理平台"),w("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(m(),v(T,{key:1},[f.value?(m(),q(c,{key:0,type:"success",closable:!1,title:"重置成功",description:f.value,"show-icon":"",class:"alert"},null,8,["description"])):x("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(h,{label:"新密码至少8位且包含字母和数字"},{default:a(()=>[s(_,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=P=>t.newPassword=P),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(h,{label:"确认密码"},{default:a(()=>[s(_,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=P=>t.confirmPassword=P),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:z(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:g.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),w("div",W,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(m(),v("span",X,G(d.value)+" 秒后自动跳转…",1)):x("",!0)])],64)):(m(),v(T,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),w("div",Q,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},se=L(Y,[["__scopeId","data-v-0bbb511c"]]);export{se as default};

View File

@@ -1 +0,0 @@
.card[data-v-b4b9e229]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.title[data-v-b4b9e229]{margin:0 0 6px;font-size:16px;font-weight:800}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{_ as t,h as o,w as c,e as d,f as r,g as s}from"./index-DvbGwVAp.js";const n={};function l(_,e){const a=d("el-card");return r(),o(a,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:c(()=>[...e[0]||(e[0]=[s("h2",{class:"title"},"定时任务",-1),s("div",{class:"app-muted"},"阶段1页面壳子已就绪功能将在后续阶段迁移。",-1)])]),_:1})}const f=t(n,[["render",l],["__scopeId","data-v-b4b9e229"]]);export{f as default};

View File

@@ -0,0 +1 @@
.page[data-v-ee36bae1]{display:flex;flex-direction:column;gap:12px}.vip-alert[data-v-ee36bae1]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.vip-actions[data-v-ee36bae1]{margin-top:10px}.panel[data-v-ee36bae1]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.panel-head[data-v-ee36bae1]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:10px}.panel-title[data-v-ee36bae1]{font-size:16px;font-weight:900}.panel-actions[data-v-ee36bae1]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.grid[data-v-ee36bae1]{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px}.schedule-card[data-v-ee36bae1]{border-radius:14px;border:1px solid var(--app-border)}.schedule-top[data-v-ee36bae1]{display:flex;justify-content:space-between;gap:12px}.schedule-main[data-v-ee36bae1]{min-width:0;flex:1}.schedule-title[data-v-ee36bae1]{display:flex;align-items:center;justify-content:space-between;gap:10px}.schedule-name[data-v-ee36bae1]{font-size:14px;font-weight:900;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.schedule-meta[data-v-ee36bae1]{margin-top:6px;display:flex;gap:10px;flex-wrap:wrap;font-size:12px}.schedule-actions[data-v-ee36bae1]{margin-top:12px;display:flex;gap:8px;flex-wrap:wrap}.logs[data-v-ee36bae1]{display:flex;flex-direction:column;gap:10px}.log-card[data-v-ee36bae1]{border-radius:12px;border:1px solid var(--app-border)}.log-head[data-v-ee36bae1]{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12px}.log-body[data-v-ee36bae1]{margin-top:8px;font-size:13px;line-height:1.6}.log-error[data-v-ee36bae1]{margin-top:6px;color:#b91c1c}.vip-body[data-v-ee36bae1]{padding:12px 0 0}.vip-tip[data-v-ee36bae1]{margin-top:10px;font-size:13px;line-height:1.6}@media(max-width:480px){.grid[data-v-ee36bae1]{grid-template-columns:1fr}}

View File

@@ -0,0 +1 @@
.panel[data-v-ff08abf8]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.panel-head[data-v-ff08abf8]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:10px}.panel-title[data-v-ff08abf8]{font-size:16px;font-weight:900}.panel-actions[data-v-ff08abf8]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.grid[data-v-ff08abf8]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}.shot-card[data-v-ff08abf8]{border-radius:14px;border:1px solid var(--app-border);overflow:hidden}.shot-img[data-v-ff08abf8]{width:100%;aspect-ratio:16/9;object-fit:cover;cursor:pointer;display:block}.shot-body[data-v-ff08abf8]{padding:12px}.shot-name[data-v-ff08abf8]{font-size:13px;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shot-meta[data-v-ff08abf8]{margin-top:4px;font-size:12px}.shot-actions[data-v-ff08abf8]{margin-top:10px;display:flex;flex-wrap:wrap;gap:6px}.preview[data-v-ff08abf8]{display:flex;justify-content:center}.preview-img[data-v-ff08abf8]{max-width:100%;max-height:78vh;object-fit:contain;border-radius:10px;border:1px solid var(--app-border);background:#fff}@media(max-width:480px){.grid[data-v-ff08abf8]{grid-template-columns:1fr}}

View File

@@ -0,0 +1 @@
import{p as C,_ as P,a as y,o as R,h as w,w as s,e as _,f as c,g as o,b,d as i,k as f,F as B,v as D,t as T,x as I,E as l}from"./index-BstQMnWL.js";async function F(){const{data:d}=await C.get("/screenshots");return d}async function L(d){const{data:u}=await C.delete(`/screenshots/${encodeURIComponent(d)}`);return u}async function O(){const{data:d}=await C.post("/screenshots/clear",{});return d}const j={class:"panel-head"},q={class:"panel-actions"},G={key:1,class:"grid"},H=["src","alt","onClick"],J={class:"shot-body"},K=["title"],Q={class:"shot-meta app-muted"},W={class:"shot-actions"},X={class:"preview"},Y=["src","alt"],Z={__name:"ScreenshotsPage",setup(d){const u=y(!1),r=y([]),p=y(!1),g=y(""),h=y("");function v(a){return`/screenshots/${encodeURIComponent(a)}`}async function x(){u.value=!0;try{const a=await F();r.value=Array.isArray(a)?a:[]}catch(a){a?.response?.status===401&&(window.location.href="/login"),r.value=[]}finally{u.value=!1}}function S(a){h.value=a.display_name||a.filename||"截图预览",g.value=v(a.filename),p.value=!0}async function U(){try{await I.confirm("确定要清空全部截图吗?","清空截图",{confirmButtonText:"清空",cancelButtonText:"取消",type:"warning"})}catch{return}try{const a=await O();if(a?.success){l.success(`已清空(删除 ${a?.deleted||0} 张)`),r.value=[],p.value=!1;return}l.error(a?.error||"操作失败")}catch(a){const e=a?.response?.data;l.error(e?.error||"操作失败")}}async function V(a){try{await I.confirm(`确定要删除截图「${a.display_name||a.filename}」吗?`,"删除截图",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await L(a.filename);if(e?.success){r.value=r.value.filter(n=>n.filename!==a.filename),g.value.includes(encodeURIComponent(a.filename))&&(p.value=!1),l.success("已删除");return}l.error(e?.error||"删除失败")}catch(e){const n=e?.response?.data;l.error(n?.error||"删除失败")}}async function E(a){const e=`${window.location.origin}${v(a.filename)}`;try{await navigator.clipboard.writeText(e),l.success("链接已复制")}catch{l.warning("复制失败,请手动复制链接")}}async function z(a){const e=v(a.filename);try{const m=await(await fetch(e,{credentials:"include"})).blob();await navigator.clipboard.write([new ClipboardItem({[m.type]:m})]),l.success("图片已复制")}catch{await E(a)}}function A(a){const e=document.createElement("a");e.href=v(a.filename),e.download=a.display_name||a.filename,document.body.appendChild(e),e.click(),e.remove()}return R(x),(a,e)=>{const n=_("el-button"),m=_("el-skeleton"),M=_("el-empty"),$=_("el-card"),N=_("el-dialog");return c(),w($,{shadow:"never",class:"panel","body-style":{padding:"14px"}},{default:s(()=>[o("div",j,[e[4]||(e[4]=o("div",{class:"panel-title"},"截图管理",-1)),o("div",q,[i(n,{loading:u.value,onClick:x},{default:s(()=>[...e[2]||(e[2]=[f("刷新",-1)])]),_:1},8,["loading"]),i(n,{type:"danger",plain:"",disabled:r.value.length===0,onClick:U},{default:s(()=>[...e[3]||(e[3]=[f("清空全部",-1)])]),_:1},8,["disabled"])])]),u.value?(c(),w(m,{key:0,rows:6,animated:""})):(c(),b(B,{key:1},[r.value.length===0?(c(),w(M,{key:0,description:"暂无截图"})):(c(),b("div",G,[(c(!0),b(B,null,D(r.value,t=>(c(),w($,{key:t.filename,shadow:"never",class:"shot-card","body-style":{padding:"0"}},{default:s(()=>[o("img",{class:"shot-img",src:v(t.filename),alt:t.display_name||t.filename,loading:"lazy",onClick:k=>S(t)},null,8,H),o("div",J,[o("div",{class:"shot-name",title:t.display_name||t.filename},T(t.display_name||t.filename),9,K),o("div",Q,T(t.created||""),1),o("div",W,[i(n,{size:"small",text:"",type:"primary",onClick:k=>z(t)},{default:s(()=>[...e[5]||(e[5]=[f("复制图片",-1)])]),_:1},8,["onClick"]),i(n,{size:"small",text:"",onClick:k=>A(t)},{default:s(()=>[...e[6]||(e[6]=[f("下载",-1)])]),_:1},8,["onClick"]),i(n,{size:"small",text:"",type:"danger",onClick:k=>V(t)},{default:s(()=>[...e[7]||(e[7]=[f("删除",-1)])]),_:1},8,["onClick"])])])]),_:2},1024))),128))]))],64)),i(N,{modelValue:p.value,"onUpdate:modelValue":e[1]||(e[1]=t=>p.value=t),title:h.value,width:"min(920px, 94vw)"},{footer:s(()=>[i(n,{onClick:e[0]||(e[0]=t=>p.value=!1)},{default:s(()=>[...e[8]||(e[8]=[f("关闭",-1)])]),_:1})]),default:s(()=>[o("div",X,[o("img",{src:g.value,alt:h.value,class:"preview-img"},null,8,Y)])]),_:1},8,["modelValue","title"])]),_:1})}}},ae=P(Z,[["__scopeId","data-v-ff08abf8"]]);export{ae as default};

View File

@@ -1 +0,0 @@
.card[data-v-08f8d2d3]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.title[data-v-08f8d2d3]{margin:0 0 6px;font-size:16px;font-weight:800}

View File

@@ -1 +0,0 @@
import{_ as t,h as o,w as c,e as d,f as r,g as s}from"./index-DvbGwVAp.js";const n={};function _(l,e){const a=d("el-card");return r(),o(a,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:c(()=>[...e[0]||(e[0]=[s("h2",{class:"title"},"截图管理",-1),s("div",{class:"app-muted"},"阶段1页面壳子已就绪功能将在后续阶段迁移。",-1)])]),_:1})}const f=t(n,[["render",_],["__scopeId","data-v-08f8d2d3"]]);export{f as default};

View File

@@ -1 +1 @@
import{_ as U,a as o,c as I,o as E,m as R,b as k,d as i,w as s,e as d,u as W,f as _,g as l,i as B,h as $,k as T,t as v}from"./index-DvbGwVAp.js";const j={class:"auth-wrap"},z={class:"actions"},D={key:0,class:"countdown app-muted"},M={__name:"VerifyResultPage",setup(q){const x=W(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function C(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function N(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const A=I(()=>!!(r.value&&u.value)),b=I(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await x.push(e)}}function P(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return E(()=>{const e=C();N(e),P()}),R(()=>{a&&window.clearInterval(a)}),(e,t)=>{const h=d("el-button"),V=d("el-result"),L=d("el-card");return _(),k("div",j,[i(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(V,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",z,[i(h,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[T(v(w.value),1)]),_:1}),A.value?(_(),$(h,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[T(v(r.value),1)]),_:1})):B("",!0)]),b.value?(_(),k("div",D,v(n.value)+" 秒后自动跳转... ",1)):B("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=U(M,[["__scopeId","data-v-1fc6b081"]]);export{G as default};
import{_ as U,a as o,c as I,o as E,m as R,b as k,d as i,w as s,e as d,u as W,f as _,g as l,i as B,h as $,k as T,t as v}from"./index-BstQMnWL.js";const j={class:"auth-wrap"},z={class:"actions"},D={key:0,class:"countdown app-muted"},M={__name:"VerifyResultPage",setup(q){const x=W(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function C(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function N(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const A=I(()=>!!(r.value&&u.value)),b=I(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await x.push(e)}}function P(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return E(()=>{const e=C();N(e),P()}),R(()=>{a&&window.clearInterval(a)}),(e,t)=>{const h=d("el-button"),V=d("el-result"),L=d("el-card");return _(),k("div",j,[i(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(V,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",z,[i(h,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[T(v(w.value),1)]),_:1}),A.value?(_(),$(h,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[T(v(r.value),1)]),_:1})):B("",!0)]),b.value?(_(),k("div",D,v(n.value)+" 秒后自动跳转... ",1)):B("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=U(M,[["__scopeId","data-v-1fc6b081"]]);export{G as default};

View File

@@ -0,0 +1 @@
import{p as c}from"./index-BstQMnWL.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};

View File

@@ -1 +1 @@
import{p as s}from"./index-DvbGwVAp.js";async function r(){const{data:t}=await s.get("/email/verify-status");return t}async function e(){const{data:t}=await s.post("/generate_captcha",{});return t}async function o(t){const{data:a}=await s.post("/login",t);return a}async function i(t){const{data:a}=await s.post("/register",t);return a}async function c(t){const{data:a}=await s.post("/resend-verify-email",t);return a}async function u(t){const{data:a}=await s.post("/forgot-password",t);return a}async function f(t){const{data:a}=await s.post("/reset_password_request",t);return a}async function d(t){const{data:a}=await s.post("/reset-password-confirm",t);return a}export{u as a,c as b,i as c,d,r as f,e as g,o as l,f as r};
import{p as s}from"./index-BstQMnWL.js";async function r(){const{data:t}=await s.get("/email/verify-status");return t}async function e(){const{data:t}=await s.post("/generate_captcha",{});return t}async function o(t){const{data:a}=await s.post("/login",t);return a}async function i(t){const{data:a}=await s.post("/register",t);return a}async function c(t){const{data:a}=await s.post("/resend-verify-email",t);return a}async function u(t){const{data:a}=await s.post("/forgot-password",t);return a}async function f(t){const{data:a}=await s.post("/reset_password_request",t);return a}async function d(t){const{data:a}=await s.post("/reset-password-confirm",t);return a}export{u as a,c as b,i as c,d,r as f,e as g,o as l,f as r};

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>知识管理平台</title>
<script type="module" crossorigin src="./assets/index-DvbGwVAp.js"></script>
<script type="module" crossorigin src="./assets/index-BstQMnWL.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-Baiuy_-z.css">
</head>
<body>