[架构之美]手动搭建Vue3 前端项目框架

[架构之美]手动搭建Vue3 前端项目框架

我们将手动创建一个完整的Vue前端项目,包含基础结构、路由、状态管理和UI组件。下面是实现方案:

一. 项目结构设计

1.1 项目架构设计

my-vue-project/
├── public/
│   ├── index.html
│   └── favicon.ico
├── src/
│   ├── assets/
│   │   └── logo.png
│   │   ├── main.css
│   ├── components/
│   │   ├── NavBar.vue
│   │   ├── AppFooter.vue
│   │   └── ProductCard.vue
│   ├── views/
│   │   ├── HomeView.vue
│   │   ├── AboutView.vue
│   │   ├── ProductsView.vue
│   │   └── ContactView.vue
│   ├── store/
│   │   └── index.js
│   ├── router/
│   │   └── index.js
│   ├── App.vue
│   └── main.js
├── package.json
└── README.md

1.2 环境准备

1.确保安装Node.js和npm

node -v
npm -v

本机之前已安装过,所以不再演示安装Node.js。

2.安装Vue CLL

npm install -g @vue/cli

3.创建Vue项目

使用Vue CLI创建一个新项目。在终端中运行

vue create my-vue-project

其中my-vue-project是项目名称。

接下来,CLI会提示选择预设。可以选择默认的Vue 2或Vue 3,也可以手动选择特性。对于这个示例,我们选择Vue 3。

4.进入项目启动开发服务器

cd my-vue-project
npm run serve
#生产构建
npm run build

在这里插入图片描述

5.访问界面

项目启动后,可以在浏览器中访问http://localhost:8080来查看Vue应用

在这里插入图片描述

6.安装其它工具(可选)

#安装Axios进行HTTP请求:
npm install axios
#安装Element Plus(UI库)
npm install element-plus
#配置ESLint和Prettier(可选)
npm install --save-dev eslint prettier eslint-plugin-vue eslint-config-prettier

二. 完整实现代码

以上是简单的将框架搭建起来,下面填充具体业务内容:

2.1 首先安装必要的依赖


npm install vue vue-router vuex

2.2 public/index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue 项目</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
  <div id="app"></div>
</body>
</html>

2.3 src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

// 全局样式
import './assets/main.css'

createApp(App)
  .use(store)
  .use(router)
  .mount('#app')

2.4 src/assets/main.css

:root {
  --primary: #3498db;
  --secondary: #2ecc71;
  --dark: #2c3e50;
  --light: #ecf0f1;
  --danger: #e74c3c;
  --warning: #f39c12;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
  background-color: #f8f9fa;
  color: #333;
}

.container {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 15px;
}

.btn {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.3s ease;
}

.btn-primary {
  background-color: var(--primary);
  color: white;
}

.btn-primary:hover {
  background-color: #2980b9;
}

.btn-outline {
  background: transparent;
  border: 2px solid var(--primary);
  color: var(--primary);
}

.btn-outline:hover {
  background-color: var(--primary);
  color: white;
}

.section {
  padding: 80px 0;
}

.section-title {
  text-align: center;
  margin-bottom: 50px;
  font-size: 2.5rem;
  color: var(--dark);
  position: relative;
}

.section-title::after {
  content: '';
  position: absolute;
  bottom: -15px;
  left: 50%;
  transform: translateX(-50%);
  width: 80px;
  height: 4px;
  background: var(--primary);
  border-radius: 2px;
}

2.5 src/App.vue

<template>
  <div id="app">
    <NavBar />
    <div class="main-content">
      <router-view />
    </div>
    <Footer />
  </div>
</template>

<script>
import NavBar from './components/NavBar.vue'
import Footer from './components/Footer.vue'

export default {
  name: 'App',
  components: {
    NavBar,
    Footer
  }
}
</script>

<style scoped>
#app {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.main-content {
  flex: 1;
  padding: 30px 0;
}
</style>

2.6 src/components/NavBar.vue

<template>
  <nav class="navbar">
    <div class="container">
      <router-link to="/" class="logo">
        <i class="fas fa-cube"></i>
        <span>VueProject</span>
      </router-link>
      
      <div class="nav-links">
        <router-link to="/">首页</router-link>
        <router-link to="/about">关于</router-link>
        <router-link to="/products">产品</router-link>
        <router-link to="/contact">联系我们</router-link>
      </div>
      
      <div class="nav-actions">
        <button class="btn btn-outline">
          <i class="fas fa-user"></i> 登录
        </button>
      </div>
    </div>
  </nav>
</template>

<script>
export default {
  name: 'NavBar'
}
</script>

<style scoped>
.navbar {
  background-color: white;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  padding: 15px 0;
  position: sticky;
  top: 0;
  z-index: 100;
}

.navbar .container {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.logo {
  display: flex;
  align-items: center;
  text-decoration: none;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--dark);
}

.logo i {
  margin-right: 10px;
  color: var(--primary);
}

.nav-links {
  display: flex;
  gap: 30px;
}

.nav-links a {
  text-decoration: none;
  color: var(--dark);
  font-weight: 500;
  position: relative;
  padding: 5px 0;
}

.nav-links a.router-link-exact-active {
  color: var(--primary);
}

.nav-links a.router-link-exact-active::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background-color: var(--primary);
  border-radius: 3px;
}

.nav-actions .btn {
  padding: 8px 15px;
}
</style>

2.7 src/components/ AppFooter.vue

<template>
  <footer class="footer">
    <div class="container">
      <div class="footer-content">
        <div class="footer-section">
          <h3>VueProject</h3>
          <p>基于Vue.js构建的现代前端项目</p>
          <div class="social-links">
            <a href="#"><i class="fab fa-github"></i></a>
            <a href="#"><i class="fab fa-twitter"></i></a>
            <a href="#"><i class="fab fa-linkedin"></i></a>
            <a href="#"><i class="fab fa-facebook"></i></a>
          </div>
        </div>
        
        <div class="footer-section">
          <h4>导航</h4>
          <ul>
            <li><router-link to="/">首页</router-link></li>
            <li><router-link to="/about">关于我们</router-link></li>
            <li><router-link to="/products">产品</router-link></li>
            <li><router-link to="/contact">联系我们</router-link></li>
          </ul>
        </div>
        
        <div class="footer-section">
          <h4>联系方式</h4>
          <ul>
            <li><i class="fas fa-map-marker-alt"></i> 北京市朝阳区科技园</li>
            <li><i class="fas fa-phone"></i> +86 123 4567 8910</li>
            <li><i class="fas fa-envelope"></i> contact@vueproject.com</li>
          </ul>
        </div>
      </div>
      
      <div class="footer-bottom">
        <p>&copy; 2023 VueProject. 保留所有权利。</p>
      </div>
    </div>
  </footer>
</template>

<script>
export default {
  name: 'AppFooter'
}
</script>

<style scoped>
.footer {
  background-color: var(--dark);
  color: white;
  padding: 60px 0 0;
}

.footer-content {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 40px;
  margin-bottom: 40px;
}

.footer-section h3 {
  font-size: 1.8rem;
  margin-bottom: 20px;
}

.footer-section h4 {
  font-size: 1.2rem;
  margin-bottom: 20px;
  position: relative;
  padding-bottom: 10px;
}

.footer-section h4::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 50px;
  height: 3px;
  background: var(--primary);
}

.footer-section p {
  margin-bottom: 20px;
  line-height: 1.6;
}

.footer-section ul {
  list-style: none;
}

.footer-section ul li {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
}

.footer-section ul li i {
  margin-right: 10px;
  color: var(--primary);
}

.footer-section a {
  color: #ddd;
  text-decoration: none;
  transition: color 0.3s;
}

.footer-section a:hover {
  color: var(--primary);
}

.social-links {
  display: flex;
  gap: 15px;
}

.social-links a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: rgba(255,255,255,0.1);
  border-radius: 50%;
  transition: all 0.3s;
}

.social-links a:hover {
  background: var(--primary);
  transform: translateY(-5px);
}

.footer-bottom {
  border-top: 1px solid rgba(255,255,255,0.1);
  padding: 20px 0;
  text-align: center;
  font-size: 0.9rem;
  color: #aaa;
}
</style>

2.8 src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
import Products from '../views/Products.vue'
import Contact from '../views/Contact.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  },
  {
    path: '/products',
    name: 'Products',
    component: Products
  },
  {
    path: '/contact',
    name: 'Contact',
    component: Contact
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
  scrollBehavior() {
    return { top: 0, behavior: 'smooth' }
  }
})

export default router

2.9 src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    products: [
      { id: 1, name: 'Vue基础课程', price: 299, image: 'https://via.placeholder.com/300' },
      { id: 2, name: 'Vue高级实战', price: 499, image: 'https://via.placeholder.com/300' },
      { id: 3, name: 'Vue状态管理', price: 399, image: 'https://via.placeholder.com/300' },
      { id: 4, name: 'Vue组件开发', price: 349, image: 'https://via.placeholder.com/300' },
      { id: 5, name: 'Vue路由管理', price: 299, image: 'https://via.placeholder.com/300' },
      { id: 6, name: 'Vue性能优化', price: 449, image: 'https://via.placeholder.com/300' }
    ],
    cart: []
  },
  mutations: {
    addToCart(state, product) {
      const existingItem = state.cart.find(item => item.id === product.id)
      if (existingItem) {
        existingItem.quantity++
      } else {
        state.cart.push({ ...product, quantity: 1 })
      }
    },
    removeFromCart(state, productId) {
      state.cart = state.cart.filter(item => item.id !== productId)
    }
  },
  actions: {
    addProductToCart({ commit }, product) {
      commit('addToCart', product)
    },
    removeProductFromCart({ commit }, productId) {
      commit('removeFromCart', productId)
    }
  },
  getters: {
    cartTotal(state) {
      return state.cart.reduce((total, item) => total + (item.price * item.quantity), 0)
    },
    cartItemCount(state) {
      return state.cart.reduce((count, item) => count + item.quantity, 0)
    }
  }
})

2.10 src/views/ HomeView.vue

<template>
  <div class="home">
    <!-- Hero Section -->
    <section class="hero">
      <div class="container">
        <div class="hero-content">
          <h1>构建现代化的 Vue 应用</h1>
          <p>使用 Vue.js 创建响应式、高性能的单页面应用程序</p>
          <div class="hero-buttons">
            <router-link to="/products" class="btn btn-primary">浏览产品</router-link>
            <router-link to="/about" class="btn btn-outline">了解更多</router-link>
          </div>
        </div>
      </div>
    </section>

    <!-- Features Section -->
    <section class="section features">
      <div class="container">
        <h2 class="section-title">为什么选择 Vue.js?</h2>
        <div class="features-grid">
          <div class="feature-card">
            <div class="feature-icon">
              <i class="fas fa-bolt"></i>
            </div>
            <h3>高性能</h3>
            <p>虚拟DOM和高效的渲染机制确保应用流畅运行</p>
          </div>

          <div class="feature-card">
            <div class="feature-icon">
              <i class="fas fa-layer-group"></i>
            </div>
            <h3>组件化</h3>
            <p>可复用组件让开发更高效,维护更简单</p>
          </div>

          <div class="feature-card">
            <div class="feature-icon">
              <i class="fas fa-code"></i>
            </div>
            <h3>渐进式框架</h3>
            <p>可根据项目需要逐步采用,灵活适配各种场景</p>
          </div>
        </div>
      </div>
    </section>

    <!-- Products Preview -->
    <section class="section products-preview">
      <div class="container">
        <h2 class="section-title">热门产品</h2>
        <div class="products-grid">
          <ProductCard
              v-for="product in featuredProducts"
              :key="product.id"
              :product="product"
              @add-to-cart="addToCart"
          />
        </div>
        <div class="text-center" style="margin-top: 30px;">
          <router-link to="/products" class="btn btn-primary">查看所有产品</router-link>
        </div>
      </div>
    </section>
  </div>
</template>

<script>
import ProductCard from '@/components/ProductCard.vue'
import { mapState, mapActions } from 'vuex'

export default {
  name: 'HomeView',
  components: {
    ProductCard
  },
  computed: {
    ...mapState(['products']),
    featuredProducts() {
      return this.products.slice(0, 3)
    }
  },
  methods: {
    ...mapActions(['addProductToCart']),
    addToCart(product) {
      this.addProductToCart(product)
      this.$notify({
        title: '已添加到购物车',
        text: `${product.name} 已添加到您的购物车`,
        type: 'success'
      })
    }
  }
}
</script>

<style scoped>
.hero {
  background: linear-gradient(135deg, #3498db, #8e44ad);
  color: white;
  padding: 100px 0;
  text-align: center;
}

.hero-content {
  max-width: 800px;
  margin: 0 auto;
}

.hero h1 {
  font-size: 3.5rem;
  margin-bottom: 20px;
  text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}

.hero p {
  font-size: 1.5rem;
  margin-bottom: 40px;
  opacity: 0.9;
}

.hero-buttons {
  display: flex;
  justify-content: center;
  gap: 20px;
}

.features-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 30px;
}

.feature-card {
  background: white;
  border-radius: 10px;
  padding: 30px;
  text-align: center;
  box-shadow: 0 5px 15px rgba(0,0,0,0.05);
  transition: transform 0.3s ease;
}

.feature-card:hover {
  transform: translateY(-10px);
}

.feature-icon {
  width: 80px;
  height: 80px;
  background: rgba(52, 152, 219, 0.1);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 20px;
  color: var(--primary);
  font-size: 2rem;
}

.feature-card h3 {
  margin-bottom: 15px;
  color: var(--dark);
}

.feature-card p {
  color: #666;
  line-height: 1.6;
}

.products-preview {
  background-color: #f8f9fa;
}

.products-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 30px;
}
</style>

2.11 src/components/ProductCard.vue

<template>
  <div class="product-card">
    <div class="product-image">
      <img :src="product.image" :alt="product.name">
    </div>
    <div class="product-details">
      <h3>{{ product.name }}</h3>
      <p class="price">¥{{ product.price }}</p>
      <p class="description">高质量Vue.js学习资源,提升你的前端开发技能</p>
      <button class="btn btn-primary" @click="$emit('add-to-cart', product)">
        <i class="fas fa-shopping-cart"></i> 加入购物车
      </button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ProductCard',
  props: {
    product: {
      type: Object,
      required: true
    }
  }
}
</script>

<style scoped>
.product-card {
  background: white;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 5px 15px rgba(0,0,0,0.05);
  transition: all 0.3s ease;
}

.product-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}

.product-image {
  height: 200px;
  overflow: hidden;
}

.product-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s ease;
}

.product-card:hover .product-image img {
  transform: scale(1.05);
}

.product-details {
  padding: 20px;
}

.product-details h3 {
  margin-bottom: 10px;
  color: var(--dark);
}

.price {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--primary);
  margin-bottom: 15px;
}

.description {
  color: #666;
  margin-bottom: 20px;
  min-height: 60px;
}
</style>

2.12 src/views/ AboutView.vue

<template>
  <div class="about">
    <section class="hero">
      <div class="container">
        <h1>关于我们</h1>
        <p>致力于提供高质量的Vue.js学习资源和开发工具</p>
      </div>
    </section>

    <section class="section">
      <div class="container">
        <div class="about-content">
          <div class="about-text">
            <h2 class="section-title">我们的故事</h2>
            <p>VueProject成立于2018年,由一群热爱前端技术的开发者共同创立。我们见证了Vue.js从一个小众框架发展成为今天的主流前端框架之一。</p>
            <p>我们的使命是帮助开发者更好地掌握Vue.js技术栈,提高开发效率,构建更优秀的Web应用。</p>
            <p>通过精心设计的课程、实用的工具和活跃的社区,我们为全球超过10万名开发者提供了学习资源和技术支持。</p>
            <div class="stats">
              <div class="stat">
                <h3>10,000+</h3>
                <p>活跃用户</p>
              </div>
              <div class="stat">
                <h3>50+</h3>
                <p>专业课程</p>
              </div>
              <div class="stat">
                <h3>100+</h3>
                <p>技术文章</p>
              </div>
            </div>
          </div>
          <div class="about-image">
            <img src="https://via.placeholder.com/500" alt="团队照片">
          </div>
        </div>
      </div>
    </section>

    <section class="section team">
      <div class="container">
        <h2 class="section-title">核心团队</h2>
        <div class="team-grid">
          <div class="team-member">
            <img src="https://via.placeholder.com/200" alt="团队成员">
            <h3>张明</h3>
            <p>创始人 & 技术总监</p>
            <p>10年前端开发经验,Vue.js官方文档贡献者</p>
          </div>
          <div class="team-member">
            <img src="https://via.placeholder.com/200" alt="团队成员">
            <h3>李华</h3>
            <p>课程总监</p>
            <p>资深前端讲师,开发多门Vue.js热门课程</p>
          </div>
          <div class="team-member">
            <img src="https://via.placeholder.com/200" alt="团队成员">
            <h3>王芳</h3>
            <p>产品经理</p>
            <p>负责产品规划和用户体验优化</p>
          </div>
        </div>
      </div>
    </section>
  </div>
</template>

<script>
export default {
  name: 'About'
}
</script>

<style scoped>
.hero {
  background: linear-gradient(135deg, #2ecc71, #3498db);
  color: white;
  padding: 100px 0;
  text-align: center;
}

.hero h1 {
  font-size: 3rem;
  margin-bottom: 20px;
}

.about-content {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 50px;
  align-items: center;
}

.about-image img {
  width: 100%;
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}

.stats {
  display: flex;
  gap: 30px;
  margin-top: 30px;
}

.stat {
  text-align: center;
}

.stat h3 {
  font-size: 2rem;
  color: var(--primary);
  margin-bottom: 5px;
}

.team {
  background-color: #f8f9fa;
}

.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 30px;
}

.team-member {
  background: white;
  border-radius: 10px;
  padding: 30px;
  text-align: center;
  box-shadow: 0 5px 15px rgba(0,0,0,0.05);
}

.team-member img {
  width: 150px;
  height: 150px;
  border-radius: 50%;
  object-fit: cover;
  margin: 0 auto 20px;
}

.team-member h3 {
  margin-bottom: 10px;
  color: var(--dark);
}

.team-member p {
  margin-bottom: 8px;
  color: #666;
}

@media (max-width: 768px) {
  .about-content {
    grid-template-columns: 1fr;
  }
  
  .stats {
    flex-direction: column;
    gap: 20px;
  }
}
</style>

2.13 src/views/ ProductsView.vue

<template>
  <div class="products">
    <section class="hero">
      <div class="container">
        <h1>产品中心</h1>
        <p>探索我们的Vue.js学习资源和开发工具</p>
      </div>
    </section>

    <section class="section">
      <div class="container">
        <div class="products-header">
          <h2 class="section-title">所有产品</h2>
          <div class="search-filter">
            <input type="text" placeholder="搜索产品..." v-model="searchQuery">
            <select v-model="sortOption">
              <option value="default">默认排序</option>
              <option value="price-asc">价格: 低到高</option>
              <option value="price-desc">价格: 高到低</option>
            </select>
          </div>
        </div>
        
        <div class="products-grid">
          <ProductCard 
            v-for="product in filteredProducts" 
            :key="product.id" 
            :product="product"
            @add-to-cart="addToCart"
          />
        </div>
      </div>
    </section>
  </div>
</template>

<script>
import ProductCard from '@/components/ProductCard.vue'
import { mapState, mapActions } from 'vuex'

export default {
  name: 'Products',
  components: {
    ProductCard
  },
  data() {
    return {
      searchQuery: '',
      sortOption: 'default'
    }
  },
  computed: {
    ...mapState(['products']),
    filteredProducts() {
      let result = [...this.products]
      
      // 搜索过滤
      if (this.searchQuery) {
        const query = this.searchQuery.toLowerCase()
        result = result.filter(product => 
          product.name.toLowerCase().includes(query)
      }
      
      // 排序
      if (this.sortOption === 'price-asc') {
        result.sort((a, b) => a.price - b.price)
      } else if (this.sortOption === 'price-desc') {
        result.sort((a, b) => b.price - a.price)
      }
      
      return result
    }
  },
  methods: {
    ...mapActions(['addProductToCart']),
    addToCart(product) {
      this.addProductToCart(product)
      this.$notify({
        title: '已添加到购物车',
        text: `${product.name} 已添加到您的购物车`,
        type: 'success'
      })
    }
  }
}
</script>

<style scoped>
.hero {
  background: linear-gradient(135deg, #f39c12, #e74c3c);
  color: white;
  padding: 100px 0;
  text-align: center;
}

.products-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 40px;
  flex-wrap: wrap;
}

.search-filter {
  display: flex;
  gap: 15px;
}

.search-filter input {
  padding: 10px 15px;
  border: 1px solid #ddd;
  border-radius: 4px;
  min-width: 250px;
}

.search-filter select {
  padding: 10px 15px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background: white;
}

.products-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 30px;
}

@media (max-width: 768px) {
  .products-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
  }
  
  .search-filter {
    width: 100%;
    flex-direction: column;
  }
  
  .search-filter input {
    min-width: 100%;
  }
}
</style>

2.14 src/views/ ContactView.vue

<template>
  <div class="contact">
    <section class="hero">
      <div class="container">
        <h1>联系我们</h1>
        <p>我们很乐意听取您的反馈和建议</p>
      </div>
    </section>

    <section class="section">
      <div class="container">
        <div class="contact-content">
          <div class="contact-info">
            <h2 class="section-title">联系方式</h2>
            <p>如果您对我们的产品有任何疑问或建议,请随时联系我们。我们的团队将尽快回复您。</p>
            
            <div class="info-item">
              <i class="fas fa-map-marker-alt"></i>
              <div>
                <h3>地址</h3>
                <p>北京市朝阳区科技园区88号</p>
              </div>
            </div>
            
            <div class="info-item">
              <i class="fas fa-phone"></i>
              <div>
                <h3>电话</h3>
                <p>+86 123 4567 8910</p>
              </div>
            </div>
            
            <div class="info-item">
              <i class="fas fa-envelope"></i>
              <div>
                <h3>邮箱</h3>
                <p>contact@vueproject.com</p>
              </div>
            </div>
            
            <div class="info-item">
              <i class="fas fa-clock"></i>
              <div>
                <h3>工作时间</h3>
                <p>周一至周五: 9:00 - 18:00</p>
                <p>周六至周日: 休息</p>
              </div>
            </div>
          </div>
          
          <div class="contact-form">
            <h2 class="section-title">发送消息</h2>
            <form @submit.prevent="submitForm">
              <div class="form-group">
                <input type="text" placeholder="您的姓名" v-model="form.name" required>
              </div>
              
              <div class="form-group">
                <input type="email" placeholder="您的邮箱" v-model="form.email" required>
              </div>
              
              <div class="form-group">
                <input type="text" placeholder="主题" v-model="form.subject" required>
              </div>
              
              <div class="form-group">
                <textarea placeholder="您的消息" v-model="form.message" required></textarea>
              </div>
              
              <button type="submit" class="btn btn-primary">发送消息</button>
            </form>
          </div>
        </div>
      </div>
    </section>

    <!-- Map Section -->
    <section class="map-section">
      <div class="map-container">
        <div class="map-placeholder">
          <i class="fas fa-map-marked-alt"></i>
          <p>北京市朝阳区科技园区88号</p>
        </div>
      </div>
    </section>
  </div>
</template>

<script>
export default {
  name: 'Contact',
  data() {
    return {
      form: {
        name: '',
        email: '',
        subject: '',
        message: ''
      }
    }
  },
  methods: {
    submitForm() {
      // 在实际应用中,这里会发送到后端API
      this.$notify({
        title: '消息已发送',
        text: '感谢您的留言,我们会尽快回复您!',
        type: 'success'
      })
      
      // 重置表单
      this.form = {
        name: '',
        email: '',
        subject: '',
        message: ''
      }
    }
  }
}
</script>

<style scoped>
.hero {
  background: linear-gradient(135deg, #9b59b6, #3498db);
  color: white;
  padding: 100px 0;
  text-align: center;
}

.contact-content {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 50px;
}

.contact-info .section-title,
.contact-form .section-title {
  text-align: left;
  margin-bottom: 30px;
}

.contact-info p {
  margin-bottom: 30px;
  line-height: 1.6;
}

.info-item {
  display: flex;
  gap: 20px;
  margin-bottom: 25px;
}

.info-item i {
  font-size: 1.5rem;
  color: var(--primary);
  margin-top: 5px;
}

.info-item h3 {
  margin-bottom: 5px;
  color: var(--dark);
}

.form-group {
  margin-bottom: 20px;
}

.form-group input,
.form-group textarea {
  width: 100%;
  padding: 12px 15px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
}

.form-group textarea {
  min-height: 150px;
  resize: vertical;
}

.map-section {
  margin-top: 50px;
}

.map-container {
  height: 400px;
  background: #f8f9fa;
  display: flex;
  align-items: center;
  justify-content: center;
}

.map-placeholder {
  text-align: center;
  color: var(--dark);
}

.map-placeholder i {
  font-size: 4rem;
  color: var(--primary);
  margin-bottom: 20px;
}

@media (max-width: 768px) {
  .contact-content {
    grid-template-columns: 1fr;
  }
}
</style>

三. 项目运行说明

3.1 安装依赖

npm install

3.2 启动开发服务器

npm run serve

3.3 构建生产版本

npm run build

3.4 项目框架界面

在这里插入图片描述

这个项目提供了完整的Vue前端应用基础,包括导航、页面路由、状态管理和响应式设计,可以轻松扩展为电子商务、博客或企业网站等各种类型的应用。

希望本教程对您有帮助,请点赞❤️收藏⭐关注支持!欢迎在评论区留言交流技术细节!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曼岛_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值