465 lines
20 KiB
HTML
465 lines
20 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>运营位管理 - Wild Growth</title>
|
||
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
|
|
<style>
|
||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
|
body {
|
||
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
|
|
min-height: 100vh;
|
||
|
|
padding: 20px;
|
||
|
|
color: #333;
|
||
|
|
}
|
||
|
|
.container {
|
||
|
|
max-width: 1000px;
|
||
|
|
margin: 0 auto;
|
||
|
|
background: white;
|
||
|
|
border-radius: 16px;
|
||
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
.header {
|
||
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
|
|
color: white;
|
||
|
|
padding: 24px 28px;
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
.header h1 { font-size: 22px; font-weight: 600; }
|
||
|
|
.header a {
|
||
|
|
color: rgba(255,255,255,0.95);
|
||
|
|
text-decoration: none;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
.header a:hover { text-decoration: underline; }
|
||
|
|
.content { padding: 24px 28px; }
|
||
|
|
.toolbar {
|
||
|
|
display: flex;
|
||
|
|
gap: 12px;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 20px;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
}
|
||
|
|
.toolbar input {
|
||
|
|
padding: 8px 12px;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
border-radius: 8px;
|
||
|
|
font-size: 14px;
|
||
|
|
width: 200px;
|
||
|
|
}
|
||
|
|
.btn {
|
||
|
|
padding: 8px 16px;
|
||
|
|
border: none;
|
||
|
|
border-radius: 8px;
|
||
|
|
font-size: 14px;
|
||
|
|
cursor: pointer;
|
||
|
|
background: #667eea;
|
||
|
|
color: white;
|
||
|
|
}
|
||
|
|
.btn:hover { background: #5568d3; }
|
||
|
|
.btn-secondary { background: #6c757d; }
|
||
|
|
.btn-secondary:hover { background: #5a6268; }
|
||
|
|
.btn-danger { background: #dc3545; }
|
||
|
|
.btn-danger:hover { background: #c82333; }
|
||
|
|
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
||
|
|
.banner-card {
|
||
|
|
border: 1px solid #e0e0e0;
|
||
|
|
border-radius: 12px;
|
||
|
|
margin-bottom: 16px;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
.banner-card.deleted { opacity: 0.7; background: #f8f9fa; }
|
||
|
|
.banner-head {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
padding: 14px 18px;
|
||
|
|
background: #f8f9fa;
|
||
|
|
border-bottom: 1px solid #eee;
|
||
|
|
}
|
||
|
|
.banner-head-left { display: flex; align-items: center; gap: 12px; }
|
||
|
|
.banner-title { font-weight: 600; font-size: 16px; }
|
||
|
|
.banner-meta { font-size: 12px; color: #666; }
|
||
|
|
.banner-actions { display: flex; gap: 8px; align-items: center; }
|
||
|
|
.banner-body { padding: 14px 18px; }
|
||
|
|
.banner-courses {
|
||
|
|
display: flex;
|
||
|
|
gap: 12px;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
align-items: flex-start;
|
||
|
|
}
|
||
|
|
.course-chip {
|
||
|
|
width: 80px;
|
||
|
|
text-align: center;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
.course-chip img {
|
||
|
|
width: 80px;
|
||
|
|
height: 106px;
|
||
|
|
object-fit: cover;
|
||
|
|
border-radius: 8px;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
.course-chip .title {
|
||
|
|
font-size: 11px;
|
||
|
|
margin-top: 4px;
|
||
|
|
overflow: hidden;
|
||
|
|
text-overflow: ellipsis;
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
.course-chip .chip-actions {
|
||
|
|
position: absolute;
|
||
|
|
top: -6px;
|
||
|
|
right: -6px;
|
||
|
|
display: flex;
|
||
|
|
gap: 2px;
|
||
|
|
}
|
||
|
|
.course-chip .remove, .course-chip .order-btn {
|
||
|
|
width: 20px;
|
||
|
|
height: 20px;
|
||
|
|
border-radius: 50%;
|
||
|
|
border: none;
|
||
|
|
cursor: pointer;
|
||
|
|
font-size: 12px;
|
||
|
|
line-height: 1;
|
||
|
|
padding: 0;
|
||
|
|
color: white;
|
||
|
|
}
|
||
|
|
.course-chip .remove { background: #dc3545; }
|
||
|
|
.course-chip .remove:hover { background: #c82333; }
|
||
|
|
.course-chip .order-btn { background: #667eea; font-size: 10px; }
|
||
|
|
.course-chip .order-btn:hover { background: #5568d3; }
|
||
|
|
.course-chip .order-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
|
|
.add-course-btn {
|
||
|
|
width: 80px;
|
||
|
|
height: 106px;
|
||
|
|
border: 2px dashed #ccc;
|
||
|
|
border-radius: 8px;
|
||
|
|
background: #fafafa;
|
||
|
|
color: #666;
|
||
|
|
font-size: 12px;
|
||
|
|
cursor: pointer;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
.add-course-btn:hover { border-color: #667eea; color: #667eea; }
|
||
|
|
.empty-state { text-align: center; padding: 40px 20px; color: #666; }
|
||
|
|
.message { padding: 10px 14px; border-radius: 8px; margin-bottom: 16px; display: none; }
|
||
|
|
.message.success { background: #d4edda; color: #155724; }
|
||
|
|
.message.error { background: #f8d7da; color: #721c24; }
|
||
|
|
.modal-mask {
|
||
|
|
position: fixed;
|
||
|
|
inset: 0;
|
||
|
|
background: rgba(0,0,0,0.5);
|
||
|
|
display: none;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
z-index: 1000;
|
||
|
|
}
|
||
|
|
.modal-mask.show { display: flex; }
|
||
|
|
.modal {
|
||
|
|
background: white;
|
||
|
|
border-radius: 12px;
|
||
|
|
padding: 24px;
|
||
|
|
max-width: 400px;
|
||
|
|
width: 90%;
|
||
|
|
max-height: 80vh;
|
||
|
|
overflow-y: auto;
|
||
|
|
}
|
||
|
|
.modal h3 { margin-bottom: 16px; font-size: 18px; }
|
||
|
|
.modal label { display: block; margin-bottom: 6px; font-size: 13px; color: #555; }
|
||
|
|
.modal input, .modal select {
|
||
|
|
width: 100%;
|
||
|
|
padding: 8px 12px;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
border-radius: 8px;
|
||
|
|
margin-bottom: 14px;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }
|
||
|
|
.course-option {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 10px;
|
||
|
|
padding: 8px 0;
|
||
|
|
border-bottom: 1px solid #f0f0f0;
|
||
|
|
}
|
||
|
|
.course-option input[type=checkbox] { flex-shrink: 0; }
|
||
|
|
.course-option img { width: 40px; height: 53px; object-fit: cover; border-radius: 4px; flex-shrink: 0; }
|
||
|
|
.course-option span { font-size: 14px; }
|
||
|
|
.loading { text-align: center; padding: 40px; color: #666; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container">
|
||
|
|
<div class="header">
|
||
|
|
<h1>运营位管理</h1>
|
||
|
|
<a href="course-admin.html">← 返回课程管理</a>
|
||
|
|
</div>
|
||
|
|
<div class="content">
|
||
|
|
<div id="message" class="message"></div>
|
||
|
|
<div class="toolbar">
|
||
|
|
<input type="text" id="newTitle" placeholder="新运营位标题" />
|
||
|
|
<input type="number" id="newOrder" placeholder="排序" value="1" min="1" style="width:80px" />
|
||
|
|
<button class="btn" id="btnCreate">新建运营位</button>
|
||
|
|
</div>
|
||
|
|
<div id="list"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="modalEdit" class="modal-mask">
|
||
|
|
<div class="modal">
|
||
|
|
<h3>编辑运营位</h3>
|
||
|
|
<input type="hidden" id="editId" />
|
||
|
|
<label>标题</label>
|
||
|
|
<input type="text" id="editTitle" />
|
||
|
|
<label>排序</label>
|
||
|
|
<input type="number" id="editOrder" min="1" />
|
||
|
|
<label><input type="checkbox" id="editEnabled" checked /> 启用</label>
|
||
|
|
<div class="modal-actions">
|
||
|
|
<button class="btn btn-secondary" id="btnEditCancel">取消</button>
|
||
|
|
<button class="btn" id="btnEditSave">保存</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="modalAddCourse" class="modal-mask">
|
||
|
|
<div class="modal">
|
||
|
|
<h3>添加课程到运营位(可多选)</h3>
|
||
|
|
<p id="addCourseBannerTitle" style="font-size:13px;color:#666;margin-bottom:12px;"></p>
|
||
|
|
<div id="courseList"></div>
|
||
|
|
<div class="modal-actions" style="margin-top:16px">
|
||
|
|
<button class="btn btn-secondary" id="btnAddCourseCancel">取消</button>
|
||
|
|
<button class="btn" id="btnAddCourseSubmit">添加选中</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
const API_BASE = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
|
||
|
|
? 'http://localhost:3000' : window.location.origin;
|
||
|
|
const ADMIN = API_BASE + '/api/admin/operational-banners';
|
||
|
|
|
||
|
|
function showMsg(text, type) {
|
||
|
|
const el = document.getElementById('message');
|
||
|
|
el.textContent = text;
|
||
|
|
el.className = 'message ' + (type || 'success');
|
||
|
|
el.style.display = 'block';
|
||
|
|
setTimeout(() => { el.style.display = 'none'; }, 4000);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function api(method, url, body) {
|
||
|
|
const opt = { method, headers: {} };
|
||
|
|
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
||
|
|
opt.headers['Content-Type'] = 'application/json';
|
||
|
|
opt.body = JSON.stringify(body);
|
||
|
|
}
|
||
|
|
const res = await fetch(url, opt);
|
||
|
|
const data = await res.json().catch(() => ({}));
|
||
|
|
if (!res.ok) throw new Error(data.error?.message || data.message || res.statusText);
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getImageUrl(url) {
|
||
|
|
if (!url) return '';
|
||
|
|
if (url.startsWith('http')) return url;
|
||
|
|
return url.startsWith('/') ? API_BASE + url : API_BASE + '/' + url;
|
||
|
|
}
|
||
|
|
|
||
|
|
let banners = [];
|
||
|
|
let publicCourses = [];
|
||
|
|
|
||
|
|
async function loadBanners() {
|
||
|
|
const listEl = document.getElementById('list');
|
||
|
|
listEl.innerHTML = '<div class="loading">加载中...</div>';
|
||
|
|
try {
|
||
|
|
const data = await api('GET', ADMIN);
|
||
|
|
banners = data.data.banners || [];
|
||
|
|
renderBanners();
|
||
|
|
} catch (e) {
|
||
|
|
listEl.innerHTML = '<div class="message error">加载失败:' + e.message + '</div>';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderBanners() {
|
||
|
|
const listEl = document.getElementById('list');
|
||
|
|
if (!banners.length) {
|
||
|
|
listEl.innerHTML = '<div class="empty-state">暂无运营位,请点击「新建运营位」添加。</div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
listEl.innerHTML = banners.map(b => {
|
||
|
|
const deleted = !!b.deletedAt;
|
||
|
|
const courses = b.courses || [];
|
||
|
|
const coursesHtml = courses.map((c, i) => `
|
||
|
|
<div class="course-chip" data-banner-id="${b.id}" data-course-id="${c.courseId}" data-index="${i}">
|
||
|
|
<div class="chip-actions">
|
||
|
|
<button type="button" class="order-btn" title="上移" onclick="moveCourse('${b.id}',${i},-1)" ${i === 0 ? 'disabled' : ''}>↑</button>
|
||
|
|
<button type="button" class="order-btn" title="下移" onclick="moveCourse('${b.id}',${i},1)" ${i === courses.length - 1 ? 'disabled' : ''}>↓</button>
|
||
|
|
<button type="button" class="remove" title="移除" onclick="removeCourse('${b.id}','${c.courseId}')">×</button>
|
||
|
|
</div>
|
||
|
|
<img src="${getImageUrl(c.cover_image) || ''}" alt="" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2280%22 height=%22106%22/>'" />
|
||
|
|
<div class="title">${escapeHtml(c.title || '')}</div>
|
||
|
|
</div>
|
||
|
|
`).join('');
|
||
|
|
const addBtn = !deleted && (b.courses || []).length < 10
|
||
|
|
? `<div class="add-course-btn" onclick="openAddCourse('${b.id}', this)">+ 添加课程</div>`
|
||
|
|
: '';
|
||
|
|
return `
|
||
|
|
<div class="banner-card ${deleted ? 'deleted' : ''}" data-id="${b.id}">
|
||
|
|
<div class="banner-head">
|
||
|
|
<div class="banner-head-left">
|
||
|
|
<span class="banner-title">${escapeHtml(b.title)}</span>
|
||
|
|
<span class="banner-meta">排序 ${b.orderIndex} · ${b.isEnabled ? '启用' : '禁用'}${deleted ? ' · 已删除' : ''} · ${(b.courses || []).length}/10 门课</span>
|
||
|
|
</div>
|
||
|
|
<div class="banner-actions">
|
||
|
|
${deleted ? '' : `<button class="btn btn-sm" onclick="openEdit('${b.id}')">编辑</button><button class="btn btn-sm btn-danger" onclick="deleteBanner('${b.id}')">删除</button>`}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="banner-body">
|
||
|
|
<div class="banner-courses">${coursesHtml}${addBtn}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function escapeHtml(s) {
|
||
|
|
const div = document.createElement('div');
|
||
|
|
div.textContent = s ?? '';
|
||
|
|
return div.innerHTML;
|
||
|
|
}
|
||
|
|
|
||
|
|
document.getElementById('btnCreate').onclick = async () => {
|
||
|
|
const title = document.getElementById('newTitle').value.trim();
|
||
|
|
if (!title) { showMsg('请输入标题', 'error'); return; }
|
||
|
|
const orderIndex = parseInt(document.getElementById('newOrder').value, 10) || 1;
|
||
|
|
try {
|
||
|
|
await api('POST', ADMIN, { title, orderIndex, isEnabled: true });
|
||
|
|
showMsg('已创建');
|
||
|
|
document.getElementById('newTitle').value = '';
|
||
|
|
loadBanners();
|
||
|
|
} catch (e) { showMsg(e.message, 'error'); }
|
||
|
|
};
|
||
|
|
|
||
|
|
function openEdit(id) {
|
||
|
|
const b = banners.find(x => x.id === id);
|
||
|
|
if (!b) return;
|
||
|
|
document.getElementById('editId').value = b.id;
|
||
|
|
document.getElementById('editTitle').value = b.title;
|
||
|
|
document.getElementById('editOrder').value = b.orderIndex;
|
||
|
|
document.getElementById('editEnabled').checked = b.isEnabled;
|
||
|
|
document.getElementById('modalEdit').classList.add('show');
|
||
|
|
}
|
||
|
|
|
||
|
|
document.getElementById('btnEditCancel').onclick = () => document.getElementById('modalEdit').classList.remove('show');
|
||
|
|
document.getElementById('btnEditSave').onclick = async () => {
|
||
|
|
const id = document.getElementById('editId').value;
|
||
|
|
const title = document.getElementById('editTitle').value.trim();
|
||
|
|
if (!title) { showMsg('标题不能为空', 'error'); return; }
|
||
|
|
const orderIndex = parseInt(document.getElementById('editOrder').value, 10) || 1;
|
||
|
|
const isEnabled = document.getElementById('editEnabled').checked;
|
||
|
|
try {
|
||
|
|
await api('PATCH', ADMIN + '/' + id, { title, orderIndex, isEnabled });
|
||
|
|
showMsg('已保存');
|
||
|
|
document.getElementById('modalEdit').classList.remove('show');
|
||
|
|
loadBanners();
|
||
|
|
} catch (e) { showMsg(e.message, 'error'); }
|
||
|
|
};
|
||
|
|
|
||
|
|
async function deleteBanner(id) {
|
||
|
|
if (!confirm('确定删除该运营位?删除后不会在发现页展示,可保留关联课程。')) return;
|
||
|
|
try {
|
||
|
|
await api('DELETE', ADMIN + '/' + id);
|
||
|
|
showMsg('已删除');
|
||
|
|
loadBanners();
|
||
|
|
} catch (e) { showMsg(e.message, 'error'); }
|
||
|
|
}
|
||
|
|
|
||
|
|
async function openAddCourse(bannerId, _btnEl) {
|
||
|
|
const banner = banners.find(b => b.id === bannerId);
|
||
|
|
document.getElementById('addCourseBannerTitle').textContent = '运营位:' + (banner ? banner.title : bannerId);
|
||
|
|
const listEl = document.getElementById('courseList');
|
||
|
|
listEl.innerHTML = '<div class="loading">加载课程列表...</div>';
|
||
|
|
document.getElementById('modalAddCourse').classList.add('show');
|
||
|
|
try {
|
||
|
|
const res = await fetch(API_BASE + '/api/courses');
|
||
|
|
const data = await res.json();
|
||
|
|
publicCourses = (data.data && data.data.courses) || [];
|
||
|
|
const banner = banners.find(x => x.id === bannerId);
|
||
|
|
const inBanner = new Set((banner.courses || []).map(c => c.courseId));
|
||
|
|
const available = publicCourses.filter(c => !inBanner.has(c.id));
|
||
|
|
if (!available.length) {
|
||
|
|
listEl.innerHTML = '<p style="color:#666">没有可添加的公开课程,或已全部添加。</p>';
|
||
|
|
document.getElementById('btnAddCourseSubmit').style.display = 'none';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
window._addCourseBannerId = bannerId;
|
||
|
|
listEl.innerHTML = available.map(c => `
|
||
|
|
<label class="course-option" style="cursor:pointer;display:flex;align-items:center;gap:10px;">
|
||
|
|
<input type="checkbox" class="add-course-cb" data-course-id="${c.id}" />
|
||
|
|
<img src="${getImageUrl(c.cover_image)}" alt="" onerror="this.style.display='none'" />
|
||
|
|
<span>${escapeHtml(c.title)}</span>
|
||
|
|
</label>
|
||
|
|
`).join('');
|
||
|
|
document.getElementById('btnAddCourseSubmit').style.display = 'inline-block';
|
||
|
|
} catch (e) {
|
||
|
|
listEl.innerHTML = '<p class="message error">加载失败:' + escapeHtml(e.message) + '</p>';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
document.getElementById('btnAddCourseCancel').onclick = () => document.getElementById('modalAddCourse').classList.remove('show');
|
||
|
|
|
||
|
|
document.getElementById('btnAddCourseSubmit').onclick = async () => {
|
||
|
|
const bannerId = window._addCourseBannerId;
|
||
|
|
if (!bannerId) return;
|
||
|
|
const checked = document.querySelectorAll('.add-course-cb:checked');
|
||
|
|
if (!checked.length) { showMsg('请至少勾选一门课程', 'error'); return; }
|
||
|
|
const banner = banners.find(b => b.id === bannerId);
|
||
|
|
const startOrder = (banner && banner.courses && banner.courses.length) || 0;
|
||
|
|
const courses = Array.from(checked).map((el, i) => ({
|
||
|
|
courseId: el.getAttribute('data-course-id'),
|
||
|
|
orderIndex: startOrder + i + 1
|
||
|
|
}));
|
||
|
|
try {
|
||
|
|
await api('POST', ADMIN + '/' + bannerId + '/courses/batch', { courses });
|
||
|
|
showMsg('已添加 ' + courses.length + ' 门课程');
|
||
|
|
document.getElementById('modalAddCourse').classList.remove('show');
|
||
|
|
loadBanners();
|
||
|
|
} catch (e) { showMsg(e.message, 'error'); }
|
||
|
|
};
|
||
|
|
|
||
|
|
async function moveCourse(bannerId, index, delta) {
|
||
|
|
const banner = banners.find(b => b.id === bannerId);
|
||
|
|
if (!banner || !banner.courses || banner.courses.length < 2) return;
|
||
|
|
const courses = banner.courses.slice();
|
||
|
|
const newIndex = index + delta;
|
||
|
|
if (newIndex < 0 || newIndex >= courses.length) return;
|
||
|
|
[courses[index], courses[newIndex]] = [courses[newIndex], courses[index]];
|
||
|
|
const orderPayload = courses.map((c, i) => ({ courseId: c.courseId, orderIndex: i + 1 }));
|
||
|
|
try {
|
||
|
|
await api('PUT', ADMIN + '/' + bannerId + '/courses/order', orderPayload);
|
||
|
|
showMsg('已调整顺序');
|
||
|
|
loadBanners();
|
||
|
|
} catch (e) { showMsg(e.message, 'error'); }
|
||
|
|
}
|
||
|
|
|
||
|
|
async function removeCourse(bannerId, courseId) {
|
||
|
|
if (!confirm('确定从该运营位移除这门课程?')) return;
|
||
|
|
try {
|
||
|
|
await api('DELETE', ADMIN + '/' + bannerId + '/courses/' + courseId);
|
||
|
|
showMsg('已移除');
|
||
|
|
loadBanners();
|
||
|
|
} catch (e) { showMsg(e.message, 'error'); }
|
||
|
|
}
|
||
|
|
|
||
|
|
loadBanners();
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|