163 lines
7.5 KiB
Vue
163 lines
7.5 KiB
Vue
<template>
|
||
<!-- 列表模式 -->
|
||
<div v-if="!editSiteId">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||
<div style="display:flex;align-items:center;gap:12px">
|
||
<h2 style="margin:0">站点列表</h2>
|
||
<el-badge v-if="pendingCount" :value="pendingCount" type="warning">
|
||
<el-button size="small" @click="$router.push('/sites/review')">变更审核</el-button>
|
||
</el-badge>
|
||
</div>
|
||
<div style="display:flex;gap:8px">
|
||
<el-button size="small" @click="refresh" :loading="loading">刷新</el-button>
|
||
<el-button size="small" type="primary" @click="newSite">新建站点</el-button>
|
||
</div>
|
||
</div>
|
||
<el-table :data="sites" stripe v-loading="loading" border empty-text="暂无站点" style="font-size:13px" :row-style="{height:'44px'}">
|
||
<el-table-column prop="siteId" label="站点ID" width="110" />
|
||
<el-table-column label="名称" min-width="130">
|
||
<template #default="s">{{ (s.row.names||[]).join(' / ') || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="地址" min-width="180" show-overflow-tooltip>
|
||
<template #default="s">{{ (s.row.urls||[]).join(' / ') }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="siteType" label="类型" width="100" />
|
||
<el-table-column label="状态" width="80">
|
||
<template #default="s">
|
||
<el-tag size="small" :type="s.row.status==='active'?'success':'info'">{{ s.row.status||'active' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="适配" width="90">
|
||
<template #default="s">
|
||
<el-tag v-if="s.row.latestVersion" size="small" type="primary">v{{ s.row.latestVersion }}</el-tag>
|
||
<span v-else style="color:#909399">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="100">
|
||
<template #default="s">
|
||
<el-button size="small" @click="editSite(s.row)">编辑</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 编辑模式:全屏双栏 -->
|
||
<div v-else style="display:flex;flex-direction:column;height:calc(100vh - 80px)">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||
<h2 style="margin:0">站点编辑: {{ editSiteId }}</h2>
|
||
<div style="display:flex;gap:8px">
|
||
<el-button size="small" type="primary" @click="saveSite" :loading="saving">保存</el-button>
|
||
<el-button size="small" @click="editSiteId=''">返回列表</el-button>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:12px;flex:1;min-height:0">
|
||
<div style="flex:0 0 400px;display:flex;flex-direction:column;min-height:0">
|
||
<div style="font-size:13px;font-weight:500;margin-bottom:4px">JSON 配置</div>
|
||
<div style="flex:1;min-height:0;border-radius:4px;overflow:hidden">
|
||
<CodeEditor v-model="editorJson" language="json" />
|
||
</div>
|
||
</div>
|
||
<div style="flex:1;overflow:auto;min-height:0">
|
||
<div style="font-size:13px;font-weight:500;margin-bottom:8px;display:flex;gap:8px;align-items:center">
|
||
<span>表单编辑(双向同步)</span>
|
||
<el-tag v-if="jsonError" type="danger" size="small">JSON解析错误</el-tag>
|
||
</div>
|
||
<el-form v-if="!jsonError" label-width="80px" label-position="left" size="small">
|
||
<el-form-item label="站点ID">
|
||
<el-input v-model="form.siteId" @input="formToJson" />
|
||
</el-form-item>
|
||
<el-form-item label="名称">
|
||
<el-input v-model="form.namesStr" type="textarea" :rows="2" @input="formToJson" placeholder="一行一个" />
|
||
</el-form-item>
|
||
<el-form-item label="地址">
|
||
<el-input v-model="form.urlsStr" type="textarea" :rows="2" @input="formToJson" placeholder="一行一个" />
|
||
</el-form-item>
|
||
<el-form-item label="标签">
|
||
<el-input v-model="form.tagsStr" @input="formToJson" placeholder="用逗号分隔" />
|
||
</el-form-item>
|
||
<el-form-item label="简介">
|
||
<el-input v-model="form.desc" type="textarea" :rows="2" @input="formToJson" />
|
||
</el-form-item>
|
||
<el-form-item label="类型">
|
||
<el-select v-model="form.siteType" @change="formToJson" style="width:100%">
|
||
<el-option v-for="t in ['nexusphp','mtorrent','gazelle','unit3d','unknown']" :key="t" :label="t" :value="t" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="form.status" @change="formToJson" style="width:100%">
|
||
<el-option label="活跃" value="active" /><el-option label="弃用" value="deprecated" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import api from '../api'
|
||
|
||
const loading = ref(false), saving = ref(false), pendingCount = ref(0)
|
||
const sites = ref([]), editSiteId = ref(''), editorJson = ref('{}'), jsonError = ref(false)
|
||
const form = reactive({ siteId:'', namesStr:'', urlsStr:'', tagsStr:'', desc:'', siteType:'nexusphp', status:'active' })
|
||
let syncing = false
|
||
|
||
const jsonToForm = () => {
|
||
try {
|
||
const s = JSON.parse(editorJson.value)
|
||
form.siteId = s.siteId||''; form.namesStr = (s.names||[]).join('\n'); form.urlsStr = (s.urls||[]).join('\n')
|
||
form.tagsStr = (s.tags||[]).join(','); form.desc = s.description||''; form.siteType = s.siteType||'nexusphp'; form.status = s.status||'active'
|
||
jsonError.value = false
|
||
} catch { jsonError.value = true }
|
||
}
|
||
|
||
const formToJson = () => {
|
||
if (syncing) return; syncing = true
|
||
jsonError.value = false
|
||
editorJson.value = JSON.stringify({
|
||
siteId: form.siteId, names: (form.namesStr||'').split('\n').map(s=>s.trim()).filter(Boolean),
|
||
urls: (form.urlsStr||'').split('\n').map(s=>s.trim()).filter(Boolean),
|
||
tags: (form.tagsStr||'').split(',').map(s=>s.trim()).filter(Boolean),
|
||
description: form.desc||'', siteType: form.siteType, status: form.status,
|
||
}, null, 2)
|
||
nextTick(() => { syncing = false })
|
||
}
|
||
|
||
const jsonToFormDebounced = null
|
||
watch(editorJson, () => { if (!syncing) jsonToForm() })
|
||
|
||
const refresh = async () => {
|
||
|
||
const refresh = async () => {
|
||
loading.value = true
|
||
try {
|
||
const r = await api.raw.get('/admin/sites'); sites.value = r?.data || r || []
|
||
try { pendingCount.value = ((await api.sites.pendingChanges()).data||[]).length } catch { pendingCount.value=0 }
|
||
} catch { ElMessage.error('加载失败') }
|
||
finally { loading.value = false }
|
||
}
|
||
|
||
const editSite = (row) => {
|
||
editSiteId.value = row.siteId
|
||
editorJson.value = JSON.stringify({siteId:row.siteId,names:row.names,urls:row.urls,tags:row.tags||[],description:row.description||'',siteType:row.siteType,status:row.status||'active'}, null, 2)
|
||
}
|
||
|
||
const newSite = () => {
|
||
editSiteId.value = '(新建)'
|
||
editorJson.value = JSON.stringify({siteId:'',names:[''],urls:[''],siteType:'nexusphp',status:'active'}, null, 2)
|
||
}
|
||
|
||
const saveSite = async () => {
|
||
saving.value = true
|
||
try {
|
||
const s = JSON.parse(editorJson.value)
|
||
if (!s.siteId) { ElMessage.warning('siteId不能为空'); return }
|
||
await api.raw.put('/admin/sites/'+s.siteId, {names:s.names||[],urls:s.urls||[],tags:s.tags||[],description:s.description||'',siteType:s.siteType||'unknown',status:s.status||'active'})
|
||
ElMessage.success('已保存'); editSiteId.value = ''; refresh()
|
||
} catch { ElMessage.error('保存失败') }
|
||
finally { saving.value = false }
|
||
}
|
||
|
||
onMounted(refresh)
|
||
</script>
|