<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>技术人员状态与级别筛选</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#36D399',
warning: '#FFAA33',
neutral: {
100: '#F5F7FA',
200: '#E5E6EB',
300: '#C9CDD4',
400: '#86909C',
500: '#4E5969',
600: '#272E3B',
700: '#1D2129',
}
},
fontFamily: {
inter: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.tab-active {
@apply text-primary border-primary font-medium;
}
.card-hover {
@apply transition-all duration-300 hover:shadow-lg hover:-translate-y-1;
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
}
</style>
</head>
<body class="bg-neutral-100 font-inter text-neutral-700 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<header class="mb-8 text-center">
<h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-neutral-700 mb-2">技术人员状态与级别筛选</h1>
<p class="text-neutral-500 text-lg">根据技术人员的当前状态和级别进行快速筛选和查看</p>
</header>
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="flex flex-col md:flex-row md:items-center justify-between mb-6">
<div class="mb-4 md:mb-0">
<h2 class="text-xl font-semibold text-neutral-700">技术人员列表</h2>
<p class="text-neutral-500">实时查看技术人员工作状态和级别</p>
</div>
<div class="flex space-x-2">
<div class="relative">
<input type="text" id="search-input" placeholder="搜索技术人员..."
class="pl-10 pr-4 py-2 border border-neutral-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 w-full md:w-64 transition-all">
<i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-neutral-400"></i>
</div>
<button id="refresh-btn" class="bg-primary text-white px-4 py-2 rounded-lg flex items-center transition-all hover:bg-primary/90">
<i class="fa fa-refresh mr-2"></i>刷新
</button>
</div>
</div>
<!-- 状态选项卡 -->
<div class="border-b border-neutral-200 mb-6">
<div class="flex flex-wrap -mb-px">
<button id="tab-all" class="tab-btn tab-active inline-block py-4 px-6 text-sm font-medium text-center border-b-2 border-transparent rounded-t-lg hover:text-primary hover:border-primary/30 transition-all">
全部
</button>
<button id="tab-busy" class="tab-btn inline-block py-4 px-6 text-sm font-medium text-center border-b-2 border-transparent rounded-t-lg hover:text-primary hover:border-primary/30 transition-all">
<span class="inline-flex items-center">
<span class="w-2 h-2 bg-warning rounded-full mr-2"></span>在忙
</span>
</button>
<button id="tab-free" class="tab-btn inline-block py-4 px-6 text-sm font-medium text-center border-b-2 border-transparent rounded-t-lg hover:text-primary hover:border-primary/30 transition-all">
<span class="inline-flex items-center">
<span class="w-2 h-2 bg-secondary rounded-full mr-2"></span>空闲
</span>
</button>
</div>
</div>
<!-- 级别筛选器 -->
<div class="mb-6 flex flex-wrap gap-2">
<button class="level-btn bg-primary text-white px-4 py-2 rounded-full text-sm flex items-center transition-all hover:bg-primary/90" data-level="all">
全部级别
</button>
<button class="level-btn bg-neutral-200 text-neutral-700 px-4 py-2 rounded-full text-sm flex items-center transition-all hover:bg-neutral-300" data-level="初级助教">
初级助教
</button>
<button class="level-btn bg-neutral-200 text-neutral-700 px-4 py-2 rounded-full text-sm flex items-center transition-all hover:bg-neutral-300" data-level="中级助教">
中级助教
</button>
<button class="level-btn bg-neutral-200 text-neutral-700 px-4 py-2 rounded-full text-sm flex items-center transition-all hover:bg-neutral-300" data-level="高级助教">
高级助教
</button>
</div>
<!-- 技术人员列表 -->
<div id="technician-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- 技术人员卡片将通过JavaScript动态生成 -->
</div>
<!-- 无数据状态 -->
<div id="empty-state" class="hidden flex-col items-center justify-center py-16 text-center">
<div class="w-24 h-24 bg-neutral-100 rounded-full flex items-center justify-center mb-4">
<i class="fa fa-users text-neutral-300 text-4xl"></i>
</div>
<h3 class="text-xl font-medium text-neutral-500 mb-2">暂无符合条件的技术人员</h3>
<p class="text-neutral-400 max-w-md">请尝试调整筛选条件或刷新页面</p>
</div>
</div>
</div>
<footer class="bg-neutral-700 text-white py-8">
<div class="container mx-auto px-4 max-w-6xl">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="mb-4 md:mb-0">
<h2 class="text-xl font-bold mb-2">技术人员管理系统</h2>
<p class="text-neutral-300 text-sm">实时监控技术人员状态,高效分配工作任务</p>
</div>
<div class="flex space-x-4">
<a href="#" class="text-neutral-300 hover:text-white transition-colors">
<i class="fa fa-question-circle mr-1"></i>帮助中心
</a>
<a href="#" class="text-neutral-300 hover:text-white transition-colors">
<i class="fa fa-cog mr-1"></i>系统设置
</a>
<a href="#" class="text-neutral-300 hover:text-white transition-colors">
<i class="fa fa-user-circle mr-1"></i>联系我们
</a>
</div>
</div>
<div class="border-t border-neutral-600 mt-6 pt-6 text-center text-neutral-400 text-sm">
© 2025 技术人员管理系统. 保留所有权利.
</div>
</div>
</footer>
<script>
// 模拟接口数据
const technicianData = [
{
"id": 555,
"technicianname": "助教测试员1",
"pricetype": 2,
"price": 20.00,
"overminute": 10,
"hourreward": 25,
"isdeleted": 0,
"adduserid": 1,
"addtime": "2025-04-03T01:06:44.000+00:00",
"edituserid": 1,
"edittime": "2025-04-28T02:47:09.000+00:00",
"donglecode": "1130511145",
"regrettime": 5,
"timingunit": 1,
"totaltimeover": 1,
"startprice": 0.00,
"techweixinid": null,
"techtel": "13501012222",
"techorder": 1,
"overworkprice": 10.00,
"overworkhourreward": 0.00,
"techlevelname": "中级助教",
"techteamname": "TOP团队",
"smallticketno": "167434",
"technicianid": 555,
"starttime": "2025-04-28 10:35",
"endtime": null,
"trainingmoney": 0.00,
"oneprice": null,
"timelong": 0,
"rewardmoney": 0,
"techplaytype": 0,
"totalminutes": 0,
"techpausestarttime": null,
"techpauseseconds": 0,
"techfreeminutes": 0
},
{
"id": 556,
"technicianname": "助教测试员2",
"pricetype": 1,
"price": 25.00,
"overminute": 15,
"hourreward": 30,
"isdeleted": 0,
"adduserid": 1,
"addtime": "2025-04-05T12:30:22.000+00:00",
"edituserid": 1,
"edittime": "2025-04-29T09:15:33.000+00:00",
"donglecode": "1130511146",
"regrettime": 8,
"timingunit": 1,
"totaltimeover": 0,
"startprice": 5.00,
"techweixinid": "wxid_123456",
"techtel": "13501012223",
"techorder": 2,
"overworkprice": 15.00,
"overworkhourreward": 5.00,
"techlevelname": "高级助教",
"techteamname": "精英团队",
"smallticketno": "167435",
"technicianid": 556,
"starttime": "2025-04-29 08:45",
"endtime": "2025-04-29 12:45",
"trainingmoney": 0.00,
"oneprice": null,
"timelong": 4,
"rewardmoney": 120,
"techplaytype": 1,
"totalminutes": 240,
"techpausestarttime": null,
"techpauseseconds": 0,
"techfreeminutes": 15
},
{
"id": 557,
"technicianname": "助教测试员3",
"pricetype": 2,
"price": 18.00,
"overminute": 8,
"hourreward": 20,
"isdeleted": 0,
"adduserid": 1,
"addtime": "2025-04-10T08:15:45.000+00:00",
"edituserid": 1,
"edittime": "2025-04-28T14:20:15.000+00:00",
"donglecode": "1130511147",
"regrettime": 3,
"timingunit": 2,
"totaltimeover": 1,
"startprice": 0.00,
"techweixinid": null,
"techtel": "13501012224",
"techorder": 3,
"overworkprice": 8.00,
"overworkhourreward": 0.00,
"techlevelname": "初级助教",
"techteamname": "TOP团队",
"smallticketno": "167436",
"technicianid": 557,
"starttime": "2025-04-28 14:30",
"endtime": null,
"trainingmoney": 0.00,
"oneprice": null,
"timelong": 0,
"rewardmoney": 0,
"techplaytype": 0,
"totalminutes": 0,
"techpausestarttime": null,
"techpauseseconds": 0,
"techfreeminutes": 0
},
{
"id": 558,
"technicianname": "助教测试员4",
"pricetype": 1,
"price": 30.00,
"overminute": 20,
"hourreward": 35,
"isdeleted": 0,
"adduserid": 1,
"addtime": "2025-04-15T16:40:30.000+00:00",
"edituserid": 1,
"edittime": "2025-04-29T11:10:25.000+00:00",
"donglecode": "1130511148",
"regrettime": 10,
"timingunit": 1,
"totaltimeover": 1,
"startprice": 8.00,
"techweixinid": "wxid_789012",
"techtel": "13501012225",
"techorder": 4,
"overworkprice": 20.00,
"overworkhourreward": 10.00,
"techlevelname": "高级助教",
"techteamname": "精英团队",
"smallticketno": "167437",
"technicianid": 558,
"starttime": "2025-04-29 10:00",
"endtime": "2025-04-29 13:30",
"trainingmoney": 0.00,
"oneprice": null,
"timelong": 3.5,
"rewardmoney": 122.5,
"techplaytype": 1,
"totalminutes": 210,
"techpausestarttime": null,
"techpauseseconds": 0,
"techfreeminutes": 20
},
{
"id": 559,
"technicianname": "助教测试员5",
"pricetype": 2,
"price": 22.00,
"overminute": 12,
"hourreward": 28,
"isdeleted": 0,
"adduserid": 1,
"addtime": "2025-04-20T09:25:18.000+00:00",
"edituserid": 1,
"edittime": "2025-04-29T14:50:40.000+00:00",
"donglecode": "1130511149",
"regrettime": 6,
"timingunit": 1,
"totaltimeover": 0,
"startprice": 0.00,
"techweixinid": null,
"techtel": "13501012226",
"techorder": 5,
"overworkprice": 12.00,
"overworkhourreward": 0.00,
"techlevelname": "中级助教",
"techteamname": "TOP团队",
"smallticketno": "167438",
"technicianid": 559,
"starttime": "2025-04-29 15:00",
"endtime": null,
"trainingmoney": 0.00,
"oneprice": null,
"timelong": 0,
"rewardmoney": 0,
"techplaytype": 0,
"totalminutes": 0,
"techpausestarttime": null,
"techpauseseconds": 0,
"techfreeminutes": 0
}
];
// 当前筛选状态
let currentFilters = {
status: 'all', // all, busy, free
level: 'all' // all, 初级助教, 中级助教, 高级助教
};
// 初始化页面
document.addEventListener('DOMContentLoaded', () => {
// 渲染技术人员列表
renderTechnicianList(technicianData);
// 状态选项卡点击事件
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
// 移除所有选项卡的活跃状态
document.querySelectorAll('.tab-btn').forEach(tab => {
tab.classList.remove('tab-active');
});
// 添加当前选项卡的活跃状态
btn.classList.add('tab-active');
// 更新筛选状态
if (btn.id === 'tab-all') {
currentFilters.status = 'all';
} else if (btn.id === 'tab-busy') {
currentFilters.status = 'busy';
} else if (btn.id === 'tab-free') {
currentFilters.status = 'free';
}
// 重新渲染列表
filterAndRenderTechnicians();
});
});
// 级别筛选按钮点击事件
document.querySelectorAll('.level-btn').forEach(btn => {
btn.addEventListener('click', () => {
// 移除所有级别按钮的活跃状态
document.querySelectorAll('.level-btn').forEach(levelBtn => {
levelBtn.classList.remove('bg-primary', 'text-white');
levelBtn.classList.add('bg-neutral-200', 'text-neutral-700');
});
// 添加当前级别按钮的活跃状态
btn.classList.remove('bg-neutral-200', 'text-neutral-700');
btn.classList.add('bg-primary', 'text-white');
// 更新筛选状态
currentFilters.level = btn.dataset.level;
// 重新渲染列表
filterAndRenderTechnicians();
});
});
// 搜索输入事件
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', debounce(function() {
const searchTerm = this.value.toLowerCase().trim();
filterAndRenderTechnicians(searchTerm);
}, 300));
// 刷新按钮点击事件
document.getElementById('refresh-btn').addEventListener('click', function() {
// 添加旋转动画
this.querySelector('i').classList.add('fa-spin');
// 模拟加载
setTimeout(() => {
// 恢复图标
this.querySelector('i').classList.remove('fa-spin');
// 刷新列表
filterAndRenderTechnicians();
// 显示刷新成功提示
showToast('数据已刷新');
}, 800);
});
});
// 防抖函数
function debounce(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
// 筛选并渲染技术人员列表
function filterAndRenderTechnicians(searchTerm = '') {
let filteredTechnicians = technicianData;
// 应用状态筛选
if (currentFilters.status === 'busy') {
filteredTechnicians = filteredTechnicians.filter(tech => tech.endtime === null);
} else if (currentFilters.status === 'free') {
filteredTechnicians = filteredTechnicians.filter(tech => tech.endtime !== null);
}
// 应用级别筛选
if (currentFilters.level !== 'all') {
filteredTechnicians = filteredTechnicians.filter(tech => tech.techlevelname === currentFilters.level);
}
// 应用搜索筛选
if (searchTerm) {
filteredTechnicians = filteredTechnicians.filter(tech =>
tech.technicianname.toLowerCase().includes(searchTerm) ||
tech.techlevelname.toLowerCase().includes(searchTerm) ||
tech.techteamname.toLowerCase().includes(searchTerm) ||
(tech.techtel && tech.techtel.includes(searchTerm))
);
}
// 渲染筛选后的列表
renderTechnicianList(filteredTechnicians);
}
// 渲染技术人员列表
function renderTechnicianList(technicians) {
const listElement = document.getElementById('technician-list');
const emptyState = document.getElementById('empty-state');
// 清空列表
listElement.innerHTML = '';
// 检查是否有数据
if (technicians.length === 0) {
emptyState.classList.remove('hidden');
return;
}
// 隐藏空状态
emptyState.classList.add('hidden');
// 渲染每个技术人员卡片
technicians.forEach(tech => {
const card = createTechnicianCard(tech);
listElement.appendChild(card);
});
}
// 创建技术人员卡片
function createTechnicianCard(tech) {
const isBusy = tech.endtime === null;
const statusText = isBusy ? '在忙' : '空闲';
const statusColor = isBusy ? 'bg-warning text-white' : 'bg-secondary text-white';
const statusIcon = isBusy ? 'fa-circle text-warning' : 'fa-circle text-secondary';
// 创建卡片元素
const card = document.createElement('div');
card.className = `bg-white rounded-xl shadow-sm overflow-hidden card-hover fade-in`;
card.innerHTML = `
<div class="p-6">
<div class="flex justify-between items-start mb-4">
<div>
<h3 class="text-lg font-semibold text-neutral-700">${tech.technicianname}</h3>
<div class="flex items-center mt-1">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getLevelColor(tech.techlevelname)}">
${tech.techlevelname}
</span>
<span class="ml-2 text-xs text-neutral-500">${tech.techteamname}</span>
</div>
</div>
<div class="flex items-center">
<i class="fa ${statusIcon} mr-1"></i>
<span class="text-sm font-medium ${isBusy ? 'text-warning' : 'text-secondary'}">${statusText}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
<div class="flex flex-col">
<span class="text-neutral-500">工号</span>
<span class="font-medium">${tech.smallticketno}</span>
</div>
<div class="flex flex-col">
<span class="text-neutral-500">电话</span>
<span class="font-medium">${tech.techtel}</span>
</div>
<div class="flex flex-col">
<span class="text-neutral-500">服务价格</span>
<span class="font-medium">¥${tech.price}/小时</span>
</div>
<div class="flex flex-col">
<span class="text-neutral-500">开始时间</span>
<span class="font-medium">${formatDateTime(tech.starttime)}</span>
</div>
</div>
${isBusy ?
`<div class="p-3 bg-neutral-50 rounded-lg mb-4">
<div class="flex justify-between items-center">
<span class="text-sm text-neutral-500">当前服务时长</span>
<span class="text-sm font-medium">${calculateServiceDuration(tech.starttime)} 小时</span>
</div>
</div>` :
`<div class="p-3 bg-neutral-50 rounded-lg mb-4">
<div class="flex justify-between items-center">
<span class="text-sm text-neutral-500">最近服务时长</span>
<span class="text-sm font-medium">${tech.timelong} 小时</span>
</div>
<div class="flex justify-between items-center mt-2">
<span class="text-sm text-neutral-500">结束时间</span>
<span class="text-sm font-medium">${formatDateTime(tech.endtime)}</span>
</div>
</div>`
}
<div class="flex space-x-2">
<button class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 rounded-lg transition-all flex items-center justify-center">
<i class="fa fa-phone mr-2"></i>联系
</button>
<button class="w-10 h-10 flex items-center justify-center border border-neutral-200 rounded-lg hover:bg-neutral-50 transition-all">
<i class="fa fa-ellipsis-v text-neutral-500"></i>
</button>
</div>
</div>
`;
return card;
}
// 获取级别对应的颜色
function getLevelColor(level) {
switch(level) {
case '初级助教':
return 'bg-blue-100 text-blue-800';
case '中级助教':
return 'bg-green-100 text-green-800';
case '高级助教':
return 'bg-purple-100 text-purple-800';
default:
return 'bg-neutral-100 text-neutral-800';
}
}
// 格式化日期时间
function formatDateTime(dateTime) {
if (!dateTime) return 'N/A';
// 处理ISO格式和非ISO格式
let date;
if (dateTime.includes('T')) {
date = new Date(dateTime);
} else {
// 尝试解析非ISO格式
const parts = dateTime.split(/[- :]/);
date = new Date(parts[0], parts[1]-1, parts[2], parts[3], parts[4]);
}
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
// 计算服务时长
function calculateServiceDuration(startTime) {
if (!startTime) return '0';
const start = new Date(startTime.includes('T') ? startTime : startTime.replace(' ', 'T'));
const now = new Date();
const diffMs = now - start;
const diffHours = Math.round(diffMs / (1000 * 60 * 60) * 10) / 10; // 保留一位小数
return Math.max(0, diffHours).toFixed(1);
}
// 显示提示消息
function showToast(message) {
// 创建提示元素
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-neutral-700 text-white px-4 py-2 rounded-lg shadow-lg z-50 transform transition-all duration-300 opacity-0 translate-y-4';
toast.textContent = message;
// 添加到页面
document.body.appendChild(toast);
// 显示动画
setTimeout(() => {
toast.classList.remove('opacity-0', 'translate-y-4');
}, 10);
// 自动隐藏
setTimeout(() => {
toast.classList.add('opacity-0', 'translate-y-4');
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
}
</script>
</body>
</html>
js分类筛查
最新推荐文章于 2025-08-07 13:24:47 发布