feat: 系统配置支持动态增删用户类型和会员套餐
新增功能: - UserTypeConfig 模型:用户类型配置支持动态增删 - MembershipPlanConfig 模型:会员套餐配置支持动态增删 - 用户类型管理页面:添加、编辑、删除、启用/禁用用户类型 - 会员套餐管理页面:添加、编辑、删除、上架/下架、推荐套餐 - 功能权限配置:支持选择功能列表 - 初始化默认配置功能 改进: - settings.html 页面重构,提供配置入口链接 - 新增API接口支持增删改查操作
This commit is contained in:
240
templates/admin/membership_plan_form.html
Normal file
240
templates/admin/membership_plan_form.html
Normal file
@@ -0,0 +1,240 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% if plan %}编辑会员套餐{% else %}添加会员套餐{% endif %} - 后台管理</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f5f5f5; }
|
||||
.sidebar { position: fixed; top: 0; left: 0; height: 100vh; width: 250px; background: #343a40; padding-top: 60px; }
|
||||
.sidebar .nav-link { color: #adb5bd; padding: 12px 20px; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,0.1); }
|
||||
.main-content { margin-left: 250px; padding: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="sidebar">
|
||||
<div class="position-absolute top-0 w-100 p-3 border-bottom border-secondary">
|
||||
<h5 class="text-white mb-0"><i class="bi bi-gear-fill"></i> 后台管理</h5>
|
||||
</div>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.dashboard') }}"><i class="bi bi-speedometer2"></i> 数据概览</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.users') }}"><i class="bi bi-people"></i> 用户管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.translations') }}"><i class="bi bi-file-text"></i> 翻译记录</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.cache_list') }}"><i class="bi bi-database"></i> 缓存管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.packages') }}"><i class="bi bi-box-seam"></i> 数据包套餐</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.stats') }}"><i class="bi bi-bar-chart"></i> 统计报表</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.logs') }}"><i class="bi bi-list-check"></i> 操作日志</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
|
||||
<li class="nav-item"><a class="nav-link active" href="{{ url_for('admin.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
|
||||
</ul>
|
||||
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">
|
||||
<a href="/" class="btn btn-outline-light btn-sm w-100"><i class="bi bi-house"></i> 返回前台</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4>
|
||||
<i class="bi bi-credit-card"></i>
|
||||
{% if plan %}编辑会员套餐{% else %}添加会员套餐{% endif %}
|
||||
</h4>
|
||||
<a href="{{ url_for('admin.membership_plans') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> 返回列表
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form id="planForm">
|
||||
{% if plan %}
|
||||
<input type="hidden" name="plan_id" value="{{ plan.id }}">
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">套餐标识 <small class="text-muted">(唯一标识符)</small></label>
|
||||
{% if plan %}
|
||||
<input type="text" class="form-control" name="plan_key"
|
||||
value="{{ plan.plan_key }}" readonly>
|
||||
<small class="text-muted">套餐标识创建后不可修改</small>
|
||||
{% else %}
|
||||
<input type="text" class="form-control" name="plan_key"
|
||||
placeholder="例如: vip_gold" required>
|
||||
<small class="text-muted">建议使用英文,如 vip_gold, trial_30days</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">显示名称</label>
|
||||
<input type="text" class="form-control" name="display_name"
|
||||
value="{{ plan.display_name if plan else '' }}" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">套餐描述</label>
|
||||
<textarea class="form-control" name="description" rows="2"
|
||||
placeholder="简短描述套餐特点">{{ plan.description if plan else '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">购买后升级的用户类型</label>
|
||||
<select class="form-select" name="user_type_key">
|
||||
<option value="">-- 不升级用户类型 --</option>
|
||||
{% for ut in user_types %}
|
||||
<option value="{{ ut.type_key }}"
|
||||
{% if plan and plan.user_type_key == ut.type_key %}selected{% endif %}>
|
||||
{{ ut.display_name }} ({{ ut.type_key }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="text-muted">用户购买此套餐后将升级到指定类型</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">价格 (元)</label>
|
||||
<input type="number" class="form-control" name="price"
|
||||
step="0.01" value="{{ plan.price if plan else 0 }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">原价 (元) <small class="text-muted">(可选)</small></label>
|
||||
<input type="number" class="form-control" name="original_price"
|
||||
step="0.01" value="{{ plan.original_price if plan else '' }}"
|
||||
placeholder="用于显示折扣">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">计费周期</label>
|
||||
<select class="form-select" name="period" id="periodSelect">
|
||||
<option value="month" {% if plan and plan.period == 'month' %}selected{% endif %}>月</option>
|
||||
<option value="quarter" {% if plan and plan.period == 'quarter' %}selected{% endif %}>季</option>
|
||||
<option value="year" {% if plan and plan.period == 'year' %}selected{% endif %}>年</option>
|
||||
<option value="custom" {% if plan and plan.period not in ['month', 'quarter', 'year'] %}selected{% endif %}>自定义天数</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="mb-3" id="periodDaysGroup"
|
||||
style="display: {% if plan and plan.period not in ['month', 'quarter', 'year'] %}block{% else %}none{% endif %}">
|
||||
<label class="form-label">周期天数</label>
|
||||
<input type="number" class="form-control" name="period_days"
|
||||
value="{{ plan.period_days if plan else 30 }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">排序权重</label>
|
||||
<input type="number" class="form-control" name="sort_order"
|
||||
value="{{ plan.sort_order if plan else 0 }}">
|
||||
<small class="text-muted">数字越小排在前面</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="is_active"
|
||||
id="is_active" {% if plan and plan.is_active %}checked{% elif not plan %}checked{% endif %}>
|
||||
<label class="form-check-label" for="is_active">上架此套餐</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="is_recommended"
|
||||
id="is_recommended" {% if plan and plan.is_recommended %}checked{% endif %}>
|
||||
<label class="form-check-label" for="is_recommended">
|
||||
<i class="bi bi-star-fill text-warning"></i> 推荐此套餐
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> {% if plan %}保存修改{% else %}添加套餐{% endif %}
|
||||
</button>
|
||||
<a href="{{ url_for('admin.membership_plans') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-x-circle"></i> 取消
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// 周期选择联动
|
||||
document.getElementById('periodSelect').addEventListener('change', function() {
|
||||
const customGroup = document.getElementById('periodDaysGroup');
|
||||
if (this.value === 'custom') {
|
||||
customGroup.style.display = 'block';
|
||||
} else {
|
||||
customGroup.style.display = 'none';
|
||||
// 自动设置天数
|
||||
const daysInput = document.querySelector('input[name="period_days"]');
|
||||
if (this.value === 'month') daysInput.value = 30;
|
||||
else if (this.value === 'quarter') daysInput.value = 90;
|
||||
else if (this.value === 'year') daysInput.value = 365;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('planForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
const data = {
|
||||
plan_key: formData.get('plan_key'),
|
||||
display_name: formData.get('display_name'),
|
||||
description: formData.get('description'),
|
||||
user_type_key: formData.get('user_type_key'),
|
||||
price: parseFloat(formData.get('price')),
|
||||
original_price: formData.get('original_price') ? parseFloat(formData.get('original_price')) : null,
|
||||
period: formData.get('period'),
|
||||
period_days: parseInt(formData.get('period_days')),
|
||||
sort_order: parseInt(formData.get('sort_order')),
|
||||
is_active: formData.get('is_active') === 'on',
|
||||
is_recommended: formData.get('is_recommended') === 'on'
|
||||
};
|
||||
|
||||
{% if plan %}
|
||||
const url = `/admin/membership-plans/{{ plan.id }}/edit`;
|
||||
{% else %}
|
||||
const url = '/admin/membership-plans/add';
|
||||
{% endif %}
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
{% if plan %}
|
||||
alert('会员套餐已更新');
|
||||
{% else %}
|
||||
alert('会员套餐已添加');
|
||||
{% endif %}
|
||||
window.location.href = '/admin/membership-plans';
|
||||
} else {
|
||||
alert('操作失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
221
templates/admin/membership_plans.html
Normal file
221
templates/admin/membership_plans.html
Normal file
@@ -0,0 +1,221 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>会员套餐配置 - 后台管理</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f5f5f5; }
|
||||
.sidebar { position: fixed; top: 0; left: 0; height: 100vh; width: 250px; background: #343a40; padding-top: 60px; }
|
||||
.sidebar .nav-link { color: #adb5bd; padding: 12px 20px; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,0.1); }
|
||||
.main-content { margin-left: 250px; padding: 20px; }
|
||||
.plan-card { border-radius: 12px; transition: all 0.3s; }
|
||||
.plan-card:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.15); }
|
||||
.plan-card.basic { border-top: 4px solid #6c757d; }
|
||||
.plan-card.pro { border-top: 4px solid #ffc107; }
|
||||
.plan-card.enterprise { border-top: 4px solid #0d6efd; }
|
||||
.plan-card.custom { border-top: 4px solid #17a2b8; }
|
||||
.price-display { font-size: 2rem; font-weight: bold; }
|
||||
.badge-recommended { background: linear-gradient(135deg, #ffc107, #ff8c00); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="sidebar">
|
||||
<div class="position-absolute top-0 w-100 p-3 border-bottom border-secondary">
|
||||
<h5 class="text-white mb-0"><i class="bi bi-gear-fill"></i> 后台管理</h5>
|
||||
</div>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.dashboard') }}"><i class="bi bi-speedometer2"></i> 数据概览</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.users') }}"><i class="bi bi-people"></i> 用户管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.translations') }}"><i class="bi bi-file-text"></i> 翻译记录</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.cache_list') }}"><i class="bi bi-database"></i> 缓存管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.packages') }}"><i class="bi bi-box-seam"></i> 数据包套餐</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.stats') }}"><i class="bi bi-bar-chart"></i> 统计报表</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.logs') }}"><i class="bi bi-list-check"></i> 操作日志</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
|
||||
<li class="nav-item"><a class="nav-link active" href="{{ url_for('admin.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
|
||||
</ul>
|
||||
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">
|
||||
<a href="/" class="btn btn-outline-light btn-sm w-100"><i class="bi bi-house"></i> 返回前台</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4><i class="bi bi-credit-card"></i> 会员套餐配置</h4>
|
||||
<div>
|
||||
<a href="{{ url_for('admin.add_membership_plan') }}" class="btn btn-success">
|
||||
<i class="bi bi-plus-circle"></i> 添加套餐
|
||||
</a>
|
||||
<a href="{{ url_for('admin.user_types') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-person-badge"></i> 用户类型配置
|
||||
</a>
|
||||
<a href="{{ url_for('admin.settings') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-gear"></i> 基础设置
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% for plan in plans %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card plan-card {% if plan.plan_key == 'vip_basic' %}basic{% elif plan.plan_key == 'vip_pro' %}pro{% elif plan.plan_key == 'vip_enterprise' %}enterprise{% else %}custom{% endif %}">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
{% if plan.is_recommended %}
|
||||
<span class="badge badge-recommended me-2"><i class="bi bi-star-fill"></i> 推荐</span>
|
||||
{% endif %}
|
||||
{% if plan.is_system %}
|
||||
<span class="badge bg-danger me-2">系统</span>
|
||||
{% else %}
|
||||
<span class="badge bg-info me-2">自定义</span>
|
||||
{% endif %}
|
||||
{{ plan.display_name }}
|
||||
</h6>
|
||||
<span class="badge {% if plan.is_active %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{% if plan.is_active %}上架{% else %}下架{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="price-display text-primary mb-2">
|
||||
{% if plan.original_price and plan.original_price > plan.price %}
|
||||
<small class="text-muted d-block" style="font-size: 1rem;">
|
||||
<del>¥{{ plan.original_price }}</del>
|
||||
</small>
|
||||
{% endif %}
|
||||
¥{{ plan.price }}
|
||||
</div>
|
||||
<div class="text-muted mb-3">
|
||||
/ {% if plan.period == 'month' %}月{% elif plan.period == 'quarter' %}季{% elif plan.period == 'year' %}年{% else %}{{ plan.period_days }}天{% endif %}
|
||||
</div>
|
||||
|
||||
{% if plan.description %}
|
||||
<p class="text-muted small mb-3">{{ plan.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if plan.user_type_key %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">购买后升级为:</small>
|
||||
<span class="badge bg-secondary">{{ plan.user_type_key }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<table class="table table-sm text-start">
|
||||
<tr>
|
||||
<td><strong>套餐标识</strong></td>
|
||||
<td><code>{{ plan.plan_key }}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>周期天数</strong></td>
|
||||
<td>{{ plan.period_days }} 天</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="d-flex gap-2 justify-content-center mt-3">
|
||||
<a href="{{ url_for('admin.edit_membership_plan', plan_id=plan.id) }}"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-pencil"></i> 编辑
|
||||
</a>
|
||||
{% if not plan.is_system %}
|
||||
<button onclick="deletePlan({{ plan.id }})"
|
||||
class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i> 删除
|
||||
</button>
|
||||
{% endif %}
|
||||
<button onclick="togglePlan({{ plan.id }})"
|
||||
class="btn btn-sm btn-outline-{% if plan.is_active %}secondary{% else %}success{% endif %}">
|
||||
<i class="bi bi-power"></i> {% if plan.is_active %}下架{% else %}上架{% endif %}
|
||||
</button>
|
||||
<button onclick="recommendPlan({{ plan.id }})"
|
||||
class="btn btn-sm btn-outline-warning">
|
||||
<i class="bi bi-star{% if plan.is_recommended %}-fill{% endif %}"></i>
|
||||
{% if plan.is_recommended %}取消推荐{% else %}推荐{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if not plans %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<p class="text-muted">数据库中暂无会员套餐配置</p>
|
||||
<button onclick="initDefaultPlans()" class="btn btn-primary">
|
||||
<i class="bi bi-download"></i> 初始化默认配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function deletePlan(planId) {
|
||||
if (!confirm('确定要删除此会员套餐?此操作不可恢复!')) return;
|
||||
|
||||
fetch(`/admin/membership-plans/${planId}/delete`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('删除失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function togglePlan(planId) {
|
||||
fetch(`/admin/membership-plans/${planId}/toggle`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('操作失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function recommendPlan(planId) {
|
||||
fetch(`/admin/membership-plans/${planId}/recommend`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('操作失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initDefaultPlans() {
|
||||
if (!confirm('确定要初始化默认会员套餐配置?')) return;
|
||||
|
||||
fetch('/admin/membership-plans/init', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('初始化失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,6 +11,8 @@
|
||||
.sidebar .nav-link { color: #adb5bd; padding: 12px 20px; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,0.1); }
|
||||
.main-content { margin-left: 250px; padding: 20px; }
|
||||
.config-card { border-radius: 10px; transition: all 0.3s; }
|
||||
.config-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -23,6 +25,7 @@
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.users') }}"><i class="bi bi-people"></i> 用户管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.translations') }}"><i class="bi bi-file-text"></i> 翻译记录</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.cache_list') }}"><i class="bi bi-database"></i> 缓存管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.packages') }}"><i class="bi bi-box-seam"></i> 数据包套餐</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.stats') }}"><i class="bi bi-bar-chart"></i> 统计报表</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.logs') }}"><i class="bi bi-list-check"></i> 操作日志</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
|
||||
@@ -34,79 +37,101 @@
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="row">
|
||||
<h4 class="mb-4"><i class="bi bi-sliders"></i> 系统配置</h4>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h6 class="mb-0"><i class="bi bi-sliders"></i> 用户权限配置</h6></div>
|
||||
<div class="card config-card h-100">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-person-badge"></i> 用户类型配置</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>用户类型</th>
|
||||
<th>每日次数</th>
|
||||
<th>最大页数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for type, limits in user_limits.items() %}
|
||||
<tr>
|
||||
<td>{{ type }}</td>
|
||||
<td>{{ limits.daily_translations if limits.daily_translations > 0 else '无限' }}</td>
|
||||
<td>{{ limits.max_pages if limits.max_pages > 0 else '无限' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<small class="text-muted">权限配置需修改 config.py 文件</small>
|
||||
<p class="text-muted">配置不同用户类型的权限限制,包括翻译次数、页数限制、功能权限等。</p>
|
||||
<p><strong>支持操作:</strong></p>
|
||||
<ul class="small">
|
||||
<li>添加新的用户类型</li>
|
||||
<li>编辑现有类型的权限</li>
|
||||
<li>删除自定义类型(系统类型不可删除)</li>
|
||||
<li>启用/禁用用户类型</li>
|
||||
</ul>
|
||||
<a href="{{ url_for('admin.user_types') }}" class="btn btn-primary">
|
||||
<i class="bi bi-gear"></i> 管理用户类型
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h6 class="mb-0"><i class="bi bi-credit-card"></i> 会员套餐配置</h6></div>
|
||||
<div class="card config-card h-100">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-credit-card"></i> 会员套餐配置</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>套餐</th>
|
||||
<th>价格</th>
|
||||
<th>周期</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key, plan in membership_plans.items() %}
|
||||
<tr>
|
||||
<td>{{ plan.name }}</td>
|
||||
<td>¥{{ plan.price }}</td>
|
||||
<td>{{ plan.period }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<small class="text-muted">套餐配置需修改 config.py 文件</small>
|
||||
<p class="text-muted">配置会员套餐的价格、周期、描述等,用户购买后可升级用户类型。</p>
|
||||
<p><strong>支持操作:</strong></p>
|
||||
<ul class="small">
|
||||
<li>添加新的会员套餐</li>
|
||||
<li>编辑套餐价格和描述</li>
|
||||
<li>删除自定义套餐(系统套餐不可删除)</li>
|
||||
<li>上架/下架套餐</li>
|
||||
<li>设置推荐套餐</li>
|
||||
</ul>
|
||||
<a href="{{ url_for('admin.membership_plans') }}" class="btn btn-primary">
|
||||
<i class="bi bi-credit-card"></i> 管理会员套餐
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card config-card h-100">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-box-seam"></i> 数据包套餐</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">管理数据包套餐,用户可以购买额外的翻译次数。</p>
|
||||
<a href="{{ url_for('admin.packages') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-seam"></i> 管理数据包
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card config-card h-100">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-cpu"></i> 大模型配置</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">配置翻译使用的LLM大模型API地址、模型名称等参数。</p>
|
||||
<p><strong>当前模型:</strong> {{ llm_config.model }}</p>
|
||||
<a href="{{ url_for('admin.llm_config') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-cpu"></i> 配置大模型
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><h6 class="mb-0"><i class="bi bi-info-circle"></i> 系统信息</h6></div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<p><strong>应用名称:</strong> PDF翻译助手</p>
|
||||
<p><strong>版本:</strong> 1.0.0</p>
|
||||
<p><strong>版本:</strong> 2.0.0</p>
|
||||
<p><strong>框架:</strong> Flask + SQLAlchemy</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<p><strong>LLM模型:</strong> {{ llm_config.model }}</p>
|
||||
<p><strong>API地址:</strong> {{ llm_config.api_base }}</p>
|
||||
<p><a href="{{ url_for('admin.llm_config') }}" class="btn btn-sm btn-outline-primary">修改配置</a></p>
|
||||
<p><strong>缓存有效期:</strong> 30天</p>
|
||||
<p><strong>默认最大文件:</strong> 50MB</p>
|
||||
<p><strong>数据库:</strong> SQLite</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<p><strong>缓存有效期:</strong> 30天</p>
|
||||
<p><strong>最大文件:</strong> 50MB</p>
|
||||
<p><strong>API地址:</strong> {{ llm_config.api_base }}</p>
|
||||
<p><strong>超时时间:</strong> {{ llm_config.timeout }}秒</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
198
templates/admin/user_type_form.html
Normal file
198
templates/admin/user_type_form.html
Normal file
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% if user_type %}编辑用户类型{% else %}添加用户类型{% endif %} - 后台管理</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f5f5f5; }
|
||||
.sidebar { position: fixed; top: 0; left: 0; height: 100vh; width: 250px; background: #343a40; padding-top: 60px; }
|
||||
.sidebar .nav-link { color: #adb5bd; padding: 12px 20px; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,0.1); }
|
||||
.main-content { margin-left: 250px; padding: 20px; }
|
||||
.feature-checkbox { margin: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="sidebar">
|
||||
<div class="position-absolute top-0 w-100 p-3 border-bottom border-secondary">
|
||||
<h5 class="text-white mb-0"><i class="bi bi-gear-fill"></i> 后台管理</h5>
|
||||
</div>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.dashboard') }}"><i class="bi bi-speedometer2"></i> 数据概览</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.users') }}"><i class="bi bi-people"></i> 用户管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.translations') }}"><i class="bi bi-file-text"></i> 翻译记录</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.cache_list') }}"><i class="bi bi-database"></i> 缓存管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.packages') }}"><i class="bi bi-box-seam"></i> 数据包套餐</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.stats') }}"><i class="bi bi-bar-chart"></i> 统计报表</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.logs') }}"><i class="bi bi-list-check"></i> 操作日志</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
|
||||
<li class="nav-item"><a class="nav-link active" href="{{ url_for('admin.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
|
||||
</ul>
|
||||
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">
|
||||
<a href="/" class="btn btn-outline-light btn-sm w-100"><i class="bi bi-house"></i> 返回前台</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4>
|
||||
<i class="bi bi-person-badge"></i>
|
||||
{% if user_type %}编辑用户类型{% else %}添加用户类型{% endif %}
|
||||
</h4>
|
||||
<a href="{{ url_for('admin.user_types') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> 返回列表
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form id="typeForm">
|
||||
{% if user_type %}
|
||||
<input type="hidden" name="type_id" value="{{ user_type.id }}">
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">类型标识 <small class="text-muted">(唯一标识符)</small></label>
|
||||
{% if user_type %}
|
||||
<input type="text" class="form-control" name="type_key"
|
||||
value="{{ user_type.type_key }}" readonly>
|
||||
<small class="text-muted">类型标识创建后不可修改</small>
|
||||
{% else %}
|
||||
<input type="text" class="form-control" name="type_key"
|
||||
placeholder="例如: vip_special" required>
|
||||
<small class="text-muted">建议使用英文,如 vip_special, trial_user</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">显示名称</label>
|
||||
<input type="text" class="form-control" name="display_name"
|
||||
value="{{ user_type.display_name if user_type else '' }}" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">排序权重</label>
|
||||
<input type="number" class="form-control" name="sort_order"
|
||||
value="{{ user_type.sort_order if user_type else 0 }}">
|
||||
<small class="text-muted">数字越小排在前面</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">每日翻译次数</label>
|
||||
<input type="number" class="form-control" name="daily_translations"
|
||||
value="{{ user_type.daily_translations if user_type else 10 }}">
|
||||
<small class="text-muted">输入 -1 表示无限制</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">单文件最大页数</label>
|
||||
<input type="number" class="form-control" name="max_pages"
|
||||
value="{{ user_type.max_pages if user_type else 50 }}">
|
||||
<small class="text-muted">输入 -1 表示无限制</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">最大文件大小 (MB)</label>
|
||||
<input type="number" class="form-control" name="max_file_size_mb"
|
||||
value="{{ (user_type.max_file_size / 1024 / 1024) | round(0) if user_type else 30 }}">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">状态</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="is_active"
|
||||
id="is_active" {% if user_type and user_type.is_active %}checked{% elif not user_type %}checked{% endif %}>
|
||||
<label class="form-check-label" for="is_active">启用此用户类型</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">功能权限</label>
|
||||
<div class="row">
|
||||
{% for feature in all_features %}
|
||||
<div class="col-md-3">
|
||||
<div class="form-check feature-checkbox">
|
||||
<input class="form-check-input" type="checkbox" name="features"
|
||||
value="{{ feature.key }}" id="feature_{{ feature.key }}"
|
||||
{% if user_type and feature.key in user_type.get_features() %}checked{% endif %}>
|
||||
<label class="form-check-label" for="feature_{{ feature.key }}">
|
||||
{{ feature.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> {% if user_type %}保存修改{% else %}添加类型{% endif %}
|
||||
</button>
|
||||
<a href="{{ url_for('admin.user_types') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-x-circle"></i> 取消
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
document.getElementById('typeForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
// 收集选中的功能
|
||||
const features = [];
|
||||
document.querySelectorAll('input[name="features"]:checked').forEach(el => {
|
||||
features.push(el.value);
|
||||
});
|
||||
|
||||
const data = {
|
||||
type_key: formData.get('type_key'),
|
||||
display_name: formData.get('display_name'),
|
||||
daily_translations: parseInt(formData.get('daily_translations')),
|
||||
max_pages: parseInt(formData.get('max_pages')),
|
||||
max_file_size_mb: parseInt(formData.get('max_file_size_mb')),
|
||||
sort_order: parseInt(formData.get('sort_order')),
|
||||
is_active: formData.get('is_active') === 'on',
|
||||
features: JSON.stringify(features)
|
||||
};
|
||||
|
||||
{% if user_type %}
|
||||
const url = `/admin/user-types/{{ user_type.id }}/edit`;
|
||||
{% else %}
|
||||
const url = '/admin/user-types/add';
|
||||
{% endif %}
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
{% if user_type %}
|
||||
alert('用户类型已更新');
|
||||
{% else %}
|
||||
alert('用户类型已添加');
|
||||
{% endif %}
|
||||
window.location.href = '/admin/user-types';
|
||||
} else {
|
||||
alert('操作失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
206
templates/admin/user_types.html
Normal file
206
templates/admin/user_types.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>用户类型配置 - 后台管理</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f5f5f5; }
|
||||
.sidebar { position: fixed; top: 0; left: 0; height: 100vh; width: 250px; background: #343a40; padding-top: 60px; }
|
||||
.sidebar .nav-link { color: #adb5bd; padding: 12px 20px; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #fff; background: rgba(255,255,255,0.1); }
|
||||
.main-content { margin-left: 250px; padding: 20px; }
|
||||
.type-card { border-radius: 10px; transition: all 0.3s; }
|
||||
.type-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
|
||||
.type-card.guest { border-top: 4px solid #6c757d; }
|
||||
.type-card.free { border-top: 4px solid #28a745; }
|
||||
.type-card.vip { border-top: 4px solid #ffc107; }
|
||||
.type-card.enterprise { border-top: 4px solid #0d6efd; }
|
||||
.badge-system { background: #dc3545; }
|
||||
.badge-custom { background: #17a2b8; }
|
||||
.feature-tag { font-size: 0.75rem; margin: 2px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="sidebar">
|
||||
<div class="position-absolute top-0 w-100 p-3 border-bottom border-secondary">
|
||||
<h5 class="text-white mb-0"><i class="bi bi-gear-fill"></i> 后台管理</h5>
|
||||
</div>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.dashboard') }}"><i class="bi bi-speedometer2"></i> 数据概览</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.users') }}"><i class="bi bi-people"></i> 用户管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.translations') }}"><i class="bi bi-file-text"></i> 翻译记录</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.cache_list') }}"><i class="bi bi-database"></i> 缓存管理</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.packages') }}"><i class="bi bi-box-seam"></i> 数据包套餐</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.stats') }}"><i class="bi bi-bar-chart"></i> 统计报表</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.logs') }}"><i class="bi bi-list-check"></i> 操作日志</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
|
||||
<li class="nav-item"><a class="nav-link active" href="{{ url_for('admin.settings') }}"><i class="bi bi-sliders"></i> 系统配置</a></li>
|
||||
</ul>
|
||||
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">
|
||||
<a href="/" class="btn btn-outline-light btn-sm w-100"><i class="bi bi-house"></i> 返回前台</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4><i class="bi bi-person-badge"></i> 用户类型配置</h4>
|
||||
<div>
|
||||
<a href="{{ url_for('admin.add_user_type') }}" class="btn btn-success">
|
||||
<i class="bi bi-plus-circle"></i> 添加用户类型
|
||||
</a>
|
||||
<a href="{{ url_for('admin.membership_plans') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-credit-card"></i> 会员套餐配置
|
||||
</a>
|
||||
<a href="{{ url_for('admin.settings') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-gear"></i> 基础设置
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% for user_type in user_types %}
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card type-card {% if user_type.type_key == 'guest' %}guest{% elif user_type.type_key == 'free' %}free{% elif user_type.type_key.startswith('vip_') and not user_type.type_key.endswith('_enterprise') %}vip{% elif user_type.type_key.endswith('_enterprise') or user_type.type_key.endswith('enterprise') %}enterprise{% endif %}">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<span class="badge {% if user_type.is_system %}badge-system{% else %}badge-custom{% endif %} me-2">
|
||||
{% if user_type.is_system %}系统{% else %}自定义{% endif %}
|
||||
</span>
|
||||
{{ user_type.display_name }}
|
||||
</h6>
|
||||
<span class="badge {% if user_type.is_active %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{% if user_type.is_active %}启用{% else %}禁用{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td><strong>类型标识</strong></td>
|
||||
<td><code>{{ user_type.type_key }}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>每日翻译</strong></td>
|
||||
<td>
|
||||
{% if user_type.daily_translations < 0 %}
|
||||
<span class="text-success">无限制</span>
|
||||
{% else %}
|
||||
{{ user_type.daily_translations }} 次
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>最大页数</strong></td>
|
||||
<td>
|
||||
{% if user_type.max_pages < 0 %}
|
||||
<span class="text-success">无限制</span>
|
||||
{% else %}
|
||||
{{ user_type.max_pages }} 页
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>最大文件</strong></td>
|
||||
<td>{{ (user_type.max_file_size / 1024 / 1024) | round(0) }} MB</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="mb-2">
|
||||
<strong>功能权限:</strong>
|
||||
</div>
|
||||
<div>
|
||||
{% for feature in user_type.get_features() %}
|
||||
<span class="badge bg-info feature-tag">{{ feature }}</span>
|
||||
{% endfor %}
|
||||
{% if not user_type.get_features() %}
|
||||
<span class="text-muted">无特殊功能</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<a href="{{ url_for('admin.edit_user_type', type_id=user_type.id) }}"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-pencil"></i> 编辑
|
||||
</a>
|
||||
{% if not user_type.is_system %}
|
||||
<button onclick="deleteType({{ user_type.id }})"
|
||||
class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i> 删除
|
||||
</button>
|
||||
{% endif %}
|
||||
<button onclick="toggleType({{ user_type.id }})"
|
||||
class="btn btn-sm btn-outline-{% if user_type.is_active %}secondary{% else %}success{% endif %}">
|
||||
<i class="bi bi-power"></i> {% if user_type.is_active %}禁用{% else %}启用{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if not user_types %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<p class="text-muted">数据库中暂无用户类型配置</p>
|
||||
<button onclick="initDefaultTypes()" class="btn btn-primary">
|
||||
<i class="bi bi-download"></i> 初始化默认配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function deleteType(typeId) {
|
||||
if (!confirm('确定要删除此用户类型?此操作不可恢复!')) return;
|
||||
|
||||
fetch(`/admin/user-types/${typeId}/delete`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('删除失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleType(typeId) {
|
||||
fetch(`/admin/user-types/${typeId}/toggle`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('操作失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initDefaultTypes() {
|
||||
if (!confirm('确定要初始化默认用户类型配置?')) return;
|
||||
|
||||
fetch('/admin/user-types/init', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('初始化失败: ' + result.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user