Files
par/web/src/views/SitePages.vue
2026-07-05 18:53:33 +08:00

196 lines
9.1 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" 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>