196 lines
9.1 KiB
Vue
196 lines
9.1 KiB
Vue
<template>
|
||
<div>
|
||
<h2>页面配置</h2>
|
||
|
||
<!-- 站点类型默认模板 -->
|
||
<el-card style="margin-top:12px" v-if="defaults.length">
|
||
<template #header><span style="font-weight:500">站点类型默认模板</span></template>
|
||
<div style="display:flex;gap:12px;flex-wrap:wrap">
|
||
<div v-for="dt in defaults" :key="dt.type" style="border:1px solid #ebeef5;border-radius:4px;padding:10px 14px;min-width:200px">
|
||
<div style="font-weight:500;margin-bottom:6px">{{ dt.label }} <el-tag size="small">{{ dt.type }}</el-tag></div>
|
||
<div style="font-size:12px;color:#909399;margin-bottom:8px">{{ dt.entries.length }} 个入口</div>
|
||
<div style="display:flex;gap:4px">
|
||
<el-button size="small" @click="previewDefault(dt)">查看</el-button>
|
||
<el-button size="small" type="primary" @click="editDefault(dt)">编辑默认</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card style="margin-top:12px">
|
||
<div style="margin-bottom:12px;display:flex;gap:8px">
|
||
<el-button @click="fetchAll" :loading="loading">刷新</el-button>
|
||
</div>
|
||
|
||
<el-table :data="merged" stripe size="small" v-loading="loading" empty-text="暂无站点">
|
||
<el-table-column prop="siteId" label="站点" width="140" />
|
||
<el-table-column label="名称" min-width="130">
|
||
<template #default="s">{{ (s.row.names||[]).join(' / ') || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="入口数" width="80" align="center">
|
||
<template #default="s">{{ s.row.entryCount || 0 }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="版本" width="70" align="center">
|
||
<template #default="s">{{ s.row.version || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="更新时间" width="150">
|
||
<template #default="s">{{ fmt(s.row.updatedAt) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180" fixed="right">
|
||
<template #default="s">
|
||
<el-button size="small" @click="toggleEdit(s.row)" :type="editSiteId===s.row.siteId?'warning':''">
|
||
{{ editSiteId===s.row.siteId ? '收起' : '编辑' }}
|
||
</el-button>
|
||
<el-button v-if="!s.row.entryCount" size="small" @click="loadDefault(s.row)">读取默认</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
</el-table>
|
||
|
||
<div v-if="editSiteId" style="margin-top:16px;padding:12px;background:#fafafa;border-radius:4px">
|
||
<div style="margin-bottom:8px;display:flex;gap:8px;align-items:center">
|
||
<strong>编辑: {{ editSiteId }}</strong>
|
||
<el-radio-group v-model="editorMode" size="small">
|
||
<el-radio-button label="table">表格</el-radio-button>
|
||
<el-radio-button label="json">JSON</el-radio-button>
|
||
</el-radio-group>
|
||
</div>
|
||
<template v-if="editorMode==='table'">
|
||
<el-table :data="editingEntries" stripe size="small" style="margin-bottom:8px">
|
||
<el-table-column prop="key" label="key" width="100"><template #default="e"><el-input v-model="e.row.key" size="small"/></template></el-table-column>
|
||
<el-table-column prop="label" label="名称" width="100"><template #default="e"><el-input v-model="e.row.label" size="small"/></template></el-table-column>
|
||
<el-table-column prop="url" label="URL" min-width="200"><template #default="e"><el-input v-model="e.row.url" size="small"/></template></el-table-column>
|
||
<el-table-column prop="method" label="方法" width="80"><template #default="e"><el-select v-model="e.row.method" size="small"><el-option label="GET" value="GET"/><el-option label="POST" value="POST"/></el-select></template></el-table-column>
|
||
<el-table-column label="" width="50"><template #default="e"><el-button size="small" type="danger" text @click="editingEntries.splice(e.$index,1)">×</el-button></template></el-table-column>
|
||
</el-table>
|
||
<el-button size="small" @click="editingEntries.push({key:'',label:'',url:'{base}',method:'GET'})">+ 添加</el-button>
|
||
</template>
|
||
<div v-else style="height:300px;border-radius:4px;overflow:hidden;margin-bottom:8px">
|
||
<CodeEditor v-model="editorJson" language="json" />
|
||
</div>
|
||
<div style="margin-top:8px;display:flex;gap:8px">
|
||
<el-button size="small" type="primary" @click="savePages" :loading="saving">保存</el-button>
|
||
<el-button size="small" @click="editSiteId=''">取消</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import api from '../api'
|
||
|
||
const loading = ref(false), saving = ref(false)
|
||
const sites = ref([]), pagesMap = ref({})
|
||
const editSiteId = ref(''), editorMode = ref('table'), editorJson = ref('')
|
||
const editingEntries = ref([])
|
||
|
||
const defaultPages = {
|
||
nexusphp: [
|
||
{key:'home', label:'首页', url:'{base}', method:'GET'},
|
||
{key:'search', label:'搜索页', url:'{base}/torrents.php', method:'GET'},
|
||
{key:'detail', label:'种子详情', url:'{base}/details.php?id=', method:'GET'},
|
||
{key:'signIn', label:'签到页', url:'{base}/attendance.php', method:'POST'},
|
||
{key:'user', label:'用户详情', url:'{base}/userdetails.php?id=', method:'GET'},
|
||
],
|
||
mtorrent: [
|
||
{key:'home', label:'首页', url:'{base}/api/member/profile', method:'GET'},
|
||
{key:'search',label:'搜索页', url:'{base}/api/torrent/search', method:'POST'},
|
||
],
|
||
gazelle: [
|
||
{key:'home', label:'首页', url:'{base}/index.php', method:'GET'},
|
||
{key:'search',label:'搜索', url:'{base}/torrents.php', method:'GET'},
|
||
],
|
||
unit3d: [
|
||
{key:'home', label:'首页', url:'{base}/api/torrents/filter', method:'GET'},
|
||
{key:'search',label:'搜索', url:'{base}/api/torrents/filter?name=', method:'GET'},
|
||
],
|
||
}
|
||
|
||
const defaults = computed(() => {
|
||
const types = [...new Set((Array.isArray(sites.value)?sites.value:[]).map(s=>s.siteType).filter(Boolean))]
|
||
const base = ['nexusphp','mtorrent','gazelle','unit3d']
|
||
return [...new Set([...types,...base])].map(t => ({type:t, label:t, entries: (defaultPages[t]||[]) }))
|
||
})
|
||
|
||
const fmt = (t) => t ? (t.substring(0,19)||'').replace('T',' ') : '-'
|
||
|
||
watch(editorMode, (m) => {
|
||
if (m === 'json') editorJson.value = JSON.stringify(editingEntries.value, null, 2)
|
||
else { try { editingEntries.value = JSON.parse(editorJson.value) } catch {} }
|
||
})
|
||
|
||
const merged = computed(() => {
|
||
return (Array.isArray(sites.value)?sites.value:[]).map(s => ({
|
||
siteId: s.siteId,
|
||
names: s.names,
|
||
version: pagesMap.value[s.siteId]?.version || null,
|
||
entryCount: pagesMap.value[s.siteId]?.entryCount || 0,
|
||
updatedAt: pagesMap.value[s.siteId]?.updatedAt || null,
|
||
entriesJson: pagesMap.value[s.siteId]?.entriesJson || null,
|
||
}))
|
||
})
|
||
|
||
const fetchAll = async () => {
|
||
loading.value = true
|
||
try {
|
||
const [sr, pr] = await Promise.allSettled([
|
||
api.raw.get('/admin/sites'),
|
||
api.raw.get('/admin/site-pages'),
|
||
])
|
||
sites.value = (sr.status==='fulfilled' ? (sr.value?.data || sr.value || []) : [])
|
||
const pages = pr.status==='fulfilled' ? (pr.value?.data || pr.value || []) : []
|
||
const map = {}
|
||
for (const p of pages) {
|
||
const entries = (() => { try { return JSON.parse(p.entriesJson||'[]') } catch {return []} })()
|
||
map[p.siteId] = { version: p.version, entryCount: entries.length, updatedAt: p.updatedAt, entriesJson: p.entriesJson }
|
||
}
|
||
pagesMap.value = map
|
||
} finally { loading.value = false }
|
||
}
|
||
|
||
const loadDefault = (row) => {
|
||
editSiteId.value = row.siteId
|
||
editingEntries.value = JSON.parse(JSON.stringify(defaultPages[row.siteType] || defaultPages['nexusphp']))
|
||
if (editingEntries.value.length === 0) editingEntries.value.push({key:'home',label:'首页',url:'{base}',method:'GET'})
|
||
editorMode.value = 'table'
|
||
window.scrollTo({top: document.body.scrollHeight, behavior:'smooth'})
|
||
}
|
||
|
||
const editDefault = (dt) => {
|
||
editSiteId.value = dt.type
|
||
editingEntries.value = JSON.parse(JSON.stringify(dt.entries))
|
||
if (editingEntries.value.length === 0) editingEntries.value.push({key:'home',label:'首页',url:'{base}',method:'GET'})
|
||
editorMode.value = 'table'
|
||
window.scrollTo({top: document.body.scrollHeight, behavior:'smooth'})
|
||
}
|
||
|
||
const previewDefault = (dt) => {
|
||
ElMessageBox.alert(JSON.stringify(dt.entries, null, 2), dt.type + ' 默认模板')
|
||
}
|
||
|
||
const toggleEdit = (row) => {
|
||
if (editSiteId.value === row.siteId) { editSiteId.value = ''; return }
|
||
editSiteId.value = row.siteId
|
||
editorMode.value = 'table'
|
||
try { editingEntries.value = JSON.parse(JSON.stringify(JSON.parse(row.entriesJson||'[]'))) }
|
||
catch { editingEntries.value = [{key:'home',label:'首页',url:'{base}',method:'GET'}] }
|
||
if (editingEntries.value.length===0) editingEntries.value.push({key:'home',label:'首页',url:'{base}',method:'GET'})
|
||
}
|
||
|
||
const savePages = async () => {
|
||
saving.value = true
|
||
try {
|
||
const entries = editorMode.value==='json' ? JSON.parse(editorJson.value) : editingEntries.value
|
||
await api.raw.put('/sites/'+editSiteId.value+'/pages', { entries })
|
||
ElMessage.success('已保存'); editSiteId.value = ''; fetchAll()
|
||
} catch { ElMessage.error('保存失败') }
|
||
finally { saving.value = false }
|
||
}
|
||
|
||
onMounted(fetchAll)
|
||
</script>
|
||
|
||
|
||
|