181 lines
7.8 KiB
Vue
181 lines
7.8 KiB
Vue
<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>
|