fix: vue2语法学习

This commit is contained in:
Guwan 2025-07-20 22:03:13 +08:00
parent d68787a929
commit c51f389a14
17 changed files with 5485 additions and 18 deletions

5
.gitignore vendored
View File

@ -21,3 +21,8 @@ pnpm-debug.log*
*.njsproj
*.sln
*.sw?
package-lock.json
yarn.lock
**/cert/

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

471
src/components/VuexDemo.vue Normal file
View File

@ -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: {
// 1state
...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: {
// 1getters
...mapGetters('styles', [
'theme',
'clickCount',
'buttonClickData'
]),
// 2使
...mapGetters('styles', {
currentTheme: 'theme',
totalClicks: 'clickCount'
})
}`;
break;
case 'actions':
this.mappingExampleTitle = 'mapActions 示例';
this.mappingExampleCode = `
//
methods: {
// 1actions
...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>

View File

@ -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')
}
}
]

View File

@ -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');
}
}
})

View File

@ -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);
}
}
}

23
src/store/modules/form.js Normal file
View File

@ -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);
}
}
}

View File

@ -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');
}
}
}

View File

@ -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>

View File

@ -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>