Files
2026-01-11 18:14:42 +08:00

401 lines
17 KiB
HTML
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.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<style>
:root {
--bg-body: #f8fafc;
--primary: #0f172a;
--accent: #3b82f6;
}
body {
background: var(--bg-body);
padding-bottom: 90px;
font-family: -apple-system, sans-serif;
color: #334155;
}
/* 头部展示 */
.profile-card {
background: #fff; padding: 30px 20px; margin-bottom: 16px;
display: flex; flex-direction: column; align-items: center; text-align: center;
}
.avatar-placeholder {
width: 80px; height: 80px; border-radius: 50%; background: #f1f5f9; color: #cbd5e1;
font-size: 32px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px;
}
.user-name-disp { font-size: 20px; font-weight: 800; color: var(--primary); margin-bottom: 4px; }
.user-phone-disp { font-size: 14px; color: #64748b; }
.btn-edit-profile {
margin-top: 16px; font-size: 12px; padding: 6px 16px; border-radius: 20px;
border: 1px solid #e2e8f0; background: transparent; color: #475569;
}
/* 菜单组 */
.menu-group { background: #fff; padding: 0 20px; margin-bottom: 16px; }
.menu-item {
padding: 16px 0; border-bottom: 1px solid #f1f5f9; display: flex; align-items: center;
color: var(--primary); font-size: 15px; cursor: pointer; text-decoration: none;
}
.menu-item:last-child { border-bottom: none; }
.menu-icon { margin-right: 12px; font-size: 18px; color: #94a3b8; }
.menu-arrow { margin-left: auto; color: #cbd5e1; font-size: 14px; }
/* 模态框扁平化 */
.modal-content { border-radius: 16px; border: none; }
.modal-header { border-bottom: 1px solid #f1f5f9; }
/* 表单输入 */
.flat-input {
width: 100%; padding: 12px 0; border: none; border-bottom: 1px solid #e2e8f0;
outline: none; transition: border-color 0.2s;
}
.flat-input:focus { border-color: var(--primary); }
.flat-input[readonly] { color: #94a3b8; border-bottom-style: dashed; }
.btn-black {
background: var(--primary); color: #fff; border: none; border-radius: 8px;
padding: 10px 20px; font-size: 14px; font-weight: 600; width: 100%;
}
/* 地址项 */
.addr-item {
border: 1px solid #f1f5f9; border-radius: 12px; padding: 16px; margin-bottom: 12px;
background: #fff; position: relative;
}
.addr-main { font-weight: 600; color: var(--primary); font-size: 15px; margin-bottom: 4px; }
.addr-sub { font-size: 13px; color: #64748b; }
.addr-tag { font-size: 10px; padding: 2px 6px; background: var(--primary); color: #fff; border-radius: 4px; margin-left: 8px; }
.addr-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 12px; padding-top: 12px; border-top: 1px solid #f8fafc; }
.act-btn { font-size: 12px; color: #64748b; border: none; background: none; padding: 0; }
/* 底部导航 */
.mobile-tabbar {
position: fixed; bottom: 0; left: 0; right: 0; background: #fff;
padding: 8px 0 20px; display: flex; border-top: 1px solid #f1f5f9; z-index: 99;
}
.tab-item { flex: 1; text-align: center; color: #94a3b8; text-decoration: none; font-size: 10px; }
.tab-item.active { color: var(--accent); }
.tab-item i { display: block; font-size: 24px; margin-bottom: 2px; }
</style>
</head>
<body>
<!-- 头部展示 -->
<div class="profile-card">
<div class="avatar-placeholder"><i class="bi bi-person"></i></div>
<div class="user-name-disp" id="dispName">--</div>
<div class="user-phone-disp" id="dispPhone">--</div>
<button class="btn-edit-profile" onclick="openProfileModal()">编辑资料</button>
</div>
<!-- 菜单 -->
<div class="menu-group">
<div class="menu-item" onclick="openAddressModalList()">
<i class="bi bi-geo-alt menu-icon"></i>
<span>地址管理</span>
<i class="bi bi-chevron-right menu-arrow"></i>
</div>
<a href="/orders.html" class="menu-item">
<i class="bi bi-receipt menu-icon"></i>
<span>我的订单</span>
<i class="bi bi-chevron-right menu-arrow"></i>
</a>
</div>
<div class="px-4 mt-4">
<button class="btn btn-outline-danger w-100 border-0 bg-white py-3" onclick="logout()">退出登录</button>
</div>
<!-- 编辑资料模态框 -->
<div class="modal fade" id="profileModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold">编辑资料</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="profileForm">
<div class="mb-3">
<label class="small text-muted">用户名</label>
<input type="text" class="flat-input" id="username" readonly>
</div>
<div class="mb-3">
<label class="small text-muted">姓名</label>
<input type="text" class="flat-input" id="name" placeholder="设置昵称">
</div>
<div class="mb-3">
<label class="small text-muted">手机号</label>
<input type="tel" class="flat-input" id="phone" placeholder="绑定手机">
</div>
<div class="mb-3">
<label class="small text-muted">邮箱</label>
<input type="email" class="flat-input" id="email" placeholder="绑定邮箱">
</div>
<button type="submit" class="btn-black mt-3">保存修改</button>
</form>
</div>
</div>
</div>
</div>
<!-- 地址列表模态框 (简单起见,用全屏 Modal 或普通 Modal 模拟新页面) -->
<div class="modal fade" id="addressListModal" tabindex="-1">
<div class="modal-dialog modal-fullscreen-sm-down modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold">我的地址</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body bg-light">
<div id="addressListContent"></div>
<button class="btn-black mt-3" onclick="openAddressEditModal()">+ 新增地址</button>
</div>
</div>
</div>
</div>
<!-- 地址编辑模态框 -->
<div class="modal fade" id="addressEditModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="addrModalTitle">新增地址</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addressForm">
<input type="hidden" id="addrId">
<div class="mb-3">
<input type="text" class="flat-input" id="recipient" placeholder="收货人" required>
</div>
<div class="mb-3">
<input type="tel" class="flat-input" id="addressPhone" placeholder="联系电话" required>
</div>
<div class="mb-3">
<textarea class="flat-input" id="address" rows="2" placeholder="详细地址" required></textarea>
</div>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isDefault">
<label class="form-check-label small text-muted" for="isDefault">设为默认</label>
</div>
</form>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-dark" onclick="saveAddress()">保存</button>
</div>
</div>
</div>
</div>
<!-- 底部导航 -->
<div class="mobile-tabbar">
<a href="/index.html" class="tab-item"><i class="bi bi-cup-hot"></i>点餐</a>
<a href="/cart.html" class="tab-item"><i class="bi bi-bag"></i>购物袋</a>
<a href="/orders.html" class="tab-item"><i class="bi bi-receipt"></i>订单</a>
<a href="/profile.html" class="tab-item active"><i class="bi bi-person-fill"></i>我的</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/custom-modal.js"></script>
<script>
// Init
loadProfile();
async function loadProfile() {
try {
const res = await fetch('/profile', { credentials: 'include' });
const data = await res.json();
if (data.code === 200) {
const u = data.data;
// 展示
document.getElementById('dispName').textContent = u.name || u.username;
document.getElementById('dispPhone').textContent = u.phone || u.email || '未绑定手机';
// 填充表单
document.getElementById('username').value = u.username || '';
document.getElementById('name').value = u.name || '';
document.getElementById('phone').value = u.phone || '';
document.getElementById('email').value = u.email || '';
} else if (data.message?.includes('登录')) {
location.href = '/login.html';
}
} catch (e) { console.error(e); }
}
function openProfileModal() {
// 使用自定义模态框
const modalContent = document.getElementById('profileForm').outerHTML;
const modal = new CustomModal({
title: '编辑资料',
content: modalContent,
size: 'medium',
showFooter: false,
onClose: () => {
// 模态框关闭时的处理
}
});
modal.show();
// 绑定表单提交事件
const form = modal.modal.querySelector('#profileForm');
if (form) {
form.onsubmit = async (e) => {
e.preventDefault();
const btn = form.querySelector('button[type="submit"]');
const old = btn.textContent;
btn.textContent = '保存中...'; btn.disabled = true;
try {
const res = await fetch('/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
name: form.querySelector('#name').value,
phone: form.querySelector('#phone').value,
email: form.querySelector('#email').value
})
});
const d = await res.json();
if(d.code===200) {
modal.hide();
loadProfile();
} else alert(d.message);
} catch(e) { alert('失败'); }
btn.textContent = old; btn.disabled = false;
};
}
}
document.getElementById('profileForm').onsubmit = async (e) => {
e.preventDefault();
const btn = e.target.querySelector('button');
const old = btn.textContent;
btn.textContent = '保存中...'; btn.disabled = true;
try {
const res = await fetch('/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
name: document.getElementById('name').value,
phone: document.getElementById('phone').value,
email: document.getElementById('email').value
})
});
const d = await res.json();
if(d.code===200) {
// 如果使用自定义模态框这里已经通过onClose处理
loadProfile(); // 刷新展示
} else alert(d.message);
} catch(e) { alert('失败'); }
btn.textContent = old; btn.disabled = false;
};
// 地址管理相关
async function openAddressModalList() {
const listModal = new bootstrap.Modal(document.getElementById('addressListModal'));
listModal.show();
loadAddressesInternal();
}
async function loadAddressesInternal() {
const box = document.getElementById('addressListContent');
box.innerHTML = '<div class="text-center py-3 text-muted">加载中...</div>';
try {
const res = await fetch('/address', { credentials: 'include' });
const d = await res.json();
if (d.code === 200 && d.data?.length) {
box.innerHTML = d.data.map(a => `
<div class="addr-item">
<div class="addr-main">
${a.contactName||a.recipient} <span class="ms-2 small fw-normal text-muted">${a.contactPhone||a.phone}</span>
${a.isDefault?'<span class="addr-tag">默认</span>':''}
</div>
<div class="addr-sub">${a.address}</div>
<div class="addr-actions">
<button class="act-btn" onclick="openAddressEditModal(${a.id})">编辑</button>
<button class="act-btn text-danger" onclick="deleteAddress(${a.id})">删除</button>
</div>
</div>
`).join('');
} else {
box.innerHTML = '<div class="text-center py-5 text-muted">暂无地址</div>';
}
} catch(e) { box.innerHTML = '加载失败'; }
}
async function openAddressEditModal(id = null) {
// 如果是在列表模态框之上打开Bootstrap会自动处理层级
const modal = new bootstrap.Modal(document.getElementById('addressEditModal'));
document.getElementById('addressForm').reset();
document.getElementById('addrId').value = '';
document.getElementById('addrModalTitle').textContent = '新增地址';
if (id) {
document.getElementById('addrModalTitle').textContent = '编辑地址';
try {
const res = await fetch('/address', { credentials: 'include' });
const d = await res.json();
const addr = d.data.find(a => a.id === id);
if (addr) {
document.getElementById('addrId').value = addr.id;
document.getElementById('recipient').value = addr.contactName || '';
document.getElementById('addressPhone').value = addr.contactPhone || '';
document.getElementById('address').value = addr.address || '';
document.getElementById('isDefault').checked = addr.isDefault === 1;
}
} catch(e) {}
}
modal.show();
}
async function saveAddress() {
const id = document.getElementById('addrId').value;
const payload = {
contactName: document.getElementById('recipient').value.trim(),
contactPhone: document.getElementById('addressPhone').value.trim(),
address: document.getElementById('address').value.trim(),
isDefault: document.getElementById('isDefault').checked ? 1 : 0
};
// 验证必填字段
if (!payload.contactName || !payload.contactPhone || !payload.address) {
alert('请填写完整信息');
return;
}
try {
const res = await fetch(id ? `/address/${id}` : '/address', {
method: id ? 'PUT' : 'POST',
headers: {'Content-Type': 'application/json'},
credentials: 'include',
body: JSON.stringify(payload)
});
const d = await res.json();
if(d.code === 200) {
bootstrap.Modal.getInstance(document.getElementById('addressEditModal')).hide();
loadAddressesInternal(); // 刷新列表
} else alert(d.message);
} catch(e) { alert('保存失败'); }
}
async function deleteAddress(id) {
if(!confirm('确认删除?')) return;
await fetch(`/address/${id}`, { method: 'DELETE', credentials: 'include' });
loadAddressesInternal();
}
function logout() {
location.href = '/login.html';
}
</script>
</body>
</html>