352 lines
17 KiB
HTML
352 lines
17 KiB
HTML
|
||
<style>
|
||
/* 仅保留必要的自定义样式(Tailwind 无法覆盖的部分) */
|
||
/* 1. 卡片右上角三角形裁剪与伪元素 */
|
||
.user-dark-card {
|
||
clip-path: polygon(0 0, calc(100% - 30px) 0, 100% 30px, 100% 100%, 0 100%);
|
||
}
|
||
.user-dark-card::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 0;
|
||
height: 0;
|
||
border-top: 30px solid white;
|
||
border-left: 30px solid transparent;
|
||
box-shadow: -3px 3px 5px rgba(0, 0, 0, 0.3);
|
||
margin: -1px -1px 0 0;
|
||
}
|
||
|
||
/* 2. 音频进度条拖动交互(Tailwind 无法覆盖的动态样式) */
|
||
.user-dark-progress {
|
||
transition: width 0.2s ease;
|
||
}
|
||
.user-dark-handle {
|
||
transition: left 0.2s ease;
|
||
}
|
||
</style>
|
||
<!-- 顶部头部区域 -->
|
||
<div class="relative overflow-hidden bg-gradient-to-r from-blue-600 to-indigo-700 text-white">
|
||
<!-- 背景装饰方块(原 squares 效果) -->
|
||
<div class="absolute inset-0">
|
||
<div class="absolute top-10 left-10 w-20 h-20 bg-white/10 rounded"></div>
|
||
<div class="absolute top-20 right-20 w-32 h-32 bg-white/10 rounded"></div>
|
||
<div class="absolute bottom-10 left-1/4 w-24 h-24 bg-white/10 rounded"></div>
|
||
<div class="absolute bottom-20 right-1/3 w-16 h-16 bg-white/10 rounded"></div>
|
||
</div>
|
||
|
||
<!-- 头部内容容器:所有文本初始隐藏(透明+下移) -->
|
||
<div class="container mx-auto px-4 py-32 relative z-10 text-center">
|
||
<!-- 1. 主标题 GVSDS• :第1个显示 -->
|
||
<h1 class="text-4xl md:text-5xl font-bold mb-2" id="title-main" style="opacity: 0; transform: translateY(10px); transition: all 0.8s ease-out;">GVSDS•</h1>
|
||
|
||
<!-- 2. 英文名称 :第2个显示 -->
|
||
<h3 class="text-xl md:text-2xl mb-1" id="title-en" style="opacity: 0; transform: translateY(10px); transition: all 0.8s ease-out;">Galaxy Vantone Software Development Studio</h3>
|
||
|
||
<!-- 3. 中文名称 :第3个显示 -->
|
||
<h3 class="text-xl md:text-2xl mb-4" id="title-cn" style="opacity: 0; transform: translateY(10px); transition: all 0.8s ease-out;">银河锋锐软件开发工作室</h3>
|
||
|
||
<!-- 4. 描述文本 :第4个显示 -->
|
||
<p class="text-lg text-white/90" id="title-desc" style="opacity: 0; transform: translateY(10px); transition: all 0.8s ease-out;">本工作室的成员们</p>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 纯JS实现依次显示逻辑:页面加载完成后执行
|
||
window.addEventListener('DOMContentLoaded', function() {
|
||
// 1. 定义需要依次显示的元素(ID + 显示延迟时间,单位:毫秒)
|
||
const textElements = [
|
||
{ id: 'title-main', delay: 100 }, // 第1个:延迟100ms显示(主标题)
|
||
{ id: 'title-en', delay: 800 }, // 第2个:延迟800ms显示(英文名称)
|
||
{ id: 'title-cn', delay: 1500 }, // 第3个:延迟1500ms显示(中文名称)
|
||
{ id: 'title-desc', delay: 2200 } // 第4个:延迟2200ms显示(描述文本)
|
||
];
|
||
|
||
// 2. 定义「显示元素」的函数:修改样式为可见状态
|
||
function showElement(elementId) {
|
||
const element = document.getElementById(elementId);
|
||
if (element) { // 避免元素不存在时报错
|
||
element.style.opacity = '1'; // 从不透明变为完全可见
|
||
element.style.transform = 'translateY(0)'; // 从下移10px归位
|
||
}
|
||
}
|
||
|
||
// 3. 按顺序触发每个元素的显示(定时器控制时序)
|
||
textElements.forEach(item => {
|
||
setTimeout(() => {
|
||
showElement(item.id);
|
||
}, item.delay);
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<!-- 加载界面(自定义,替代 layer) -->
|
||
<div id="loading" class="fixed inset-0 bg-black/80 flex flex-col items-center justify-center z-50">
|
||
<div class="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mb-4"></div>
|
||
<p class="text-gray-700 text-lg">加载成员数据中...</p>
|
||
</div>
|
||
|
||
<!-- 成员卡片容器 -->
|
||
<div id="members" class="container mx-auto px-4 py-8 flex flex-col items-center gap-8 mb-10"></div>
|
||
|
||
<script>
|
||
// 1. 解析URL参数(替代原 eval 方式,更安全)
|
||
const URLARGS = new URLSearchParams(window.location.search);
|
||
const Type = URLARGS.get('Type') || ''; // 默认空值,避免Type未传时报错
|
||
console.log('请求类型 Type:', Type);
|
||
|
||
// 2. 页面加载完成后请求数据
|
||
window.addEventListener('load', async function () {
|
||
try {
|
||
// 发起 fetch 请求(替代原 $.get)
|
||
const response = await fetch(`/api/gerp/site-users/?Type=${Type}`);
|
||
if (!response.ok) {
|
||
throw new Error(`请求失败: ${response.status} ${response.statusText}`);
|
||
}
|
||
const data = await response.json();
|
||
console.log('成员数据:', data);
|
||
|
||
if (!data.data || !Array.isArray(data.data)) {
|
||
throw new Error('成员数据格式错误');
|
||
}
|
||
|
||
// 渲染成员卡片
|
||
renderMemberCards(data.data);
|
||
} catch (error) {
|
||
console.error('加载成员数据失败:', error);
|
||
// 加载失败提示
|
||
document.getElementById('loading').innerHTML = `
|
||
<div class="text-red-500 mb-4">
|
||
<i class="layui-icon layui-icon-error text-4xl"></i>
|
||
</div>
|
||
<p class="text-gray-700 text-lg">加载失败: ${error.message}</p>
|
||
<button onclick="window.location.reload()" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||
重试
|
||
</button>
|
||
`;
|
||
} finally {
|
||
// 隐藏加载界面(成功/失败都执行)
|
||
setTimeout(() => {
|
||
document.getElementById('loading').classList.add('opacity-0', 'pointer-events-none');
|
||
setTimeout(() => {
|
||
document.getElementById('loading').classList.add('hidden');
|
||
}, 300);
|
||
}, 500);
|
||
}
|
||
});
|
||
|
||
// 3. 渲染成员卡片
|
||
function renderMemberCards(members) {
|
||
const container = document.getElementById('members');
|
||
members.forEach(member => {
|
||
// 生成联系方式HTML(微信/QQ/邮箱)
|
||
const contactHtml = generateContactHtml(member);
|
||
// 生成音频播放器HTML(如有音频路径)
|
||
const audioHtml = member.UserSoundPath ? generateAudioHtml(member.UserSoundPath) : '';
|
||
// 生成标签HTML(成员职责)
|
||
const tagsHtml = generateTagsHtml(member.UserWork || []);
|
||
|
||
// 卡片HTML结构(Tailwind 样式)
|
||
const cardHtml = `
|
||
<div class="user-dark-card w-full md:w-10/12 lg:w-8/12 bg-gray-900 text-white rounded-lg shadow-lg overflow-hidden relative p-5 flex flex-col md:flex-row">
|
||
<!-- 左侧图片区域 -->
|
||
<div class="w-full md:w-48 md:h-48 mb-4 md:mb-0 md:mr-6 flex-shrink-0">
|
||
<img src="${member.UserPhotoPath || 'https://picsum.photos/200/200'}"
|
||
alt="${member.UserName || '成员头像'}"
|
||
class="w-full h-full object-cover rounded-lg">
|
||
</div>
|
||
|
||
<!-- 右侧内容区域 -->
|
||
<div class="flex-1 flex flex-col">
|
||
<!-- 标题与标签 -->
|
||
<div class="mb-3">
|
||
<h2 class="text-2xl font-bold flex flex-wrap items-center">
|
||
${member.UserName || '未命名成员'}
|
||
${tagsHtml}
|
||
</h2>
|
||
</div>
|
||
|
||
<!-- 成员简介 -->
|
||
<p class="text-gray-300 text-base leading-relaxed mb-4 flex-1">
|
||
${member.UserBrInt || '暂无成员简介'}
|
||
</p>
|
||
|
||
<!-- 音频播放器(如有) -->
|
||
${audioHtml}
|
||
|
||
<!-- 联系方式 -->
|
||
${contactHtml ? `
|
||
<div class="mt-4 flex flex-wrap gap-2">
|
||
${contactHtml}
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
container.insertAdjacentHTML('beforeend', cardHtml);
|
||
});
|
||
|
||
// 初始化所有音频播放器
|
||
initAllAudioPlayers();
|
||
}
|
||
|
||
// 4. 生成联系方式HTML
|
||
function generateContactHtml(member) {
|
||
let html = '';
|
||
// 微信
|
||
if (member.UserWX) {
|
||
html += `
|
||
<a href="javascript:;" class="px-3 py-1.5 bg-blue-500 text-white rounded text-sm flex items-center gap-1.5 hover:bg-blue-600 transition-colors">
|
||
<i class="layui-icon layui-icon-wechat"></i> ${member.UserWXName || '微信'} | ${member.UserWX}
|
||
</a>
|
||
`;
|
||
}
|
||
// QQ
|
||
if (member.UserQQ) {
|
||
html += `
|
||
<a href="javascript:;" class="px-3 py-1.5 bg-blue-400 text-white rounded text-sm flex items-center gap-1.5 hover:bg-blue-500 transition-colors">
|
||
<i class="layui-icon layui-icon-qq"></i> ${member.UserQQName || 'QQ'} | ${member.UserQQ}
|
||
</a>
|
||
`;
|
||
}
|
||
// 邮箱
|
||
if (member.UserEmail) {
|
||
html += `
|
||
<a href="mailto:${member.UserEmail}" class="px-3 py-1.5 bg-blue-600 text-white rounded text-sm flex items-center gap-1.5 hover:bg-blue-700 transition-colors">
|
||
<i class="layui-icon layui-icon-mail"></i> Email | ${member.UserEmail}
|
||
</a>
|
||
`;
|
||
}
|
||
return html;
|
||
}
|
||
|
||
// 5. 生成音频播放器HTML
|
||
function generateAudioHtml(audioSrc) {
|
||
return `
|
||
<div class="user-dark-audio-player flex items-center mt-3">
|
||
<!-- 播放/暂停按钮 -->
|
||
<button class="user-dark-play-pause-btn px-3 py-2 bg-blue-500 text-white rounded mr-3 hover:bg-blue-600 transition-colors">
|
||
<i class="layui-icon layui-icon-play"></i>
|
||
</button>
|
||
|
||
<!-- 进度条与时间 -->
|
||
<div class="user-dark-progress-container flex items-center flex-1 gap-2">
|
||
<span class="user-dark-current-time text-xs text-gray-400">0:00</span>
|
||
|
||
<!-- 进度条容器 -->
|
||
<div class="user-dark-progress-bar flex-1 h-2.5 bg-gray-700 rounded relative cursor-pointer">
|
||
<div class="user-dark-progress h-full bg-blue-500 rounded w-0"></div>
|
||
<div class="user-dark-handle absolute top-1/2 -translate-y-1/2 w-5 h-5 bg-white rounded-full -ml-2.5 left-0"></div>
|
||
</div>
|
||
|
||
<span class="user-dark-total-time text-xs text-gray-400">0:00</span>
|
||
</div>
|
||
|
||
<!-- 音频元素(隐藏) -->
|
||
<audio src="${audioSrc}" preload="metadata"></audio>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 6. 生成成员标签HTML
|
||
function generateTagsHtml(tags) {
|
||
if (!tags.length) return '';
|
||
return tags.map(tag => `
|
||
<span class="ml-2 px-2 py-0.5 bg-blue-500 text-white rounded text-xs">${tag}</span>
|
||
`).join('');
|
||
}
|
||
|
||
// 7. 初始化所有音频播放器
|
||
function initAllAudioPlayers() {
|
||
const cards = document.querySelectorAll('.user-dark-card');
|
||
cards.forEach(card => {
|
||
const audio = card.querySelector('audio');
|
||
if (!audio) return;
|
||
|
||
// 绑定元素
|
||
const playPauseBtn = card.querySelector('.user-dark-play-pause-btn');
|
||
const playIcon = playPauseBtn.querySelector('i');
|
||
const progressBar = card.querySelector('.user-dark-progress-bar');
|
||
const progress = card.querySelector('.user-dark-progress');
|
||
const handle = card.querySelector('.user-dark-handle');
|
||
const currentTimeEl = card.querySelector('.user-dark-current-time');
|
||
const totalTimeEl = card.querySelector('.user-dark-total-time');
|
||
|
||
// 播放/暂停切换
|
||
playPauseBtn.addEventListener('click', () => {
|
||
if (audio.paused) {
|
||
audio.play().catch(err => {
|
||
console.error('音频播放失败:', err);
|
||
alert('音频播放失败,请检查文件路径或浏览器权限');
|
||
});
|
||
playIcon.classList.replace('layui-icon-play', 'layui-icon-pause');
|
||
} else {
|
||
audio.pause();
|
||
playIcon.classList.replace('layui-icon-pause', 'layui-icon-play');
|
||
}
|
||
});
|
||
|
||
// 音频元数据加载完成(获取总时长)
|
||
audio.addEventListener('loadedmetadata', () => {
|
||
totalTimeEl.textContent = formatTime(audio.duration);
|
||
});
|
||
|
||
// 音频播放进度更新
|
||
audio.addEventListener('timeupdate', () => {
|
||
const percent = (audio.currentTime / audio.duration) * 100;
|
||
progress.style.width = `${percent}%`;
|
||
handle.style.left = `calc(${percent}% - 10px)`;
|
||
currentTimeEl.textContent = formatTime(audio.currentTime);
|
||
});
|
||
|
||
// 音频播放结束重置
|
||
audio.addEventListener('ended', () => {
|
||
playIcon.classList.replace('layui-icon-pause', 'layui-icon-play');
|
||
progress.style.width = '0%';
|
||
handle.style.left = '0';
|
||
currentTimeEl.textContent = '0:00';
|
||
audio.currentTime = 0;
|
||
});
|
||
|
||
// 进度条点击跳转
|
||
progressBar.addEventListener('click', (e) => {
|
||
const rect = progressBar.getBoundingClientRect();
|
||
const clickX = e.clientX - rect.left;
|
||
const percent = (clickX / progressBar.offsetWidth) * 100;
|
||
updateProgress(percent);
|
||
});
|
||
|
||
// 进度条拖动(鼠标事件)
|
||
let isDragging = false;
|
||
handle.addEventListener('mousedown', () => isDragging = true);
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (isDragging) {
|
||
const rect = progressBar.getBoundingClientRect();
|
||
const clickX = e.clientX - rect.left;
|
||
const percent = (clickX / progressBar.offsetWidth) * 100;
|
||
updateProgress(Math.max(0, Math.min(100, percent))); // 限制在0-100%
|
||
}
|
||
});
|
||
document.addEventListener('mouseup', () => isDragging = false);
|
||
|
||
// 更新进度条与音频时间的工具函数
|
||
function updateProgress(percent) {
|
||
progress.style.width = `${percent}%`;
|
||
handle.style.left = `calc(${percent}% - 10px)`;
|
||
audio.currentTime = (percent / 100) * audio.duration;
|
||
currentTimeEl.textContent = formatTime(audio.currentTime);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 8. 时间格式化工具函数(秒转 分:秒)
|
||
function formatTime(seconds) {
|
||
if (isNaN(seconds)) return '0:00';
|
||
const minutes = Math.floor(seconds / 60);
|
||
const remainingSeconds = Math.floor(seconds % 60);
|
||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||
}
|
||
</script>
|