|
|
@@ -0,0 +1,385 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
|
+ <style>
|
|
|
+ body {
|
|
|
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
|
+ padding: 20px;
|
|
|
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
|
+ min-height: 100vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ #app {
|
|
|
+ max-width: 1200px;
|
|
|
+ margin: 0 auto;
|
|
|
+ background-color: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
|
+ padding: 25px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ text-align: center;
|
|
|
+ color: #2c3e50;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .description {
|
|
|
+ text-align: center;
|
|
|
+ color: #7f8c8d;
|
|
|
+ margin-bottom: 25px;
|
|
|
+ font-size: 0.95em;
|
|
|
+ }
|
|
|
+
|
|
|
+ .controls {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 15px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ button {
|
|
|
+ padding: 10px 20px;
|
|
|
+ background-color: #4CAF50;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-weight: 600;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ button:hover {
|
|
|
+ background-color: #3d8b40;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ }
|
|
|
+
|
|
|
+ button:active {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ button.pause {
|
|
|
+ background-color: #f39c12;
|
|
|
+ }
|
|
|
+
|
|
|
+ button.pause:hover {
|
|
|
+ background-color: #d68910;
|
|
|
+ }
|
|
|
+
|
|
|
+ button.reset {
|
|
|
+ background-color: #e74c3c;
|
|
|
+ }
|
|
|
+
|
|
|
+ button.reset:hover {
|
|
|
+ background-color: #c0392b;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-container {
|
|
|
+ max-height: 600px;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ }
|
|
|
+
|
|
|
+ .scroll-message {
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ background-color: #4CAF50;
|
|
|
+ color: white;
|
|
|
+ padding: 5px 10px;
|
|
|
+ border-radius: 20px;
|
|
|
+ font-size: 0.8em;
|
|
|
+ z-index: 10;
|
|
|
+ animation: pulse 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes pulse {
|
|
|
+ 0% { opacity: 1; }
|
|
|
+ 50% { opacity: 0.7; }
|
|
|
+ 100% { opacity: 1; }
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-wrapper {
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .scrolling-table {
|
|
|
+ animation: scrollUp 50s linear infinite;
|
|
|
+ animation-play-state: running;
|
|
|
+ }
|
|
|
+
|
|
|
+ .scrolling-table.paused {
|
|
|
+ animation-play-state: paused;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes scrollUp {
|
|
|
+ 0% {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: translateY(calc(-100% + 600px));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ table {
|
|
|
+ border-collapse: collapse;
|
|
|
+ width: 100%;
|
|
|
+ font-size: 0.95em;
|
|
|
+ }
|
|
|
+
|
|
|
+ th, td {
|
|
|
+ border: 1px solid #e0e0e0;
|
|
|
+ padding: 12px 15px;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+
|
|
|
+ th {
|
|
|
+ background: #4CAF50;
|
|
|
+ color: white;
|
|
|
+ font-weight: 600;
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dept {
|
|
|
+ background: #f0f8ff;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #2c3e50;
|
|
|
+ }
|
|
|
+
|
|
|
+ tr:nth-child(even) {
|
|
|
+ background-color: #f9f9f9;
|
|
|
+ }
|
|
|
+
|
|
|
+ tr:hover {
|
|
|
+ background-color: #f1f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .footer {
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 20px;
|
|
|
+ color: #7f8c8d;
|
|
|
+ font-size: 0.9em;
|
|
|
+ }
|
|
|
+
|
|
|
+ .counter {
|
|
|
+ display: inline-block;
|
|
|
+ background-color: #ecf0f1;
|
|
|
+ padding: 5px 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #2c3e50;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+<div id="app">
|
|
|
+ <h1>部门员工信息表</h1>
|
|
|
+ <p class="description">当数据超过15条时,表格会自动向上滚动。使用下方按钮控制滚动行为。</p>
|
|
|
+
|
|
|
+ <div class="controls">
|
|
|
+ <button @click="toggleScroll" :class="{pause: isPaused}">
|
|
|
+ {{ isPaused ? '继续滚动' : '暂停滚动' }}
|
|
|
+ </button>
|
|
|
+ <button @click="resetScroll" class="reset">重置到顶部</button>
|
|
|
+ <button @click="toggleSpeed">{{ speedLabel }}</button>
|
|
|
+ <div class="counter">当前数据: {{ data.length }} 条</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="table-container">
|
|
|
+ <div v-if="shouldScroll" class="scroll-message">自动滚动中</div>
|
|
|
+ <div class="table-wrapper">
|
|
|
+ <table :class="['scrolling-table', {paused: isPaused}]" ref="table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>部门编号</th>
|
|
|
+ <th>部门</th>
|
|
|
+ <th>姓名</th>
|
|
|
+ <th>职位</th>
|
|
|
+ <th>薪资</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <template v-for="dept in data" :key="dept.id">
|
|
|
+ <tr>
|
|
|
+ <td class="dept" rowspan="2">{{dept.id}}</td>
|
|
|
+ <td class="dept" rowspan="2">{{dept.name}}</td>
|
|
|
+ <td>{{dept.emp1.name}}</td>
|
|
|
+ <td>{{dept.emp1.position}}</td>
|
|
|
+ <td>¥{{dept.emp1.salary.toLocaleString()}}</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>{{dept.emp2.name}}</td>
|
|
|
+ <td>{{dept.emp2.position}}</td>
|
|
|
+ <td>¥{{dept.emp2.salary.toLocaleString()}}</td>
|
|
|
+ </tr>
|
|
|
+ </template>
|
|
|
+ <!-- 添加重复数据以实现无缝滚动效果 -->
|
|
|
+ <template v-if="shouldScroll" v-for="dept in data" :key="`repeat-${dept.id}`">
|
|
|
+ <tr>
|
|
|
+ <td class="dept" rowspan="2">{{dept.id}}</td>
|
|
|
+ <td class="dept" rowspan="2">{{dept.name}}</td>
|
|
|
+ <td>{{dept.emp1.name}}</td>
|
|
|
+ <td>{{dept.emp1.position}}</td>
|
|
|
+ <td>¥{{dept.emp1.salary.toLocaleString()}}</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>{{dept.emp2.name}}</td>
|
|
|
+ <td>{{dept.emp2.position}}</td>
|
|
|
+ <td>¥{{dept.emp2.salary.toLocaleString()}}</td>
|
|
|
+ </tr>
|
|
|
+ </template>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="footer">
|
|
|
+ <p>每行显示一个部门的两名员工信息 | 数据条数超过15条时自动滚动</p>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<script>
|
|
|
+ Vue.createApp({
|
|
|
+ setup() {
|
|
|
+ // 生成更多数据(超过15条)
|
|
|
+ const generateData = () => {
|
|
|
+ const departments = [
|
|
|
+ {id:'D001', name:'技术部'},
|
|
|
+ {id:'D002', name:'市场部'},
|
|
|
+ {id:'D003', name:'人力资源部'},
|
|
|
+ {id:'D004', name:'财务部'},
|
|
|
+ {id:'D005', name:'销售部'},
|
|
|
+ {id:'D006', name:'产品部'},
|
|
|
+ {id:'D007', name:'设计部'},
|
|
|
+ {id:'D008', name:'客服部'},
|
|
|
+ {id:'D009', name:'研发部'},
|
|
|
+ {id:'D010', name:'运营部'},
|
|
|
+ {id:'D011', name:'质量部'},
|
|
|
+ {id:'D012', name:'采购部'},
|
|
|
+ {id:'D013', name:'行政部'},
|
|
|
+ {id:'D014', name:'法务部'},
|
|
|
+ {id:'D015', name:'公关部'},
|
|
|
+ {id:'D016', name:'物流部'},
|
|
|
+ {id:'D017', name:'工程部'},
|
|
|
+ {id:'D018', name:'信息部'},
|
|
|
+ {id:'D019', name:'培训部'},
|
|
|
+ {id:'D020', name:'战略部'}
|
|
|
+ ];
|
|
|
+
|
|
|
+ const positions = [
|
|
|
+ {title:'工程师', salaryRange:[8000, 15000]},
|
|
|
+ {title:'高级工程师', salaryRange:[12000, 25000]},
|
|
|
+ {title:'经理', salaryRange:[15000, 30000]},
|
|
|
+ {title:'专员', salaryRange:[6000, 10000]},
|
|
|
+ {title:'主管', salaryRange:[10000, 18000]},
|
|
|
+ {title:'总监', salaryRange:[20000, 40000]},
|
|
|
+ {title:'助理', salaryRange:[5000, 8000]},
|
|
|
+ {title:'分析师', salaryRange:[9000, 16000]}
|
|
|
+ ];
|
|
|
+
|
|
|
+ const firstNames = ['张', '李', '王', '赵', '刘', '陈', '杨', '黄', '周', '吴', '郑', '孙', '马', '朱', '胡', '林', '郭', '何', '高', '罗'];
|
|
|
+ const lastNames = ['伟', '芳', '娜', '敏', '静', '磊', '洋', '艳', '勇', '军', '霞', '强', '秀英', '文', '明', '华', '建国', '桂英', '亮', '玲'];
|
|
|
+
|
|
|
+ const getRandomName = () => {
|
|
|
+ const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
|
|
|
+ const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
|
|
|
+ return firstName + lastName;
|
|
|
+ };
|
|
|
+
|
|
|
+ const getRandomPosition = () => {
|
|
|
+ return positions[Math.floor(Math.random() * positions.length)];
|
|
|
+ };
|
|
|
+
|
|
|
+ return departments.map(dept => {
|
|
|
+ const pos1 = getRandomPosition();
|
|
|
+ const pos2 = getRandomPosition();
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: dept.id,
|
|
|
+ name: dept.name,
|
|
|
+ emp1: {
|
|
|
+ name: getRandomName(),
|
|
|
+ position: pos1.title,
|
|
|
+ salary: Math.floor(Math.random() * (pos1.salaryRange[1] - pos1.salaryRange[0])) + pos1.salaryRange[0]
|
|
|
+ },
|
|
|
+ emp2: {
|
|
|
+ name: getRandomName(),
|
|
|
+ position: pos2.title,
|
|
|
+ salary: Math.floor(Math.random() * (pos2.salaryRange[1] - pos2.salaryRange[0])) + pos2.salaryRange[0]
|
|
|
+ }
|
|
|
+ };
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const data = generateData();
|
|
|
+ const shouldScroll = Vue.ref(data.length > 15);
|
|
|
+ const isPaused = Vue.ref(false);
|
|
|
+ const scrollSpeed = Vue.ref(50); // 动画时长,值越大滚动越慢
|
|
|
+
|
|
|
+ const speedLabel = Vue.computed(() => {
|
|
|
+ if (scrollSpeed.value === 30) return '快速滚动';
|
|
|
+ if (scrollSpeed.value === 70) return '慢速滚动';
|
|
|
+ return '中速滚动';
|
|
|
+ });
|
|
|
+
|
|
|
+ const toggleScroll = () => {
|
|
|
+ isPaused.value = !isPaused.value;
|
|
|
+ };
|
|
|
+
|
|
|
+ const resetScroll = () => {
|
|
|
+ const table = document.querySelector('.scrolling-table');
|
|
|
+ if (table) {
|
|
|
+ table.style.animation = 'none';
|
|
|
+ setTimeout(() => {
|
|
|
+ table.style.animation = `scrollUp ${scrollSpeed.value}s linear infinite`;
|
|
|
+ table.style.animationPlayState = isPaused.value ? 'paused' : 'running';
|
|
|
+ }, 10);
|
|
|
+ }
|
|
|
+ isPaused.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const toggleSpeed = () => {
|
|
|
+ if (scrollSpeed.value === 50) {
|
|
|
+ scrollSpeed.value = 30; // 快速
|
|
|
+ } else if (scrollSpeed.value === 30) {
|
|
|
+ scrollSpeed.value = 70; // 慢速
|
|
|
+ } else {
|
|
|
+ scrollSpeed.value = 50; // 中速
|
|
|
+ }
|
|
|
+
|
|
|
+ // 应用新的速度
|
|
|
+ const table = document.querySelector('.scrolling-table');
|
|
|
+ if (table) {
|
|
|
+ table.style.animationDuration = `${scrollSpeed.value}s`;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 动态更新动画时长
|
|
|
+ Vue.onMounted(() => {
|
|
|
+ const table = document.querySelector('.scrolling-table');
|
|
|
+ if (table && shouldScroll.value) {
|
|
|
+ table.style.animationDuration = `${scrollSpeed.value}s`;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return {
|
|
|
+ data,
|
|
|
+ shouldScroll,
|
|
|
+ isPaused,
|
|
|
+ speedLabel,
|
|
|
+ toggleScroll,
|
|
|
+ resetScroll,
|
|
|
+ toggleSpeed
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }).mount('#app');
|
|
|
+</script>
|
|
|
+</body>
|
|
|
+</html>
|