Files
par/web/src/views/AccessResults.vue

181 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<h2>脚本管理</h2>
<!-- 默认模板 -->
<el-card style="margin-top:12px">
<template #header><span style="font-weight:500">站点类型默认模板</span></template>
<div style="display:flex;gap:12px;flex-wrap:wrap">
<div v-for="dt in defaultTypes" :key="dt" style="border:1px solid #ebeef5;border-radius:4px;padding:10px 14px;min-width:180px">
<div style="font-weight:500;margin-bottom:6px"><el-tag size="small">{{ dt }}</el-tag></div>
<div style="display:flex;gap:4px">
<el-button size="small" @click="editDefaultScript(dt)">编辑</el-button>
<el-button size="small" @click="viewDefaultScript(dt)">查看</el-button>
</div>
</div>
</div>
<el-dialog v-model="defaultEditorVisible" :title="'默认脚本: '+editingDefaultType" width="800px" destroy-on-close>
<div style="height:500px;border-radius:4px;overflow:hidden">
<CodeEditor v-model="defaultScriptContent" language="groovy" />
</div>
<template #footer>
<el-button @click="defaultEditorVisible=false">取消</el-button>
<el-button type="primary" @click="saveDefaultScript" :loading="defaultSaving">保存</el-button>
</template>
</el-dialog>
<el-dialog v-model="defaultViewVisible" :title="'默认脚本: '+viewingDefaultType" width="800px">
<pre style="background:#1e1e1e;color:#d4d4d4;padding:12px;border-radius:4px;max-height:500px;overflow:auto;font-size:12px;white-space:pre-wrap">{{ viewingDefaultScript }}</pre>
</el-dialog>
</el-card>
<el-card style="margin-top: 12px">
<div style="margin-bottom: 12px;display:flex;gap:8px">
<el-button @click="fetchAll" :icon="Refresh">刷新</el-button>
<el-button @click="exportScripts" :loading="exporting">导出脚本</el-button>
<el-upload :auto-upload="false" :show-file-list="false" accept=".zip" :on-change="importScripts">
<el-button :loading="importing">导入脚本</el-button>
</el-upload>
</div>
<el-table :data="sites" v-loading="loading" stripe size="small" empty-text="暂无访问记录">
<el-table-column prop="siteId" label="站点ID" width="110" />
<el-table-column label="站点名称" min-width="130">
<template #default="scope">
<span>{{ scope.row.names?.join(' / ') || scope.row.siteId }}</span>
</template>
</el-table-column>
<el-table-column label="地址" min-width="180" show-overflow-tooltip>
<template #default="scope">
<span style="font-size:12px;color:#909399">{{ (scope.row.urls||[]).join(' / ') || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="版本" width="60" align="center">
<template #default="scope">
<el-tag v-if="scope.row.latestVersion" size="small" type="primary">v{{ scope.row.latestVersion }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="上报" width="100" align="center">
<template #default="scope">
<span>{{ (scope.row.pages?.length || 0) + '页·' + (scope.row.totalResults || 0) + '次' }}</span>
</template>
</el-table-column>
<el-table-column label="上报时间" width="110" align="center">
<template #default="scope">
<span style="font-size:12px;color:#909399">{{ friendlyTime(scope.row.lastSubmitted) || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="保存" width="110" align="center">
<template #default="scope">
<span style="font-size:12px;color:#909399">{{ friendlyTime(scope.row.configSavedAt) || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="发布" width="110" align="center">
<template #default="scope">
<span style="font-size:12px;color:#909399">{{ friendlyTime(scope.row.configPublishedAt) || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="待审核" width="70" align="center">
<template #default="scope">
<el-tag v-if="scope.row.pendingConfigCount" size="small" type="danger">{{ scope.row.pendingConfigCount }}</el-tag>
<span v-else style="color:#909399">0</span>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button size="small" type="primary" @click="$router.push(`/access-results/check/${scope.row.siteId}`)">
校验
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup>
import api from '../api'
import { Refresh } from '@element-plus/icons-vue'
const loading = ref(false), exporting = ref(false), importing = ref(false)
const sites = ref([])
const defaultTypes = ['nexusphp','mtorrent','gazelle','unit3d']
const defaultEditorVisible = ref(false), defaultViewVisible = ref(false)
const editingDefaultType = ref(''), viewingDefaultType = ref(''), viewingDefaultScript = ref('')
const defaultScriptContent = ref(''), defaultSaving = ref(false)
const editDefaultScript = async (type) => {
editingDefaultType.value = type
defaultScriptContent.value = ''
defaultEditorVisible.value = true
try {
const r = await api.raw.get('/admin/scripts/default/' + type)
defaultScriptContent.value = (r.data||r||{}).script || (r||{}).script || ''
} catch { ElMessage.error('加载默认脚本失败') }
}
const viewDefaultScript = async (type) => {
viewingDefaultType.value = type
viewingDefaultScript.value = '加载中...'
defaultViewVisible.value = true
try {
const r = await api.raw.get('/admin/scripts/default/' + type)
viewingDefaultScript.value = (r.data||r||{}).script || (r||{}).script || '(空)'
} catch { viewingDefaultScript.value = '加载失败' }
}
const saveDefaultScript = async () => {
defaultSaving.value = true
try {
await api.raw.put('/admin/scripts/default/' + editingDefaultType.value, { script: defaultScriptContent.value })
ElMessage.success('已保存'); defaultEditorVisible.value = false
} catch { ElMessage.error('保存失败') }
finally { defaultSaving.value = false }
}
const exportScripts = async () => {
exporting.value = true
try {
// api 响应拦截器返回 response.data,所以 r 直接就是 blob
const r = await api.raw.get('/admin/scripts/export', { responseType: 'blob' })
const a = document.createElement('a')
a.href = URL.createObjectURL(new Blob([r]))
a.download = 'scripts.zip'; a.click(); URL.revokeObjectURL(a.href)
} catch { ElMessage.error('导出失败') }
finally { exporting.value = false }
}
const importScripts = async (file) => {
importing.value = true
try {
const fd = new FormData(); fd.append('file', file.raw)
const r = await api.raw.post('/admin/scripts/import', fd)
ElMessage.success('导入完成: ' + ((r.ok || 0) + ' 成功, ' + (r.fail || 0) + ' 失败'))
fetchAll()
} catch { ElMessage.error('导入失败') }
finally { importing.value = false }
}
const friendlyTime = (t) => {
if (!t) return null
const d = new Date(t), now = new Date()
const diffMs = now - d, diffMin = Math.floor(diffMs / 60000), diffH = Math.floor(diffMs / 3600000), diffD = Math.floor(diffMs / 86400000)
if (diffMin < 1) return '刚刚'
if (diffMin < 60) return diffMin + '分钟前'
if (diffH < 24) return diffH + '小时前'
if (diffD < 30) return diffD + '天前'
return d.toISOString().substring(0, 10)
}
const fetchAll = async () => {
loading.value = true
try { sites.value = (await api.accessResults.summary()).data || [] }
catch { ElMessage.error('获取汇总失败') }
finally { loading.value = false }
}
onMounted(fetchAll)
</script>