宇恒一号官网
This commit is contained in:
217
admin/src/views/sites/HomepageEdit.vue
Normal file
217
admin/src/views/sites/HomepageEdit.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div class="homepage-edit">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>首页编辑与下载</span>
|
||||
<div>
|
||||
<el-select v-model="siteId" placeholder="选择站点" filterable style="width: 220px; margin-right: 12px" @change="fetchData">
|
||||
<el-option v-for="s in sites" :key="s.id" :label="s.name" :value="s.id" />
|
||||
</el-select>
|
||||
<el-button type="primary" :loading="saving" :disabled="!siteId" @click="handleSave">保存</el-button>
|
||||
<el-button type="success" :loading="downloading" :disabled="!siteId" @click="handleDownload">下载首页 HTML</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form v-if="siteId" ref="formRef" :model="form" label-width="120px" style="max-width: 720px">
|
||||
<el-divider content-position="left">导航与标题</el-divider>
|
||||
<el-form-item label="Logo 文案">
|
||||
<el-input v-model="form.logo_text" placeholder="YUHENG ONE" />
|
||||
</el-form-item>
|
||||
<el-form-item label="主标题">
|
||||
<el-input v-model="form.title" placeholder="宇恒一号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题">
|
||||
<el-input v-model="form.subtitle" placeholder="INTERSTELLAR EXPLORER EDITION" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="支持换行,会显示在首页" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="导航链接">
|
||||
<div v-for="(link, i) in form.nav_links" :key="i" style="display: flex; gap: 8px; margin-bottom: 8px">
|
||||
<el-input v-model="link.label" placeholder="Label" style="width: 120px" />
|
||||
<el-input v-model="link.url" placeholder="URL" style="flex: 1" />
|
||||
<el-button link type="danger" @click="form.nav_links.splice(i, 1)">删除</el-button>
|
||||
</div>
|
||||
<el-button link type="primary" @click="form.nav_links.push({ label: '', url: '#' })">+ 添加链接</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">下载按钮</el-divider>
|
||||
<el-form-item label="按钮文案">
|
||||
<el-input v-model="form.download_text" placeholder="START EXPLORING" />
|
||||
</el-form-item>
|
||||
<el-form-item label="按钮链接">
|
||||
<el-input v-model="form.download_url" placeholder="#" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">平台(轨道)</el-divider>
|
||||
<el-form-item label="平台列表">
|
||||
<div v-for="(p, i) in form.platforms" :key="i" style="display: flex; gap: 8px; margin-bottom: 8px">
|
||||
<el-input v-model="p.name" placeholder="如 WINDOWS" style="width: 140px" />
|
||||
<el-input v-model="p.url" placeholder="链接" style="flex: 1" />
|
||||
<el-button link type="danger" @click="form.platforms.splice(i, 1)">删除</el-button>
|
||||
</div>
|
||||
<el-button link type="primary" @click="form.platforms.push({ name: '', url: '#' })">+ 添加平台</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">版本与徽章</el-divider>
|
||||
<el-form-item label="版本">
|
||||
<el-input v-model="form.version" placeholder="VERSION 3.2.1" style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发射年份">
|
||||
<el-input v-model="form.launch_year" placeholder="LAUNCH: 2024" style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="徽章文案">
|
||||
<el-input v-model="form.badge_text" placeholder="FREE ACCESS" style="width: 200px" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">特性卡片</el-divider>
|
||||
<el-form-item label="特性">
|
||||
<div v-for="(f, i) in form.features" :key="i" style="margin-bottom: 12px; padding: 12px; border: 1px solid #eee; border-radius: 8px">
|
||||
<el-input v-model="f.title" placeholder="标题" style="margin-bottom: 8px" />
|
||||
<el-input v-model="f.desc" type="textarea" :rows="2" placeholder="描述" />
|
||||
<el-button link type="danger" size="small" @click="form.features.splice(i, 1)">删除</el-button>
|
||||
</div>
|
||||
<el-button link type="primary" @click="form.features.push({ title: '', desc: '' })">+ 添加特性</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="页脚文案">
|
||||
<el-input v-model="form.footer_text" placeholder="© 2024 YUHENG ONE" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-empty v-else description="请先选择站点" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getSites, getOfficialSite, getHomepage, updateHomepage } from '../../api/admin'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
|
||||
const siteId = ref('')
|
||||
const sites = ref([])
|
||||
const saving = ref(false)
|
||||
const downloading = ref(false)
|
||||
const formRef = ref(null)
|
||||
|
||||
const defaultForm = () => ({
|
||||
logo_text: 'YUHENG ONE',
|
||||
nav_links: [{ label: 'MISSION', url: '#' }, { label: 'DOWNLOAD', url: '#' }, { label: 'CONTACT', url: '#' }],
|
||||
title: '宇恒一号',
|
||||
subtitle: 'INTERSTELLAR EXPLORER EDITION',
|
||||
description: '跨越星际的智能伙伴 · 探索无限可能\n引领您进入前所未有的数字宇宙',
|
||||
download_text: 'START EXPLORING',
|
||||
download_url: '#',
|
||||
platforms: [
|
||||
{ name: 'WINDOWS', url: '#' },
|
||||
{ name: 'MACOS', url: '#' },
|
||||
{ name: 'LINUX', url: '#' },
|
||||
{ name: 'IOS', url: '#' },
|
||||
{ name: 'ANDROID', url: '#' }
|
||||
],
|
||||
version: 'VERSION 3.2.1',
|
||||
launch_year: 'LAUNCH: 2024',
|
||||
badge_text: 'FREE ACCESS',
|
||||
features: [
|
||||
{ title: '星际导航', desc: '先进的AI导航系统,精准定位您的需求,引领探索之旅' },
|
||||
{ title: '量子同步', desc: '跨维度数据同步技术,您的数据在多宇宙中保持一致' },
|
||||
{ title: '星际防护', desc: '来自未来的安全加密协议,守护您的数字资产安全' }
|
||||
],
|
||||
footer_text: '© 2024 YUHENG ONE // STELLAR EXPLORATION INITIATIVE'
|
||||
})
|
||||
|
||||
const form = reactive(defaultForm())
|
||||
|
||||
const fetchSites = async () => {
|
||||
try {
|
||||
const sitesRes = await getSites()
|
||||
sites.value = sitesRes.list || []
|
||||
let officialSiteId = ''
|
||||
try {
|
||||
const officialRes = await getOfficialSite()
|
||||
officialSiteId = officialRes.site_id || ''
|
||||
} catch (_) {
|
||||
// 兼容旧后端未提供 official-site 接口时仍可正常选站
|
||||
}
|
||||
const q = new URLSearchParams(window.location.search)
|
||||
if (q.get('site_id') && sites.value.some((s) => s.id === q.get('site_id'))) {
|
||||
siteId.value = q.get('site_id')
|
||||
} else if (officialSiteId && sites.value.some((s) => s.id === officialSiteId)) {
|
||||
siteId.value = officialSiteId
|
||||
} else if (sites.value.length) {
|
||||
siteId.value = sites.value[0].id
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!siteId.value) return
|
||||
try {
|
||||
const data = await getHomepage(siteId.value)
|
||||
Object.assign(form, {
|
||||
...defaultForm(),
|
||||
...data,
|
||||
nav_links: Array.isArray(data.nav_links) && data.nav_links.length ? data.nav_links : defaultForm().nav_links,
|
||||
platforms: Array.isArray(data.platforms) && data.platforms.length ? data.platforms : defaultForm().platforms,
|
||||
features: Array.isArray(data.features) && data.features.length ? data.features : defaultForm().features
|
||||
})
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
watch(siteId, fetchData)
|
||||
|
||||
const handleSave = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
await updateHomepage(siteId.value, form)
|
||||
ElMessage.success('保存成功')
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const handleDownload = async () => {
|
||||
downloading.value = true
|
||||
try {
|
||||
const token = authStore.getToken()
|
||||
const url = `/api/admin/sites/${siteId.value}/homepage/download`
|
||||
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } })
|
||||
if (!res.ok) throw new Error('下载失败')
|
||||
const blob = await res.blob()
|
||||
const a = document.createElement('a')
|
||||
a.href = URL.createObjectURL(blob)
|
||||
a.download = 'index.html'
|
||||
a.click()
|
||||
URL.revokeObjectURL(a.href)
|
||||
ElMessage.success('已下载 index.html')
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message || '下载失败')
|
||||
} finally {
|
||||
downloading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSites().then(() => fetchData())
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
129
admin/src/views/sites/ModuleUpload.vue
Normal file
129
admin/src/views/sites/ModuleUpload.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="module-upload">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>功能模块上传</span>
|
||||
<div>
|
||||
<el-select v-model="siteId" placeholder="选择站点" filterable style="width: 220px; margin-right: 12px" @change="fetchList">
|
||||
<el-option v-for="s in sites" :key="s.id" :label="s.name" :value="s.id" />
|
||||
</el-select>
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
:disabled="!siteId"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-button type="primary" :disabled="!siteId" :loading="uploading">上传文件</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert v-if="!siteId" title="请先选择站点" type="info" style="margin-bottom: 16px" />
|
||||
|
||||
<el-table v-else :data="list" v-loading="loading" stripe>
|
||||
<el-table-column label="文件名" prop="name" min-width="180" />
|
||||
<el-table-column label="存储路径" prop="file_path" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="大小" width="100">
|
||||
<template #default="{ row }">{{ formatSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" prop="created_at" width="180" />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-empty v-if="siteId && !loading && list.length === 0" description="暂无上传文件,请点击上传" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getSites, getSiteAssets, uploadSiteAsset, deleteSiteAsset } from '../../api/admin'
|
||||
|
||||
const siteId = ref('')
|
||||
const sites = ref([])
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
const uploading = ref(false)
|
||||
|
||||
const fetchSites = async () => {
|
||||
try {
|
||||
const res = await getSites()
|
||||
sites.value = res.list || []
|
||||
const q = new URLSearchParams(window.location.search)
|
||||
if (q.get('site_id') && sites.value.some((s) => s.id === q.get('site_id'))) {
|
||||
siteId.value = q.get('site_id')
|
||||
} else if (sites.value.length) {
|
||||
siteId.value = sites.value[0].id
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchList = async () => {
|
||||
if (!siteId.value) {
|
||||
list.value = []
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getSiteAssets(siteId.value)
|
||||
list.value = res.list || []
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(siteId, fetchList)
|
||||
|
||||
const beforeUpload = async (file) => {
|
||||
uploading.value = true
|
||||
try {
|
||||
await uploadSiteAsset(siteId.value, file)
|
||||
ElMessage.success('上传成功')
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message || '上传失败')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
await ElMessageBox.confirm('确定删除该文件?', '提示', { type: 'warning' })
|
||||
try {
|
||||
await deleteSiteAsset(siteId.value, row.id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const formatSize = (bytes) => {
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSites().then(() => fetchList())
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
176
admin/src/views/sites/PageList.vue
Normal file
176
admin/src/views/sites/PageList.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="page-list">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>网页管理</span>
|
||||
<div>
|
||||
<el-select v-model="siteId" placeholder="选择站点" filterable style="width: 220px; margin-right: 12px" @change="fetchList">
|
||||
<el-option v-for="s in sites" :key="s.id" :label="s.name" :value="s.id" />
|
||||
</el-select>
|
||||
<el-button type="primary" :disabled="!siteId" @click="openDialog()">新增网页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="list" v-loading="loading" stripe>
|
||||
<el-table-column label="ID" width="240">
|
||||
<template #default="{ row }">{{ row.id }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="slug" label="Slug" width="120" />
|
||||
<el-table-column prop="title" label="标题" width="160" />
|
||||
<el-table-column prop="type" label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.type === 'homepage'" type="success" size="small">首页</el-tag>
|
||||
<el-tag v-else size="small">页面</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updated_at" label="更新时间" width="180" />
|
||||
<el-table-column label="操作" fixed="right" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="editId ? '编辑网页' : '新增网页'" width="560px" @close="resetForm">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="Slug" prop="slug">
|
||||
<el-input v-model="form.slug" placeholder="如 about、index" :disabled="!!editId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" placeholder="页面标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="form.type" placeholder="类型">
|
||||
<el-option label="普通页面" value="page" />
|
||||
<el-option label="首页" value="homepage" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容" prop="content">
|
||||
<el-input v-model="form.content" type="textarea" :rows="8" placeholder="HTML 或 JSON" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitting">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getSites } from '../../api/admin'
|
||||
import { getPages, createPage, updatePage, deletePage } from '../../api/admin'
|
||||
|
||||
const siteId = ref('')
|
||||
const sites = ref([])
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const fetchSites = async () => {
|
||||
try {
|
||||
const res = await getSites()
|
||||
sites.value = res.list || []
|
||||
if (sites.value.length && !siteId.value) {
|
||||
const q = new URLSearchParams(window.location.search)
|
||||
siteId.value = q.get('site_id') || sites.value[0].id
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchList = async () => {
|
||||
if (!siteId.value) {
|
||||
list.value = []
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getPages({ site_id: siteId.value })
|
||||
list.value = res.list || []
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(siteId, fetchList)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const editId = ref('')
|
||||
const submitting = ref(false)
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ site_id: '', slug: '', title: '', type: 'page', content: '' })
|
||||
const rules = {
|
||||
slug: [{ required: true, message: '请输入 slug', trigger: 'blur' }],
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const openDialog = (row) => {
|
||||
form.site_id = siteId.value
|
||||
editId.value = row ? row.id : ''
|
||||
form.slug = row ? row.slug : ''
|
||||
form.title = row ? row.title : ''
|
||||
form.type = row ? row.type || 'page' : 'page'
|
||||
form.content = row ? row.content || '' : ''
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.slug = ''
|
||||
form.title = ''
|
||||
form.type = 'page'
|
||||
form.content = ''
|
||||
editId.value = ''
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
await formRef.value?.validate()
|
||||
submitting.value = true
|
||||
try {
|
||||
if (editId.value) {
|
||||
await updatePage(editId.value, { slug: form.slug, title: form.title, type: form.type, content: form.content })
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await createPage({ ...form, site_id: siteId.value })
|
||||
ElMessage.success('创建成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
await ElMessageBox.confirm('确定删除该网页?', '提示', { type: 'warning' })
|
||||
try {
|
||||
await deletePage(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSites().then(() => fetchList())
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
160
admin/src/views/sites/SiteList.vue
Normal file
160
admin/src/views/sites/SiteList.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="site-list">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>站点管理</span>
|
||||
<el-button type="primary" @click="openDialog()">新增站点</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="list" v-loading="loading" stripe>
|
||||
<el-table-column label="ID" width="240">
|
||||
<template #default="{ row }">{{ row.id }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="站点名称" width="160" />
|
||||
<el-table-column prop="domain" label="域名" width="180" />
|
||||
<el-table-column prop="description" label="描述" show-overflow-tooltip />
|
||||
<el-table-column label="操作" fixed="right" width="380">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="$router.push(`/pages?site_id=${row.id}`)">网页管理</el-button>
|
||||
<el-button link type="primary" size="small" @click="$router.push(`/homepage-edit?site_id=${row.id}`)">首页编辑</el-button>
|
||||
<el-button link type="primary" size="small" @click="$router.push(`/module-upload?site_id=${row.id}`)">功能模块</el-button>
|
||||
<el-tag v-if="officialSiteId === row.id" type="success" size="small">官网</el-tag>
|
||||
<el-button v-else link type="success" size="small" @click="setAsOfficial(row)">设为官网</el-button>
|
||||
<el-button link type="primary" size="small" @click="openDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="editId ? '编辑站点' : '新增站点'" width="500px" @close="resetForm">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="站点名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="域名" prop="domain">
|
||||
<el-input v-model="form.domain" placeholder="如 www.example.com" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitting">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getSites, createSite, updateSite, deleteSite, getOfficialSite, setOfficialSite } from '../../api/admin'
|
||||
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
const officialSiteId = ref('')
|
||||
|
||||
const fetchOfficialSite = async () => {
|
||||
try {
|
||||
const res = await getOfficialSite()
|
||||
officialSiteId.value = res.site_id || ''
|
||||
} catch (_) {
|
||||
officialSiteId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const setAsOfficial = async (row) => {
|
||||
try {
|
||||
await setOfficialSite(row.id)
|
||||
ElMessage.success('已设为官网站点')
|
||||
officialSiteId.value = row.id
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const sitesRes = await getSites()
|
||||
list.value = sitesRes.list || []
|
||||
try {
|
||||
const officialRes = await getOfficialSite()
|
||||
officialSiteId.value = officialRes.site_id || ''
|
||||
} catch (_) {
|
||||
officialSiteId.value = ''
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const editId = ref('')
|
||||
const submitting = ref(false)
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ name: '', domain: '', description: '' })
|
||||
const rules = { name: [{ required: true, message: '请输入站点名称', trigger: 'blur' }] }
|
||||
|
||||
const openDialog = (row) => {
|
||||
editId.value = row ? row.id : ''
|
||||
form.name = row ? row.name : ''
|
||||
form.domain = row ? row.domain || '' : ''
|
||||
form.description = row ? row.description || '' : ''
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.name = ''
|
||||
form.domain = ''
|
||||
form.description = ''
|
||||
editId.value = ''
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
await formRef.value?.validate()
|
||||
submitting.value = true
|
||||
try {
|
||||
if (editId.value) {
|
||||
await updateSite(editId.value, { name: form.name, domain: form.domain, description: form.description })
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await createSite(form)
|
||||
ElMessage.success('创建成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
await ElMessageBox.confirm('确定删除该站点?其网页与上传文件将一并删除。', '提示', { type: 'warning' })
|
||||
try {
|
||||
await deleteSite(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.error || e.message)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user