Files

401 lines
17 KiB
HTML
Raw Permalink Normal View History

2026-01-11 18:14:42 +08:00
<!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>