O基础构建VUE3框架前端的skills

技能名称:vue3-tailwind-style

描述:提供基于 Tailwind CSS v3 的 Vue 3 项目样式配置,包括颜色方案、工具类和组件样式。用于在毕业设计项目中应用统一的界面风格。

指令:Vue 3 Tailwind 样式技能

概述

本技能提供了一套基于 Tailwind CSS v3 的 Vue 3 项目样式配置,基于属地权益业务平台运营端的界面风格。它包含了完整的颜色方案、自定义工具类和组件样式,可直接应用于毕业设计项目的前端开发。

技术栈

  • Vue 3:前端框架
  • Tailwind CSS v3:实用优先的 CSS 框架
  • Font Awesome 4.7.0:图标库

安装和配置

1. 初始化 Vue 3 项目

npm create vite@latest my-project -- --template vue
cd my-project
npm install

2. 安装依赖

# 安装 Tailwind CSS v3
npm install -D tailwindcss postcss autoprefixer

# 初始化 Tailwind 配置
npx tailwindcss init -p

# 安装 Font Awesome
npm install @fortawesome/fontawesome-free

3. 配置 Tailwind CSS

修改 tailwind.config.js 文件:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      colors: {
        primary: '#1890ff',
        success: '#52c41a',
        warning: '#faad14',
        error: '#f5222d',
        background: '#f0f2f5',
        'card-bg': '#ffffff',
        'text-primary': '#000000',
        'text-secondary': '#666666',
        'text-light': '#999999',
        'border-color': '#e8e8e8'
      },
      fontFamily: {
        sans: ['-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif']
      },
      boxShadow: {
        card: '0 2px 8px rgba(0, 0, 0, 0.15)'
      }
    },
  },
  plugins: [],
}

4. 配置 CSS

修改 src/index.css 文件:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .content-auto {
    content-visibility: auto;
  }
  .scrollbar-hide::-webkit-scrollbar {
    display: none;
  }
  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
  .card {
    @apply bg-card-bg rounded-md shadow-card p-4;
  }
  .btn {
    @apply px-4 py-2 rounded-md font-medium transition-all duration-200;
  }
  .btn-primary {
    @apply bg-primary text-white hover:bg-opacity-90;
  }
  .btn-default {
    @apply bg-white border border-border-color hover:bg-background;
  }
  .btn-danger {
    @apply bg-error text-white hover:bg-opacity-90;
  }
  .btn-success {
    @apply bg-success text-white hover:bg-opacity-90;
  }
  .table-row-hover {
    @apply hover:bg-background transition-colors duration-150;
  }
  .nav-item {
    @apply flex items-center px-4 py-3 rounded-md transition-colors duration-150;
  }
  .nav-item i {
    @apply w-5 text-center mr-3;
  }
  .nav-item-active {
    @apply bg-primary bg-opacity-10 text-primary;
  }
  .nav-item-inactive {
    @apply text-text-secondary hover:bg-background;
  }
  .breadcrumb {
    @apply text-sm text-text-secondary;
  }
  .breadcrumb-active {
    @apply text-text-primary;
  }
  .breadcrumb-separator {
    @apply mx-2;
  }
  .badge {
    @apply px-2 py-1 text-xs rounded-full;
  }
  .badge-success {
    @apply bg-success bg-opacity-10 text-success;
  }
  .badge-warning {
    @apply bg-warning bg-opacity-10 text-warning;
  }
  .badge-error {
    @apply bg-error bg-opacity-10 text-error;
  }
  .badge-default {
    @apply bg-background text-text-secondary;
  }
  .input {
    @apply w-full px-3 py-2 border border-border-color rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50 focus:border-primary;
  }
  .select {
    @apply w-full px-3 py-2 border border-border-color rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50 focus:border-primary appearance-none bg-white;
  }
  .date-picker {
    @apply w-full px-3 py-2 border border-border-color rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50 focus:border-primary;
  }
  .tab {
    @apply px-4 py-2 font-medium border-b-2 transition-colors duration-150;
  }
  .tab-active {
    @apply text-primary border-primary;
  }
  .tab-inactive {
    @apply text-text-secondary border-transparent hover:text-text-primary hover:border-border-color;
  }
}

5. 导入样式

修改 src/main.js 文件:

import { createApp } from 'vue'
import './index.css'
import '@fortawesome/fontawesome-free/css/all.css'
import App from './App.vue'

createApp(App).mount('#app')

组件样式示例

1. 布局组件

以下内容为示例代码,仅供参考,请根据实际项目需求进行修改和调整。

主布局

<template>
  <div class="bg-background font-sans text-text-primary min-h-screen flex flex-col">
    <!-- 顶部导航栏 -->
    <header class="bg-white border-b border-border-color h-16 flex items-center justify-between px-6 sticky top-0 z-10">
      <div class="flex items-center">
        <img src="@/assets/logo.png" alt="Logo" class="h-8 mr-3">
        <h1 class="text-xl font-bold mr-8">项目名称</h1>
        <div class="breadcrumb">
          <a href="#" class="hover:text-primary">首页</a>
          <span class="breadcrumb-separator">/</span>
          <span class="breadcrumb-active">当前页面</span>
        </div>
      </div>
      <div class="flex items-center gap-4">
        <div class="relative">
          <input type="text" placeholder="搜索..." class="input w-64 pl-10">
          <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-text-light"></i>
        </div>
        <div class="relative">
          <button class="btn btn-default flex items-center gap-2">
            <span>通知</span>
            <span class="absolute -top-1 -right-1 bg-error text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">3</span>
            <i class="fa fa-bell-o"></i>
          </button>
        </div>
        <div class="flex items-center gap-2">
          <div class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center">
            <span>管</span>
          </div>
          <div>
            <div class="text-sm font-medium">用户名</div>
            <div class="text-xs text-text-light">管理员</div>
          </div>
          <button class="text-text-light hover:text-text-primary transition-colors" title="退出登录">
            <i class="fa fa-sign-out"></i>
          </button>
        </div>
      </div>
    </header>

    <div class="flex flex-1 overflow-hidden">
      <!-- 左侧导航栏 -->
      <aside class="w-64 bg-white border-r border-border-color flex flex-col" style="height: calc(100vh - 4rem);">
        <nav class="flex-1 overflow-y-auto p-2">
          <ul id="navMenu">
            <li class="mb-1">
              <a href="#" class="nav-item nav-item-active">
                <i class="fa fa-tachometer"></i>
                <span>数据概览</span>
              </a>
            </li>
            <li class="mb-1">
              <a href="#" class="nav-item nav-item-inactive">
                <i class="fa fa-shopping-cart"></i>
                <span>订单管理</span>
              </a>
            </li>
            <li class="mb-1">
              <a href="#" class="nav-item nav-item-inactive">
                <i class="fa fa-building-o"></i>
                <span>商户管理</span>
              </a>
            </li>
            <li class="mb-1">
              <a href="#" class="nav-item nav-item-inactive">
                <i class="fa fa-th-large"></i>
                <span>门店管理</span>
              </a>
            </li>
          </ul>
        </nav>
        <div class="p-4 border-t border-border-color">
          <div class="flex items-center gap-2">
            <i class="fa fa-question-circle text-text-light"></i>
            <span class="text-sm text-text-secondary">帮助中心</span>
          </div>
          <div class="flex items-center gap-2 mt-2">
            <i class="fa fa-life-ring text-text-light"></i>
            <span class="text-sm text-text-secondary">联系客服</span>
          </div>
        </div>
      </aside>

      <!-- 主内容区 -->
      <main class="flex-1 overflow-y-auto p-6">
        <slot></slot>
      </main>
    </div>
  </div>
</template>

<script setup>
// 布局组件逻辑
</script>

2. 卡片组件

<template>
  <div class="card mb-4">
    <div class="flex justify-between items-center mb-4">
      <h3 class="text-lg font-semibold">{{ title }}</h3>
      <slot name="header-actions"></slot>
    </div>
    <slot></slot>
  </div>
</template>

<script setup>
defineProps({
  title: {
    type: String,
    default: ''
  }
})
</script>

3. 表格组件

<template>
  <div class="card">
    <div class="overflow-x-auto">
      <table class="w-full">
        <thead>
          <tr class="border-b border-border-color">
            <th v-for="column in columns" :key="column.key" class="text-left py-3 px-4 font-medium text-text-secondary">
              {{ column.title }}
            </th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(row, index) in data" :key="index" class="border-b border-border-color table-row-hover">
            <td v-for="column in columns" :key="column.key" class="py-3 px-4">
              <slot :name="`cell-${column.key}`" :row="row" :index="index">
                {{ row[column.key] }}
              </slot>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <div class="flex justify-between items-center mt-4">
      <div class="text-sm text-text-secondary">
        共 {{ data.length }} 条记录
      </div>
      <div class="flex items-center gap-2">
        <button class="btn btn-default" :disabled="currentPage === 1">
          <i class="fa fa-chevron-left"></i>
        </button>
        <span class="text-sm">第 {{ currentPage }} 页,共 {{ totalPages }} 页</span>
        <button class="btn btn-default" :disabled="currentPage === totalPages">
          <i class="fa fa-chevron-right"></i>
        </button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  columns: {
    type: Array,
    required: true
  },
  data: {
    type: Array,
    default: () => []
  },
  currentPage: {
    type: Number,
    default: 1
  },
  pageSize: {
    type: Number,
    default: 10
  }
})

const totalPages = computed(() => {
  return Math.ceil(props.data.length / props.pageSize)
})
</script>

4. 表单组件

<template>
  <div class="card">
    <h3 class="text-lg font-semibold mb-4">{{ title }}</h3>
    <form @submit.prevent="handleSubmit">
      <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
        <div v-for="field in fields" :key="field.name" class="form-item">
          <label :for="field.name" class="block text-sm font-medium text-text-secondary mb-1">{{ field.label }}</label>
          <input
            v-if="field.type === 'text'"
            :type="field.type"
            :id="field.name"
            :name="field.name"
            v-model="formData[field.name]"
            :placeholder="field.placeholder"
            class="input"
          />
          <select
            v-else-if="field.type === 'select'"
            :id="field.name"
            :name="field.name"
            v-model="formData[field.name]"
            class="select"
          >
            <option v-for="option in field.options" :key="option.value" :value="option.value">
              {{ option.label }}
            </option>
          </select>
        </div>
      </div>
      <div class="flex justify-end gap-2">
        <button type="button" class="btn btn-default" @click="handleCancel">取消</button>
        <button type="submit" class="btn btn-primary">保存</button>
      </div>
    </form>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'

const props = defineProps({
  title: {
    type: String,
    default: '表单'
  },
  fields: {
    type: Array,
    required: true
  },
  initialData: {
    type: Object,
    default: () => {}
  }
})

const emit = defineEmits(['submit', 'cancel'])

const formData = reactive({ ...props.initialData })

const handleSubmit = () => {
  emit('submit', formData)
}

const handleCancel = () => {
  emit('cancel')
}
</script>

页面示例

数据概览页面

<template>
  <Layout>
    <!-- 页面标题和操作按钮 -->
    <div class="flex justify-between items-center mb-6">
      <h2 class="text-xl font-semibold">数据概览</h2>
      <button class="btn btn-primary flex items-center gap-2">
        <i class="fa fa-download"></i>
        <span>导出数据</span>
      </button>
    </div>

    <!-- 数据卡片 -->
    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
      <div class="card">
        <div class="flex items-center justify-between">
          <div>
            <div class="text-text-light text-sm">总订单数</div>
            <div class="text-2xl font-bold mt-1">12,345</div>
            <div class="text-success text-sm mt-1 flex items-center">
              <i class="fa fa-arrow-up mr-1"></i> 12.5%
            </div>
          </div>
          <div class="w-12 h-12 rounded-full bg-primary bg-opacity-10 flex items-center justify-center text-primary">
            <i class="fa fa-shopping-cart text-xl"></i>
          </div>
        </div>
      </div>
      <div class="card">
        <div class="flex items-center justify-between">
          <div>
            <div class="text-text-light text-sm">总商户数</div>
            <div class="text-2xl font-bold mt-1">567</div>
            <div class="text-success text-sm mt-1 flex items-center">
              <i class="fa fa-arrow-up mr-1"></i> 8.3%
            </div>
          </div>
          <div class="w-12 h-12 rounded-full bg-success bg-opacity-10 flex items-center justify-center text-success">
            <i class="fa fa-building-o text-xl"></i>
          </div>
        </div>
      </div>
      <div class="card">
        <div class="flex items-center justify-between">
          <div>
            <div class="text-text-light text-sm">总门店数</div>
            <div class="text-2xl font-bold mt-1">1,234</div>
            <div class="text-success text-sm mt-1 flex items-center">
              <i class="fa fa-arrow-up mr-1"></i> 15.2%
            </div>
          </div>
          <div class="w-12 h-12 rounded-full bg-warning bg-opacity-10 flex items-center justify-center text-warning">
            <i class="fa fa-th-large text-xl"></i>
          </div>
        </div>
      </div>
      <div class="card">
        <div class="flex items-center justify-between">
          <div>
            <div class="text-text-light text-sm">总销售额</div>
            <div class="text-2xl font-bold mt-1">¥896,789</div>
            <div class="text-success text-sm mt-1 flex items-center">
              <i class="fa fa-arrow-up mr-1"></i> 23.1%
            </div>
          </div>
          <div class="w-12 h-12 rounded-full bg-error bg-opacity-10 flex items-center justify-center text-error">
            <i class="fa fa-money text-xl"></i>
          </div>
        </div>
      </div>
    </div>

    <!-- 图表区域 -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
      <div class="card">
        <h3 class="text-lg font-semibold mb-4">订单趋势</h3>
        <div class="h-64">
          <canvas id="orderChart"></canvas>
        </div>
      </div>
      <div class="card">
        <h3 class="text-lg font-semibold mb-4">商户分布</h3>
        <div class="h-64">
          <canvas id="merchantChart"></canvas>
        </div>
      </div>
    </div>

    <!-- 最近订单 -->
    <Card title="最近订单">
      <Table
        :columns="orderColumns"
        :data="recentOrders"
      />
    </Card>
  </Layout>
</template>

<script setup>
import Layout from '@/components/Layout.vue'
import Card from '@/components/Card.vue'
import Table from '@/components/Table.vue'
import { onMounted } from 'vue'
import Chart from 'chart.js/auto'

const orderColumns = [
  { key: 'id', title: '订单号' },
  { key: 'merchant', title: '商户' },
  { key: 'amount', title: '金额' },
  { key: 'status', title: '状态' },
  { key: 'createTime', title: '创建时间' },
  { key: 'action', title: '操作' }
]

const recentOrders = [
  { id: 'ORD20260409001', merchant: '商户A', amount: '¥1,234', status: '已完成', createTime: '2026-04-09 10:30' },
  { id: 'ORD20260409002', merchant: '商户B', amount: '¥567', status: '处理中', createTime: '2026-04-09 09:15' },
  { id: 'ORD20260408001', merchant: '商户C', amount: '¥2,345', status: '已完成', createTime: '2026-04-08 16:45' },
  { id: 'ORD20260408002', merchant: '商户A', amount: '¥890', status: '已取消', createTime: '2026-04-08 14:20' },
  { id: 'ORD20260407001', merchant: '商户D', amount: '¥1,567', status: '已完成', createTime: '2026-04-07 11:00' }
]

onMounted(() => {
  // 订单趋势图表
  const orderCtx = document.getElementById('orderChart')
  new Chart(orderCtx, {
    type: 'line',
    data: {
      labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
      datasets: [{
        label: '订单数',
        data: [1200, 1900, 3000, 5000, 8000, 12000],
        borderColor: '#1890ff',
        backgroundColor: 'rgba(24, 144, 255, 0.1)',
        tension: 0.4
      }]
    },
    options: {
      responsive: true,
      maintainAspectRatio: false
    }
  })

  // 商户分布图表
  const merchantCtx = document.getElementById('merchantChart')
  new Chart(merchantCtx, {
    type: 'pie',
    data: {
      labels: ['餐饮', '零售', '娱乐', '其他'],
      datasets: [{
        data: [35, 25, 20, 20],
        backgroundColor: ['#1890ff', '#52c41a', '#faad14', '#f5222d']
      }]
    },
    options: {
      responsive: true,
      maintainAspectRatio: false
    }
  })
})
</script>

最佳实践

  1. 组件化开发:将界面拆分为可复用的组件,如布局、卡片、表格、表单等
  2. 样式一致性:使用统一的颜色方案和工具类,确保整个应用的视觉一致性
  3. 响应式设计:利用 Tailwind 的响应式类,确保在不同屏幕尺寸上的良好显示
  4. 性能优化:使用 content-auto 等工具类优化渲染性能
  5. 代码规范:保持组件代码的清晰和可维护性

注意事项

  1. 确保安装了所有必要的依赖包
  2. 正确配置 Tailwind CSS 的内容路径,确保样式能够正确应用
  3. 在 Vue 3 项目中使用组合式 API 编写组件逻辑
  4. 对于图表等复杂功能,需要安装相应的库(如 Chart.js)
  5. 根据实际项目需求调整样式和组件

1 个赞