473 lines
19 KiB
JavaScript
473 lines
19 KiB
JavaScript
|
|
lucide.createIcons();
|
|||
|
|
|
|||
|
|
async function checkAuth() {
|
|||
|
|
const r = await fetch('/api/auth/me');
|
|||
|
|
const d = await r.json();
|
|||
|
|
const profile = document.getElementById('userProfile');
|
|||
|
|
const entry = document.getElementById('loginEntryBtn');
|
|||
|
|
const loginHint = document.getElementById('loginHint');
|
|||
|
|
const submitBtn = document.getElementById('submitBtn');
|
|||
|
|
|
|||
|
|
if(d.logged_in) {
|
|||
|
|
if(profile) profile.classList.remove('hidden');
|
|||
|
|
if(entry) entry.classList.add('hidden');
|
|||
|
|
if(loginHint) loginHint.classList.add('hidden');
|
|||
|
|
submitBtn.disabled = false;
|
|||
|
|
submitBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|||
|
|
const phoneDisp = document.getElementById('userPhoneDisplay');
|
|||
|
|
if(phoneDisp) phoneDisp.innerText = d.phone;
|
|||
|
|
|
|||
|
|
// 处理积分显示
|
|||
|
|
const pointsBadge = document.getElementById('pointsBadge');
|
|||
|
|
const pointsDisplay = document.getElementById('pointsDisplay');
|
|||
|
|
if(pointsBadge && pointsDisplay) {
|
|||
|
|
pointsBadge.classList.remove('hidden');
|
|||
|
|
pointsDisplay.innerText = d.points;
|
|||
|
|
}
|
|||
|
|
const headerPoints = document.getElementById('headerPoints');
|
|||
|
|
if(headerPoints) headerPoints.innerText = d.points;
|
|||
|
|
|
|||
|
|
// 如果用户已经有绑定的 Key,且当前没手动输入,则默认切到 Key 模式
|
|||
|
|
if(d.api_key) {
|
|||
|
|
switchMode('key');
|
|||
|
|
const keyInput = document.getElementById('apiKey');
|
|||
|
|
if(keyInput && !keyInput.value) keyInput.value = d.api_key;
|
|||
|
|
} else {
|
|||
|
|
switchMode('trial');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if(profile) profile.classList.add('hidden');
|
|||
|
|
if(entry) entry.classList.remove('hidden');
|
|||
|
|
if(loginHint) loginHint.classList.remove('hidden');
|
|||
|
|
submitBtn.disabled = true;
|
|||
|
|
submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|||
|
|
}
|
|||
|
|
lucide.createIcons();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 移除 redundant logout 监听,因为 base.html 已处理全局登出
|
|||
|
|
|
|||
|
|
// 历史记录分页状态
|
|||
|
|
let currentHistoryPage = 1;
|
|||
|
|
let hasMoreHistory = true;
|
|||
|
|
let isHistoryLoading = false;
|
|||
|
|
|
|||
|
|
// 存储当前生成的所有图片 URL
|
|||
|
|
let currentGeneratedUrls = [];
|
|||
|
|
let currentMode = 'trial'; // 'trial' 或 'key'
|
|||
|
|
|
|||
|
|
function switchMode(mode) {
|
|||
|
|
currentMode = mode;
|
|||
|
|
const trialBtn = document.getElementById('modeTrialBtn');
|
|||
|
|
const keyBtn = document.getElementById('modeKeyBtn');
|
|||
|
|
const keyInputGroup = document.getElementById('keyInputGroup');
|
|||
|
|
const premiumToggle = document.getElementById('premiumToggle');
|
|||
|
|
|
|||
|
|
if(mode === 'trial') {
|
|||
|
|
trialBtn.classList.add('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2');
|
|||
|
|
trialBtn.classList.remove('border-slate-200', 'text-slate-400');
|
|||
|
|
keyBtn.classList.remove('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2');
|
|||
|
|
keyBtn.classList.add('border-slate-200', 'text-slate-400');
|
|||
|
|
keyInputGroup.classList.add('hidden');
|
|||
|
|
if(premiumToggle) premiumToggle.classList.remove('hidden');
|
|||
|
|
} else {
|
|||
|
|
keyBtn.classList.add('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2');
|
|||
|
|
keyBtn.classList.remove('border-slate-200', 'text-slate-400');
|
|||
|
|
trialBtn.classList.remove('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2');
|
|||
|
|
trialBtn.classList.add('border-slate-200', 'text-slate-400');
|
|||
|
|
keyInputGroup.classList.remove('hidden');
|
|||
|
|
if(premiumToggle) premiumToggle.classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
updateCostPreview(); // 切换模式时同步计费预览
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function downloadImage(url) {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(url);
|
|||
|
|
const blob = await response.blob();
|
|||
|
|
const blobUrl = window.URL.createObjectURL(blob);
|
|||
|
|
const a = document.createElement('a');
|
|||
|
|
a.href = blobUrl;
|
|||
|
|
// 从 URL 提取文件名
|
|||
|
|
const filename = url.split('/').pop().split('?')[0] || 'ai-vision-image.png';
|
|||
|
|
a.download = filename;
|
|||
|
|
document.body.appendChild(a);
|
|||
|
|
a.click();
|
|||
|
|
document.body.removeChild(a);
|
|||
|
|
window.URL.revokeObjectURL(blobUrl);
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('下载失败:', e);
|
|||
|
|
showToast('下载失败,请尝试右键保存', 'error');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadHistory(isLoadMore = false) {
|
|||
|
|
if (isHistoryLoading || (!hasMoreHistory && isLoadMore)) return;
|
|||
|
|
|
|||
|
|
isHistoryLoading = true;
|
|||
|
|
if (!isLoadMore) {
|
|||
|
|
currentHistoryPage = 1;
|
|||
|
|
document.getElementById('historyList').innerHTML = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const footer = document.getElementById('historyFooter');
|
|||
|
|
footer.classList.remove('hidden');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const r = await fetch(`/api/history?page=${currentHistoryPage}&per_page=10`);
|
|||
|
|
const d = await r.json();
|
|||
|
|
|
|||
|
|
const list = document.getElementById('historyList');
|
|||
|
|
|
|||
|
|
if (d.history && d.history.length > 0) {
|
|||
|
|
const html = d.history.map(item => `
|
|||
|
|
<div class="bg-white border border-slate-100 rounded-2xl p-4 space-y-3 hover:border-indigo-100 transition-all shadow-sm group">
|
|||
|
|
<div class="flex items-center justify-between">
|
|||
|
|
<span class="text-[10px] font-black text-slate-400 bg-slate-50 px-2 py-0.5 rounded-md uppercase tracking-widest">${item.time}</span>
|
|||
|
|
<span class="text-[10px] font-bold text-indigo-500">${item.model}</span>
|
|||
|
|
</div>
|
|||
|
|
<p class="text-[11px] text-slate-600 line-clamp-2 leading-relaxed">${item.prompt}</p>
|
|||
|
|
<div class="grid grid-cols-3 gap-2">
|
|||
|
|
${item.urls.map(url => `
|
|||
|
|
<div class="aspect-square rounded-lg overflow-hidden border border-slate-100 cursor-pointer transition-transform hover:scale-105" onclick="window.open('${url}')">
|
|||
|
|
<img src="${url}" class="w-full h-full object-cover" loading="lazy">
|
|||
|
|
</div>
|
|||
|
|
`).join('')}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`).join('');
|
|||
|
|
|
|||
|
|
if (isLoadMore) {
|
|||
|
|
list.insertAdjacentHTML('beforeend', html);
|
|||
|
|
} else {
|
|||
|
|
list.innerHTML = html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
hasMoreHistory = d.has_next;
|
|||
|
|
currentHistoryPage++;
|
|||
|
|
} else if (!isLoadMore) {
|
|||
|
|
list.innerHTML = `<div class="flex flex-col items-center justify-center h-64 text-slate-300">
|
|||
|
|
<i data-lucide="inbox" class="w-12 h-12 mb-4"></i>
|
|||
|
|
<span class="text-xs font-bold">暂无生成记录</span>
|
|||
|
|
</div>`;
|
|||
|
|
}
|
|||
|
|
lucide.createIcons();
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('加载历史失败:', e);
|
|||
|
|
if (!isLoadMore) {
|
|||
|
|
document.getElementById('historyList').innerHTML = `<div class="text-center text-rose-400 text-xs font-bold py-10">加载失败: ${e.message}</div>`;
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
isHistoryLoading = false;
|
|||
|
|
footer.classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function init() {
|
|||
|
|
checkAuth();
|
|||
|
|
|
|||
|
|
// 模式切换监听
|
|||
|
|
const modeTrialBtn = document.getElementById('modeTrialBtn');
|
|||
|
|
const modeKeyBtn = document.getElementById('modeKeyBtn');
|
|||
|
|
const isPremiumCheckbox = document.getElementById('isPremium');
|
|||
|
|
if(modeTrialBtn) modeTrialBtn.onclick = () => switchMode('trial');
|
|||
|
|
if(modeKeyBtn) modeKeyBtn.onclick = () => switchMode('key');
|
|||
|
|
if(isPremiumCheckbox) isPremiumCheckbox.onchange = () => updateCostPreview();
|
|||
|
|
|
|||
|
|
// 历史记录控制
|
|||
|
|
const historyDrawer = document.getElementById('historyDrawer');
|
|||
|
|
const showHistoryBtn = document.getElementById('showHistoryBtn');
|
|||
|
|
const closeHistoryBtn = document.getElementById('closeHistoryBtn');
|
|||
|
|
const historyList = document.getElementById('historyList');
|
|||
|
|
|
|||
|
|
if(showHistoryBtn) {
|
|||
|
|
showHistoryBtn.onclick = () => {
|
|||
|
|
historyDrawer.classList.remove('translate-x-full');
|
|||
|
|
loadHistory(false);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
if(closeHistoryBtn) {
|
|||
|
|
closeHistoryBtn.onclick = () => {
|
|||
|
|
historyDrawer.classList.add('translate-x-full');
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 瀑布流滚动加载
|
|||
|
|
if (historyList) {
|
|||
|
|
historyList.onscroll = () => {
|
|||
|
|
const threshold = 100;
|
|||
|
|
if (historyList.scrollTop + historyList.clientHeight >= historyList.scrollHeight - threshold) {
|
|||
|
|
loadHistory(true);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 全部下载按钮逻辑
|
|||
|
|
const downloadAllBtn = document.getElementById('downloadAllBtn');
|
|||
|
|
if(downloadAllBtn) {
|
|||
|
|
downloadAllBtn.onclick = async () => {
|
|||
|
|
if(currentGeneratedUrls.length === 0) return;
|
|||
|
|
showToast(`正在准备下载 ${currentGeneratedUrls.length} 张作品...`, 'info');
|
|||
|
|
for(const url of currentGeneratedUrls) {
|
|||
|
|
await downloadImage(url);
|
|||
|
|
// 稍微延迟一下,防止浏览器拦截
|
|||
|
|
await new Promise(r => setTimeout(r, 300));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新生成按钮逻辑
|
|||
|
|
const regenBtn = document.getElementById('regenBtn');
|
|||
|
|
if(regenBtn) {
|
|||
|
|
regenBtn.onclick = () => {
|
|||
|
|
const submitBtn = document.getElementById('submitBtn');
|
|||
|
|
if(submitBtn) submitBtn.click();
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否有来自 URL 的错误提示
|
|||
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|||
|
|
if(urlParams.has('error')) {
|
|||
|
|
showToast(urlParams.get('error'), 'error');
|
|||
|
|
// 清理 URL 参数以防刷新时重复提示
|
|||
|
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const r = await fetch('/api/config');
|
|||
|
|
const d = await r.json();
|
|||
|
|
fillSelect('modelSelect', d.models);
|
|||
|
|
fillSelect('ratioSelect', d.ratios);
|
|||
|
|
fillSelect('sizeSelect', d.sizes);
|
|||
|
|
fillSelect('promptTpl', [{label:'✨ 自定义创作', value:'manual'}, ...d.prompts]);
|
|||
|
|
updateCostPreview(); // 初始化时显示默认模型的积分
|
|||
|
|
} catch(e) { console.error(e); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function fillSelect(id, list) {
|
|||
|
|
const el = document.getElementById(id);
|
|||
|
|
if(!el) return;
|
|||
|
|
// 如果是模型选择,增加积分显示
|
|||
|
|
if(id === 'modelSelect') {
|
|||
|
|
el.innerHTML = list.map(i => `<option value="${i.value}" data-cost="${i.cost || 0}">${i.label} (${i.cost || 0}积分)</option>`).join('');
|
|||
|
|
} else {
|
|||
|
|
el.innerHTML = list.map(i => `<option value="${i.value}">${i.label}</option>`).join('');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新计费预览显示
|
|||
|
|
function updateCostPreview() {
|
|||
|
|
const modelSelect = document.getElementById('modelSelect');
|
|||
|
|
const costPreview = document.getElementById('costPreview');
|
|||
|
|
const isPremium = document.getElementById('isPremium')?.checked || false;
|
|||
|
|
const selectedOption = modelSelect.options[modelSelect.selectedIndex];
|
|||
|
|
|
|||
|
|
if (currentMode === 'trial' && selectedOption) {
|
|||
|
|
let cost = parseInt(selectedOption.getAttribute('data-cost') || 0);
|
|||
|
|
if(isPremium) cost *= 2; // 优质模式 2 倍积分
|
|||
|
|
costPreview.innerText = `本次生成将消耗 ${cost} 积分`;
|
|||
|
|
costPreview.classList.remove('hidden');
|
|||
|
|
} else {
|
|||
|
|
costPreview.classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
document.getElementById('fileInput').onchange = (e) => {
|
|||
|
|
const prev = document.getElementById('imagePreview');
|
|||
|
|
prev.innerHTML = '';
|
|||
|
|
Array.from(e.target.files).forEach(file => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = (ev) => {
|
|||
|
|
const d = document.createElement('div');
|
|||
|
|
d.className = 'w-20 h-20 rounded-2xl overflow-hidden flex-shrink-0 border-2 border-white shadow-md';
|
|||
|
|
d.innerHTML = `<img src="${ev.target.result}" class="w-full h-full object-cover">`;
|
|||
|
|
prev.appendChild(d);
|
|||
|
|
};
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
document.getElementById('modelSelect').onchange = (e) => {
|
|||
|
|
document.getElementById('sizeGroup').classList.toggle('hidden', e.target.value !== 'nano-banana-2');
|
|||
|
|
updateCostPreview(); // 切换模型时更新计费预览
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
document.getElementById('promptTpl').onchange = (e) => {
|
|||
|
|
const area = document.getElementById('manualPrompt');
|
|||
|
|
if(e.target.value !== 'manual') {
|
|||
|
|
area.value = e.target.value;
|
|||
|
|
area.readOnly = true;
|
|||
|
|
} else {
|
|||
|
|
area.value = '';
|
|||
|
|
area.readOnly = false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
document.getElementById('submitBtn').onclick = async () => {
|
|||
|
|
const btn = document.getElementById('submitBtn');
|
|||
|
|
const files = document.getElementById('fileInput').files;
|
|||
|
|
const apiKey = document.getElementById('apiKey').value;
|
|||
|
|
const num = parseInt(document.getElementById('numSelect').value);
|
|||
|
|
|
|||
|
|
// 检查登录状态并获取积分
|
|||
|
|
const authCheck = await fetch('/api/auth/me');
|
|||
|
|
const authData = await authCheck.json();
|
|||
|
|
if(!authData.logged_in) {
|
|||
|
|
showToast('请先登录后再生成作品', 'warning');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据模式验证
|
|||
|
|
if(currentMode === 'key') {
|
|||
|
|
if(!apiKey) return showToast('请输入您的 API 密钥', 'warning');
|
|||
|
|
} else {
|
|||
|
|
if(authData.points <= 0) return showToast('可用积分已耗尽,请充值或切换至自定义 Key 模式', 'warning');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(files.length === 0) return showToast('请上传照片', 'warning');
|
|||
|
|
|
|||
|
|
// UI 锁定
|
|||
|
|
btn.disabled = true;
|
|||
|
|
const btnText = btn.querySelector('span');
|
|||
|
|
btnText.innerText = "正在同步参考图...";
|
|||
|
|
|
|||
|
|
document.getElementById('statusInfo').classList.remove('hidden');
|
|||
|
|
document.getElementById('placeholder').classList.add('hidden');
|
|||
|
|
document.getElementById('finalWrapper').classList.remove('hidden');
|
|||
|
|
const grid = document.getElementById('imageGrid');
|
|||
|
|
grid.innerHTML = ''; // 清空
|
|||
|
|
currentGeneratedUrls = []; // 重置当前生成列表
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 1. 先上传参考图获取持久化 URL
|
|||
|
|
const uploadData = new FormData();
|
|||
|
|
for(let f of files) uploadData.append('images', f);
|
|||
|
|
const upR = await fetch('/api/upload', { method: 'POST', body: uploadData });
|
|||
|
|
const upRes = await upR.json();
|
|||
|
|
if(upRes.error) throw new Error(upRes.error);
|
|||
|
|
const image_urls = upRes.urls;
|
|||
|
|
|
|||
|
|
// 2. 并行启动多个生成任务
|
|||
|
|
btnText.innerText = `AI 构思中 (0/${num})...`;
|
|||
|
|
let finishedCount = 0;
|
|||
|
|
|
|||
|
|
const startTask = async (index) => {
|
|||
|
|
const slot = document.createElement('div');
|
|||
|
|
slot.className = 'image-frame relative bg-white/50 animate-pulse min-h-[300px] flex items-center justify-center rounded-[2.5rem] border border-slate-100 shadow-sm';
|
|||
|
|
slot.innerHTML = `<div class="text-slate-400 text-xs font-bold italic">正在创作第 ${index + 1} 张...</div>`;
|
|||
|
|
grid.appendChild(slot);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const r = await fetch('/api/generate', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
mode: currentMode,
|
|||
|
|
is_premium: document.getElementById('isPremium')?.checked || false,
|
|||
|
|
apiKey: currentMode === 'key' ? apiKey : '',
|
|||
|
|
prompt: document.getElementById('manualPrompt').value,
|
|||
|
|
model: document.getElementById('modelSelect').value,
|
|||
|
|
ratio: document.getElementById('ratioSelect').value,
|
|||
|
|
size: document.getElementById('sizeSelect').value,
|
|||
|
|
image_urls
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
const res = await r.json();
|
|||
|
|
if(res.error) throw new Error(res.error);
|
|||
|
|
|
|||
|
|
if(res.message) showToast(res.message, 'success');
|
|||
|
|
|
|||
|
|
const imgUrl = res.data[0].url;
|
|||
|
|
currentGeneratedUrls.push(imgUrl);
|
|||
|
|
|
|||
|
|
slot.className = 'image-frame group relative animate-in zoom-in-95 duration-700';
|
|||
|
|
slot.innerHTML = `
|
|||
|
|
<img src="${imgUrl}" class="w-full h-auto rounded-[2.5rem] object-contain shadow-xl">
|
|||
|
|
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-black/20 rounded-[2.5rem]">
|
|||
|
|
<button onclick="downloadImage('${imgUrl}')" class="bg-white/90 p-4 rounded-full text-indigo-600 shadow-xl hover:scale-110 transition-transform">
|
|||
|
|
<i data-lucide="download-cloud" class="w-6 h-6"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
lucide.createIcons();
|
|||
|
|
} catch (e) {
|
|||
|
|
slot.className = 'image-frame relative bg-red-50/50 flex items-center justify-center rounded-[2.5rem] border border-red-100 p-6';
|
|||
|
|
if(e.message.includes('401') || e.message.includes('请先登录')) {
|
|||
|
|
slot.innerHTML = `<div class="text-red-400 text-[10px] font-bold text-center">登录已过期,请重新登录</div>`;
|
|||
|
|
} else {
|
|||
|
|
slot.innerHTML = `<div class="text-red-400 text-[10px] font-bold text-center">渲染异常: ${e.message}</div>`;
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
finishedCount++;
|
|||
|
|
btnText.innerText = `AI 构思中 (${finishedCount}/${num})...`;
|
|||
|
|
// 每次生成任务结束后,刷新一次积分显示
|
|||
|
|
if(currentMode === 'trial') checkAuth();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const tasks = Array.from({ length: num }, (_, i) => startTask(i));
|
|||
|
|
await Promise.all(tasks);
|
|||
|
|
|
|||
|
|
} catch (e) {
|
|||
|
|
showToast('创作引擎中断: ' + e.message, 'error');
|
|||
|
|
document.getElementById('placeholder').classList.remove('hidden');
|
|||
|
|
} finally {
|
|||
|
|
btn.disabled = false;
|
|||
|
|
btnText.innerText = "立即生成作品";
|
|||
|
|
document.getElementById('statusInfo').classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
init();
|
|||
|
|
|
|||
|
|
// 修改密码弹窗控制
|
|||
|
|
function openPwdModal() {
|
|||
|
|
const modal = document.getElementById('pwdModal');
|
|||
|
|
if(!modal) return;
|
|||
|
|
modal.classList.remove('hidden');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
modal.classList.add('opacity-100');
|
|||
|
|
modal.querySelector('div').classList.remove('scale-95');
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function closePwdModal() {
|
|||
|
|
const modal = document.getElementById('pwdModal');
|
|||
|
|
if(!modal) return;
|
|||
|
|
modal.classList.remove('opacity-100');
|
|||
|
|
modal.querySelector('div').classList.add('scale-95');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
modal.classList.add('hidden');
|
|||
|
|
document.getElementById('pwdForm').reset();
|
|||
|
|
}, 300);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
document.addEventListener('click', (e) => {
|
|||
|
|
if(e.target.closest('#openPwdModalBtn')) {
|
|||
|
|
openPwdModal();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
document.getElementById('pwdForm')?.addEventListener('submit', async (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
const old_password = document.getElementById('oldPwd').value;
|
|||
|
|
const new_password = document.getElementById('newPwd').value;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const r = await fetch('/api/auth/change_password', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {'Content-Type': 'application/json'},
|
|||
|
|
body: JSON.stringify({old_password, new_password})
|
|||
|
|
});
|
|||
|
|
const d = await r.json();
|
|||
|
|
if(r.ok) {
|
|||
|
|
showToast('密码修改成功,请记牢新密码', 'success');
|
|||
|
|
closePwdModal();
|
|||
|
|
} else {
|
|||
|
|
showToast(d.error || '修改失败', 'error');
|
|||
|
|
}
|
|||
|
|
} catch(err) {
|
|||
|
|
showToast('网络连接失败', 'error');
|
|||
|
|
}
|
|||
|
|
});
|