421 lines
21 KiB
HTML
421 lines
21 KiB
HTML
<div class="bg-white rounded-2xl shadow-lg border border-slate-100 p-6">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h3 class="font-bold text-slate-800 flex items-center gap-3">
|
|
<i class="fas fa-file-alt text-blue-500"></i> {{ if .User }}{{.User.Name}}{{ else }}未知用户{{ end }} 的简历管理
|
|
</h3>
|
|
<p class="text-sm text-slate-500 mt-1">管理用户的简历信息和内容</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<a href="{{.AdminPath}}/user/{{.User.ID}}/resume/new/edit" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> 创建简历
|
|
</a>
|
|
<a href="{{.AdminPath}}/users" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i> 返回列表
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full">
|
|
<thead>
|
|
<tr class="bg-slate-50 border-b border-slate-200">
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-slate-600">姓名</th>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-slate-600">职位</th>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-slate-600">访问路由</th>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-slate-600">首页展示</th>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-slate-600">密码保护</th>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-slate-600">创建时间</th>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-slate-600">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{ range .Resumes }}
|
|
<tr class="border-b border-slate-100 hover:bg-slate-50 transition-colors">
|
|
<td class="px-6 py-4">
|
|
<span class="font-medium text-slate-800">{{.BasicInfo.Name}}</span>
|
|
</td>
|
|
<td class="px-6 py-4 text-slate-600">{{.BasicInfo.Title}}</td>
|
|
<td class="px-6 py-4 text-slate-600">
|
|
<a href="/{{.Route}}" target="_blank" class="text-blue-500 hover:text-blue-700 hover:underline">
|
|
/{{.Route}}
|
|
</a>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium {{ if .ShowInHome }}bg-green-100 text-green-700{{ else }}bg-slate-100 text-slate-500{{ end }}">
|
|
{{ if .ShowInHome }}<i class="fas fa-check mr-1"></i>展示{{ else }}隐藏{{ end }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
{{ if .Password }}
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700">
|
|
<i class="fas fa-lock mr-1"></i>已设置
|
|
</span>
|
|
{{ else }}
|
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-500">
|
|
<i class="fas fa-unlock mr-1"></i>无密码
|
|
</span>
|
|
{{ end }}
|
|
</td>
|
|
<td class="px-6 py-4 text-slate-500 text-sm">{{.CreatedAt}}</td>
|
|
<td class="px-6 py-4">
|
|
<div class="btn-group">
|
|
<a href="/{{.Route}}" target="_blank" class="btn btn-primary btn-xs">
|
|
<i class="fas fa-eye"></i> 预览
|
|
</a>
|
|
<a href="{{$.AdminPath}}/user/{{$.User.ID}}/resume/{{.ID}}/edit" class="btn btn-warning btn-xs">
|
|
<i class="fas fa-edit"></i> 编辑
|
|
</a>
|
|
<button onclick="toggleShowInHome('{{.ID}}', {{.ShowInHome}})" class="btn btn-success btn-xs">
|
|
<i class="fas {{ if .ShowInHome }}fa-eye-slash{{ else }}fa-eye{{ end }}"></i> {{ if .ShowInHome }}隐藏{{ else }}展示{{ end }}
|
|
</button>
|
|
<button onclick="setPassword('{{.ID}}')" class="btn btn-info btn-xs">
|
|
<i class="fas fa-key"></i> 密码
|
|
</button>
|
|
<button onclick="deleteResume('{{.ID}}')" class="btn btn-danger btn-xs">
|
|
<i class="fas fa-trash"></i> 删除
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{ else }}
|
|
<tr>
|
|
<td colspan="8" class="px-6 py-12 text-center text-slate-500">
|
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
|
|
<i class="fas fa-file-alt text-slate-400 text-2xl"></i>
|
|
</div>
|
|
<p>暂无简历数据</p>
|
|
</td>
|
|
</tr>
|
|
{{ end }}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50" id="resumeModal">
|
|
<div class="modal-content bg-white rounded-xl shadow-xl w-full max-w-lg mx-4">
|
|
<div class="flex items-center justify-between p-5 border-b border-slate-200">
|
|
<h3 class="font-semibold text-slate-800 flex items-center gap-2" id="resumeModalTitle">
|
|
<i class="fas fa-file-alt text-blue-500"></i> 新建简历
|
|
</h3>
|
|
<button onclick="closeResumeModal()" class="text-slate-400 hover:text-slate-600 p-1 hover:bg-slate-100 rounded-lg transition-all">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="p-5">
|
|
<form id="resumeForm" onsubmit="saveResume(event)">
|
|
<input type="hidden" id="resumeId">
|
|
<input type="hidden" id="resumeUserId">
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">访问路由</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-link"></i>
|
|
</div>
|
|
<input type="text" id="resumeRoute" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" required>
|
|
</div>
|
|
<p class="text-xs text-slate-500 mt-1">用于访问简历的路径,如 zhangsan,访问地址为 /zhangsan</p>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">姓名</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-user"></i>
|
|
</div>
|
|
<input type="text" id="resumeName" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" required>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">职位</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-briefcase"></i>
|
|
</div>
|
|
<input type="text" id="resumeTitle" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">邮箱</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-envelope"></i>
|
|
</div>
|
|
<input type="email" id="resumeEmail" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">手机号</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-phone"></i>
|
|
</div>
|
|
<input type="tel" id="resumePhone" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">所在地</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-map-marker-alt"></i>
|
|
</div>
|
|
<input type="text" id="resumeLocation" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="checkbox" id="resumeShowInHome" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500">
|
|
<span class="text-sm font-medium text-slate-700">在首页展示</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center justify-center gap-3 mt-6">
|
|
<button type="button" onclick="closeResumeModal()" class="btn btn-secondary">
|
|
<i class="fas fa-times"></i> 取消
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i> 保存
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50" id="passwordModal">
|
|
<div class="modal-content bg-white rounded-xl shadow-xl w-full max-w-md mx-4">
|
|
<div class="flex items-center justify-between p-5 border-b border-slate-200">
|
|
<h3 class="font-semibold text-slate-800 flex items-center gap-2">
|
|
<i class="fas fa-key text-purple-500"></i> 设置访问密码
|
|
</h3>
|
|
<button onclick="closePasswordModal()" class="text-slate-400 hover:text-slate-600 p-1 hover:bg-slate-100 rounded-lg transition-all">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="p-5">
|
|
<form id="passwordForm" onsubmit="savePassword(event)">
|
|
<input type="hidden" id="passwordResumeId">
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">访问密码</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-lock"></i>
|
|
</div>
|
|
<input type="password" id="resumePassword" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" placeholder="输入密码,留空则取消密码保护">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5">确认密码</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
|
<i class="fas fa-lock"></i>
|
|
</div>
|
|
<input type="password" id="resumePasswordConfirm" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" placeholder="再次输入密码">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center justify-center gap-3 mt-6">
|
|
<button type="button" onclick="closePasswordModal()" class="btn btn-secondary">
|
|
<i class="fas fa-times"></i> 取消
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i> 保存
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function getUserId() {
|
|
const pathParts = window.location.pathname.split('/');
|
|
return pathParts[pathParts.indexOf('user') + 1];
|
|
}
|
|
|
|
function openAddResumeModal() {
|
|
const userId = getUserId();
|
|
if (!userId) {
|
|
showCustomAlert('error', '错误', '用户ID无效');
|
|
return;
|
|
}
|
|
|
|
fetch('/dashboard/api/user/' + userId)
|
|
.then(response => response.json())
|
|
.then(user => {
|
|
document.getElementById('resumeModalTitle').innerHTML = '<i class="fas fa-file-alt text-blue-500"></i> 新建简历';
|
|
document.getElementById('resumeId').value = '';
|
|
document.getElementById('resumeUserId').value = userId;
|
|
document.getElementById('resumeRoute').value = user.username || 'resume-' + userId.substring(0, 8);
|
|
document.getElementById('resumeName').value = user.name || '';
|
|
document.getElementById('resumeTitle').value = '';
|
|
document.getElementById('resumeEmail').value = user.email || '';
|
|
document.getElementById('resumePhone').value = user.phone || '';
|
|
document.getElementById('resumeLocation').value = '';
|
|
document.getElementById('resumeShowInHome').checked = true;
|
|
document.getElementById('resumeModal').classList.remove('hidden');
|
|
})
|
|
.catch(error => {
|
|
showCustomAlert('error', '错误', '获取用户信息失败');
|
|
});
|
|
}
|
|
|
|
function closeResumeModal() {
|
|
document.getElementById('resumeModal').classList.add('hidden');
|
|
}
|
|
|
|
function editResume(id) {
|
|
fetch('/dashboard/api/resume/' + id)
|
|
.then(response => response.json())
|
|
.then(resume => {
|
|
document.getElementById('resumeModalTitle').innerHTML = '<i class="fas fa-file-edit text-yellow-500"></i> 编辑简历';
|
|
document.getElementById('resumeId').value = resume.id || resume.ID || '';
|
|
document.getElementById('resumeUserId').value = resume.user_id || resume.UserID || '';
|
|
document.getElementById('resumeRoute').value = resume.route || resume.Route || '';
|
|
document.getElementById('resumeName').value = resume.basic_info?.name || resume.BasicInfo?.Name || '';
|
|
document.getElementById('resumeTitle').value = resume.basic_info?.title || resume.BasicInfo?.Title || '';
|
|
document.getElementById('resumeEmail').value = resume.basic_info?.email || resume.BasicInfo?.Email || '';
|
|
document.getElementById('resumePhone').value = resume.basic_info?.phone || resume.BasicInfo?.Phone || '';
|
|
document.getElementById('resumeLocation').value = resume.basic_info?.location || resume.BasicInfo?.Location || '';
|
|
document.getElementById('resumeShowInHome').checked = resume.show_in_home || resume.ShowInHome || false;
|
|
document.getElementById('resumeModal').classList.remove('hidden');
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showCustomAlert('error', '错误', '获取简历信息失败');
|
|
});
|
|
}
|
|
|
|
function saveResume(event) {
|
|
event.preventDefault();
|
|
|
|
const id = document.getElementById('resumeId').value;
|
|
const userId = document.getElementById('resumeUserId').value;
|
|
|
|
const data = {
|
|
user_id: userId,
|
|
route: document.getElementById('resumeRoute').value,
|
|
show_in_home: document.getElementById('resumeShowInHome').checked,
|
|
basic_info: {
|
|
name: document.getElementById('resumeName').value,
|
|
title: document.getElementById('resumeTitle').value,
|
|
email: document.getElementById('resumeEmail').value,
|
|
phone: document.getElementById('resumePhone').value,
|
|
location: document.getElementById('resumeLocation').value
|
|
}
|
|
};
|
|
|
|
let url, method;
|
|
if (id) {
|
|
url = '/dashboard/api/resume/' + id;
|
|
method = 'PUT';
|
|
} else {
|
|
url = '/dashboard/api/resume/create';
|
|
method = 'POST';
|
|
}
|
|
|
|
fetch(url, {
|
|
method: method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
showCustomAlert('success', '成功', '保存成功');
|
|
closeResumeModal();
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
showCustomAlert('error', '错误', '保存失败:' + (result.message || '未知错误'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showCustomAlert('error', '错误', '保存失败');
|
|
});
|
|
}
|
|
|
|
function toggleShowInHome(id, currentValue) {
|
|
fetch('/dashboard/api/resume/' + id + '/show_in_home', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ show_in_home: !currentValue })
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
location.reload();
|
|
} else {
|
|
showCustomAlert('error', '错误', '操作失败:' + (result.message || '未知错误'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showCustomAlert('error', '错误', '操作失败');
|
|
});
|
|
}
|
|
|
|
function setPassword(id) {
|
|
document.getElementById('passwordResumeId').value = id;
|
|
document.getElementById('resumePassword').value = '';
|
|
document.getElementById('resumePasswordConfirm').value = '';
|
|
document.getElementById('passwordModal').classList.remove('hidden');
|
|
}
|
|
|
|
function closePasswordModal() {
|
|
document.getElementById('passwordModal').classList.add('hidden');
|
|
}
|
|
|
|
function savePassword(event) {
|
|
event.preventDefault();
|
|
|
|
const id = document.getElementById('passwordResumeId').value;
|
|
const password = document.getElementById('resumePassword').value;
|
|
const confirmPassword = document.getElementById('resumePasswordConfirm').value;
|
|
|
|
if (password !== confirmPassword) {
|
|
showCustomAlert('error', '错误', '两次输入的密码不一致');
|
|
return;
|
|
}
|
|
|
|
fetch('/dashboard/api/resume/' + id + '/password', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ password: password })
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
showCustomAlert('success', '成功', '密码设置成功');
|
|
closePasswordModal();
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
showCustomAlert('error', '错误', '设置失败:' + (result.message || '未知错误'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showCustomAlert('error', '错误', '设置失败');
|
|
});
|
|
}
|
|
|
|
function deleteResume(id) {
|
|
showCustomConfirm('确认删除', '确定要删除该简历吗?此操作不可恢复。', function(confirmed) {
|
|
if (!confirmed) return;
|
|
fetch('/dashboard/api/resume/' + id, { method: 'DELETE' })
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
location.reload();
|
|
} else {
|
|
showCustomAlert('error', '错误', '删除失败:' + (result.message || '未知错误'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showCustomAlert('error', '错误', '删除失败');
|
|
});
|
|
});
|
|
}
|
|
</script> |