feat(app): migrate schedules and screenshots (stage 4)
This commit is contained in:
42
app-frontend/src/api/schedules.js
Normal file
42
app-frontend/src/api/schedules.js
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
17
app-frontend/src/api/screenshots.js
Normal file
17
app-frontend/src/api/screenshots.js
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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>
|
<template>
|
||||||
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
|
<div class="page">
|
||||||
<h2 class="title">定时任务</h2>
|
<el-alert
|
||||||
<div class="app-muted">阶段1:页面壳子已就绪,功能将在后续阶段迁移。</div>
|
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>
|
</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>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-alert {
|
||||||
border-radius: var(--app-radius);
|
border-radius: var(--app-radius);
|
||||||
border: 1px solid var(--app-border);
|
border: 1px solid var(--app-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.vip-actions {
|
||||||
margin: 0 0 6px;
|
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-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>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
<template>
|
||||||
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
|
<el-card shadow="never" class="panel" :body-style="{ padding: '14px' }">
|
||||||
<h2 class="title">截图管理</h2>
|
<div class="panel-head">
|
||||||
<div class="app-muted">阶段1:页面壳子已就绪,功能将在后续阶段迁移。</div>
|
<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>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.panel {
|
||||||
border-radius: var(--app-radius);
|
border-radius: var(--app-radius);
|
||||||
border: 1px solid var(--app-border);
|
border: 1px solid var(--app-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.panel-head {
|
||||||
margin: 0 0 6px;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
font-size: 16px;
|
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;
|
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>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
{
|
{
|
||||||
"_auth-yhlOdREj.js": {
|
"_accounts-DI7tHfNj.js": {
|
||||||
"file": "assets/auth-yhlOdREj.js",
|
"file": "assets/accounts-DI7tHfNj.js",
|
||||||
|
"name": "accounts",
|
||||||
|
"imports": [
|
||||||
|
"index.html"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_auth-CgGRYkq1.js": {
|
||||||
|
"file": "assets/auth-CgGRYkq1.js",
|
||||||
"name": "auth",
|
"name": "auth",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
@@ -11,7 +18,7 @@
|
|||||||
"name": "password"
|
"name": "password"
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"file": "assets/index-DvbGwVAp.js",
|
"file": "assets/index-BstQMnWL.js",
|
||||||
"name": "index",
|
"name": "index",
|
||||||
"src": "index.html",
|
"src": "index.html",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
@@ -29,11 +36,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/AccountsPage.vue": {
|
"src/pages/AccountsPage.vue": {
|
||||||
"file": "assets/AccountsPage-C2BSK5Ns.js",
|
"file": "assets/AccountsPage-Bcis23qR.js",
|
||||||
"name": "AccountsPage",
|
"name": "AccountsPage",
|
||||||
"src": "src/pages/AccountsPage.vue",
|
"src": "src/pages/AccountsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
|
"_accounts-DI7tHfNj.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -41,13 +49,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/LoginPage.vue": {
|
"src/pages/LoginPage.vue": {
|
||||||
"file": "assets/LoginPage-DYohZsxn.js",
|
"file": "assets/LoginPage-BNb9NBzk.js",
|
||||||
"name": "LoginPage",
|
"name": "LoginPage",
|
||||||
"src": "src/pages/LoginPage.vue",
|
"src": "src/pages/LoginPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-yhlOdREj.js",
|
"_auth-CgGRYkq1.js",
|
||||||
"_password-7ryi82gE.js"
|
"_password-7ryi82gE.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -55,26 +63,26 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/RegisterPage.vue": {
|
"src/pages/RegisterPage.vue": {
|
||||||
"file": "assets/RegisterPage-CGBzvBqd.js",
|
"file": "assets/RegisterPage-CFiuiL-s.js",
|
||||||
"name": "RegisterPage",
|
"name": "RegisterPage",
|
||||||
"src": "src/pages/RegisterPage.vue",
|
"src": "src/pages/RegisterPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-yhlOdREj.js"
|
"_auth-CgGRYkq1.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/RegisterPage-CVjBOq6i.css"
|
"assets/RegisterPage-CVjBOq6i.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ResetPasswordPage.vue": {
|
"src/pages/ResetPasswordPage.vue": {
|
||||||
"file": "assets/ResetPasswordPage-ClLk6uyu.js",
|
"file": "assets/ResetPasswordPage-DeWXyaHc.js",
|
||||||
"name": "ResetPasswordPage",
|
"name": "ResetPasswordPage",
|
||||||
"src": "src/pages/ResetPasswordPage.vue",
|
"src": "src/pages/ResetPasswordPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-yhlOdREj.js",
|
"_auth-CgGRYkq1.js",
|
||||||
"_password-7ryi82gE.js"
|
"_password-7ryi82gE.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -82,19 +90,20 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SchedulesPage.vue": {
|
"src/pages/SchedulesPage.vue": {
|
||||||
"file": "assets/SchedulesPage-DHlqgLCv.js",
|
"file": "assets/SchedulesPage-BXyRqadY.js",
|
||||||
"name": "SchedulesPage",
|
"name": "SchedulesPage",
|
||||||
"src": "src/pages/SchedulesPage.vue",
|
"src": "src/pages/SchedulesPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
|
"_accounts-DI7tHfNj.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/SchedulesPage-BAj1X6GW.css"
|
"assets/SchedulesPage-Gu_OsDUd.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ScreenshotsPage.vue": {
|
"src/pages/ScreenshotsPage.vue": {
|
||||||
"file": "assets/ScreenshotsPage-jZuEr5af.js",
|
"file": "assets/ScreenshotsPage-BwyU04c1.js",
|
||||||
"name": "ScreenshotsPage",
|
"name": "ScreenshotsPage",
|
||||||
"src": "src/pages/ScreenshotsPage.vue",
|
"src": "src/pages/ScreenshotsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -102,11 +111,11 @@
|
|||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/ScreenshotsPage-CmPGicmh.css"
|
"assets/ScreenshotsPage-B66M6Olm.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/VerifyResultPage.vue": {
|
"src/pages/VerifyResultPage.vue": {
|
||||||
"file": "assets/VerifyResultPage-B_i4AM-j.js",
|
"file": "assets/VerifyResultPage-DgCNTQ4L.js",
|
||||||
"name": "VerifyResultPage",
|
"name": "VerifyResultPage",
|
||||||
"src": "src/pages/VerifyResultPage.vue",
|
"src": "src/pages/VerifyResultPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
|
|||||||
1
static/app/assets/AccountsPage-Bcis23qR.js
Normal file
1
static/app/assets/AccountsPage-Bcis23qR.js
Normal file
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
@@ -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};
|
||||||
@@ -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};
|
||||||
@@ -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}
|
|
||||||
1
static/app/assets/SchedulesPage-BXyRqadY.js
Normal file
1
static/app/assets/SchedulesPage-BXyRqadY.js
Normal file
File diff suppressed because one or more lines are too long
@@ -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};
|
|
||||||
1
static/app/assets/SchedulesPage-Gu_OsDUd.css
Normal file
1
static/app/assets/SchedulesPage-Gu_OsDUd.css
Normal 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}}
|
||||||
1
static/app/assets/ScreenshotsPage-B66M6Olm.css
Normal file
1
static/app/assets/ScreenshotsPage-B66M6Olm.css
Normal 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}}
|
||||||
1
static/app/assets/ScreenshotsPage-BwyU04c1.js
Normal file
1
static/app/assets/ScreenshotsPage-BwyU04c1.js
Normal 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};
|
||||||
@@ -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}
|
|
||||||
@@ -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};
|
|
||||||
@@ -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};
|
||||||
1
static/app/assets/accounts-DI7tHfNj.js
Normal file
1
static/app/assets/accounts-DI7tHfNj.js
Normal 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};
|
||||||
@@ -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
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
||||||
<title>知识管理平台</title>
|
<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">
|
<link rel="stylesheet" crossorigin href="./assets/index-Baiuy_-z.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user