Let’s code a dribble design with Vue.js & Tailwindcss (Working demo) — Part 1 of 2

2025-06-08

使用 Vue.js 和 Tailwindcss 编写一个 dribble 设计代码(工作演示)— 第 1 部分(共 2 部分)

使用 Vue.js 和 Tailwindcss 编写一个 dribble 设计代码(工作演示)— 第 1 部分(共 2 部分)

我的“每天一篇文章,直到封锁结束”中的第 5 篇文章

更新:这是第二部分

让我们从 dribble 中挑选一个小设计并使用 Vue.js 和 tailwindcss 对其进行编码,您将了解 Vue 的双向绑定如何工作,以及 tailwind 如何让您在完全灵活的情况下制作漂亮的应用程序,而无需手动编写任何 css。

让我们选择一个易于实现的设计,这样文章就不会太长,而且直观、有趣、外观漂亮。我发现了这个设计,它可以通过让你选择性别、身高、体重和年龄来计算BMI。

Rubel Vaalt 的简易 BMI 计算器

我将使用Nuxt.js ,它是Vuetailwindcss的框架

为什么选择 Vue 和 Nuxt?

Vue.js是一个渐进式框架,可让您快速构建 Web 应用。它支持双向绑定,并使用单文件组件,允许您创建自定义 HTML 标签(例如 或 等等)。它允许您将 HTML 标记拆分成更小的块,从而使代码更简洁、可读且更易于维护。Nuxt是一个为 Vue 而生的框架。为什么我们需要为框架而生框架呢?Nuxt 处理了大量网站的实际用例,开箱即用,非常实用。如果您选择了 Vue,最终可以减少样板代码的编写。如果您愿意,您仍然可以选择 Vue,我只是更喜欢 Nuxt,因为它的路由和文件夹结构。

为什么选择 Tailwindcss?

Tailwindcss是一个只包含底层 CSS 类的 CSS 框架,它不仅提供了成熟 CSS 框架的所有功能,还保留了 CSS 应有的灵活性和自由度。它不会像传统框架(例如 Bootstrap)那样强制你以受限的方式编写 CSS。简单来说,几乎每条 CSS 规则(你最常使用的规则)都有一个对应的类。从技术角度来看,这意味着它是一个非常庞大的框架,事实也确实如此。但是 purgeCss 可以帮你解决这个问题,它会删除所有多余的类,只保留 HTML 中使用的类(Nuxt.js 内置了 purgeCSS,因此你无需手动操作)。

步骤 1:让我们创建一个 Nuxt 项目并选择 TailwindCSS 作为我们的 UI 框架。

使用 Tailwindcss 创建 Nuxtjs 项目

完成后,在你最喜欢的代码编辑器(我更喜欢 VS Code)中打开项目。你可以运行 npm run dev 命令,以开发模式启动项目,然后你会看到一个模板。删除 pages/index.vue 中的所有代码,并从 layouts/default.vue 中删除 CSS。layouts/default.vue 将作为项目的入口点,所有路由内容都将动态显示,你的路由将在 pages 文件夹中定义。pages 文件夹内的每个 .vue 文件都代表一个网页,而路由就是文件名。你可以在这里找到更多关于 nuxt 路由的信息,以及一个专门讲解 nuxt.js 的免费 udemy课程。

我们将在应用程序中有两个页面,一个是计算器,它将在网站打开时立即显示,一个是结果页面,我们已经在 pages 文件夹中为它创建了一个 index.vue 文件,添加另一个名为 result.vue 的文件,这将为我们的项目添加一条新路线,如 example.com/result。

这就是在 nuxt.js 中创建路由的简单方法。项目结构如下。

应用程序的基本项目结构

通过运行 npm run dev 启动项目,在浏览器中打开 localhost:3000,查看 index.vue 渲染的“主页”文本,尝试打开 localhost:3000/result,您将看到“结果页”,即渲染的 result.vue 页面。

default.vue 会在组件内部渲染这些路由。如果您希望某个组件在两个路由中都显示,只需将其添加到 default.vue 中即可,它会自动显示,无需在两个页面重复代码。这对于 n_avbar_、抽屉式导航栏返回顶部按钮等非常有用。

第 2 步:让我们编写计算器/索引/主页的代码。

我们将首先将代码划分为不同的组件

成分

所以我们这里有四个不同的组件。导航栏会同时显示在首页和结果页中,所以我们只需在layout/default.vue中添加一次即可。其他三个组件会显示在首页/计算器页中。以下是我始终遵循的文件夹结构,以保持代码简洁易读。

步骤 3:编写组件代码

  • 整个应用程序都有一个深色主题,所以让我们将 bg-gray-900 类应用到最顶层的 div,即 layouts/default.vue。
  • 制作导航栏。它由一个 div 组成,中间有一个图标和一些文本,并带有一个厚厚的 box-shadow 阴影。我们将使用 CSS flex 来对齐内容,并使用 tailwindcss 的阴影类。以下 HTML 代码会为我们生成导航栏。
<template>
<div class="shadow-lg py-6 px-4 flex items-center">
<div>
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
class="w-8 h-8"
>
<path d="M4 6h16M4 12h16M4 18h7"></path>
</svg>
</div>
<div class="text-center flex-1">
<p class="font-bold">BMI CALCULATOR</p>
</div>
</div>
</template>
view raw navbar.vue hosted with ❤ by GitHub
<template>
<div class="shadow-lg py-6 px-4 flex items-center">
<div>
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
class="w-8 h-8"
>
<path d="M4 6h16M4 12h16M4 18h7"></path>
</svg>
</div>
<div class="text-center flex-1">
<p class="font-bold">BMI CALCULATOR</p>
</div>
</div>
</template>
view raw navbar.vue hosted with ❤ by GitHub

图标取自Heroicons

上面的代码无需任何自定义 CSS 即可生成此导航栏。基本上,我们有一个父 div,它包含两个子 div,一个带有 SVG 图标的 div,以及一个带有文本的 paragrarh 标签。我给它应用了 flex 和 items-center 属性,使它们在同一行上平分,并通过 items-center 属性将它们垂直居中对齐。

我将在我们的 layouts/default.vue 文件中添加导航栏组件,以便它在两种路线中都可用。

在 default.vue 文件中导入导航栏代码

  • Gender 组件有两个 div,我们将使用 css 网格,没有必要使用 css 网格,这也可以通过其他方式轻松实现,只是想为您解释 tailwindcss 功能。

以下是 CSS Grid 在 tailwindcss 中的工作方式,更多信息请点击此处

<template>
<section class="grid grid-cols-2 gap-4">
<div class="rounded-md shadow-md bg-gray-800 h-48"></div>
<div class="rounded-md shadow-md bg-gray-800 h-48"></div>
</section>
</template>
view raw gender.vue hosted with ❤ by GitHub
<template>
<section class="grid grid-cols-2 gap-4">
<div class="rounded-md shadow-md bg-gray-800 h-48"></div>
<div class="rounded-md shadow-md bg-gray-800 h-48"></div>
</section>
</template>
view raw gender.vue hosted with ❤ by GitHub

带有 tailwindcss 的 Gnder 卡片网格

上面的代码生成了这个布局,它很简单,并且可以通过 tailwinds 响应助手进行响应,grid-cols-2 指定我们的布局将有两列,并且它们之间有 4 个单位的间隙,不再有列的边距黑客。

<template>
<section class="grid grid-cols-2 gap-2 mb-6">
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<svg
class="w-16 h-16 mx-auto"
fill="currentColor"
stroke="currentColor"
viewBox="0 0 384 384"
>
<path
d="M383.793 13.938c-.176-1.38-.48-2.708-.984-3.954-.016-.03-.016-.074-.024-.113 0-.008-.008-.016-.015-.023-.555-1.313-1.313-2.504-2.168-3.61-.211-.261-.418-.52-.641-.765-.914-1.032-1.906-1.985-3.059-2.762-.03-.024-.07-.031-.101-.055-1.114-.734-2.344-1.289-3.633-1.726-.32-.114-.633-.211-.961-.297C370.855.266 369.465 0 368 0H256c-8.832 0-16 7.168-16 16s7.168 16 16 16h73.367l-95.496 95.496C208.406 107.13 177.055 96 144 96 64.602 96 0 160.602 0 240s64.602 144 144 144 144-64.602 144-144c0-33.04-11.121-64.383-31.504-89.871L352 54.625V128c0 8.832 7.168 16 16 16s16-7.168 16-16V16c0-.336-.078-.656-.098-.984a16.243 16.243 0 00-.109-1.079zM144 352c-61.762 0-112-50.238-112-112s50.238-112 112-112c29.902 0 58.055 11.64 79.223 32.734C244.359 181.945 256 210.098 256 240c0 61.762-50.238 112-112 112zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">male</p>
</div>
<div class="rounded-md bg-gray-800 p-4 w-full opacity-50">
<svg
class="w-16 h-16 mx-auto"
fill="currentColor"
stroke="currentColor"
viewBox="-56 0 384 384"
>
<path
d="M272 136C272 61.008 210.992 0 136 0S0 61.008 0 136c0 69.566 52.535 127.016 120 134.977V304H88c-8.832 0-16 7.168-16 16s7.168 16 16 16h32v32c0 8.832 7.168 16 16 16s16-7.168 16-16v-32h32c8.832 0 16-7.168 16-16s-7.168-16-16-16h-32v-33.023c67.465-7.961 120-65.41 120-134.977zm-240 0C32 78.656 78.656 32 136 32s104 46.656 104 104-46.656 104-104 104S32 193.344 32 136zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">female</p>
</div>
</section>
</template>
<script>
export default {};
</script>
<style></style>
view raw gender.vue hosted with ❤ by GitHub
<template>
<section class="grid grid-cols-2 gap-2 mb-6">
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<svg
class="w-16 h-16 mx-auto"
fill="currentColor"
stroke="currentColor"
viewBox="0 0 384 384"
>
<path
d="M383.793 13.938c-.176-1.38-.48-2.708-.984-3.954-.016-.03-.016-.074-.024-.113 0-.008-.008-.016-.015-.023-.555-1.313-1.313-2.504-2.168-3.61-.211-.261-.418-.52-.641-.765-.914-1.032-1.906-1.985-3.059-2.762-.03-.024-.07-.031-.101-.055-1.114-.734-2.344-1.289-3.633-1.726-.32-.114-.633-.211-.961-.297C370.855.266 369.465 0 368 0H256c-8.832 0-16 7.168-16 16s7.168 16 16 16h73.367l-95.496 95.496C208.406 107.13 177.055 96 144 96 64.602 96 0 160.602 0 240s64.602 144 144 144 144-64.602 144-144c0-33.04-11.121-64.383-31.504-89.871L352 54.625V128c0 8.832 7.168 16 16 16s16-7.168 16-16V16c0-.336-.078-.656-.098-.984a16.243 16.243 0 00-.109-1.079zM144 352c-61.762 0-112-50.238-112-112s50.238-112 112-112c29.902 0 58.055 11.64 79.223 32.734C244.359 181.945 256 210.098 256 240c0 61.762-50.238 112-112 112zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">male</p>
</div>
<div class="rounded-md bg-gray-800 p-4 w-full opacity-50">
<svg
class="w-16 h-16 mx-auto"
fill="currentColor"
stroke="currentColor"
viewBox="-56 0 384 384"
>
<path
d="M272 136C272 61.008 210.992 0 136 0S0 61.008 0 136c0 69.566 52.535 127.016 120 134.977V304H88c-8.832 0-16 7.168-16 16s7.168 16 16 16h32v32c0 8.832 7.168 16 16 16s16-7.168 16-16v-32h32c8.832 0 16-7.168 16-16s-7.168-16-16-16h-32v-33.023c67.465-7.961 120-65.41 120-134.977zm-240 0C32 78.656 78.656 32 136 32s104 46.656 104 104-46.656 104-104 104S32 193.344 32 136zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">female</p>
</div>
</section>
</template>
<script>
export default {};
</script>
<style></style>
view raw gender.vue hosted with ❤ by GitHub

性别成分

我们从flaticon中选择性别图标,并将它们添加到这些卡片中。最终效果如下。我给未选中的卡片添加了一点不透明度,类名为 opacity-75,以突出显示另一张卡片。

看起来不错,现在我们来设计高度组件。我使用了一个 HTML 范围滑块,并附带一些自定义 CSS,因为 TailwindCSS 不允许在此级别进行自定义。我制作了一个小图像作为滑块按钮,因为它带有一个带不透明度的边框半径,而我们目前还无法用 CSS 来实现。

<template>
<section class="rounded-md shadow-md bg-gray-800 p-4 text-center">
<p>HEIGHT</p>
<p class="text-5xl font-bold">
{{ height }}<small class="text-sm">cm</small>
</p>
<input
type="range"
min="120"
max="215"
v-model="height"
class="slider w-full h-1 rounded-lg outline-none opacity-75 transition-all duration-300 hover:opacity-100"
/>
</section>
</template>
<script>
export default {
data() {
return {
height: 120
};
}
};
</script>
<style>
.slider {
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 35px;
height: 35px;
border: 0;
background: url("/slider.png");
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 35px;
height: 35px;
border: 0;
background: url("/slider.png");
cursor: pointer;
}
</style>
view raw height.vue hosted with ❤ by GitHub
<template>
<section class="rounded-md shadow-md bg-gray-800 p-4 text-center">
<p>HEIGHT</p>
<p class="text-5xl font-bold">
{{ height }}<small class="text-sm">cm</small>
</p>
<input
type="range"
min="120"
max="215"
v-model="height"
class="slider w-full h-1 rounded-lg outline-none opacity-75 transition-all duration-300 hover:opacity-100"
/>
</section>
</template>
<script>
export default {
data() {
return {
height: 120
};
}
};
</script>
<style>
.slider {
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 35px;
height: 35px;
border: 0;
background: url("/slider.png");
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 35px;
height: 35px;
border: 0;
background: url("/slider.png");
cursor: pointer;
}
</style>
view raw height.vue hosted with ❤ by GitHub

现在,我们的代码中已经实现了这个设计,虽然不是像素级的,但与设计完全匹配,我对结果还是很满意的。我添加了一个 v-model 指令,用于在用户滑动滑块时捕获滑块的值,并使用字符串插值在其上方显示该值{{height}}。这很好地展现了双向绑定的工作原理。

与具有两个相等 div 的性别组件类似,让我们制作一个组件来获取用户的年龄和体重。

<template>
<section class="grid grid-cols-2 gap-2 mb-6 text-center">
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<p class="uppercase font-bold">Weight</p>
<p class="text-5xl font-bold">74</p>
<div class="flex items-center justify-around">
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">-</button>
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">+</button>
</div>
</div>
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<p class="uppercase font-bold">Age</p>
<p class="text-5xl font-bold">19</p>
<div class="flex items-center justify-around">
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">-</button>
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">+</button>
</div>
</div>
</section>
</template>
<script>
export default {};
</script>
<style></style>
view raw ageweight.vue hosted with ❤ by GitHub
<template>
<section class="grid grid-cols-2 gap-2 mb-6 text-center">
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<p class="uppercase font-bold">Weight</p>
<p class="text-5xl font-bold">74</p>
<div class="flex items-center justify-around">
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">-</button>
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">+</button>
</div>
</div>
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<p class="uppercase font-bold">Age</p>
<p class="text-5xl font-bold">19</p>
<div class="flex items-center justify-around">
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">-</button>
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">+</button>
</div>
</div>
</section>
</template>
<script>
export default {};
</script>
<style></style>
view raw ageweight.vue hosted with ❤ by GitHub

到目前为止,我们已经完成了计算器的大部分工作,剩下要在标记中添加的只是底部的一个按钮。

我们将在底部添加一个固定按钮,它将计算并在第二页显示结果,我们将利用从我们的组件发出并在父级中捕获的事件。

index.vue 页面最终会呈现如下效果。我还在页面底部添加了一个按钮,用于完成第一页的编码。虽然像素并非完美,但我们已经取得了不错的进展。以下是目前提交的代码(项目完成后我可能会尽快更新)以及一个在线演示

这篇文章已经够长了,因此我将把它分成两部分,并在今天晚些时候发布第二部分。

希望你喜欢。你可以在推特上关注我,了解第二部分的更新,我会继续在那里发布我的系列文章。

如果您需要任何帮助或对此有任何建议,请告诉我。

附言:祝大家斋月快乐,平安!

鏂囩珷鏉ユ簮锛�https://dev.to/fayaz/let-s-code-a-dribble-design-with-vue-js-tailwindcss-working-demo-part-1-of-2-3h9
PREV
自动管理个人和工作 git 配置
NEXT
我想我已经破解了自由职业作品集项目和登陆页面。