2026-03-18 13:38:17 +08:00
|
|
|
|
{% extends "base.html" %}
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
|
|
|
|
<h4 class="mb-0">账号管理</h4>
|
|
|
|
|
|
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addModal">
|
|
|
|
|
|
<i class="bi bi-plus-lg"></i> 添加账号
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="table-responsive">
|
|
|
|
|
|
<table class="table table-hover mb-0">
|
|
|
|
|
|
<thead><tr>
|
|
|
|
|
|
<th>ID</th><th>名称</th><th>手机号</th><th>登录状态</th><th>更新时间</th><th>操作</th>
|
|
|
|
|
|
</tr></thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
{% for a in accounts %}
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>{{ a.id }}</td>
|
|
|
|
|
|
<td>{{ a.name }}</td>
|
|
|
|
|
|
<td>{{ a.phone[:3] }}****{{ a.phone[-4:] if a.phone|length > 4 else '' }}</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
{% if a.is_logged_in %}
|
|
|
|
|
|
<span class="badge bg-success">已登录</span>
|
|
|
|
|
|
{% elif a.login_msg == '登录中...' %}
|
|
|
|
|
|
<span class="badge bg-warning">
|
|
|
|
|
|
<span class="spinner-border spinner-border-sm" style="width:.7rem;height:.7rem"></span> 登录中...
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{% else %}
|
|
|
|
|
|
<span class="badge bg-secondary" title="{{ a.login_msg or '' }}">未登录</span>
|
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>{{ a.updated_at }}</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<button class="btn btn-outline-primary btn-sm" onclick="doLogin({{ a.id }}, this)">
|
2026-03-31 15:18:02 +08:00
|
|
|
|
<i class="bi bi-key"></i> 密码登录
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn btn-outline-info btn-sm" onclick="doSmsLogin({{ a.id }}, this)">
|
|
|
|
|
|
<i class="bi bi-chat-dots"></i> 短信登录
|
2026-03-18 13:38:17 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn btn-outline-danger btn-sm" onclick="deleteAccount({{ a.id }})">
|
|
|
|
|
|
<i class="bi bi-trash"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
{% endfor %}
|
|
|
|
|
|
{% if not accounts %}
|
|
|
|
|
|
<tr><td colspan="6" class="text-center text-muted py-4">暂无账号,点击右上角添加</td></tr>
|
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal fade" id="addModal" tabindex="-1">
|
|
|
|
|
|
<div class="modal-dialog"><div class="modal-content">
|
|
|
|
|
|
<div class="modal-header"><h5 class="modal-title">添加微店账号</h5></div>
|
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
|
<form id="addForm">
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
|
<label class="form-label">备注名称</label>
|
|
|
|
|
|
<input type="text" class="form-control" name="name" required placeholder="如:主号、小号1">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
|
<label class="form-label">手机号</label>
|
|
|
|
|
|
<input type="tel" class="form-control" name="phone" required placeholder="微店登录手机号">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
|
<label class="form-label">密码</label>
|
|
|
|
|
|
<input type="password" class="form-control" name="password" required placeholder="微店登录密码">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
|
<button class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
|
|
|
|
|
<button class="btn btn-primary" onclick="addAccount()">添加</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
{% block scripts %}
|
|
|
|
|
|
<script>
|
|
|
|
|
|
function addAccount() {
|
|
|
|
|
|
var form = document.getElementById('addForm');
|
|
|
|
|
|
var data = new FormData(form);
|
|
|
|
|
|
var btn = event.target;
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.textContent = '添加中...';
|
|
|
|
|
|
fetch('/accounts/add', { method: 'POST', body: data })
|
|
|
|
|
|
.then(function(r) { return r.json(); })
|
|
|
|
|
|
.then(function(d) {
|
|
|
|
|
|
if (d.success) {
|
|
|
|
|
|
bootstrap.Modal.getInstance(document.getElementById('addModal')).hide();
|
|
|
|
|
|
form.reset();
|
|
|
|
|
|
location.reload();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert(d.msg);
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
btn.textContent = '添加';
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(function() { btn.disabled = false; btn.textContent = '添加'; });
|
|
|
|
|
|
}
|
|
|
|
|
|
function doLogin(id, btn) {
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> 登录中...';
|
|
|
|
|
|
var badge = btn.closest('tr').querySelector('.badge');
|
|
|
|
|
|
if (badge) { badge.className = 'badge bg-warning'; badge.textContent = '登录中...'; }
|
|
|
|
|
|
fetch('/accounts/login/' + id, { method: 'POST' })
|
|
|
|
|
|
.then(function(r) { return r.json(); })
|
|
|
|
|
|
.then(function() { pollStatus(id, btn); })
|
2026-03-31 15:18:02 +08:00
|
|
|
|
.catch(function() { btn.disabled = false; btn.innerHTML = '<i class="bi bi-key"></i> 密码登录'; });
|
|
|
|
|
|
}
|
|
|
|
|
|
function doSmsLogin(id, btn) {
|
|
|
|
|
|
if (!confirm('短信登录需要您在弹出的浏览器中拖动滑块,并在服务器终端输入验证码。确定继续?')) return;
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> 等待交互...';
|
|
|
|
|
|
var badge = btn.closest('tr').querySelector('.badge');
|
|
|
|
|
|
if (badge) { badge.className = 'badge bg-info'; badge.textContent = '等待人机交互...'; }
|
|
|
|
|
|
fetch('/accounts/login_sms/' + id, { method: 'POST' })
|
|
|
|
|
|
.then(function(r) { return r.json(); })
|
|
|
|
|
|
.then(function(d) {
|
|
|
|
|
|
if (d.success) {
|
|
|
|
|
|
alert(d.msg);
|
|
|
|
|
|
pollStatus(id, btn);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert(d.msg);
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
btn.innerHTML = '<i class="bi bi-chat-dots"></i> 短信登录';
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(function() { btn.disabled = false; btn.innerHTML = '<i class="bi bi-chat-dots"></i> 短信登录'; });
|
2026-03-18 13:38:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
function pollStatus(id, btn) {
|
|
|
|
|
|
var interval = setInterval(function() {
|
|
|
|
|
|
fetch('/accounts/status/' + id)
|
|
|
|
|
|
.then(function(r) { return r.json(); })
|
|
|
|
|
|
.then(function(d) {
|
|
|
|
|
|
if (d.done) {
|
|
|
|
|
|
clearInterval(interval);
|
|
|
|
|
|
var row = btn ? btn.closest('tr') : null;
|
|
|
|
|
|
if (row) {
|
|
|
|
|
|
var badge = row.querySelector('.badge');
|
|
|
|
|
|
if (d.is_logged_in) {
|
|
|
|
|
|
badge.className = 'badge bg-success';
|
|
|
|
|
|
badge.textContent = '已登录';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
badge.className = 'badge bg-danger';
|
|
|
|
|
|
badge.textContent = '失败';
|
|
|
|
|
|
badge.title = d.login_msg;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (btn) {
|
|
|
|
|
|
btn.disabled = false;
|
2026-03-31 15:18:02 +08:00
|
|
|
|
btn.innerHTML = btn.textContent.includes('短信') ?
|
|
|
|
|
|
'<i class="bi bi-chat-dots"></i> 短信登录' :
|
|
|
|
|
|
'<i class="bi bi-key"></i> 密码登录';
|
2026-03-18 13:38:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
}
|
|
|
|
|
|
function deleteAccount(id) {
|
|
|
|
|
|
if (!confirm('确定删除此账号?相关任务也会被删除。')) return;
|
|
|
|
|
|
fetch('/accounts/delete/' + id, { method: 'POST' })
|
|
|
|
|
|
.then(function(r) { return r.json(); })
|
|
|
|
|
|
.then(function(d) { if (d.success) location.reload(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
{% endblock %}
|