feat(ECR-024): OpsCMS Banner 只读并 Closed
运营横幅目录(ops_banners + admin /cms),锁定 024–040 全队列。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -385,6 +385,34 @@ export const adminApi = {
|
||||
helpline_text?: string
|
||||
}>
|
||||
}>('POST', '/crisis/evaluate', { text }),
|
||||
banners: () =>
|
||||
request<{
|
||||
items: Array<{
|
||||
id: string
|
||||
code: string
|
||||
title: string
|
||||
placement: string
|
||||
image_url?: string
|
||||
link_path?: string
|
||||
sort_order: number
|
||||
active: boolean
|
||||
system: boolean
|
||||
updated_at: string
|
||||
}>
|
||||
}>('GET', '/cms/banners'),
|
||||
banner: (id: string) =>
|
||||
request<{
|
||||
id: string
|
||||
code: string
|
||||
title: string
|
||||
placement: string
|
||||
image_url?: string
|
||||
link_path?: string
|
||||
sort_order: number
|
||||
active: boolean
|
||||
system: boolean
|
||||
updated_at: string
|
||||
}>('GET', `/cms/banners/${id}`),
|
||||
orders: () =>
|
||||
request<{
|
||||
items: Array<{
|
||||
|
||||
@@ -34,6 +34,7 @@ async function onLogout() {
|
||||
<RouterLink to="/safety">安全</RouterLink>
|
||||
<RouterLink to="/ai">AI</RouterLink>
|
||||
<RouterLink to="/crisis">危机</RouterLink>
|
||||
<RouterLink to="/cms">CMS</RouterLink>
|
||||
<RouterLink to="/orders">订单</RouterLink>
|
||||
<RouterLink to="/audit">审计</RouterLink>
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { adminApi } from '@/api/client'
|
||||
|
||||
type Banner = Awaited<ReturnType<typeof adminApi.banners>>['items'][number]
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const items = ref<Banner[]>([])
|
||||
const selected = ref<Banner | null>(null)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const res = await adminApi.banners()
|
||||
items.value = res.items || []
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '加载失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openBanner(id: string) {
|
||||
try {
|
||||
selected.value = await adminApi.banner(id)
|
||||
} catch {
|
||||
selected.value = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h1>运营位 CMS</h1>
|
||||
<p class="muted">Banner 只读目录 · 非 UGC · 本切片不可发布</p>
|
||||
<p v-if="loading" class="muted">加载中…</p>
|
||||
<p v-else-if="error" class="err">{{ error }}</p>
|
||||
<div v-else class="layout">
|
||||
<div class="card">
|
||||
<h2>横幅</h2>
|
||||
<p v-if="!items.length" class="muted">暂无</p>
|
||||
<table v-else>
|
||||
<thead>
|
||||
<tr><th>代码</th><th>标题</th><th>位置</th><th>状态</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="b in items" :key="b.id">
|
||||
<td><code>{{ b.code }}</code></td>
|
||||
<td>{{ b.title }}</td>
|
||||
<td>{{ b.placement }}</td>
|
||||
<td>{{ b.active ? '启用' : '停用' }}</td>
|
||||
<td><button class="btn" type="button" @click="openBanner(b.id)">查看</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>详情</h2>
|
||||
<template v-if="selected">
|
||||
<p>{{ selected.title }} · {{ selected.placement }}</p>
|
||||
<p class="muted">链接 {{ selected.link_path || '—' }} · 排序 {{ selected.sort_order }}</p>
|
||||
</template>
|
||||
<p v-else class="muted">选择左侧横幅</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 { margin: 0 0 0.35rem; font-size: 1.35rem; }
|
||||
h2 { margin: 0 0 0.6rem; font-size: 1.05rem; }
|
||||
.layout { display: grid; grid-template-columns: 1.2fr 1fr; gap: 1rem; margin-top: 1rem; }
|
||||
code { font-size: 0.8rem; }
|
||||
@media (max-width: 900px) { .layout { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
@@ -21,6 +21,7 @@ const router = createRouter({
|
||||
{ path: 'safety', name: 'safety', component: () => import('@/pages/SafetyPage.vue') },
|
||||
{ path: 'ai', name: 'ai', component: () => import('@/pages/AIConfigPage.vue') },
|
||||
{ path: 'crisis', name: 'crisis', component: () => import('@/pages/CrisisPage.vue') },
|
||||
{ path: 'cms', name: 'cms', component: () => import('@/pages/CMSPage.vue') },
|
||||
{ path: 'audit', name: 'audit', component: () => import('@/pages/AuditPage.vue') },
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user