fix: vue2语法学习
This commit is contained in:
parent
d68787a929
commit
c51f389a14
|
@ -21,3 +21,8 @@ pnpm-debug.log*
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
**/cert/
|
||||
|
|
37
src/App.vue
37
src/App.vue
|
@ -1,13 +1,32 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<div id="app" :class="{ 'theme-dark': isDarkTheme }">
|
||||
<nav>
|
||||
<router-link to="/">Home</router-link> |
|
||||
<router-link to="/about">About</router-link>
|
||||
<router-link to="/">首页</router-link> |
|
||||
<router-link to="/vuex-demo">Vuex演示</router-link> |
|
||||
<router-link to="/auto-sync">汽车仪表盘</router-link> |
|
||||
<router-link to="/css-basics">CSS基础</router-link> |
|
||||
<router-link to="/css-layout">CSS布局</router-link> |
|
||||
<router-link to="/css-animation">CSS动画</router-link> |
|
||||
<router-link to="/about">关于</router-link>
|
||||
</nav>
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
computed: {
|
||||
...mapGetters('styles', ['theme']),
|
||||
isDarkTheme() {
|
||||
return this.theme === 'dark';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
|
@ -15,6 +34,13 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
min-height: 100vh;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
background-color: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
nav {
|
||||
|
@ -24,6 +50,11 @@ nav {
|
|||
nav a {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.theme-dark nav a {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
|
|
|
@ -0,0 +1,742 @@
|
|||
<template>
|
||||
<div class="autosync-hub" :class="{ 'theme-dark': isDarkMode }">
|
||||
<div class="hub-header">
|
||||
<h1>AutoSync Hub</h1>
|
||||
</div>
|
||||
|
||||
<div class="hub-grid">
|
||||
<!-- 轮胎监测 -->
|
||||
<div class="hub-card">
|
||||
<div class="tire-monitor">
|
||||
<div class="car-icon">
|
||||
<i class="icon-car"></i>
|
||||
</div>
|
||||
<div class="tire-grid">
|
||||
<div class="tire-item">
|
||||
<div class="tire-value">{{ tireFL }} psi</div>
|
||||
<div class="tire-label">FL</div>
|
||||
</div>
|
||||
<div class="tire-item">
|
||||
<div class="tire-value">{{ tireFR }} psi</div>
|
||||
<div class="tire-label">FR</div>
|
||||
</div>
|
||||
<div class="tire-item">
|
||||
<div class="tire-value">{{ tireRL }} psi</div>
|
||||
<div class="tire-label">RL</div>
|
||||
</div>
|
||||
<div class="tire-item">
|
||||
<div class="tire-value">{{ tireRR }} psi</div>
|
||||
<div class="tire-label">RR</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-title">Tire Monitor</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安全状态 -->
|
||||
<div class="hub-card">
|
||||
<div class="safety-status">
|
||||
<div class="status-row">
|
||||
<div class="status-item" :class="{ active: isDriverActive }">
|
||||
<div class="status-icon driver-icon"></div>
|
||||
<div class="status-label">Driver</div>
|
||||
</div>
|
||||
<div class="status-item" :class="{ active: isPassengerActive }">
|
||||
<div class="status-icon passenger-icon"></div>
|
||||
<div class="status-label">Passenger</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-row">
|
||||
<div class="status-item" :class="{ locked: isLeftLocked }">
|
||||
<div class="status-icon lock-icon"></div>
|
||||
<div class="status-label">Left</div>
|
||||
</div>
|
||||
<div class="status-item" :class="{ locked: isRightLocked }">
|
||||
<div class="status-icon lock-icon"></div>
|
||||
<div class="status-label">Right</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-title">Safety Status</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 智能驾驶 -->
|
||||
<div class="hub-card">
|
||||
<div class="smart-drive">
|
||||
<div class="smart-drive-display">
|
||||
<div class="active-badge">ACTIVE</div>
|
||||
<div class="smart-drive-visual"></div>
|
||||
</div>
|
||||
<div class="card-title">SmartDrive</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日程安排 -->
|
||||
<div class="hub-card">
|
||||
<div class="schedule">
|
||||
<h3>SCHEDULE</h3>
|
||||
<div class="schedule-item">
|
||||
<div class="time-indicator orange"></div>
|
||||
<div class="schedule-details">
|
||||
<div class="schedule-time">9:00 AM - 10:00 AM</div>
|
||||
<div class="schedule-description">Client Meeting</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="schedule-item">
|
||||
<div class="time-indicator purple"></div>
|
||||
<div class="schedule-details">
|
||||
<div class="schedule-time">2:00 PM - 3:30 PM</div>
|
||||
<div class="schedule-description">Project Review</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more-events">3 more events...</div>
|
||||
<div class="card-title">Schedule</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 天气 -->
|
||||
<div class="hub-card">
|
||||
<div class="climate">
|
||||
<div class="location">
|
||||
<i class="icon-location"></i>
|
||||
<span>{{ weatherLocation }}</span>
|
||||
</div>
|
||||
<div class="temperature">
|
||||
<span class="temp-value">{{ temperature }}</span>
|
||||
<span class="temp-unit">°</span>
|
||||
</div>
|
||||
<div class="weather-condition">
|
||||
<i class="icon-weather"></i>
|
||||
<span>{{ weatherCondition }}</span>
|
||||
</div>
|
||||
<div class="temp-range">
|
||||
H: {{ highTemp }}° L: {{ lowTemp }}°
|
||||
</div>
|
||||
<div class="card-title">Climate</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 行程统计 -->
|
||||
<div class="hub-card">
|
||||
<div class="journey-stats">
|
||||
<h3>Journey Stats</h3>
|
||||
<div class="stat-row">
|
||||
<div class="stat-label">Distance</div>
|
||||
<div class="stat-value">{{ journeyDistance }} mi</div>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<div class="stat-label">Duration</div>
|
||||
<div class="stat-value">{{ journeyDuration }}</div>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<div class="stat-label">Speed</div>
|
||||
<div class="stat-value">{{ journeySpeed }} mph</div>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<div class="stat-label">Fuel Economy</div>
|
||||
<div class="stat-value">{{ journeyFuel }} mpg</div>
|
||||
</div>
|
||||
<div class="card-title">Journey Stats</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 电池电量 -->
|
||||
<div class="hub-card">
|
||||
<div class="power-cell">
|
||||
<div class="power-value">
|
||||
<span class="large-value">{{ powerValue }}</span>
|
||||
<span class="power-unit">mi</span>
|
||||
</div>
|
||||
<div class="power-label">PowerCell</div>
|
||||
<div class="power-progress">
|
||||
<div class="progress-bar" :style="{ width: powerPercentage + '%' }"></div>
|
||||
</div>
|
||||
<div class="power-details">
|
||||
<div class="power-detail">
|
||||
<div class="detail-label">Full Charge in</div>
|
||||
<div class="detail-value">{{ chargeTime }} min</div>
|
||||
</div>
|
||||
<div class="power-detail">
|
||||
<div class="detail-label">Rate</div>
|
||||
<div class="detail-value">{{ chargeRate }} kW</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-title">PowerCell</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航 -->
|
||||
<div class="hub-card">
|
||||
<div class="navigator">
|
||||
<div class="nav-info">
|
||||
<div class="nav-distance">{{ navDistance }} mi</div>
|
||||
<div class="nav-action">Turn Right</div>
|
||||
</div>
|
||||
<div class="nav-map"></div>
|
||||
<div class="card-title">Navigator</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 计时器 -->
|
||||
<div class="hub-card">
|
||||
<div class="timekeeper">
|
||||
<div class="clock-face">
|
||||
<div class="clock-hand" :style="{ transform: `rotate(${clockHandRotation}deg)` }"></div>
|
||||
</div>
|
||||
<div class="card-title">Timekeeper</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 音乐 -->
|
||||
<div class="hub-card">
|
||||
<div class="sound-wave">
|
||||
<div class="sound-visual"></div>
|
||||
<div class="sound-info">
|
||||
<div class="sound-title">Midnight City</div>
|
||||
<div class="sound-artist">M83</div>
|
||||
</div>
|
||||
<div class="card-title">SoundWave</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AutoSyncHub',
|
||||
data() {
|
||||
return {
|
||||
// 轮胎压力
|
||||
tireFL: 41,
|
||||
tireFR: 40,
|
||||
tireRL: 42,
|
||||
tireRR: 41,
|
||||
|
||||
// 安全状态
|
||||
isDriverActive: true,
|
||||
isPassengerActive: true,
|
||||
isLeftLocked: true,
|
||||
isRightLocked: true,
|
||||
|
||||
// 天气
|
||||
weatherLocation: 'Seattle',
|
||||
temperature: 68,
|
||||
weatherCondition: 'Light Rain',
|
||||
highTemp: 71,
|
||||
lowTemp: 54,
|
||||
|
||||
// 行程统计
|
||||
journeyDistance: 127,
|
||||
journeyDuration: '2 hr 15 min',
|
||||
journeySpeed: 58,
|
||||
journeyFuel: 42.1,
|
||||
|
||||
// 电池电量
|
||||
powerValue: 284,
|
||||
powerPercentage: 65,
|
||||
chargeTime: 28,
|
||||
chargeRate: 87,
|
||||
|
||||
// 导航
|
||||
navDistance: 18,
|
||||
|
||||
// 计时器
|
||||
clockHandRotation: 45,
|
||||
|
||||
// 主题
|
||||
isDarkMode: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleDarkMode() {
|
||||
this.isDarkMode = !this.isDarkMode;
|
||||
},
|
||||
|
||||
// 模拟数据更新
|
||||
updateData() {
|
||||
// 随机更新轮胎压力
|
||||
this.tireFL = Math.floor(39 + Math.random() * 4);
|
||||
this.tireFR = Math.floor(39 + Math.random() * 4);
|
||||
this.tireRL = Math.floor(39 + Math.random() * 4);
|
||||
this.tireRR = Math.floor(39 + Math.random() * 4);
|
||||
|
||||
// 更新时钟指针
|
||||
this.clockHandRotation = (this.clockHandRotation + 6) % 360;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 定时更新数据
|
||||
setInterval(this.updateData, 5000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.autosync-hub {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #1a2b45 0%, #0f172a 100%);
|
||||
color: #ffffff;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.hub-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.hub-header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.hub-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hub-card {
|
||||
background-color: rgba(30, 41, 59, 0.8);
|
||||
border-radius: 16px;
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
overflow: hidden;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.hub-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* 轮胎监测样式 */
|
||||
.tire-monitor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.car-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.icon-car:before {
|
||||
content: "🚗";
|
||||
}
|
||||
|
||||
.tire-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tire-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tire-value {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tire-label {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* 安全状态样式 */
|
||||
.safety-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.status-item.active, .status-item.locked {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #666;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.driver-icon {
|
||||
background-color: #4ade80;
|
||||
}
|
||||
|
||||
.passenger-icon {
|
||||
background-color: #facc15;
|
||||
}
|
||||
|
||||
.lock-icon {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* 智能驾驶样式 */
|
||||
.smart-drive {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.smart-drive-display {
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
background: url('https://images.unsplash.com/photo-1534796636912-3b95b3ab5986?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80') center/cover;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.active-badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background-color: rgba(79, 209, 197, 0.8);
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 日程安排样式 */
|
||||
.schedule {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.schedule h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.schedule-item {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.time-indicator {
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.orange {
|
||||
background-color: #f97316;
|
||||
}
|
||||
|
||||
.purple {
|
||||
background-color: #a855f7;
|
||||
}
|
||||
|
||||
.schedule-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.schedule-time {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.schedule-description {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.more-events {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 天气样式 */
|
||||
.climate {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.icon-location:before {
|
||||
content: "📍";
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.temperature {
|
||||
font-size: 3rem;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.weather-condition {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.icon-weather:before {
|
||||
content: "🌧️";
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.temp-range {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 行程统计样式 */
|
||||
.journey-stats {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.journey-stats h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.stat-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 电池电量样式 */
|
||||
.power-cell {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.power-value {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.large-value {
|
||||
font-size: 3rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.power-unit {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.power-label {
|
||||
font-size: 1rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.power-progress {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #3b82f6, #60a5fa);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.power-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.power-detail {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 导航样式 */
|
||||
.navigator {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
margin-bottom: 10px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.nav-distance {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.nav-action {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.nav-map {
|
||||
flex: 1;
|
||||
background: url('https://images.unsplash.com/photo-1553290322-e4e8ddd8e9ef?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80') center/cover;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* 计时器样式 */
|
||||
.timekeeper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.clock-face {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.clock-hand {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 45%;
|
||||
height: 2px;
|
||||
background-color: #f97316;
|
||||
transform-origin: left center;
|
||||
transition: transform 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* 音乐样式 */
|
||||
.sound-wave {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sound-visual {
|
||||
flex: 1;
|
||||
background: url('https://images.unsplash.com/photo-1614102073832-030967418971?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80') center/cover;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sound-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sound-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.sound-artist {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hub-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.hub-header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,252 @@
|
|||
<template>
|
||||
<div class="calculator">
|
||||
<h2>计算器组件</h2>
|
||||
<div class="calculator-display">
|
||||
<p>当前值: {{ displayValue }}</p>
|
||||
</div>
|
||||
<div class="calculator-inputs">
|
||||
<div class="input-group">
|
||||
<label>第一个数字:</label>
|
||||
<input type="number" v-model.number="firstNumber" />
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>第二个数字:</label>
|
||||
<input type="number" v-model.number="secondNumber" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-operations">
|
||||
<button @click="calculate('add')">加法 (+)</button>
|
||||
<button @click="calculate('subtract')">减法 (-)</button>
|
||||
<button @click="calculate('multiply')">乘法 (×)</button>
|
||||
<button @click="calculate('divide')">除法 (÷)</button>
|
||||
<button @click="calculate('power')">幂运算 (x^y)</button>
|
||||
<button @click="calculate('sqrt')">平方根 (√x)</button>
|
||||
<button @click="calculate('round')">四舍五入</button>
|
||||
<button @click="calculate('floor')">向下取整</button>
|
||||
<button @click="calculate('ceil')">向上取整</button>
|
||||
<button @click="calculate('max')">最大值</button>
|
||||
<button @click="calculate('min')">最小值</button>
|
||||
</div>
|
||||
<div class="calculator-result">
|
||||
<p>计算结果: <strong>{{ result }}</strong></p>
|
||||
<p v-if="lastOperation">上次操作: {{ lastOperation }}</p>
|
||||
</div>
|
||||
<div class="calculator-history">
|
||||
<h3>计算历史 (从父组件传入)</h3>
|
||||
<ul>
|
||||
<li v-for="(item, index) in calculationHistory" :key="index">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button @click="resetCalculator" :disabled="initialValue === 0">重置计算器</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Calculator',
|
||||
props: {
|
||||
// 从父组件接收的初始值
|
||||
initialValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: function(value) {
|
||||
// 数字校验示例:初始值必须是非负数
|
||||
return value >= 0;
|
||||
}
|
||||
},
|
||||
// 从父组件接收的计算历史
|
||||
calculationHistory: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 精度设置
|
||||
precision: {
|
||||
type: Number,
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
firstNumber: 0,
|
||||
secondNumber: 0,
|
||||
result: 0,
|
||||
displayValue: 0,
|
||||
lastOperation: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化显示值和结果
|
||||
this.displayValue = this.initialValue;
|
||||
this.result = this.initialValue;
|
||||
},
|
||||
methods: {
|
||||
calculate(operation) {
|
||||
let calculationResult = 0;
|
||||
let operationDescription = '';
|
||||
|
||||
switch(operation) {
|
||||
case 'add':
|
||||
calculationResult = this.firstNumber + this.secondNumber;
|
||||
operationDescription = `${this.firstNumber} + ${this.secondNumber} = ${calculationResult}`;
|
||||
break;
|
||||
case 'subtract':
|
||||
calculationResult = this.firstNumber - this.secondNumber;
|
||||
operationDescription = `${this.firstNumber} - ${this.secondNumber} = ${calculationResult}`;
|
||||
break;
|
||||
case 'multiply':
|
||||
calculationResult = this.firstNumber * this.secondNumber;
|
||||
operationDescription = `${this.firstNumber} × ${this.secondNumber} = ${calculationResult}`;
|
||||
break;
|
||||
case 'divide':
|
||||
if (this.secondNumber === 0) {
|
||||
alert('除数不能为零!');
|
||||
return;
|
||||
}
|
||||
calculationResult = this.firstNumber / this.secondNumber;
|
||||
operationDescription = `${this.firstNumber} ÷ ${this.secondNumber} = ${calculationResult}`;
|
||||
break;
|
||||
case 'power':
|
||||
calculationResult = Math.pow(this.firstNumber, this.secondNumber);
|
||||
operationDescription = `${this.firstNumber}^${this.secondNumber} = ${calculationResult}`;
|
||||
break;
|
||||
case 'sqrt':
|
||||
if (this.firstNumber < 0) {
|
||||
alert('不能对负数开平方根!');
|
||||
return;
|
||||
}
|
||||
calculationResult = Math.sqrt(this.firstNumber);
|
||||
operationDescription = `√${this.firstNumber} = ${calculationResult}`;
|
||||
break;
|
||||
case 'round':
|
||||
calculationResult = Math.round(this.firstNumber);
|
||||
operationDescription = `四舍五入(${this.firstNumber}) = ${calculationResult}`;
|
||||
break;
|
||||
case 'floor':
|
||||
calculationResult = Math.floor(this.firstNumber);
|
||||
operationDescription = `向下取整(${this.firstNumber}) = ${calculationResult}`;
|
||||
break;
|
||||
case 'ceil':
|
||||
calculationResult = Math.ceil(this.firstNumber);
|
||||
operationDescription = `向上取整(${this.firstNumber}) = ${calculationResult}`;
|
||||
break;
|
||||
case 'max':
|
||||
calculationResult = Math.max(this.firstNumber, this.secondNumber);
|
||||
operationDescription = `最大值(${this.firstNumber}, ${this.secondNumber}) = ${calculationResult}`;
|
||||
break;
|
||||
case 'min':
|
||||
calculationResult = Math.min(this.firstNumber, this.secondNumber);
|
||||
operationDescription = `最小值(${this.firstNumber}, ${this.secondNumber}) = ${calculationResult}`;
|
||||
break;
|
||||
}
|
||||
|
||||
// 根据精度处理结果
|
||||
this.result = Number(calculationResult.toFixed(this.precision));
|
||||
this.displayValue = this.result;
|
||||
this.lastOperation = operationDescription;
|
||||
|
||||
// 向父组件发送计算结果
|
||||
this.$emit('calculation-completed', {
|
||||
result: this.result,
|
||||
operation: operationDescription
|
||||
});
|
||||
},
|
||||
resetCalculator() {
|
||||
this.firstNumber = 0;
|
||||
this.secondNumber = 0;
|
||||
this.result = this.initialValue;
|
||||
this.displayValue = this.initialValue;
|
||||
this.lastOperation = '';
|
||||
|
||||
// 通知父组件重置了计算器
|
||||
this.$emit('calculator-reset');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.calculator {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.calculator-display {
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.calculator-inputs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.calculator-operations {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 12px;
|
||||
background-color: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #3aa876;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.calculator-result {
|
||||
background-color: #e8f5e9;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.calculator-history {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border: 1px dashed #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.calculator-history ul {
|
||||
padding-left: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,955 @@
|
|||
<template>
|
||||
<div class="css-animation">
|
||||
<h1 class="page-title">CSS动画与过渡</h1>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>1. 过渡效果 (Transitions)</h2>
|
||||
|
||||
<div class="transition-description">
|
||||
<p>CSS过渡允许在指定时间内平滑地改变属性值。</p>
|
||||
</div>
|
||||
|
||||
<div class="transition-demos">
|
||||
<div class="transition-demo">
|
||||
<h3>基础过渡</h3>
|
||||
<div class="transition-box basic-transition">
|
||||
<p>鼠标悬停查看效果</p>
|
||||
</div>
|
||||
<p class="demo-caption">transition: all 0.3s ease;</p>
|
||||
</div>
|
||||
|
||||
<div class="transition-demo">
|
||||
<h3>多属性过渡</h3>
|
||||
<div class="transition-box multi-property-transition">
|
||||
<p>鼠标悬停查看效果</p>
|
||||
</div>
|
||||
<p class="demo-caption">transition: background-color 0.5s ease, transform 0.3s ease-in-out;</p>
|
||||
</div>
|
||||
|
||||
<div class="transition-demo">
|
||||
<h3>过渡时间函数</h3>
|
||||
<div class="transition-timing-functions">
|
||||
<div class="timing-box ease">ease</div>
|
||||
<div class="timing-box linear">linear</div>
|
||||
<div class="timing-box ease-in">ease-in</div>
|
||||
<div class="timing-box ease-out">ease-out</div>
|
||||
<div class="timing-box ease-in-out">ease-in-out</div>
|
||||
<div class="timing-box cubic-bezier">cubic-bezier</div>
|
||||
</div>
|
||||
<p class="demo-caption">鼠标悬停查看不同的时间函数效果</p>
|
||||
</div>
|
||||
|
||||
<div class="transition-demo">
|
||||
<h3>过渡延迟</h3>
|
||||
<div class="transition-delay-container">
|
||||
<div class="delay-box delay-0">无延迟</div>
|
||||
<div class="delay-box delay-1">延迟 0.2s</div>
|
||||
<div class="delay-box delay-2">延迟 0.4s</div>
|
||||
<div class="delay-box delay-3">延迟 0.6s</div>
|
||||
<div class="delay-box delay-4">延迟 0.8s</div>
|
||||
</div>
|
||||
<p class="demo-caption">鼠标悬停在容器上查看延迟效果</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>2. 动画 (Animations)</h2>
|
||||
|
||||
<div class="animation-description">
|
||||
<p>CSS动画使元素从一种样式逐渐变化为另一种样式,可以改变任意多的样式任意多的次数。</p>
|
||||
</div>
|
||||
|
||||
<div class="animation-demos">
|
||||
<div class="animation-demo">
|
||||
<h3>基础动画</h3>
|
||||
<div class="animation-box pulse-animation"></div>
|
||||
<p class="demo-caption">animation: pulse 2s infinite;</p>
|
||||
</div>
|
||||
|
||||
<div class="animation-demo">
|
||||
<h3>多步动画</h3>
|
||||
<div class="animation-box color-change-animation"></div>
|
||||
<p class="demo-caption">animation: colorChange 5s infinite;</p>
|
||||
</div>
|
||||
|
||||
<div class="animation-demo">
|
||||
<h3>动画方向</h3>
|
||||
<div class="animation-directions">
|
||||
<div class="direction-box normal">normal</div>
|
||||
<div class="direction-box reverse">reverse</div>
|
||||
<div class="direction-box alternate">alternate</div>
|
||||
<div class="direction-box alternate-reverse">alternate-reverse</div>
|
||||
</div>
|
||||
<p class="demo-caption">不同的animation-direction值</p>
|
||||
</div>
|
||||
|
||||
<div class="animation-demo">
|
||||
<h3>动画填充模式</h3>
|
||||
<div class="animation-fill-modes">
|
||||
<div class="fill-box none">none</div>
|
||||
<div class="fill-box forwards">forwards</div>
|
||||
<div class="fill-box backwards">backwards</div>
|
||||
<div class="fill-box both">both</div>
|
||||
</div>
|
||||
<p class="demo-caption">不同的animation-fill-mode值</p>
|
||||
</div>
|
||||
|
||||
<div class="animation-demo">
|
||||
<h3>动画播放状态</h3>
|
||||
<div class="play-state-container">
|
||||
<div class="play-state-box running">running (默认)</div>
|
||||
<div class="play-state-box paused">paused (鼠标悬停暂停)</div>
|
||||
</div>
|
||||
<p class="demo-caption">animation-play-state属性</p>
|
||||
</div>
|
||||
|
||||
<div class="animation-demo">
|
||||
<h3>动画时间函数</h3>
|
||||
<div class="animation-timing-functions">
|
||||
<div class="timing-animation ease">ease</div>
|
||||
<div class="timing-animation linear">linear</div>
|
||||
<div class="timing-animation ease-in">ease-in</div>
|
||||
<div class="timing-animation ease-out">ease-out</div>
|
||||
<div class="timing-animation ease-in-out">ease-in-out</div>
|
||||
<div class="timing-animation steps">steps</div>
|
||||
</div>
|
||||
<p class="demo-caption">不同的animation-timing-function值</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>3. 变换 (Transforms)</h2>
|
||||
|
||||
<div class="transform-description">
|
||||
<p>CSS变换允许你旋转、缩放、倾斜或平移元素。</p>
|
||||
</div>
|
||||
|
||||
<div class="transform-demos">
|
||||
<div class="transform-demo">
|
||||
<h3>2D变换</h3>
|
||||
<div class="transform-2d-container">
|
||||
<div class="transform-box original">原始元素</div>
|
||||
<div class="transform-box translate">translate(20px, 20px)</div>
|
||||
<div class="transform-box rotate">rotate(45deg)</div>
|
||||
<div class="transform-box scale">scale(1.5)</div>
|
||||
<div class="transform-box skew">skew(15deg, 10deg)</div>
|
||||
<div class="transform-box multiple">多重变换</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="transform-demo">
|
||||
<h3>3D变换</h3>
|
||||
<div class="transform-3d-container">
|
||||
<div class="transform-box-3d rotateX">rotateX(45deg)</div>
|
||||
<div class="transform-box-3d rotateY">rotateY(45deg)</div>
|
||||
<div class="transform-box-3d rotateZ">rotateZ(45deg)</div>
|
||||
<div class="transform-box-3d translate3d">translate3d(20px, 20px, 50px)</div>
|
||||
<div class="transform-box-3d perspective">perspective(200px)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="transform-demo">
|
||||
<h3>变换原点</h3>
|
||||
<div class="transform-origin-container">
|
||||
<div class="origin-box center">center (默认)</div>
|
||||
<div class="origin-box top-left">top left</div>
|
||||
<div class="origin-box bottom-right">bottom right</div>
|
||||
</div>
|
||||
<p class="demo-caption">鼠标悬停查看不同transform-origin的效果</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>4. 实用动画示例</h2>
|
||||
|
||||
<div class="practical-demos">
|
||||
<div class="practical-demo">
|
||||
<h3>加载动画</h3>
|
||||
<div class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-dots">
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
<div class="loading-bar">
|
||||
<div class="bar-progress"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="practical-demo">
|
||||
<h3>按钮动画</h3>
|
||||
<div class="button-container">
|
||||
<button class="animated-button pulse-button">脉冲效果</button>
|
||||
<button class="animated-button scale-button">缩放效果</button>
|
||||
<button class="animated-button shadow-button">阴影效果</button>
|
||||
<button class="animated-button border-button">边框效果</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="practical-demo">
|
||||
<h3>悬浮卡片</h3>
|
||||
<div class="hover-card-container">
|
||||
<div class="hover-card">
|
||||
<div class="card-image"></div>
|
||||
<div class="card-content">
|
||||
<h4>悬浮卡片标题</h4>
|
||||
<p>鼠标悬停在卡片上查看动画效果。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="practical-demo">
|
||||
<h3>视差滚动效果</h3>
|
||||
<div class="parallax-container">
|
||||
<div class="parallax-layer layer-1"></div>
|
||||
<div class="parallax-layer layer-2"></div>
|
||||
<div class="parallax-layer layer-3"></div>
|
||||
<div class="parallax-content">
|
||||
<h4>视差滚动</h4>
|
||||
<p>鼠标悬停在此区域移动查看视差效果。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CssAnimation'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.css-animation {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 40px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
color: #42b983;
|
||||
border-bottom: 2px solid #42b983;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.transition-description,
|
||||
.animation-description,
|
||||
.transform-description {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.demo-caption {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.transition-demos,
|
||||
.animation-demos,
|
||||
.transform-demos,
|
||||
.practical-demos {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.transition-demo h3,
|
||||
.animation-demo h3,
|
||||
.transform-demo h3,
|
||||
.practical-demo h3 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* 过渡效果演示 */
|
||||
.transition-box {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.basic-transition {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.basic-transition:hover {
|
||||
background-color: #f44336;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.multi-property-transition {
|
||||
transition: background-color 0.5s ease, transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.multi-property-transition:hover {
|
||||
background-color: #4caf50;
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
|
||||
/* 过渡时间函数演示 */
|
||||
.transition-timing-functions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.timing-box {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background-color: #673ab7;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ease {
|
||||
transition: transform 1s ease;
|
||||
}
|
||||
|
||||
.linear {
|
||||
transition: transform 1s linear;
|
||||
}
|
||||
|
||||
.ease-in {
|
||||
transition: transform 1s ease-in;
|
||||
}
|
||||
|
||||
.ease-out {
|
||||
transition: transform 1s ease-out;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
transition: transform 1s ease-in-out;
|
||||
}
|
||||
|
||||
.cubic-bezier {
|
||||
transition: transform 1s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
||||
}
|
||||
|
||||
.timing-box:hover {
|
||||
transform: translateX(100px);
|
||||
}
|
||||
|
||||
/* 过渡延迟演示 */
|
||||
.transition-delay-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.delay-box {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.delay-0 {
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.delay-1 {
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
|
||||
.delay-2 {
|
||||
transition-delay: 0.4s;
|
||||
}
|
||||
|
||||
.delay-3 {
|
||||
transition-delay: 0.6s;
|
||||
}
|
||||
|
||||
.delay-4 {
|
||||
transition-delay: 0.8s;
|
||||
}
|
||||
|
||||
.transition-delay-container:hover .delay-box {
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
/* 动画演示 */
|
||||
.animation-box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #ff9800;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 脉冲动画 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
background-color: #ff9800;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
background-color: #f44336;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
background-color: #ff9800;
|
||||
}
|
||||
}
|
||||
|
||||
.pulse-animation {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
/* 颜色变化动画 */
|
||||
@keyframes colorChange {
|
||||
0% { background-color: #f44336; }
|
||||
25% { background-color: #4caf50; }
|
||||
50% { background-color: #2196f3; }
|
||||
75% { background-color: #9c27b0; }
|
||||
100% { background-color: #f44336; }
|
||||
}
|
||||
|
||||
.color-change-animation {
|
||||
animation: colorChange 5s infinite;
|
||||
}
|
||||
|
||||
/* 动画方向演示 */
|
||||
.animation-directions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.direction-box {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: #9c27b0;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@keyframes slideRight {
|
||||
0% { transform: translateX(0); }
|
||||
50% { transform: translateX(100px); }
|
||||
100% { transform: translateX(0); }
|
||||
}
|
||||
|
||||
.normal {
|
||||
animation: slideRight 3s infinite normal;
|
||||
}
|
||||
|
||||
.reverse {
|
||||
animation: slideRight 3s infinite reverse;
|
||||
}
|
||||
|
||||
.alternate {
|
||||
animation: slideRight 3s infinite alternate;
|
||||
}
|
||||
|
||||
.alternate-reverse {
|
||||
animation: slideRight 3s infinite alternate-reverse;
|
||||
}
|
||||
|
||||
/* 动画填充模式演示 */
|
||||
.animation-fill-modes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.fill-box {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: #00bcd4;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@keyframes fillAnimation {
|
||||
0% { transform: translateY(0); background-color: #00bcd4; }
|
||||
100% { transform: translateY(20px); background-color: #ff5722; }
|
||||
}
|
||||
|
||||
.none {
|
||||
animation: fillAnimation 2s 1s none;
|
||||
}
|
||||
|
||||
.forwards {
|
||||
animation: fillAnimation 2s 1s forwards;
|
||||
}
|
||||
|
||||
.backwards {
|
||||
animation: fillAnimation 2s 1s backwards;
|
||||
}
|
||||
|
||||
.both {
|
||||
animation: fillAnimation 2s 1s both;
|
||||
}
|
||||
|
||||
/* 动画播放状态演示 */
|
||||
.play-state-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.play-state-box {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: #ff5722;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.paused:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
/* 动画时间函数演示 */
|
||||
.animation-timing-functions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.timing-animation {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background-color: #795548;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@keyframes moveAcross {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(200px); }
|
||||
}
|
||||
|
||||
.timing-animation.ease {
|
||||
animation: moveAcross 3s infinite alternate ease;
|
||||
}
|
||||
|
||||
.timing-animation.linear {
|
||||
animation: moveAcross 3s infinite alternate linear;
|
||||
}
|
||||
|
||||
.timing-animation.ease-in {
|
||||
animation: moveAcross 3s infinite alternate ease-in;
|
||||
}
|
||||
|
||||
.timing-animation.ease-out {
|
||||
animation: moveAcross 3s infinite alternate ease-out;
|
||||
}
|
||||
|
||||
.timing-animation.ease-in-out {
|
||||
animation: moveAcross 3s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
.timing-animation.steps {
|
||||
animation: moveAcross 3s infinite alternate steps(5, end);
|
||||
}
|
||||
|
||||
/* 变换演示 */
|
||||
.transform-2d-container, .transform-3d-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.transform-box, .transform-box-3d {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #e91e63;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.translate {
|
||||
transform: translate(20px, 20px);
|
||||
}
|
||||
|
||||
.rotate {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.scale {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.skew {
|
||||
transform: skew(15deg, 10deg);
|
||||
}
|
||||
|
||||
.multiple {
|
||||
transform: rotate(15deg) scale(1.2) translateX(20px);
|
||||
}
|
||||
|
||||
.rotateX {
|
||||
transform: rotateX(45deg);
|
||||
}
|
||||
|
||||
.rotateY {
|
||||
transform: rotateY(45deg);
|
||||
}
|
||||
|
||||
.rotateZ {
|
||||
transform: rotateZ(45deg);
|
||||
}
|
||||
|
||||
.translate3d {
|
||||
transform: translate3d(20px, 20px, 50px);
|
||||
}
|
||||
|
||||
.perspective {
|
||||
transform: perspective(200px) rotateX(20deg);
|
||||
}
|
||||
|
||||
/* 变换原点演示 */
|
||||
.transform-origin-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.origin-box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #009688;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.center {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.bottom-right {
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.origin-box:hover {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* 实用动画示例 */
|
||||
/* 加载动画 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(0, 0, 0, 0.1);
|
||||
border-top: 5px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background-color: #3498db;
|
||||
border-radius: 50%;
|
||||
animation: bounce 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% { transform: scale(0); }
|
||||
40% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
width: 200px;
|
||||
height: 10px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.bar-progress {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #4caf50;
|
||||
animation: progress 2s infinite;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
0% { transform: scaleX(0); }
|
||||
50% { transform: scaleX(0.7); }
|
||||
100% { transform: scaleX(0); }
|
||||
}
|
||||
|
||||
/* 按钮动画 */
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.animated-button {
|
||||
padding: 10px 20px;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.pulse-button:hover {
|
||||
animation: buttonPulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes buttonPulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.scale-button:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.shadow-button {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.shadow-button:hover {
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.border-button {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.border-button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f44336;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.3s ease;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.border-button:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
/* 悬浮卡片 */
|
||||
.hover-card-container {
|
||||
width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hover-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hover-card:hover {
|
||||
transform: translateY(-10px);
|
||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
height: 150px;
|
||||
background-color: #3f51b5;
|
||||
background-image: linear-gradient(45deg, #3f51b5, #7986cb);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-content h4 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 视差滚动效果 */
|
||||
.parallax-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.parallax-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
.layer-1 {
|
||||
background-color: #3f51b5;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.layer-2 {
|
||||
background-color: #673ab7;
|
||||
opacity: 0.3;
|
||||
clip-path: polygon(0 20%, 100% 0, 100% 80%, 0 100%);
|
||||
}
|
||||
|
||||
.layer-3 {
|
||||
background-color: #9c27b0;
|
||||
opacity: 0.3;
|
||||
clip-path: circle(30% at 70% 50%);
|
||||
}
|
||||
|
||||
.parallax-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.parallax-container:hover .layer-1 {
|
||||
transform: translateX(-10px) translateY(-10px);
|
||||
}
|
||||
|
||||
.parallax-container:hover .layer-2 {
|
||||
transform: translateX(15px) translateY(5px);
|
||||
}
|
||||
|
||||
.parallax-container:hover .layer-3 {
|
||||
transform: translateX(-5px) translateY(10px);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,719 @@
|
|||
<template>
|
||||
<div class="css-basics">
|
||||
<h1 class="page-title">CSS基础知识</h1>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>1. CSS选择器</h2>
|
||||
|
||||
<div class="selector-demo">
|
||||
<h3>元素选择器</h3>
|
||||
<p>这是一个段落,使用元素选择器 <code>p { }</code> 设置样式</p>
|
||||
<div>这是一个div,使用元素选择器 <code>div { }</code> 设置样式</div>
|
||||
</div>
|
||||
|
||||
<div class="selector-demo">
|
||||
<h3>类选择器</h3>
|
||||
<p class="highlight">这个段落使用类选择器 <code>.highlight { }</code> 设置样式</p>
|
||||
<p class="muted">这个段落使用类选择器 <code>.muted { }</code> 设置样式</p>
|
||||
</div>
|
||||
|
||||
<div class="selector-demo">
|
||||
<h3>ID选择器</h3>
|
||||
<p id="unique-element">这个段落使用ID选择器 <code>#unique-element { }</code> 设置样式</p>
|
||||
</div>
|
||||
|
||||
<div class="selector-demo">
|
||||
<h3>属性选择器</h3>
|
||||
<a href="https://example.com" target="_blank">外部链接 (使用 <code>[target="_blank"] { }</code>)</a>
|
||||
<input type="text" placeholder="文本输入框 (使用 [type='text'])">
|
||||
</div>
|
||||
|
||||
<div class="selector-demo">
|
||||
<h3>伪类选择器</h3>
|
||||
<p>鼠标悬停在此文本上会改变颜色 (使用 <code>:hover</code>)</p>
|
||||
<ul class="pseudo-list">
|
||||
<li>第一个项目 (使用 <code>:first-child</code>)</li>
|
||||
<li>第二个项目</li>
|
||||
<li>第三个项目</li>
|
||||
<li>第四个项目 (使用 <code>:nth-child(even)</code>)</li>
|
||||
<li>第五个项目</li>
|
||||
<li>最后一个项目 (使用 <code>:last-child</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="selector-demo">
|
||||
<h3>伪元素选择器</h3>
|
||||
<p class="first-letter">这段文字的第一个字母使用 <code>::first-letter</code> 设置样式</p>
|
||||
<p class="first-line">这段文字的第一行使用 <code>::first-line</code> 设置样式。这是一段很长的文字,目的是为了演示第一行的样式效果,所以需要足够长才能换行。</p>
|
||||
<p class="with-before">这段文字前面使用 <code>::before</code> 添加内容</p>
|
||||
<p class="with-after">这段文字后面使用 <code>::after</code> 添加内容</p>
|
||||
</div>
|
||||
|
||||
<div class="selector-demo">
|
||||
<h3>组合选择器</h3>
|
||||
<div class="parent">
|
||||
<p>父元素中的段落 (使用 <code>.parent p { }</code>)</p>
|
||||
<span>父元素中的span</span>
|
||||
<p>另一个段落</p>
|
||||
</div>
|
||||
<p>父元素外的段落</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>2. 文本样式</h2>
|
||||
|
||||
<div class="text-demo">
|
||||
<div class="text-property">
|
||||
<h3>字体系列 (font-family)</h3>
|
||||
<p class="font-serif">这是衬线字体 (serif)</p>
|
||||
<p class="font-sans-serif">这是无衬线字体 (sans-serif)</p>
|
||||
<p class="font-monospace">这是等宽字体 (monospace)</p>
|
||||
</div>
|
||||
|
||||
<div class="text-property">
|
||||
<h3>字体大小 (font-size)</h3>
|
||||
<p class="font-small">小字体 (12px)</p>
|
||||
<p class="font-medium">中等字体 (16px)</p>
|
||||
<p class="font-large">大字体 (24px)</p>
|
||||
<p class="font-relative">相对字体大小 (1.5em)</p>
|
||||
</div>
|
||||
|
||||
<div class="text-property">
|
||||
<h3>字体粗细 (font-weight)</h3>
|
||||
<p class="font-normal">正常粗细 (400)</p>
|
||||
<p class="font-bold">粗体 (700)</p>
|
||||
<p class="font-light">细体 (300)</p>
|
||||
</div>
|
||||
|
||||
<div class="text-property">
|
||||
<h3>字体样式 (font-style)</h3>
|
||||
<p class="font-italic">斜体字</p>
|
||||
<p class="font-oblique">倾斜字</p>
|
||||
</div>
|
||||
|
||||
<div class="text-property">
|
||||
<h3>文本装饰 (text-decoration)</h3>
|
||||
<p class="text-underline">下划线文本</p>
|
||||
<p class="text-overline">上划线文本</p>
|
||||
<p class="text-line-through">删除线文本</p>
|
||||
</div>
|
||||
|
||||
<div class="text-property">
|
||||
<h3>文本对齐 (text-align)</h3>
|
||||
<p class="text-left">左对齐文本</p>
|
||||
<p class="text-center">居中对齐文本</p>
|
||||
<p class="text-right">右对齐文本</p>
|
||||
<p class="text-justify">两端对齐文本。这是一段很长的文字,目的是为了演示两端对齐的效果,所以需要足够长才能看出效果。</p>
|
||||
</div>
|
||||
|
||||
<div class="text-property">
|
||||
<h3>行高 (line-height)</h3>
|
||||
<p class="line-height-small">这是一段行高较小的文本。这是一段很长的文字,目的是为了演示行高较小的效果,所以需要足够长才能换行。</p>
|
||||
<p class="line-height-normal">这是一段正常行高的文本。这是一段很长的文字,目的是为了演示正常行高的效果,所以需要足够长才能换行。</p>
|
||||
<p class="line-height-large">这是一段行高较大的文本。这是一段很长的文字,目的是为了演示行高较大的效果,所以需要足够长才能换行。</p>
|
||||
</div>
|
||||
|
||||
<div class="text-property">
|
||||
<h3>字母间距 (letter-spacing)</h3>
|
||||
<p class="letter-spacing-normal">正常字母间距</p>
|
||||
<p class="letter-spacing-wide">宽字母间距</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>3. 颜色与背景</h2>
|
||||
|
||||
<div class="color-demo">
|
||||
<div class="color-property">
|
||||
<h3>颜色值表示方法</h3>
|
||||
<div class="color-box color-name">颜色名称 (red)</div>
|
||||
<div class="color-box color-hex">#FF5733 (十六进制)</div>
|
||||
<div class="color-box color-rgb">rgb(255, 87, 51) (RGB)</div>
|
||||
<div class="color-box color-rgba">rgba(255, 87, 51, 0.5) (RGBA)</div>
|
||||
<div class="color-box color-hsl">hsl(14, 100%, 60%) (HSL)</div>
|
||||
</div>
|
||||
|
||||
<div class="color-property">
|
||||
<h3>背景属性</h3>
|
||||
<div class="bg-color">纯色背景</div>
|
||||
<div class="bg-gradient">渐变背景</div>
|
||||
<div class="bg-image">图片背景</div>
|
||||
<div class="bg-repeat">重复背景</div>
|
||||
<div class="bg-position">背景位置</div>
|
||||
<div class="bg-size">背景大小</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>4. 盒模型</h2>
|
||||
|
||||
<div class="box-model-demo">
|
||||
<div class="box-model-explanation">
|
||||
<h3>盒模型组成</h3>
|
||||
<div class="box-model-visual">
|
||||
<div class="box-margin">
|
||||
<p>外边距 (margin)</p>
|
||||
<div class="box-border">
|
||||
<p>边框 (border)</p>
|
||||
<div class="box-padding">
|
||||
<p>内边距 (padding)</p>
|
||||
<div class="box-content">
|
||||
<p>内容 (content)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-sizing-demo">
|
||||
<h3>盒模型类型</h3>
|
||||
<div class="box content-box">
|
||||
<p>content-box (默认)</p>
|
||||
<p>宽度 = 内容宽度</p>
|
||||
</div>
|
||||
<div class="box border-box">
|
||||
<p>border-box</p>
|
||||
<p>宽度 = 内容 + 内边距 + 边框</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>5. 边框与圆角</h2>
|
||||
|
||||
<div class="border-demo">
|
||||
<div class="border-styles">
|
||||
<h3>边框样式</h3>
|
||||
<div class="border-solid">实线边框 (solid)</div>
|
||||
<div class="border-dashed">虚线边框 (dashed)</div>
|
||||
<div class="border-dotted">点线边框 (dotted)</div>
|
||||
<div class="border-double">双线边框 (double)</div>
|
||||
<div class="border-groove">凹槽边框 (groove)</div>
|
||||
<div class="border-ridge">凸脊边框 (ridge)</div>
|
||||
<div class="border-inset">嵌入边框 (inset)</div>
|
||||
<div class="border-outset">突出边框 (outset)</div>
|
||||
</div>
|
||||
|
||||
<div class="border-radius">
|
||||
<h3>圆角边框</h3>
|
||||
<div class="radius-small">小圆角 (5px)</div>
|
||||
<div class="radius-medium">中等圆角 (15px)</div>
|
||||
<div class="radius-large">大圆角 (25px)</div>
|
||||
<div class="radius-circle">圆形 (50%)</div>
|
||||
<div class="radius-ellipse">椭圆 (50% / 25%)</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CssBasics'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.css-basics {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 40px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
color: #42b983;
|
||||
border-bottom: 2px solid #42b983;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.selector-demo, .text-demo, .color-demo, .box-model-demo, .border-demo {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.selector-demo h3, .text-property h3, .color-property h3 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f0f0f0;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
color: #e83e8c;
|
||||
}
|
||||
|
||||
/* 元素选择器演示 */
|
||||
.selector-demo p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.selector-demo div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 类选择器演示 */
|
||||
.highlight {
|
||||
background-color: #ffff99;
|
||||
padding: 5px;
|
||||
border-left: 3px solid #ffcc00;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ID选择器演示 */
|
||||
#unique-element {
|
||||
background-color: #e3f2fd;
|
||||
border: 2px dashed #2196f3;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 属性选择器演示 */
|
||||
[target="_blank"] {
|
||||
color: #e91e63;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #e91e63;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
[type="text"] {
|
||||
padding: 8px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 伪类选择器演示 */
|
||||
.selector-demo p:hover {
|
||||
color: #42b983;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pseudo-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pseudo-list li {
|
||||
padding: 8px;
|
||||
margin-bottom: 5px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.pseudo-list li:first-child {
|
||||
background-color: #e3f2fd;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pseudo-list li:nth-child(even) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.pseudo-list li:last-child {
|
||||
background-color: #e8f5e9;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 伪元素选择器演示 */
|
||||
.first-letter::first-letter {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #e91e63;
|
||||
}
|
||||
|
||||
.first-line::first-line {
|
||||
font-weight: bold;
|
||||
color: #3f51b5;
|
||||
}
|
||||
|
||||
.with-before::before {
|
||||
content: "【前缀】";
|
||||
color: #ff9800;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.with-after::after {
|
||||
content: "【后缀】";
|
||||
color: #9c27b0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 组合选择器演示 */
|
||||
.parent {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.parent p {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
/* 文本样式演示 */
|
||||
.text-property {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* 字体系列 */
|
||||
.font-serif {
|
||||
font-family: Georgia, Times, "Times New Roman", serif;
|
||||
}
|
||||
|
||||
.font-sans-serif {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.font-monospace {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
/* 字体大小 */
|
||||
.font-small {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.font-large {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.font-relative {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
/* 字体粗细 */
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-light {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* 字体样式 */
|
||||
.font-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.font-oblique {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
/* 文本装饰 */
|
||||
.text-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.text-overline {
|
||||
text-decoration: overline;
|
||||
}
|
||||
|
||||
.text-line-through {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* 文本对齐 */
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* 行高 */
|
||||
.line-height-small {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.line-height-normal {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.line-height-large {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
/* 字母间距 */
|
||||
.letter-spacing-normal {
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
.letter-spacing-wide {
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
/* 颜色演示 */
|
||||
.color-property {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.color-box {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.color-name {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.color-hex {
|
||||
background-color: #FF5733;
|
||||
}
|
||||
|
||||
.color-rgb {
|
||||
background-color: rgb(255, 87, 51);
|
||||
}
|
||||
|
||||
.color-rgba {
|
||||
background-color: rgba(255, 87, 51, 0.5);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.color-hsl {
|
||||
background-color: hsl(14, 100%, 60%);
|
||||
}
|
||||
|
||||
/* 背景属性 */
|
||||
.bg-color {
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bg-gradient {
|
||||
background: linear-gradient(to right, #00c6ff, #0072ff);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
background-image: url('https://via.placeholder.com/150');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
height: 150px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bg-repeat {
|
||||
background-image: url('https://via.placeholder.com/50');
|
||||
background-repeat: repeat;
|
||||
height: 150px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bg-position {
|
||||
background-image: url('https://via.placeholder.com/100');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
height: 150px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bg-size {
|
||||
background-image: url('https://via.placeholder.com/100');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
height: 150px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 盒模型演示 */
|
||||
.box-model-visual {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.box-margin {
|
||||
background-color: #ffcdd2;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box-border {
|
||||
background-color: #f8bbd0;
|
||||
border: 10px solid #ec407a;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.box-padding {
|
||||
background-color: #e1bee7;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.box-content {
|
||||
background-color: #d1c4e9;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 盒模型类型 */
|
||||
.box-sizing-demo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
border: 10px solid #3f51b5;
|
||||
margin: 10px;
|
||||
background-color: #e8eaf6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.border-box {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 边框样式演示 */
|
||||
.border-styles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.border-styles > div {
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.border-solid {
|
||||
border: 3px solid #2196f3;
|
||||
}
|
||||
|
||||
.border-dashed {
|
||||
border: 3px dashed #f44336;
|
||||
}
|
||||
|
||||
.border-dotted {
|
||||
border: 3px dotted #4caf50;
|
||||
}
|
||||
|
||||
.border-double {
|
||||
border: 3px double #ff9800;
|
||||
}
|
||||
|
||||
.border-groove {
|
||||
border: 3px groove #9c27b0;
|
||||
}
|
||||
|
||||
.border-ridge {
|
||||
border: 3px ridge #673ab7;
|
||||
}
|
||||
|
||||
.border-inset {
|
||||
border: 3px inset #3f51b5;
|
||||
}
|
||||
|
||||
.border-outset {
|
||||
border: 3px outset #607d8b;
|
||||
}
|
||||
|
||||
/* 圆角边框演示 */
|
||||
.border-radius {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.border-radius > div {
|
||||
height: 100px;
|
||||
background-color: #e3f2fd;
|
||||
border: 2px solid #2196f3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.radius-small {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.radius-medium {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.radius-large {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.radius-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.radius-ellipse {
|
||||
border-radius: 50% / 25%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,876 @@
|
|||
<template>
|
||||
<div class="css-layout">
|
||||
<h1 class="page-title">CSS布局模型</h1>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>1. Flexbox布局</h2>
|
||||
|
||||
<div class="layout-description">
|
||||
<p>Flexbox是一种一维布局模型,适用于在一行或一列中布局元素。</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-demos">
|
||||
<div class="flex-demo">
|
||||
<h3>flex-direction</h3>
|
||||
<div class="flex-container direction-row">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
<div class="flex-item">4</div>
|
||||
</div>
|
||||
<p class="demo-caption">flex-direction: row (默认)</p>
|
||||
|
||||
<div class="flex-container direction-column">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
<div class="flex-item">4</div>
|
||||
</div>
|
||||
<p class="demo-caption">flex-direction: column</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-demo">
|
||||
<h3>justify-content</h3>
|
||||
<div class="flex-container justify-start">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">justify-content: flex-start (默认)</p>
|
||||
|
||||
<div class="flex-container justify-center">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">justify-content: center</p>
|
||||
|
||||
<div class="flex-container justify-end">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">justify-content: flex-end</p>
|
||||
|
||||
<div class="flex-container justify-between">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">justify-content: space-between</p>
|
||||
|
||||
<div class="flex-container justify-around">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">justify-content: space-around</p>
|
||||
|
||||
<div class="flex-container justify-evenly">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">justify-content: space-evenly</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-demo">
|
||||
<h3>align-items</h3>
|
||||
<div class="flex-container align-start">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item tall">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">align-items: flex-start</p>
|
||||
|
||||
<div class="flex-container align-center">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item tall">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">align-items: center</p>
|
||||
|
||||
<div class="flex-container align-end">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item tall">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">align-items: flex-end</p>
|
||||
|
||||
<div class="flex-container align-stretch">
|
||||
<div class="flex-item no-height">1</div>
|
||||
<div class="flex-item no-height">2</div>
|
||||
<div class="flex-item no-height">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">align-items: stretch (默认)</p>
|
||||
|
||||
<div class="flex-container align-baseline">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item large-text">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
</div>
|
||||
<p class="demo-caption">align-items: baseline</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-demo">
|
||||
<h3>flex-wrap</h3>
|
||||
<div class="flex-container wrap-nowrap">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
<div class="flex-item">4</div>
|
||||
<div class="flex-item">5</div>
|
||||
<div class="flex-item">6</div>
|
||||
<div class="flex-item">7</div>
|
||||
<div class="flex-item">8</div>
|
||||
</div>
|
||||
<p class="demo-caption">flex-wrap: nowrap (默认)</p>
|
||||
|
||||
<div class="flex-container wrap-wrap">
|
||||
<div class="flex-item">1</div>
|
||||
<div class="flex-item">2</div>
|
||||
<div class="flex-item">3</div>
|
||||
<div class="flex-item">4</div>
|
||||
<div class="flex-item">5</div>
|
||||
<div class="flex-item">6</div>
|
||||
<div class="flex-item">7</div>
|
||||
<div class="flex-item">8</div>
|
||||
</div>
|
||||
<p class="demo-caption">flex-wrap: wrap</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-demo">
|
||||
<h3>flex-grow, flex-shrink, flex-basis</h3>
|
||||
<div class="flex-container">
|
||||
<div class="flex-item grow-1">flex-grow: 1</div>
|
||||
<div class="flex-item grow-2">flex-grow: 2</div>
|
||||
<div class="flex-item grow-1">flex-grow: 1</div>
|
||||
</div>
|
||||
<p class="demo-caption">不同的flex-grow值</p>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="flex-item basis-100">flex-basis: 100px</div>
|
||||
<div class="flex-item basis-200">flex-basis: 200px</div>
|
||||
<div class="flex-item basis-auto">flex-basis: auto</div>
|
||||
</div>
|
||||
<p class="demo-caption">不同的flex-basis值</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-demo">
|
||||
<h3>flex简写属性</h3>
|
||||
<div class="flex-container">
|
||||
<div class="flex-item flex-1">flex: 1</div>
|
||||
<div class="flex-item flex-2">flex: 2</div>
|
||||
<div class="flex-item flex-1">flex: 1</div>
|
||||
</div>
|
||||
<p class="demo-caption">flex: 1 和 flex: 2 (flex-grow)</p>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="flex-item flex-auto">flex: auto</div>
|
||||
<div class="flex-item flex-initial">flex: initial</div>
|
||||
<div class="flex-item flex-none">flex: none</div>
|
||||
</div>
|
||||
<p class="demo-caption">flex: auto, initial, none</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-demo">
|
||||
<h3>align-self</h3>
|
||||
<div class="flex-container">
|
||||
<div class="flex-item self-start">self-start</div>
|
||||
<div class="flex-item self-center">self-center</div>
|
||||
<div class="flex-item self-end">self-end</div>
|
||||
<div class="flex-item">默认</div>
|
||||
</div>
|
||||
<p class="demo-caption">单个项目的对齐方式</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>2. Grid布局</h2>
|
||||
|
||||
<div class="layout-description">
|
||||
<p>Grid是一种二维布局模型,可以同时控制行和列。</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-demos">
|
||||
<div class="grid-demo">
|
||||
<h3>基本网格</h3>
|
||||
<div class="grid-container basic-grid">
|
||||
<div class="grid-item">1</div>
|
||||
<div class="grid-item">2</div>
|
||||
<div class="grid-item">3</div>
|
||||
<div class="grid-item">4</div>
|
||||
<div class="grid-item">5</div>
|
||||
<div class="grid-item">6</div>
|
||||
</div>
|
||||
<p class="demo-caption">grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(2, 100px);</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-demo">
|
||||
<h3>网格间距</h3>
|
||||
<div class="grid-container grid-gap">
|
||||
<div class="grid-item">1</div>
|
||||
<div class="grid-item">2</div>
|
||||
<div class="grid-item">3</div>
|
||||
<div class="grid-item">4</div>
|
||||
<div class="grid-item">5</div>
|
||||
<div class="grid-item">6</div>
|
||||
</div>
|
||||
<p class="demo-caption">gap: 20px;</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-demo">
|
||||
<h3>网格线定位</h3>
|
||||
<div class="grid-container grid-lines">
|
||||
<div class="grid-item item-a">A</div>
|
||||
<div class="grid-item item-b">B</div>
|
||||
<div class="grid-item item-c">C</div>
|
||||
<div class="grid-item item-d">D</div>
|
||||
<div class="grid-item item-e">E</div>
|
||||
</div>
|
||||
<p class="demo-caption">使用grid-column和grid-row定位元素</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-demo">
|
||||
<h3>网格区域</h3>
|
||||
<div class="grid-container grid-areas">
|
||||
<div class="grid-item header">Header</div>
|
||||
<div class="grid-item sidebar">Sidebar</div>
|
||||
<div class="grid-item content">Content</div>
|
||||
<div class="grid-item footer">Footer</div>
|
||||
</div>
|
||||
<p class="demo-caption">使用grid-template-areas定义区域</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-demo">
|
||||
<h3>自动填充</h3>
|
||||
<div class="grid-container auto-fill">
|
||||
<div class="grid-item">1</div>
|
||||
<div class="grid-item">2</div>
|
||||
<div class="grid-item">3</div>
|
||||
<div class="grid-item">4</div>
|
||||
<div class="grid-item">5</div>
|
||||
<div class="grid-item">6</div>
|
||||
<div class="grid-item">7</div>
|
||||
<div class="grid-item">8</div>
|
||||
</div>
|
||||
<p class="demo-caption">grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-demo">
|
||||
<h3>minmax函数</h3>
|
||||
<div class="grid-container minmax-grid">
|
||||
<div class="grid-item">1</div>
|
||||
<div class="grid-item">2</div>
|
||||
<div class="grid-item">3</div>
|
||||
<div class="grid-item">4</div>
|
||||
</div>
|
||||
<p class="demo-caption">grid-template-columns: minmax(100px, 1fr) 2fr minmax(100px, 1fr);</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-demo">
|
||||
<h3>自动放置</h3>
|
||||
<div class="grid-container auto-placement">
|
||||
<div class="grid-item">1</div>
|
||||
<div class="grid-item">2</div>
|
||||
<div class="grid-item">3</div>
|
||||
<div class="grid-item">4</div>
|
||||
<div class="grid-item">5</div>
|
||||
<div class="grid-item">6</div>
|
||||
<div class="grid-item">7</div>
|
||||
<div class="grid-item">8</div>
|
||||
<div class="grid-item">9</div>
|
||||
</div>
|
||||
<p class="demo-caption">grid-auto-flow: dense;</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>3. 响应式布局</h2>
|
||||
|
||||
<div class="layout-description">
|
||||
<p>使用媒体查询和现代布局技术创建响应式设计。</p>
|
||||
</div>
|
||||
|
||||
<div class="responsive-demo">
|
||||
<h3>响应式卡片布局</h3>
|
||||
<div class="responsive-cards">
|
||||
<div class="card">
|
||||
<div class="card-header">卡片 1</div>
|
||||
<div class="card-body">这是一个响应式卡片,会根据屏幕宽度自动调整布局。</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">卡片 2</div>
|
||||
<div class="card-body">这是一个响应式卡片,会根据屏幕宽度自动调整布局。</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">卡片 3</div>
|
||||
<div class="card-body">这是一个响应式卡片,会根据屏幕宽度自动调整布局。</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">卡片 4</div>
|
||||
<div class="card-body">这是一个响应式卡片,会根据屏幕宽度自动调整布局。</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="demo-caption">调整窗口大小查看效果</p>
|
||||
</div>
|
||||
|
||||
<div class="responsive-demo">
|
||||
<h3>响应式导航栏</h3>
|
||||
<div class="responsive-nav">
|
||||
<div class="nav-brand">Brand</div>
|
||||
<div class="nav-links">
|
||||
<a href="#" class="nav-link">首页</a>
|
||||
<a href="#" class="nav-link">关于</a>
|
||||
<a href="#" class="nav-link">服务</a>
|
||||
<a href="#" class="nav-link">联系</a>
|
||||
</div>
|
||||
<div class="nav-toggle">☰</div>
|
||||
</div>
|
||||
<p class="demo-caption">调整窗口大小查看效果</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-section">
|
||||
<h2>4. 定位与浮动</h2>
|
||||
|
||||
<div class="layout-description">
|
||||
<p>传统的CSS定位和浮动技术。</p>
|
||||
</div>
|
||||
|
||||
<div class="position-demos">
|
||||
<div class="position-demo">
|
||||
<h3>定位属性</h3>
|
||||
<div class="position-container">
|
||||
<div class="position-box static">position: static (默认)</div>
|
||||
<div class="position-box relative">position: relative</div>
|
||||
<div class="position-box absolute">position: absolute</div>
|
||||
<div class="position-box fixed">position: fixed</div>
|
||||
<div class="position-box sticky">position: sticky</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="position-demo">
|
||||
<h3>浮动</h3>
|
||||
<div class="float-container">
|
||||
<div class="float-box float-left">float: left</div>
|
||||
<div class="float-box float-right">float: right</div>
|
||||
<div class="clearfix">清除浮动</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CssLayout'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.css-layout {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 40px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
color: #42b983;
|
||||
border-bottom: 2px solid #42b983;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.layout-description {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.demo-caption {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Flexbox演示 */
|
||||
.flex-demos, .grid-demos {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.flex-demo, .grid-demo {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.flex-demo h3, .grid-demo h3 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
background-color: #e3f2fd;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.flex-item {
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
margin: 5px;
|
||||
min-width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* flex-direction */
|
||||
.direction-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.direction-column {
|
||||
flex-direction: column;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
/* justify-content */
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.justify-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
/* align-items */
|
||||
.align-start {
|
||||
align-items: flex-start;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.align-end {
|
||||
align-items: flex-end;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.align-stretch {
|
||||
align-items: stretch;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.align-baseline {
|
||||
align-items: baseline;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.tall {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.no-height {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.large-text {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
/* flex-wrap */
|
||||
.wrap-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.wrap-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* flex属性 */
|
||||
.grow-1 {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.grow-2 {
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
.basis-100 {
|
||||
flex-basis: 100px;
|
||||
}
|
||||
|
||||
.basis-200 {
|
||||
flex-basis: 200px;
|
||||
}
|
||||
|
||||
.basis-auto {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flex-2 {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.flex-initial {
|
||||
flex: initial;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.flex-none {
|
||||
flex: none;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
/* align-self */
|
||||
.self-start {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.self-center {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.self-end {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
/* Grid演示 */
|
||||
.grid-container {
|
||||
display: grid;
|
||||
background-color: #e8f5e9;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 基本网格 */
|
||||
.basic-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 100px);
|
||||
}
|
||||
|
||||
/* 网格间距 */
|
||||
.grid-gap {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 100px);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 网格线定位 */
|
||||
.grid-lines {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 100px);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.item-a {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
.item-b {
|
||||
grid-column: 3 / 4;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.item-c {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2 / 4;
|
||||
}
|
||||
|
||||
.item-d {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
.item-e {
|
||||
grid-column: 2 / 4;
|
||||
grid-row: 3 / 4;
|
||||
}
|
||||
|
||||
/* 网格区域 */
|
||||
.grid-areas {
|
||||
grid-template-areas:
|
||||
"header header header"
|
||||
"sidebar content content"
|
||||
"footer footer footer";
|
||||
grid-template-columns: 1fr 2fr 1fr;
|
||||
grid-template-rows: 80px 200px 80px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-area: content;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-area: footer;
|
||||
}
|
||||
|
||||
/* 自动填充 */
|
||||
.auto-fill {
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
grid-auto-rows: 80px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* minmax函数 */
|
||||
.minmax-grid {
|
||||
grid-template-columns: minmax(100px, 1fr) 2fr minmax(100px, 1fr);
|
||||
grid-template-rows: 100px 100px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 自动放置 */
|
||||
.auto-placement {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-auto-rows: 80px;
|
||||
gap: 10px;
|
||||
grid-auto-flow: dense;
|
||||
}
|
||||
|
||||
.auto-placement .grid-item:nth-child(4) {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.auto-placement .grid-item:nth-child(6) {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
/* 响应式布局 */
|
||||
.responsive-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/* 响应式导航栏 */
|
||||
.responsive-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 定位与浮动 */
|
||||
.position-container {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
background-color: #f0f0f0;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.position-box {
|
||||
width: 200px;
|
||||
padding: 15px;
|
||||
background-color: #9c27b0;
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background-color: #673ab7;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
background-color: #3f51b5;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background-color: #2196f3;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
background-color: #00bcd4;
|
||||
}
|
||||
|
||||
/* 浮动 */
|
||||
.float-container {
|
||||
background-color: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.float-box {
|
||||
width: 200px;
|
||||
padding: 15px;
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
clear: both;
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 媒体查询 */
|
||||
@media (max-width: 768px) {
|
||||
.nav-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.responsive-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,523 @@
|
|||
<template>
|
||||
<div class="dynamic-styles">
|
||||
<h2>动态样式组件</h2>
|
||||
|
||||
<section class="example-section">
|
||||
<h3>1. 对象语法</h3>
|
||||
<div
|
||||
class="box"
|
||||
:class="{
|
||||
'is-active': isActive,
|
||||
'is-error': hasError,
|
||||
'is-warning': hasWarning,
|
||||
'is-success': isSuccess
|
||||
}"
|
||||
>
|
||||
使用对象语法的动态类
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label>
|
||||
<input type="checkbox" v-model="isActive"> 激活
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="hasError"> 错误
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="hasWarning"> 警告
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isSuccess"> 成功
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="example-section">
|
||||
<h3>2. 数组语法</h3>
|
||||
<div
|
||||
class="box"
|
||||
:class="[
|
||||
activeClass,
|
||||
errorClass,
|
||||
{ 'text-bold': isBold, 'text-italic': isItalic }
|
||||
]"
|
||||
>
|
||||
使用数组语法的动态类
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<label>活动类:</label>
|
||||
<select v-model="activeClass">
|
||||
<option value="">无</option>
|
||||
<option value="bg-primary">主色背景</option>
|
||||
<option value="bg-secondary">次色背景</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>错误类:</label>
|
||||
<select v-model="errorClass">
|
||||
<option value="">无</option>
|
||||
<option value="text-danger">危险文本</option>
|
||||
<option value="text-warning">警告文本</option>
|
||||
</select>
|
||||
</div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isBold"> 粗体
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isItalic"> 斜体
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="example-section">
|
||||
<h3>3. 计算属性</h3>
|
||||
<div
|
||||
class="box"
|
||||
:class="computedClassObject"
|
||||
>
|
||||
使用计算属性的动态类
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label>大小:</label>
|
||||
<select v-model="size">
|
||||
<option value="small">小</option>
|
||||
<option value="medium">中</option>
|
||||
<option value="large">大</option>
|
||||
</select>
|
||||
<label>主题:</label>
|
||||
<select v-model="theme">
|
||||
<option value="light">亮色</option>
|
||||
<option value="dark">暗色</option>
|
||||
</select>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isRounded"> 圆角
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="hasShadow"> 阴影
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="example-section">
|
||||
<h3>4. 内联样式</h3>
|
||||
<div
|
||||
class="box"
|
||||
:style="{
|
||||
backgroundColor: bgColor,
|
||||
color: textColor,
|
||||
fontSize: fontSize + 'px',
|
||||
padding: padding + 'px',
|
||||
borderWidth: borderWidth + 'px'
|
||||
}"
|
||||
>
|
||||
使用内联样式
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<label>背景颜色:</label>
|
||||
<input type="color" v-model="bgColor">
|
||||
</div>
|
||||
<div>
|
||||
<label>文字颜色:</label>
|
||||
<input type="color" v-model="textColor">
|
||||
</div>
|
||||
<div>
|
||||
<label>字体大小: {{ fontSize }}px</label>
|
||||
<input type="range" v-model.number="fontSize" min="12" max="32">
|
||||
</div>
|
||||
<div>
|
||||
<label>内边距: {{ padding }}px</label>
|
||||
<input type="range" v-model.number="padding" min="5" max="30">
|
||||
</div>
|
||||
<div>
|
||||
<label>边框宽度: {{ borderWidth }}px</label>
|
||||
<input type="range" v-model.number="borderWidth" min="0" max="10">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="example-section">
|
||||
<h3>5. 组合使用</h3>
|
||||
<button
|
||||
:class="[
|
||||
'btn',
|
||||
buttonSize,
|
||||
{
|
||||
'btn-primary': type === 'primary',
|
||||
'btn-secondary': type === 'secondary',
|
||||
'btn-success': type === 'success',
|
||||
'btn-danger': type === 'danger',
|
||||
'btn-warning': type === 'warning',
|
||||
'btn-info': type === 'info',
|
||||
'btn-outline': isOutline,
|
||||
'btn-rounded': isButtonRounded,
|
||||
'btn-block': isBlock,
|
||||
'btn-disabled': isDisabled
|
||||
}
|
||||
]"
|
||||
:disabled="isDisabled"
|
||||
@click="clickButton"
|
||||
>
|
||||
{{ buttonText || '按钮' }}
|
||||
</button>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<label>按钮文本:</label>
|
||||
<input type="text" v-model="buttonText" placeholder="按钮">
|
||||
</div>
|
||||
<div>
|
||||
<label>按钮类型:</label>
|
||||
<select v-model="type">
|
||||
<option value="primary">Primary</option>
|
||||
<option value="secondary">Secondary</option>
|
||||
<option value="success">Success</option>
|
||||
<option value="danger">Danger</option>
|
||||
<option value="warning">Warning</option>
|
||||
<option value="info">Info</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>按钮大小:</label>
|
||||
<select v-model="buttonSize">
|
||||
<option value="btn-sm">小</option>
|
||||
<option value="btn-md">中</option>
|
||||
<option value="btn-lg">大</option>
|
||||
</select>
|
||||
</div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isOutline"> 轮廓按钮
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isButtonRounded"> 圆角按钮
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isBlock"> 块级按钮
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isDisabled"> 禁用按钮
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="buttonClicked" class="click-message">
|
||||
按钮被点击了!
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DynamicStyles',
|
||||
data() {
|
||||
return {
|
||||
// 对象语法示例
|
||||
isActive: true,
|
||||
hasError: false,
|
||||
hasWarning: false,
|
||||
isSuccess: false,
|
||||
|
||||
// 数组语法示例
|
||||
activeClass: 'bg-primary',
|
||||
errorClass: '',
|
||||
isBold: false,
|
||||
isItalic: false,
|
||||
|
||||
// 计算属性示例
|
||||
size: 'medium',
|
||||
theme: 'light',
|
||||
isRounded: true,
|
||||
hasShadow: true,
|
||||
|
||||
// 内联样式示例
|
||||
bgColor: '#ffffff',
|
||||
textColor: '#333333',
|
||||
fontSize: 16,
|
||||
padding: 15,
|
||||
borderWidth: 1,
|
||||
|
||||
// 组合使用示例
|
||||
buttonText: '点击我',
|
||||
type: 'primary',
|
||||
buttonSize: 'btn-md',
|
||||
isOutline: false,
|
||||
isButtonRounded: true,
|
||||
isBlock: false,
|
||||
isDisabled: false,
|
||||
buttonClicked: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedClassObject() {
|
||||
return {
|
||||
[`size-${this.size}`]: true,
|
||||
[`theme-${this.theme}`]: true,
|
||||
'is-rounded': this.isRounded,
|
||||
'has-shadow': this.hasShadow
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickButton() {
|
||||
this.buttonClicked = true;
|
||||
|
||||
// 3秒后隐藏消息
|
||||
setTimeout(() => {
|
||||
this.buttonClicked = false;
|
||||
}, 3000);
|
||||
|
||||
// 向父组件发送点击事件
|
||||
this.$emit('button-clicked', {
|
||||
type: this.type,
|
||||
text: this.buttonText
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dynamic-styles {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.example-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.box {
|
||||
padding: 20px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.controls > div {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 对象语法样式 */
|
||||
.is-active {
|
||||
background-color: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
}
|
||||
|
||||
.is-error {
|
||||
background-color: #ffebee;
|
||||
border-color: #f44336;
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
.is-warning {
|
||||
background-color: #fff8e1;
|
||||
border-color: #ffc107;
|
||||
color: #ff8f00;
|
||||
}
|
||||
|
||||
.is-success {
|
||||
background-color: #e8f5e9;
|
||||
border-color: #4caf50;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
/* 数组语法样式 */
|
||||
.bg-primary {
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.bg-secondary {
|
||||
background-color: #673ab7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 计算属性样式 */
|
||||
.size-small {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.size-medium {
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.size-large {
|
||||
font-size: 18px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.is-rounded {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.has-shadow {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.15s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.btn-md {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #2196f3;
|
||||
border-color: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #673ab7;
|
||||
border-color: #673ab7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #4caf50;
|
||||
border-color: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #f44336;
|
||||
border-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: #ff9800;
|
||||
border-color: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background-color: #00bcd4;
|
||||
border-color: #00bcd4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.btn-outline.btn-primary {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.btn-outline.btn-secondary {
|
||||
color: #673ab7;
|
||||
}
|
||||
|
||||
.btn-outline.btn-success {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.btn-outline.btn-danger {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.btn-outline.btn-warning {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.btn-outline.btn-info {
|
||||
color: #00bcd4;
|
||||
}
|
||||
|
||||
.btn-rounded {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-disabled, .btn:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn:hover:not(:disabled):not(.btn-disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.click-message {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: #e8f5e9;
|
||||
border-radius: 4px;
|
||||
color: #2e7d32;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,313 @@
|
|||
<template>
|
||||
<div class="form-validator">
|
||||
<h2>表单验证组件</h2>
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="form-group" :class="{ 'has-error': errors.age }">
|
||||
<label>年龄 (必须是18-120之间的整数):</label>
|
||||
<input
|
||||
type="number"
|
||||
v-model.number="formData.age"
|
||||
@blur="validateAge"
|
||||
/>
|
||||
<span class="error-message" v-if="errors.age">{{ errors.age }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" :class="{ 'has-error': errors.price }">
|
||||
<label>价格 (必须是正数,最多两位小数):</label>
|
||||
<input
|
||||
type="number"
|
||||
v-model.number="formData.price"
|
||||
step="0.01"
|
||||
@blur="validatePrice"
|
||||
/>
|
||||
<span class="error-message" v-if="errors.price">{{ errors.price }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" :class="{ 'has-error': errors.phoneNumber }">
|
||||
<label>手机号码 (必须是11位数字):</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="formData.phoneNumber"
|
||||
@blur="validatePhoneNumber"
|
||||
/>
|
||||
<span class="error-message" v-if="errors.phoneNumber">{{ errors.phoneNumber }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" :class="{ 'has-error': errors.zipCode }">
|
||||
<label>邮政编码 (必须是6位数字):</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="formData.zipCode"
|
||||
@blur="validateZipCode"
|
||||
/>
|
||||
<span class="error-message" v-if="errors.zipCode">{{ errors.zipCode }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" :class="{ 'has-error': errors.score }">
|
||||
<label>分数 (必须是0-100之间的数字):</label>
|
||||
<input
|
||||
type="number"
|
||||
v-model.number="formData.score"
|
||||
@blur="validateScore"
|
||||
/>
|
||||
<span class="error-message" v-if="errors.score">{{ errors.score }}</span>
|
||||
<div class="score-level" :class="scoreClass">
|
||||
分数等级: {{ scoreLevel }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" :disabled="!isFormValid">提交表单</button>
|
||||
</form>
|
||||
|
||||
<div v-if="formSubmitted" class="form-success">
|
||||
<h3>表单提交成功!</h3>
|
||||
<pre>{{ JSON.stringify(formData, null, 2) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormValidator',
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
age: null,
|
||||
price: null,
|
||||
phoneNumber: '',
|
||||
zipCode: '',
|
||||
score: null
|
||||
},
|
||||
errors: {
|
||||
age: '',
|
||||
price: '',
|
||||
phoneNumber: '',
|
||||
zipCode: '',
|
||||
score: ''
|
||||
},
|
||||
formSubmitted: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isFormValid() {
|
||||
// 检查所有字段是否已填写且没有错误
|
||||
return this.formData.age &&
|
||||
this.formData.price &&
|
||||
this.formData.phoneNumber &&
|
||||
this.formData.zipCode &&
|
||||
this.formData.score !== null &&
|
||||
!this.errors.age &&
|
||||
!this.errors.price &&
|
||||
!this.errors.phoneNumber &&
|
||||
!this.errors.zipCode &&
|
||||
!this.errors.score;
|
||||
},
|
||||
scoreLevel() {
|
||||
const score = this.formData.score;
|
||||
if (score === null) return '未评分';
|
||||
if (score >= 90) return '优秀';
|
||||
if (score >= 80) return '良好';
|
||||
if (score >= 70) return '中等';
|
||||
if (score >= 60) return '及格';
|
||||
return '不及格';
|
||||
},
|
||||
scoreClass() {
|
||||
const score = this.formData.score;
|
||||
if (score === null) return '';
|
||||
if (score >= 90) return 'excellent';
|
||||
if (score >= 80) return 'good';
|
||||
if (score >= 70) return 'average';
|
||||
if (score >= 60) return 'pass';
|
||||
return 'fail';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateAge() {
|
||||
// 验证年龄是否为整数且在18-120之间
|
||||
const age = this.formData.age;
|
||||
|
||||
if (age === null || age === '') {
|
||||
this.errors.age = '年龄不能为空';
|
||||
} else if (!Number.isInteger(age)) {
|
||||
this.errors.age = '年龄必须是整数';
|
||||
} else if (age < 18 || age > 120) {
|
||||
this.errors.age = '年龄必须在18-120之间';
|
||||
} else {
|
||||
this.errors.age = '';
|
||||
}
|
||||
},
|
||||
validatePrice() {
|
||||
// 验证价格是否为正数,且最多两位小数
|
||||
const price = this.formData.price;
|
||||
|
||||
if (price === null || price === '') {
|
||||
this.errors.price = '价格不能为空';
|
||||
} else if (price <= 0) {
|
||||
this.errors.price = '价格必须大于0';
|
||||
} else if (!/^\d+(\.\d{1,2})?$/.test(price.toString())) {
|
||||
this.errors.price = '价格最多保留两位小数';
|
||||
} else {
|
||||
this.errors.price = '';
|
||||
}
|
||||
},
|
||||
validatePhoneNumber() {
|
||||
// 验证手机号是否为11位数字
|
||||
const phoneNumber = this.formData.phoneNumber;
|
||||
|
||||
if (!phoneNumber) {
|
||||
this.errors.phoneNumber = '手机号不能为空';
|
||||
} else if (!/^\d{11}$/.test(phoneNumber)) {
|
||||
this.errors.phoneNumber = '手机号必须是11位数字';
|
||||
} else {
|
||||
this.errors.phoneNumber = '';
|
||||
}
|
||||
},
|
||||
validateZipCode() {
|
||||
// 验证邮政编码是否为6位数字
|
||||
const zipCode = this.formData.zipCode;
|
||||
|
||||
if (!zipCode) {
|
||||
this.errors.zipCode = '邮政编码不能为空';
|
||||
} else if (!/^\d{6}$/.test(zipCode)) {
|
||||
this.errors.zipCode = '邮政编码必须是6位数字';
|
||||
} else {
|
||||
this.errors.zipCode = '';
|
||||
}
|
||||
},
|
||||
validateScore() {
|
||||
// 验证分数是否在0-100之间
|
||||
const score = this.formData.score;
|
||||
|
||||
if (score === null || score === '') {
|
||||
this.errors.score = '分数不能为空';
|
||||
} else if (score < 0 || score > 100) {
|
||||
this.errors.score = '分数必须在0-100之间';
|
||||
} else {
|
||||
this.errors.score = '';
|
||||
}
|
||||
},
|
||||
validateAllFields() {
|
||||
this.validateAge();
|
||||
this.validatePrice();
|
||||
this.validatePhoneNumber();
|
||||
this.validateZipCode();
|
||||
this.validateScore();
|
||||
},
|
||||
submitForm() {
|
||||
this.validateAllFields();
|
||||
|
||||
if (this.isFormValid) {
|
||||
// 模拟表单提交
|
||||
this.formSubmitted = true;
|
||||
|
||||
// 向父组件发出表单提交事件
|
||||
this.$emit('form-submitted', this.formData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-validator {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.has-error input {
|
||||
border-color: #ff5252;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff5252;
|
||||
font-size: 0.9em;
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 15px;
|
||||
background-color: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #3aa876;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.form-success {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: #e8f5e9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.form-success pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.score-level {
|
||||
margin-top: 5px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.excellent {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.good {
|
||||
background-color: #8bc34a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.average {
|
||||
background-color: #ffc107;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pass {
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.fail {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,471 @@
|
|||
<template>
|
||||
<div class="vuex-demo">
|
||||
<h2>Vuex状态管理演示</h2>
|
||||
|
||||
<div class="vuex-section">
|
||||
<h3>模块化Vuex状态</h3>
|
||||
<div class="state-display">
|
||||
<div class="state-card">
|
||||
<h4>计算器模块 (calculator)</h4>
|
||||
<ul>
|
||||
<li>初始值: {{ calculatorModule.initialValue }}</li>
|
||||
<li>精度: {{ calculatorModule.precision }}</li>
|
||||
<li>最后结果: {{ calculatorModule.lastResult }}</li>
|
||||
<li>历史记录数: {{ calculatorModule.history.length }}</li>
|
||||
</ul>
|
||||
<div class="state-actions">
|
||||
<button @click="resetCalculator">重置计算器</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="state-card">
|
||||
<h4>表单模块 (form)</h4>
|
||||
<ul>
|
||||
<li>提交次数: {{ formModule.submissionCount }}</li>
|
||||
<li>最后提交: {{ formModule.submittedData ? '有数据' : '无数据' }}</li>
|
||||
</ul>
|
||||
<div v-if="formModule.submittedData" class="data-preview">
|
||||
<pre>{{ JSON.stringify(formModule.submittedData, null, 2) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="state-card">
|
||||
<h4>样式模块 (styles)</h4>
|
||||
<ul>
|
||||
<li>当前主题: {{ stylesModule.theme }}</li>
|
||||
<li>按钮点击次数: {{ stylesModule.clickCount }}</li>
|
||||
</ul>
|
||||
<div class="state-actions">
|
||||
<button @click="toggleTheme">
|
||||
切换主题: {{ stylesModule.theme === 'light' ? '🌙 暗色' : '☀️ 亮色' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vuex-section">
|
||||
<h3>模块化Vuex操作演示</h3>
|
||||
<div class="action-demo">
|
||||
<div class="action-group">
|
||||
<h4>直接访问模块</h4>
|
||||
<button @click="directModuleAccess">直接访问模块状态</button>
|
||||
<p class="note">通过 $store.state.moduleName 直接访问</p>
|
||||
</div>
|
||||
|
||||
<div class="action-group">
|
||||
<h4>命名空间Actions</h4>
|
||||
<button @click="dispatchNamespacedAction">分发命名空间Action</button>
|
||||
<p class="note">使用 dispatch('module/action') 分发</p>
|
||||
</div>
|
||||
|
||||
<div class="action-group">
|
||||
<h4>添加自定义消息到历史</h4>
|
||||
<div class="input-group">
|
||||
<input type="text" v-model="customMessage" placeholder="输入自定义消息" />
|
||||
<button @click="addCustomMessage">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vuex-section">
|
||||
<h3>历史记录</h3>
|
||||
<ul class="history-list">
|
||||
<li v-for="(item, index) in calculatorModule.history" :key="index">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="state-actions">
|
||||
<button @click="clearHistory">清除历史记录</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vuex-section">
|
||||
<h3>模块映射方法示例</h3>
|
||||
<div class="mapping-examples">
|
||||
<div class="mapping-card">
|
||||
<h4>使用mapState</h4>
|
||||
<pre>...mapState('calculator', ['initialValue', 'precision'])</pre>
|
||||
<button @click="showMappingExample('state')">查看示例</button>
|
||||
</div>
|
||||
|
||||
<div class="mapping-card">
|
||||
<h4>使用mapGetters</h4>
|
||||
<pre>...mapGetters('styles', ['theme', 'clickCount'])</pre>
|
||||
<button @click="showMappingExample('getters')">查看示例</button>
|
||||
</div>
|
||||
|
||||
<div class="mapping-card">
|
||||
<h4>使用mapActions</h4>
|
||||
<pre>...mapActions('calculator', ['addHistory', 'clearHistory'])</pre>
|
||||
<button @click="showMappingExample('actions')">查看示例</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mappingExample" class="mapping-example-display">
|
||||
<h4>{{ mappingExampleTitle }}</h4>
|
||||
<pre>{{ mappingExampleCode }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'VuexDemo',
|
||||
data() {
|
||||
return {
|
||||
customMessage: '',
|
||||
mappingExample: null,
|
||||
mappingExampleTitle: '',
|
||||
mappingExampleCode: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 使用不同的方式映射Vuex状态,展示多种用法
|
||||
|
||||
// 使用命名空间直接从模块获取状态
|
||||
...mapState({
|
||||
calculatorModule: state => state.calculator,
|
||||
formModule: state => state.form,
|
||||
stylesModule: state => state.styles
|
||||
}),
|
||||
|
||||
// 使用命名空间映射getter
|
||||
...mapGetters('calculator', {
|
||||
calcHistory: 'history',
|
||||
calcInitialValue: 'initialValue'
|
||||
}),
|
||||
|
||||
// 使用命名空间映射getter的数组形式
|
||||
...mapGetters('styles', [
|
||||
'theme',
|
||||
'clickCount'
|
||||
]),
|
||||
|
||||
// 为了兼容性,仍然保留根级别的getter
|
||||
...mapGetters([
|
||||
'calculatorHistory',
|
||||
'calculatorInitialValue',
|
||||
'calculatorPrecision',
|
||||
'calculatorLastResult',
|
||||
'formSubmittedData',
|
||||
'formSubmissionCount',
|
||||
'buttonClickData',
|
||||
'buttonClickCount',
|
||||
'currentTheme'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
// 直接映射命名空间的mutation
|
||||
...mapMutations('calculator', {
|
||||
addToHistory: 'ADD_HISTORY'
|
||||
}),
|
||||
|
||||
// 映射命名空间的actions
|
||||
...mapActions('calculator', [
|
||||
'clearHistory',
|
||||
'addHistory'
|
||||
]),
|
||||
|
||||
...mapActions('styles', [
|
||||
'toggleTheme'
|
||||
]),
|
||||
|
||||
// 自定义方法
|
||||
resetCalculator() {
|
||||
// 使用命名空间dispatch
|
||||
this.$store.dispatch('calculator/setInitialValue', 0);
|
||||
this.$store.dispatch('calculator/setPrecision', 2);
|
||||
this.addHistory('计算器状态已重置(通过命名空间)');
|
||||
},
|
||||
|
||||
directModuleAccess() {
|
||||
// 直接访问模块状态
|
||||
const theme = this.$store.state.styles.theme;
|
||||
const count = this.$store.state.calculator.history.length;
|
||||
|
||||
// 直接提交模块mutation
|
||||
this.$store.commit('calculator/ADD_HISTORY',
|
||||
`直接访问模块状态: 当前主题=${theme}, 历史记录数=${count}`);
|
||||
},
|
||||
|
||||
dispatchNamespacedAction() {
|
||||
// 分发命名空间action
|
||||
this.$store.dispatch('calculator/addHistory', '通过命名空间action添加的消息');
|
||||
},
|
||||
|
||||
addCustomMessage() {
|
||||
if (this.customMessage.trim()) {
|
||||
// 使用映射的action
|
||||
this.addHistory(`自定义消息: ${this.customMessage}`);
|
||||
this.customMessage = '';
|
||||
}
|
||||
},
|
||||
|
||||
showMappingExample(type) {
|
||||
switch(type) {
|
||||
case 'state':
|
||||
this.mappingExampleTitle = 'mapState 示例';
|
||||
this.mappingExampleCode = `
|
||||
// 在组件中
|
||||
computed: {
|
||||
// 方式1:直接映射state
|
||||
...mapState('calculator', [
|
||||
'initialValue',
|
||||
'precision',
|
||||
'history'
|
||||
]),
|
||||
|
||||
// 方式2:使用对象形式重命名
|
||||
...mapState('calculator', {
|
||||
calcValue: 'initialValue',
|
||||
calcPrecision: 'precision'
|
||||
}),
|
||||
|
||||
// 方式3:访问整个模块
|
||||
...mapState({
|
||||
calculator: state => state.calculator
|
||||
})
|
||||
}`;
|
||||
break;
|
||||
case 'getters':
|
||||
this.mappingExampleTitle = 'mapGetters 示例';
|
||||
this.mappingExampleCode = `
|
||||
// 在组件中
|
||||
computed: {
|
||||
// 方式1:直接映射getters
|
||||
...mapGetters('styles', [
|
||||
'theme',
|
||||
'clickCount',
|
||||
'buttonClickData'
|
||||
]),
|
||||
|
||||
// 方式2:使用对象形式重命名
|
||||
...mapGetters('styles', {
|
||||
currentTheme: 'theme',
|
||||
totalClicks: 'clickCount'
|
||||
})
|
||||
}`;
|
||||
break;
|
||||
case 'actions':
|
||||
this.mappingExampleTitle = 'mapActions 示例';
|
||||
this.mappingExampleCode = `
|
||||
// 在组件中
|
||||
methods: {
|
||||
// 方式1:直接映射actions
|
||||
...mapActions('calculator', [
|
||||
'addHistory',
|
||||
'clearHistory',
|
||||
'setInitialValue'
|
||||
]),
|
||||
|
||||
// 方式2:使用对象形式重命名
|
||||
...mapActions('calculator', {
|
||||
addToHistory: 'addHistory',
|
||||
resetHistory: 'clearHistory'
|
||||
}),
|
||||
|
||||
// 方式3:手动分发
|
||||
manualDispatch() {
|
||||
this.$store.dispatch('calculator/addHistory', 'message');
|
||||
}
|
||||
}`;
|
||||
break;
|
||||
}
|
||||
|
||||
this.mappingExample = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vuex-demo {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.vuex-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.vuex-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #42b983;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #ddd;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #2c3e50;
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.state-display {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.state-card {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.state-card ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.state-card li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px dotted #eee;
|
||||
}
|
||||
|
||||
.state-actions {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-demo {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.action-group {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 12px;
|
||||
background-color: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #3aa876;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.input-group button {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.history-list li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px dotted #eee;
|
||||
}
|
||||
|
||||
.data-preview {
|
||||
margin-top: 10px;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.mapping-examples {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mapping-card {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.mapping-card pre {
|
||||
margin-bottom: 15px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.mapping-example-display {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.mapping-example-display h4 {
|
||||
margin-top: 0;
|
||||
color: #42b983;
|
||||
border-bottom: 1px dashed #ddd;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
|
@ -19,6 +19,41 @@ const routes = [
|
|||
component: function () {
|
||||
return import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/vuex-demo',
|
||||
name: 'vuex-demo',
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "vuex-demo" */ '../components/VuexDemo.vue')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/auto-sync',
|
||||
name: 'auto-sync',
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "auto-sync" */ '../components/AutoSyncHub.vue')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/css-basics',
|
||||
name: 'css-basics',
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "css-basics" */ '../components/CssBasics.vue')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/css-layout',
|
||||
name: 'css-layout',
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "css-layout" */ '../components/CssLayout.vue')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/css-animation',
|
||||
name: 'css-animation',
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "css-animation" */ '../components/CssAnimation.vue')
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -1,17 +1,64 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import calculator from './modules/calculator'
|
||||
import form from './modules/form'
|
||||
import styles from './modules/styles'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
},
|
||||
getters: {
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
actions: {
|
||||
},
|
||||
modules: {
|
||||
calculator,
|
||||
form,
|
||||
styles
|
||||
},
|
||||
// 根级别的getters,用于兼容旧代码
|
||||
getters: {
|
||||
// 计算器相关的getter
|
||||
calculatorHistory: state => state.calculator.history,
|
||||
calculatorInitialValue: state => state.calculator.initialValue,
|
||||
calculatorPrecision: state => state.calculator.precision,
|
||||
calculatorLastResult: state => state.calculator.lastResult,
|
||||
|
||||
// 表单相关的getter
|
||||
formSubmittedData: state => state.form.submittedData,
|
||||
formSubmissionCount: state => state.form.submissionCount,
|
||||
|
||||
// 动态样式相关的getter
|
||||
buttonClickData: state => state.styles.buttonClickData,
|
||||
buttonClickCount: state => state.styles.clickCount,
|
||||
currentTheme: state => state.styles.theme
|
||||
},
|
||||
// 根级别的actions,用于兼容旧代码
|
||||
actions: {
|
||||
// 计算器相关的actions
|
||||
addCalculationHistory({ dispatch }, operation) {
|
||||
dispatch('calculator/addHistory', operation);
|
||||
},
|
||||
clearCalculationHistory({ dispatch }) {
|
||||
dispatch('calculator/clearHistory');
|
||||
},
|
||||
setCalculatorInitialValue({ dispatch }, value) {
|
||||
dispatch('calculator/setInitialValue', value);
|
||||
},
|
||||
setCalculatorPrecision({ dispatch }, precision) {
|
||||
dispatch('calculator/setPrecision', precision);
|
||||
},
|
||||
handleCalculation({ dispatch }, data) {
|
||||
dispatch('calculator/handleCalculation', data);
|
||||
},
|
||||
|
||||
// 表单相关的actions
|
||||
submitFormData({ dispatch }, data) {
|
||||
dispatch('form/submitData', data);
|
||||
},
|
||||
|
||||
// 动态样式相关的actions
|
||||
recordButtonClick({ dispatch }, data) {
|
||||
dispatch('styles/recordButtonClick', data);
|
||||
},
|
||||
toggleTheme({ dispatch }) {
|
||||
dispatch('styles/toggleTheme');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// 计算器模块
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
initialValue: 10,
|
||||
precision: 2,
|
||||
history: ['初始值: 10'],
|
||||
lastResult: 10
|
||||
},
|
||||
getters: {
|
||||
history: state => state.history,
|
||||
initialValue: state => state.initialValue,
|
||||
precision: state => state.precision,
|
||||
lastResult: state => state.lastResult
|
||||
},
|
||||
mutations: {
|
||||
ADD_HISTORY(state, operation) {
|
||||
state.history.push(operation);
|
||||
},
|
||||
CLEAR_HISTORY(state) {
|
||||
state.history = [];
|
||||
},
|
||||
SET_INITIAL_VALUE(state, value) {
|
||||
state.initialValue = value;
|
||||
},
|
||||
SET_PRECISION(state, precision) {
|
||||
state.precision = precision;
|
||||
},
|
||||
SET_RESULT(state, result) {
|
||||
state.lastResult = result;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
addHistory({ commit }, operation) {
|
||||
commit('ADD_HISTORY', operation);
|
||||
},
|
||||
clearHistory({ commit }) {
|
||||
commit('CLEAR_HISTORY');
|
||||
},
|
||||
setInitialValue({ commit }, value) {
|
||||
commit('SET_INITIAL_VALUE', value);
|
||||
},
|
||||
setPrecision({ commit }, precision) {
|
||||
commit('SET_PRECISION', precision);
|
||||
},
|
||||
handleCalculation({ commit }, data) {
|
||||
commit('ADD_HISTORY', data.operation);
|
||||
commit('SET_RESULT', data.result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 表单模块
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
submittedData: null,
|
||||
submissionCount: 0
|
||||
},
|
||||
getters: {
|
||||
submittedData: state => state.submittedData,
|
||||
submissionCount: state => state.submissionCount
|
||||
},
|
||||
mutations: {
|
||||
SUBMIT_DATA(state, data) {
|
||||
state.submittedData = data;
|
||||
state.submissionCount++;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
submitData({ commit }, data) {
|
||||
commit('SUBMIT_DATA', data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 样式模块
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
buttonClickData: null,
|
||||
clickCount: 0,
|
||||
theme: 'light' // 全局主题:light或dark
|
||||
},
|
||||
getters: {
|
||||
buttonClickData: state => state.buttonClickData,
|
||||
clickCount: state => state.clickCount,
|
||||
theme: state => state.theme
|
||||
},
|
||||
mutations: {
|
||||
RECORD_BUTTON_CLICK(state, data) {
|
||||
state.buttonClickData = data;
|
||||
state.clickCount++;
|
||||
},
|
||||
TOGGLE_THEME(state) {
|
||||
state.theme = state.theme === 'light' ? 'dark' : 'light';
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
recordButtonClick({ commit }, data) {
|
||||
commit('RECORD_BUTTON_CLICK', data);
|
||||
},
|
||||
toggleTheme({ commit }) {
|
||||
commit('TOGGLE_THEME');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,118 @@
|
|||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
<h1>关于 Vue2 学习项目</h1>
|
||||
|
||||
<div class="about-content">
|
||||
<section class="about-section">
|
||||
<h2>项目简介</h2>
|
||||
<p>这个项目是为了学习Vue2的基础知识而创建的,包含了多个示例组件,展示了Vue2的核心功能和用法。</p>
|
||||
</section>
|
||||
|
||||
<section class="about-section">
|
||||
<h2>学习内容</h2>
|
||||
<ul>
|
||||
<li><strong>Props传递</strong> - 父组件向子组件传递数据</li>
|
||||
<li><strong>自定义事件 (Emit)</strong> - 子组件向父组件传递数据和事件</li>
|
||||
<li><strong>数字校验</strong> - 表单输入验证和处理</li>
|
||||
<li><strong>Math API</strong> - 在Vue中使用JavaScript的Math对象</li>
|
||||
<li><strong>动态类绑定</strong> - 使用:class实现动态样式</li>
|
||||
<li><strong>计算属性</strong> - 使用computed属性处理数据</li>
|
||||
<li><strong>表单处理</strong> - v-model和表单元素的双向绑定</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="about-section">
|
||||
<h2>组件说明</h2>
|
||||
|
||||
<div class="component-info">
|
||||
<h3>1. 计算器组件</h3>
|
||||
<p>展示了Props传递、自定义事件和Math API的使用。</p>
|
||||
<ul>
|
||||
<li><strong>Props</strong>: initialValue, calculationHistory, precision</li>
|
||||
<li><strong>Events</strong>: calculation-completed, calculator-reset</li>
|
||||
<li><strong>Math API</strong>: Math.pow, Math.sqrt, Math.round, Math.floor, Math.ceil, Math.max, Math.min</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="component-info">
|
||||
<h3>2. 表单验证组件</h3>
|
||||
<p>展示了表单验证和数字校验的使用。</p>
|
||||
<ul>
|
||||
<li><strong>验证类型</strong>: 整数验证、范围验证、小数位数验证、格式验证</li>
|
||||
<li><strong>Events</strong>: form-submitted</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="component-info">
|
||||
<h3>3. 动态样式组件</h3>
|
||||
<p>展示了:class和:style的多种用法。</p>
|
||||
<ul>
|
||||
<li><strong>对象语法</strong>: :class="{ 'class-name': condition }"</li>
|
||||
<li><strong>数组语法</strong>: :class="[activeClass, errorClass]"</li>
|
||||
<li><strong>计算属性</strong>: :class="computedClassObject"</li>
|
||||
<li><strong>内联样式</strong>: :style="{ property: value }"</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.about {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.about-content {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.about-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.about-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.about-section h2 {
|
||||
color: #42b983;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.component-info {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.component-info h3 {
|
||||
color: #2c3e50;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,18 +1,298 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<img alt="Vue logo" src="../assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
<div class="home" :class="{ 'theme-dark': isDarkTheme }">
|
||||
<h1>Vue2 基础学习示例</h1>
|
||||
|
||||
<div class="theme-toggle">
|
||||
<button @click="toggleTheme">
|
||||
切换主题: {{ theme === 'light' ? '🌙 暗色' : '☀️ 亮色' }}
|
||||
</button>
|
||||
<div class="vuex-info">
|
||||
<p>按钮点击次数: {{ clickCount }}</p>
|
||||
<p>表单提交次数: {{ submissionCount }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-section">
|
||||
<h2>1. 计算器组件 (Props & Emit & Math API)</h2>
|
||||
<Calculator
|
||||
:initial-value="initialValue"
|
||||
:calculation-history="history"
|
||||
:precision="precision"
|
||||
@calculation-completed="handleCalculation"
|
||||
@calculator-reset="handleCalculatorReset"
|
||||
/>
|
||||
<div class="component-controls">
|
||||
<div>
|
||||
<label>初始值:</label>
|
||||
<input type="number" v-model.number="localInitialValue" @change="updateInitialValue" />
|
||||
</div>
|
||||
<div>
|
||||
<label>精度:</label>
|
||||
<select v-model.number="localPrecision" @change="updatePrecision">
|
||||
<option :value="0">0</option>
|
||||
<option :value="1">1</option>
|
||||
<option :value="2">2</option>
|
||||
<option :value="3">3</option>
|
||||
<option :value="4">4</option>
|
||||
</select>
|
||||
</div>
|
||||
<button @click="clearHistory">清除历史记录</button>
|
||||
</div>
|
||||
<div class="vuex-state">
|
||||
<h3>Vuex状态:</h3>
|
||||
<p>最后计算结果: {{ lastResult }}</p>
|
||||
<p>历史记录数量: {{ history.length }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-section">
|
||||
<h2>2. 表单验证组件 (数字校验)</h2>
|
||||
<FormValidator @form-submitted="handleFormSubmit" />
|
||||
<div v-if="submittedData" class="form-data">
|
||||
<h3>父组件接收到的表单数据 (来自Vuex):</h3>
|
||||
<pre>{{ JSON.stringify(submittedData, null, 2) }}</pre>
|
||||
<p class="vuex-info">表单已提交 {{ submissionCount }} 次</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-section">
|
||||
<h2>3. 动态样式组件 (:class 用法)</h2>
|
||||
<DynamicStyles @button-clicked="handleButtonClick" />
|
||||
<div v-if="buttonClickData" class="button-click-data">
|
||||
<h3>父组件接收到的按钮点击数据 (来自Vuex):</h3>
|
||||
<pre>{{ JSON.stringify(buttonClickData, null, 2) }}</pre>
|
||||
<p class="vuex-info">按钮已点击 {{ clickCount }} 次</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
import { mapState, mapGetters, mapActions } from 'vuex'
|
||||
import Calculator from '@/components/Calculator.vue'
|
||||
import FormValidator from '@/components/FormValidator.vue'
|
||||
import DynamicStyles from '@/components/DynamicStyles.vue'
|
||||
|
||||
export default {
|
||||
name: 'HomeView',
|
||||
components: {
|
||||
HelloWorld
|
||||
Calculator,
|
||||
FormValidator,
|
||||
DynamicStyles
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 本地数据,用于双向绑定
|
||||
localInitialValue: 10,
|
||||
localPrecision: 2
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 使用命名空间映射各模块的状态和getter
|
||||
...mapState('calculator', {
|
||||
initialValue: state => state.initialValue,
|
||||
precision: state => state.precision
|
||||
}),
|
||||
|
||||
...mapGetters('calculator', [
|
||||
'history',
|
||||
'lastResult'
|
||||
]),
|
||||
|
||||
...mapGetters('form', [
|
||||
'submittedData',
|
||||
'submissionCount'
|
||||
]),
|
||||
|
||||
...mapGetters('styles', [
|
||||
'buttonClickData',
|
||||
'clickCount',
|
||||
'theme'
|
||||
]),
|
||||
|
||||
// 计算属性:判断是否为暗色主题
|
||||
isDarkTheme() {
|
||||
return this.theme === 'dark';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 组件创建时,从store中获取初始值
|
||||
this.localInitialValue = this.initialValue;
|
||||
this.localPrecision = this.precision;
|
||||
},
|
||||
methods: {
|
||||
// 使用命名空间映射各模块的actions
|
||||
...mapActions('calculator', [
|
||||
'handleCalculation',
|
||||
'clearHistory',
|
||||
'setInitialValue',
|
||||
'setPrecision',
|
||||
'addHistory'
|
||||
]),
|
||||
|
||||
...mapActions('form', [
|
||||
'submitData'
|
||||
]),
|
||||
|
||||
...mapActions('styles', [
|
||||
'recordButtonClick',
|
||||
'toggleTheme'
|
||||
]),
|
||||
|
||||
// 计算器相关方法
|
||||
handleCalculatorReset() {
|
||||
this.addHistory('计算器已重置');
|
||||
},
|
||||
|
||||
updateInitialValue() {
|
||||
this.setInitialValue(this.localInitialValue);
|
||||
},
|
||||
|
||||
updatePrecision() {
|
||||
this.setPrecision(this.localPrecision);
|
||||
},
|
||||
|
||||
// 表单相关方法
|
||||
handleFormSubmit(data) {
|
||||
this.submitData(data);
|
||||
},
|
||||
|
||||
// 动态样式相关方法
|
||||
handleButtonClick(data) {
|
||||
this.recordButtonClick(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
background-color: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
.theme-dark .component-section {
|
||||
background-color: #34495e;
|
||||
border-color: #7f8c8d;
|
||||
}
|
||||
|
||||
.theme-dark .component-section h2 {
|
||||
color: #42b983;
|
||||
border-color: #7f8c8d;
|
||||
}
|
||||
|
||||
.theme-dark pre {
|
||||
background-color: #1e2a38;
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
.theme-dark .component-controls,
|
||||
.theme-dark .form-data,
|
||||
.theme-dark .button-click-data,
|
||||
.theme-dark .vuex-state {
|
||||
background-color: #2c3e50;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.theme-dark h1 {
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
.component-section {
|
||||
margin-bottom: 50px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.component-section h2 {
|
||||
margin-top: 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.component-controls {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: #e8f5e9;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.component-controls > div {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.component-controls label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.form-data, .button-click-data, .vuex-state {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: #e3f2fd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.theme-dark .theme-toggle {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
.theme-toggle button {
|
||||
padding: 8px 16px;
|
||||
background-color: #42b983;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.theme-toggle button:hover {
|
||||
background-color: #3aa876;
|
||||
}
|
||||
|
||||
.vuex-info {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.theme-dark .vuex-info {
|
||||
color: #bdc3c7;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue