Files
ns2.0/templates/admin_add.html

581 lines
17 KiB
HTML
Executable File
Raw Permalink 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.

{% extends "base.html" %}
{% block content %}
{% include 'admin_nav.html' %}
<div class="admin-container">
<div class="admin-content">
<!-- 单个应用添加 -->
<div class="admin-card" style="margin-bottom: 20px;">
<div class="card-header">
<div class="header-left">
<i class="fas fa-plus-circle"></i>
<h3>添加单个应用</h3>
</div>
</div>
<form onsubmit="submitAddAppForm(event, this)" enctype="multipart/form-data" class="admin-form">
<div class="form-group">
<label for="app-name">应用名称</label>
<input type="text" id="app-name" name="name" placeholder="请输入应用名称" required>
</div>
<div class="form-group">
<label for="app-icon">应用图标</label>
<div class="file-input-wrapper">
<input type="file" id="app-icon" name="icon">
<label for="app-icon" class="file-input-label">
<i class="fas fa-cloud-upload-alt"></i>
<span>选择文件</span>
</label>
</div>
<div class="form-group">
<label for="icon-url">或输入图标URL</label>
<input type="url" id="icon-url" name="icon_url" placeholder="http://example.com/icon.png">
</div>
</div>
<div class="form-group">
<label for="category">分类</label>
<select id="category" name="category_id" required>
{% for category in categories %}
<option value="{{ category.id }}">{{ category.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>显示区域</label>
<div class="platform-buttons">
<button type="button" class="platform-btn active" data-value="mobile">
<i class="fas fa-mobile-alt"></i>
仅手机区
</button>
<button type="button" class="platform-btn" data-value="tablet">
<i class="fas fa-tablet-alt"></i>
仅平板区
</button>
<button type="button" class="platform-btn" data-value="both">
<i class="fas fa-desktop"></i>
全部显示
</button>
</div>
<input type="hidden" name="platform" id="platform" value="mobile">
</div>
<div class="form-group">
<label for="download-url">下载链接(可选)</label>
<input type="url" id="download-url" name="download_url" placeholder="http://example.com/download">
</div>
<button type="submit" class="btn-primary">
<i class="fas fa-plus"></i> 添加应用
</button>
</form>
</div>
<!-- 批量导入 -->
<div class="admin-card">
<div class="card-header">
<div class="header-left">
<i class="fas fa-cloud-upload-alt"></i>
<h3>批量导入应用</h3>
</div>
</div>
<form id="batch-import-form" class="admin-form">
<div class="form-group">
<label for="batch-category">选择分类</label>
<select id="batch-category" name="category_id" required>
{% for category in categories %}
<option value="{{ category.id }}">{{ category.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>图标类型</label>
<div class="icon-type-options">
<label class="icon-option">
<input type="radio" name="icon_type" value="service" onchange="updateIconUrl()" required>
<span>元服务</span>
</label>
<label class="icon-option">
<input type="radio" name="icon_type" value="app" onchange="updateIconUrl()" required>
<span>应用</span>
</label>
</div>
</div>
<input type="hidden" id="batch-icon-url" name="icon_url">
<div class="form-group">
<label for="batch-names">应用名称(用英文逗号分隔)</label>
<textarea id="batch-names" name="app_names" rows="5" placeholder="输入应用名称,多个应用用英文逗号分隔" required></textarea>
</div>
<button type="submit" class="btn-primary">
<i class="fas fa-cloud-upload-alt"></i> 批量导入
</button>
</form>
</div>
<!-- 修改导入结果列表的标题和图标 -->
<div class="admin-card full-width" id="import-result" style="display: none;">
<div class="card-header">
<div class="header-left">
<i class="fas fa-clock"></i>
<h3>最近导入</h3>
</div>
</div>
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>图标</th>
<th>名称</th>
<th>分类</th>
<th>操作</th>
</tr>
</thead>
<tbody id="result-tbody">
<!-- 导入结果将在这里显示 -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
// 添加通知函数
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = message;
document.body.appendChild(notification);
// 添加动画样式
setTimeout(() => {
notification.classList.add('show');
}, 10);
// 3秒后移除通知
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// 原有的单个应用添加函数
function submitAddAppForm(event, form) {
event.preventDefault();
const formData = new FormData(form);
fetch('{{ url_for("add_app") }}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification(data.message, 'success');
form.reset();
} else {
showNotification(data.error, 'error');
}
})
.catch(error => {
showNotification('操作失败,请重试', 'error');
console.error('Error:', error);
});
}
// 修改图标URL更新函数
function updateIconUrl() {
const iconType = document.querySelector('input[name="icon_type"]:checked')?.value;
const iconUrlInput = document.getElementById('batch-icon-url');
if (iconType === 'service') {
iconUrlInput.value = 'https://developer.huawei.com/allianceCmsResource/resource/HUAWEI_Developer_VUE/images/yuanfuwuicon.png';
} else if (iconType === 'app') {
iconUrlInput.value = 'https://consumer.huawei.com/content/dam/huawei-cbg-site/cn/mkt/harmonyos-next/images/hero/harmonyos-next-kv-2x.webp';
} else {
iconUrlInput.value = '';
}
}
// 修改批量导入处理函数
document.getElementById('batch-import-form').onsubmit = function(e) {
e.preventDefault();
const categoryId = document.getElementById('batch-category').value;
const iconUrl = document.getElementById('batch-icon-url').value;
const names = document.getElementById('batch-names').value;
if (!iconUrl) {
showNotification('请选择图标类型', 'error');
return;
}
// 分割应用名称并过滤空值
const apps = names.split(',')
.map(name => name.trim())
.filter(name => name)
.map(name => ({
name: name,
icon_url: iconUrl,
category_id: categoryId
}));
if (apps.length === 0) {
showNotification('请输入至少一个应用名称', 'error');
return;
}
// 发送请求前显示加载状态
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 导入中...';
fetch('{{ url_for("admin_batch_add") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest' // 添加这个头部
},
body: JSON.stringify({apps: apps})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
showNotification(data.message, 'success');
displayImportResult(apps, data.results);
this.reset();
updateIconUrl();
} else {
showNotification(data.error || '导入失败', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('导入失败,请重试', 'error');
})
.finally(() => {
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
});
};
// 添加错误处理函数
function handleFetchError(response) {
if (!response.ok) {
return response.text().then(text => {
try {
// 尝试解析错误响应为 JSON
const data = JSON.parse(text);
throw new Error(data.error || '请求失败');
} catch (e) {
// 如果不是 JSON返回原始错误文本
throw new Error(text || '请求失败');
}
});
}
return response.json();
}
function displayImportResult(apps, results) {
const tbody = document.getElementById('result-tbody');
tbody.innerHTML = '';
apps.forEach(app => {
const tr = document.createElement('tr');
const isSuccess = results.success.includes(app.name);
const failReason = results.failed[app.name];
tr.innerHTML = `
<td>
<div class="app-icon-small">
<img src="${app.icon_url}" alt="${app.name}" loading="lazy">
</div>
</td>
<td>${app.name}</td>
<td>${document.getElementById('batch-category').options[document.getElementById('batch-category').selectedIndex].text}</td>
<td>
${isSuccess ?
`<button onclick="deleteApp('${app.name}')" class="btn-delete" title="删除">
<i class="fas fa-trash"></i>
</button>` :
`<span class="error-text" title="${failReason}">导入失败</span>`
}
</td>
`;
tbody.appendChild(tr);
});
document.getElementById('import-result').style.display = 'block';
}
// 修改删除应用函<E794A8><E587BD>
function deleteApp(appName) {
if (confirm(`确定要删除应用 "${appName}" 吗?`)) {
fetch(`/delete_app_by_name/${encodeURIComponent(appName)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('删除成功', 'success');
// 找到并移除对应的表格行
const rows = document.querySelectorAll('#result-tbody tr');
for (let row of rows) {
if (row.querySelector('td:nth-child(2)').textContent === appName) {
row.remove();
break;
}
}
// 如果表格为空,隐藏结果区域
if (document.getElementById('result-tbody').children.length === 0) {
document.getElementById('import-result').style.display = 'none';
}
} else {
showNotification(data.error || '删除失败', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('删除失败,请重试', 'error');
});
}
}
// 添加 contains 选择器的 polyfill
if (!HTMLElement.prototype.contains) {
HTMLElement.prototype.contains = function(node) {
return this.textContent.includes(node);
}
}
document.addEventListener('DOMContentLoaded', function() {
const platformButtons = document.querySelectorAll('.platform-btn');
const platformInput = document.getElementById('platform');
platformButtons.forEach(button => {
button.addEventListener('click', function() {
// 移除所有按钮的 active 类
platformButtons.forEach(btn => btn.classList.remove('active'));
// 添加当前按钮的 active 类
this.classList.add('active');
// 更新隐藏输入框的值
platformInput.value = this.dataset.value;
});
});
});
</script>
<style>
#batch-names {
width: 100%;
min-height: 100px;
padding: 10px;
border: 1px solid #d2d2d7;
border-radius: 8px;
font-size: 14px;
resize: vertical;
}
.preview-list {
margin-top: 20px;
}
.btn-primary {
margin-right: 10px;
}
.status-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
cursor: help;
}
.status-badge.success {
background: #34c759;
color: white;
}
.status-badge.error {
background: #ff3b30;
color: white;
}
/* 通知样式 */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
}
.notification.show {
opacity: 1;
transform: translateY(0);
}
.notification.success {
background: #34c759;
color: white;
}
.notification.error {
background: #ff3b30;
color: white;
}
/* 添加失败原因样式 */
.fail-reason {
font-size: 12px;
color: #ff3b30;
margin-top: 4px;
}
.icon-type-options {
display: flex;
gap: 20px;
margin-top: 8px;
}
.icon-option {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 8px 16px;
border: 1px solid #d2d2d7;
border-radius: 6px;
transition: all 0.3s ease;
}
.icon-option:hover {
border-color: #0066cc;
}
.icon-option input[type="radio"] {
margin: 0;
}
.icon-option input[type="radio"]:checked + span {
color: #0066cc;
font-weight: 500;
}
.icon-option:has(input[type="radio"]:checked) {
border-color: #0066cc;
background-color: #f5f8ff;
}
/* 修改删除按钮样式 */
.btn-delete {
background: #ff3b30;
color: white;
border: none;
padding: 6px;
width: 28px;
height: 28px;
border-radius: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.btn-delete:hover {
background: #ff1a1a;
}
.btn-delete i {
font-size: 14px;
}
/* 添加错误文本样式 */
.error-text {
color: #ff3b30;
font-size: 12px;
cursor: help;
}
/* 修改表格样式 */
.admin-table td {
vertical-align: middle;
}
.app-icon-small {
width: 32px;
height: 32px;
border-radius: 6px;
overflow: hidden;
}
.app-icon-small img {
width: 100%;
height: 100%;
object-fit: cover;
}
.platform-buttons {
display: flex;
gap: 10px;
margin-top: 8px;
}
.platform-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px;
border: 1px solid #d2d2d7;
border-radius: 8px;
background: white;
color: #333;
cursor: pointer;
transition: all 0.3s ease;
}
.platform-btn i {
font-size: 16px;
}
.platform-btn:hover {
border-color: #007AFF;
color: #007AFF;
}
.platform-btn.active {
background: #007AFF;
color: white;
border-color: #007AFF;
}
@media (max-width: 768px) {
.platform-buttons {
flex-direction: column;
}
.platform-btn {
width: 100%;
}
}
</style>
{% endblock %}