218 lines
7.7 KiB
HTML
218 lines
7.7 KiB
HTML
|
|
{% extends "base.html" %}
|
||
|
|
|
||
|
|
{% block title %}套餐管理 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
|
||
|
|
|
||
|
|
{% block page_title %}套餐管理{% endblock %}
|
||
|
|
|
||
|
|
{% block page_actions %}
|
||
|
|
<a href="{{ url_for('web.products') }}" class="btn btn-outline-secondary">
|
||
|
|
<i class="fas fa-arrow-left me-2"></i>
|
||
|
|
返回产品列表
|
||
|
|
</a>
|
||
|
|
<a href="{{ url_for('web.create_package') }}?product_id={{ product.product_id }}" class="btn btn-primary">
|
||
|
|
<i class="fas fa-plus me-2"></i>
|
||
|
|
创建套餐
|
||
|
|
</a>
|
||
|
|
{% endblock %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="row">
|
||
|
|
<div class="col-12">
|
||
|
|
<div class="card shadow mb-4">
|
||
|
|
<div class="card-header">
|
||
|
|
<h6 class="mb-0">产品信息</h6>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
<div class="row">
|
||
|
|
<div class="col-md-6">
|
||
|
|
<dl class="row">
|
||
|
|
<dt class="col-sm-4">产品ID:</dt>
|
||
|
|
<dd class="col-sm-8"><code>{{ product.product_id }}</code></dd>
|
||
|
|
|
||
|
|
<dt class="col-sm-4">产品名称:</dt>
|
||
|
|
<dd class="col-sm-8">{{ product.product_name }}</dd>
|
||
|
|
</dl>
|
||
|
|
</div>
|
||
|
|
<div class="col-md-6">
|
||
|
|
<dl class="row">
|
||
|
|
<dt class="col-sm-4">状态:</dt>
|
||
|
|
<dd class="col-sm-8">
|
||
|
|
<span class="badge {% if product.status == 1 %}bg-success{% else %}bg-secondary{% endif %}">
|
||
|
|
{{ product.status_name }}
|
||
|
|
</span>
|
||
|
|
</dd>
|
||
|
|
</dl>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="col-12">
|
||
|
|
<div class="card shadow">
|
||
|
|
<div class="card-body">
|
||
|
|
<div class="table-responsive">
|
||
|
|
<table class="table table-hover">
|
||
|
|
<thead class="table-light">
|
||
|
|
<tr>
|
||
|
|
<th>套餐名称</th>
|
||
|
|
<th>价格</th>
|
||
|
|
<th>时长</th>
|
||
|
|
<th>最大设备数</th>
|
||
|
|
<th>库存</th>
|
||
|
|
<th>状态</th>
|
||
|
|
<th>排序</th>
|
||
|
|
<th>操作</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody id="package-list">
|
||
|
|
<tr>
|
||
|
|
<td colspan="8" class="text-center text-muted">加载中...</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 删除确认模态框 -->
|
||
|
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
||
|
|
<div class="modal-dialog">
|
||
|
|
<div class="modal-content">
|
||
|
|
<div class="modal-header">
|
||
|
|
<h5 class="modal-title">确认删除</h5>
|
||
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
|
|
</div>
|
||
|
|
<div class="modal-body">
|
||
|
|
确定要删除套餐 "<strong id="delete-package-name"></strong>" 吗?此操作不可恢复。
|
||
|
|
</div>
|
||
|
|
<div class="modal-footer">
|
||
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
|
|
<button type="button" class="btn btn-danger" id="confirm-delete">确定删除</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{% endblock %}
|
||
|
|
|
||
|
|
{% block extra_js %}
|
||
|
|
<script>
|
||
|
|
// 页面加载完成后初始化
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
loadPackages();
|
||
|
|
initEventListeners();
|
||
|
|
});
|
||
|
|
|
||
|
|
// 初始化事件监听器
|
||
|
|
function initEventListeners() {
|
||
|
|
// 删除确认
|
||
|
|
document.getElementById('confirm-delete').addEventListener('click', function() {
|
||
|
|
const packageId = this.dataset.packageId;
|
||
|
|
deletePackage(packageId);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 加载套餐列表
|
||
|
|
function loadPackages() {
|
||
|
|
const product_id = "{{ product.product_id }}";
|
||
|
|
|
||
|
|
apiRequest(`/api/v1/packages?product_id=${product_id}`)
|
||
|
|
.then(data => {
|
||
|
|
if (data && data.success) {
|
||
|
|
renderPackageList(data.data.packages);
|
||
|
|
} else {
|
||
|
|
renderPackageList([]);
|
||
|
|
showNotification('加载套餐列表失败: ' + (data.message || '未知错误'), 'error');
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
renderPackageList([]);
|
||
|
|
showNotification('加载套餐列表失败: ' + error.message, 'error');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 渲染套餐列表
|
||
|
|
function renderPackageList(packages) {
|
||
|
|
const tbody = document.getElementById('package-list');
|
||
|
|
|
||
|
|
if (!Array.isArray(packages) || packages.length === 0) {
|
||
|
|
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无数据</td></tr>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
tbody.innerHTML = packages.map(pkg => `
|
||
|
|
<tr>
|
||
|
|
<td>
|
||
|
|
<strong>${pkg.name}</strong>
|
||
|
|
${pkg.description ? `<br><small class="text-muted">${pkg.description}</small>` : ''}
|
||
|
|
</td>
|
||
|
|
<td>¥${pkg.price.toFixed(2)}</td>
|
||
|
|
<td>${pkg.duration_text}</td>
|
||
|
|
<td>${pkg.max_devices}</td>
|
||
|
|
<td>${pkg.stock === -1 ? '无限' : pkg.stock}</td>
|
||
|
|
<td>
|
||
|
|
<span class="badge ${pkg.status === 1 ? 'bg-success' : 'bg-secondary'}">
|
||
|
|
${pkg.status_name}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
<td>${pkg.sort_order}</td>
|
||
|
|
<td>
|
||
|
|
<div class="btn-group btn-group-sm" role="group">
|
||
|
|
<a href="/packages/${pkg.package_id}/edit" class="btn btn-outline-primary" title="编辑">
|
||
|
|
<i class="fas fa-edit"></i>
|
||
|
|
</a>
|
||
|
|
<button type="button" class="btn btn-outline-danger btn-delete"
|
||
|
|
data-package-id="${pkg.package_id}"
|
||
|
|
data-package-name="${pkg.name}"
|
||
|
|
title="删除">
|
||
|
|
<i class="fas fa-trash"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
`).join('');
|
||
|
|
|
||
|
|
// 绑定删除按钮事件
|
||
|
|
document.querySelectorAll('.btn-delete').forEach(btn => {
|
||
|
|
btn.addEventListener('click', function() {
|
||
|
|
const packageId = this.dataset.packageId;
|
||
|
|
const packageName = this.dataset.packageName;
|
||
|
|
showDeleteModal(packageId, packageName);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 显示删除确认模态框
|
||
|
|
function showDeleteModal(packageId, packageName) {
|
||
|
|
document.getElementById('delete-package-name').textContent = packageName;
|
||
|
|
document.getElementById('confirm-delete').dataset.packageId = packageId;
|
||
|
|
|
||
|
|
const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
||
|
|
modal.show();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 删除套餐
|
||
|
|
function deletePackage(packageId) {
|
||
|
|
apiRequest(`/api/v1/packages/${packageId}`, {
|
||
|
|
method: 'DELETE'
|
||
|
|
})
|
||
|
|
.then(data => {
|
||
|
|
if (data && data.success) {
|
||
|
|
showNotification('套餐删除成功', 'success');
|
||
|
|
loadPackages();
|
||
|
|
|
||
|
|
// 关闭模态框
|
||
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteModal'));
|
||
|
|
modal.hide();
|
||
|
|
} else {
|
||
|
|
showNotification('删除失败: ' + (data.message || '未知错误'), 'error');
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
showNotification('删除失败: ' + error.message, 'error');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
{% endblock %}
|