在规划篇和设计篇中已经确定我们的需求,前端的技术实现层面我们选中了 nuxtjs 这个实现 vuejs ssr 的框架,目的是为之后的扩展打下一个好的基础,比如增加资讯页面、作品详情页面、Blog等。

结合设计稿,我们本来计划是用一些第三方的UI框架或者是直接写一套样式进来,但是考虑这套设计稿中的色彩、元素和预留的一些组件扩展,比如Button、Text、Card等这些组件在以后的业务中也会经常用到,虽然有很多前端框架实现的很好,但是并不是我们自己创作的UI/UX,最终无法形成很好的品牌效应。

最终考虑还是决定自行实现,恰好刚完成的一个大数据展示的业务中用到 tailwindCss 这个框架,这个是工具优先的 css 框架,灵活性非常高,内部很好的实现了CSS中常用的工具,比如网格、网站自适应工具等,只需要按我们的设计进行定制规划就可以满足我们的需求,并且 nuxtjs 也实现了 tailwind 的插件。那么我就开始前端实现的旅程吧:

项目搭建

$ yarn create nuxt-app caixie-website-nuxtjs # 执行选择结果如下 create-nuxt-app v2.14.0 ✨ Generating Nuxt.js project in caixie-website-nuxtjs ? Project name caixie-website-nuxtjs ? Project description 北京采撷科技有限公司 ? Author name baisheng ? Choose the package manager Yarn ? Choose UI framework Tailwind CSS ? Choose custom server framework none (Recommended) ? Choose Nuxt.js modules DotEnv ? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection) ? Choose test framework None ? Choose rendering mode Universal (SSR) ? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection) Successfully created project caixie-website-nuxtjs To get started: cd caixie-website-nuxtjs yarn dev To build & start for production: cd caixie-website-nuxtjs yarn build yarn start ✨ Done in 68.03s.

接下来我们开始规划工程目录工程目录如下

web前端练手项目代码(打造创业公司官网)(1)

前端工程目录

components 目录是我们根据设计稿的规划,将整个页面进行组件化分形成的,方便后期调整各组件样式和结构。由于采用了 tailwind 去做样式,实现上就简单很多,拿 service 组件为例:components/Service.vue

<template> <div id="service" class="container bg-center bg-no-repeat bg-contain sm:pb-16 sm:mt-16 landing-changelog-bg text-center "> <span class="text-3xl font-black leading-tight text-indigo-100 sm:text-4xl"> 服务范围 </span> <p class="mt-8 mb-12 font-light text-gray-700 sm:mt-4 sm:text-xl"> <span class="block font-normal text-indigo-80"> 从概念到现实需要大量的时间和工作,你需要一个补充你技能的团队</span> </p> <div class="grid grid-cols-1 lg:grid-cols-3 sm:grid-cols-1"> <div class="flex flex-col justify-center text-gray-700 text-center bg-white px-8 py-2 m-4 h-40 rounded shadow hover:shadow-large transition-shadow duration-700 ease-in-out " v-for="(item, key) in services" :key="key"> <span class="font-bold text-indigo-100">{{item.title}}</span> <p class="font-normal text-indigo-60">{{item.desc}}</p> </div> </div> </div> </template> <script> export default { data() { return { services: [{ title: '网站开发', desc: '响应式网站设计、H5、WebGL' }, { title: '移动端研发', desc: 'IOS & Android、小程序' }, { title: '后端服务', desc: '云服务、API 服务' }, { title: '运维服务', desc: '服务器维护、部署、故障处理' }, { title: '产品策划', desc: '需要分析、产品整理、原型设计' }, { title: '设计服务', desc: '标准设计、高水平设计' }] } } } </script>

自适应样式的效果如下:

web前端练手项目代码(打造创业公司官网)(2)

PC端


web前端练手项目代码(打造创业公司官网)(3)

移动端


以上简单的10几行代码就可以很精准的实现出自适应效果。


tailwind.config.js包含的是全部的样式配置,包含配色、排版、字体、阴影等,配置文件内容如下:

const {COLORS} = require('./colors') // Depth level 1 (sitting on background) const BOX_SHADOW_BORDER_LIGHTER = `0 0 0 1px rgba(${COLORS.indigoRgb}, .08)`; const BOX_SHADOW_BORDER = `0 0 0 1px rgba(${COLORS.indigoRgb}, .08)`; const BOX_SHADOW_BORDER_DARKER = `0 0 0 1px rgba(${COLORS.indigoRgb}, .16)`; const BOX_SHADOW_SITTING = `0 4px 4px -4px rgba(${COLORS.indigoRgb}, .32)`; // Depth level 2 (dropdowns, dialogs, draggables when lifted) const BOX_SHADOW_LIFTED = `0 16px 24px -8px rgba(${COLORS.indigoRgb}, .32)`; // Depth level 3 (huge shadows) const BOX_SHADOW_LARGE = `0 16px 40px -4px rgba(${COLORS.indigoRgb}, .2)`; // Focus style const BOX_SHADOW_FOCUS = `0 0 0 3px ${COLORS.focus}`; const BOX_SHADOW_BUTTON = `0 0 0 1px rgba(20, 11, 47, 0.12), 0 4px 4px -4px rgba(0, 0, 0, .32)` const BOX_SHADOW_BUTTON_hover = `0 0 0 1px rgba(20, 11, 47, 0.32), 0 4px 4px -4px rgba(0, 0, 0, .32)` module.exports = { prefix: '', important: false, separator: ':', theme: { screens: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', }, colors: { transparent: 'transparent', black: '#000', white: '#fff', gray: { 100: '#f7fafc', 200: '#edf2f7', 300: '#e2e8f0', 400: '#cbd5e0', 500: '#a0aec0', 600: '#718096', 700: '#4a5568', 800: '#2d3748', 900: '#1a202c', }, red: { 100: '#fff5f5', 200: '#fed7d7', 300: '#feb2b2', 400: '#fc8181', 500: '#f56565', 600: '#e53e3e', 700: '#c53030', 800: '#9b2c2c', 900: '#742a2a', }, orange: { 100: '#fffaf0', 200: '#feebc8', 300: '#fbd38d', 400: '#f6ad55', 500: '#ed8936', 600: '#dd6b20', 700: '#c05621', 800: '#9c4221', 900: '#7b341e', }, // yellow: "#ffb300", // y80: "#ffc233", // y60: "#ffd166", // y40: "#ffe199", // y20: "#fff0cc", // y12: "#fff5e0", // y08: "#fff9eb", // y04: "#fffcf5", // y02: "#fffefa", yellow: { 100: '#ffb300', 80: '#ffc233', 60: '#faf089', 40: '#ffe199', 20: '#fff0cc', 12: '#fff5e0', 8: '#fff9eb', 4: '#fffcf5', 2: '#fffefa', }, green: { 100: '#f0fff4', 200: '#c6f6d5', 300: '#9ae6b4', 400: '#68d391', 500: '#48bb78', 600: '#38a169', 700: '#2f855a', 800: '#276749', 900: '#22543d', }, teal: { 100: '#e6fffa', 200: '#b2f5ea', 300: '#81e6d9', 400: '#4fd1c5', 500: '#38b2ac', 600: '#319795', 700: '#2c7a7b', 800: '#285e61', 900: '#234e52', }, blue: { 100: '#ebf8ff', 200: '#bee3f8', 300: '#90cdf4', 400: '#63b3ed', 500: '#4299e1', 600: '#3182ce', 700: '#2b6cb0', 800: '#2c5282', 900: '#2a4365', }, indigo: { 100: "#24124d", 80: "#4f4270", 60: "#7b7194", 40: "#a7a0b8", 20: "#d3d0db", 12: "#e4e2e9", 8: "#eeecf1", 4: "#f4f3f6", 2: "#fbfafc", }, purple: { 100: "#512da8", 80: "#7457b9", 60: "#9781cb", 40: "#b9abdc", 20: "#dcd5ee", 12: "#eae5f4", 8: "#f1eff8", 4: "#f8f7fc", 2: "#fcfbfd", rgba: `rgba(${COLORS.purpleRgb}, 0.04)`, rgbaDark: `rgba(${COLORS.purpleRgb}, 0.08)` }, pink: { 100: '#fff5f7', 200: '#fed7e2', 300: '#fbb6ce', 400: '#f687b3', 500: '#ed64a6', 600: '#d53f8c', 700: '#b83280', 800: '#97266d', 900: '#702459', }, magenta: { 100: "#f84f77", 80: "#f97292", 60: "#fb95ad", 40: "#fcb9c9", 20: "#fedce4", 12: "#fee9ee", 8: "#fff1f5", 4: "#fff8fa", 2: "#fffcfc" }, }, spacing: { px: '1px', '0': '0', '1': '0.25rem', '2': '0.5rem', '3': '0.75rem', '4': '1rem', '5': '1.25rem', '6': '1.5rem', '8': '2rem', '10': '2.5rem', '12': '3rem', '16': '4rem', '20': '5rem', '24': '6rem', '32': '8rem', '40': '10rem', '48': '12rem', '56': '14rem', '64': '16rem', }, backgroundColor: theme => theme('colors'), backgroundPosition: { bottom: 'bottom', center: 'center', left: 'left', 'left-bottom': 'left bottom', 'left-top': 'left top', right: 'right', 'right-bottom': 'right bottom', 'right-top': 'right top', top: 'top', }, backgroundSize: { auto: 'auto', cover: 'cover', contain: 'contain', }, borderColor: theme => ({ ...theme('colors'), default: theme('colors.gray.300', 'currentColor'), }), borderRadius: { none: '0', sm: '0.125rem', default: '0.25rem', lg: '0.5rem', full: '9999px', }, borderWidth: { default: '1px', '0': '0', '2': '2px', '4': '4px', '8': '8px', }, boxShadow: { default: '0 0 0 1px rgba(20, 11, 47, 0.12), 0 4px 4px -4px rgba(0, 0, 0, .32)', md: BOX_SHADOW_BUTTON_HOVER, lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', none: 'none', borderLighter: BOX_SHADOW_BORDER_LIGHTER, border: BOX_SHADOW_BORDER, borderDark: BOX_SHADOW_BORDER_DARKER, sitting: BOX_SHADOW_SITTING, lifted: BOX_SHADOW_LIFTED, large: BOX_SHADOW_LARGE, focus: BOX_SHADOW_FOCUS, buttonHover: BOX_SHADOW_BUTTON_HOVER }, container: {}, cursor: { auto: 'auto', default: 'default', pointer: 'pointer', wait: 'wait', text: 'text', move: 'move', 'not-allowed': 'not-allowed', }, fill: { current: 'currentColor', }, flex: { '1': '1 1 0%', auto: '1 1 auto', initial: '0 1 auto', none: 'none', }, flexGrow: { '0': '0', default: '1', }, flexShrink: { '0': '0', default: '1', }, fontFamily: { sans: [ '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"', ], serif: [ 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif', ], mono: [ 'Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace', ], }, fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem', '5xl': '3rem', '6xl': '4rem', }, fontWeight: { hairline: '100', thin: '200', light: '300', normal: '400', medium: '500', semibold: '600', bold: '700', extrabold: '800', black: '900', }, height: theme => ({ auto: 'auto', ...theme('spacing'), full: '100%', screen: '100vh', }), inset: { '0': '0', auto: 'auto', }, letterSpacing: { tighter: '-0.05em', tight: '-0.025em', normal: '0', wide: '0.025em', wider: '0.05em', widest: '0.1em', }, lineHeight: { none: '1', tight: '1.25', snug: '1.375', normal: '1.5', relaxed: '1.625', loose: '2', }, listStyleType: { none: 'none', disc: 'disc', decimal: 'decimal', }, margin: (theme, { negative }) => ({ auto: 'auto', ...theme('spacing'), ...negative(theme('spacing')), }), maxHeight: { full: '100%', screen: '100vh', }, maxWidth: { xs: '20rem', sm: '24rem', md: '28rem', lg: '32rem', xl: '36rem', '2xl': '42rem', '3xl': '48rem', '4xl': '56rem', '5xl': '64rem', '6xl': '72rem', full: '100%', }, minHeight: { '0': '0', full: '100%', screen: '100vh', }, minWidth: { '0': '0', full: '100%', }, objectPosition: { bottom: 'bottom', center: 'center', left: 'left', 'left-bottom': 'left bottom', 'left-top': 'left top', right: 'right', 'right-bottom': 'right bottom', 'right-top': 'right top', top: 'top', }, opacity: { '0': '0', '25': '0.25', '50': '0.5', '75': '0.75', '90': '.9', '100': '1', }, order: { first: '-9999', last: '9999', none: '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '10': '10', '11': '11', '12': '12', }, padding: theme => theme('spacing'), stroke: { current: 'currentColor', }, textColor: theme => theme('colors'), width: theme => ({ auto: 'auto', ...theme('spacing'), '1/2': '50%', '1/3': '33.33333%', '2/3': '66.66667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', '1/5': '20%', '2/5': '40%', '3/5': '60%', '4/5': '80%', '1/6': '16.66667%', '2/6': '33.33333%', '3/6': '50%', '4/6': '66.66667%', '5/6': '83.33333%', '1/12': '8.33333%', '2/12': '16.66667%', '3/12': '25%', '4/12': '33.33333%', '5/12': '41.66667%', '6/12': '50%', '7/12': '58.33333%', '8/12': '66.66667%', '9/12': '75%', '10/12': '83.33333%', '11/12': '91.66667%', full: '100%', screen: '100vw', }), zIndex: { auto: 'auto', '0': '0', '10': '10', '20': '20', '30': '30', '40': '40', '50': '50', }, transitionProperty: { none: 'none', all: 'all', default: 'background-color, border-color, color, opacity, transform', colors: 'background-color, border-color, color', opacity: 'opacity', transform: 'transform', shadow: 'box-shadow', }, transitionTimingFunction: { linear: 'linear', in: 'cubic-bezier(0.4, 0, 1, 1)', out: 'cubic-bezier(0, 0, 0.2, 1)', // 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', 'in-out': 'cubic-bezier(0.2, .6, .6, 1)', }, // transition: box-shadow 150ms cubic-bezier(.2,.6,.6,1); transitionDuration: { '75': '75ms', '100': '100ms', '150': '150ms', '200': '200ms', '300': '300ms', '500': '500ms', '700': '700ms', '1000': '1000ms', }, }, variants: { transitionProperty: ['responsive', 'focus', 'hover'], transitionTimingFunction: ['responsive', 'focus', 'hover'], transitionDuration: ['responsive', 'focus', 'hover'], alignContent: ['responsive'], alignItems: ['responsive'], alignSelf: ['responsive'], appearance: ['responsive'], backgroundAttachment: ['responsive'], backgroundColor: ['responsive', 'hover', 'focus'], backgroundPosition: ['responsive'], backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], borderColor: ['responsive', 'hover', 'focus'], borderRadius: ['responsive'], borderStyle: ['responsive'], borderWidth: ['responsive'], boxShadow: ['responsive', 'hover', 'focus'], cursor: ['responsive'], display: ['responsive'], fill: ['responsive'], flex: ['responsive'], flexDirection: ['responsive'], flexGrow: ['responsive'], flexShrink: ['responsive'], flexWrap: ['responsive'], float: ['responsive'], fontFamily: ['responsive'], fontSize: ['responsive'], fontSmoothing: ['responsive'], fontStyle: ['responsive'], fontWeight: ['responsive', 'hover', 'focus'], height: ['responsive'], inset: ['responsive'], justifyContent: ['responsive'], letterSpacing: ['responsive'], lineHeight: ['responsive'], listStylePosition: ['responsive'], listStyleType: ['responsive'], margin: ['responsive'], maxHeight: ['responsive'], maxWidth: ['responsive'], minHeight: ['responsive'], minWidth: ['responsive'], objectFit: ['responsive'], objectPosition: ['responsive'], opacity: ['responsive', 'hover', 'focus', 'active'], order: ['responsive'], outline: ['responsive', 'focus'], overflow: ['responsive'], padding: ['responsive'], pointerEvents: ['responsive'], position: ['responsive'], resize: ['responsive'], stroke: ['responsive'], tableLayout: ['responsive'], textAlign: ['responsive'], textColor: ['responsive', 'hover', 'focus'], textDecoration: ['responsive', 'hover', 'focus'], textTransform: ['responsive'], userSelect: ['responsive'], verticalAlign: ['responsive'], visibility: ['responsive'], whitespace: ['responsive'], width: ['responsive'], wordBreak: ['responsive'], zIndex: ['responsive'], transition: ['responsive', 'hover', 'focus'], }, corePlugins: { // outline: false, }, plugins: [ function({ addBase, config }) { addBase({ 'a': { color: config('theme.colors.teal.500'), }, 'h1': { fontSize: config('theme.fontSize.2xl'), fontWeight: config('theme.fontWeight.bold') }, 'h2': { fontSize: config('theme.fontSize.xl'), fontWeight: config('theme.fontWeight.bold') }, 'h3': { fontSize: config('theme.fontSize.base'), fontWeight: config('theme.fontWeight.bold') }, 'p': { marginTop: '0.5em' }, }) }, ] }

nuxt.config.js中包含了 nuxt的基础配置,和 tailwind 模块配置,重点配置如下:

/* ** Nuxt.js dev-modules */ buildModules: [ // Doc: https://github.com/nuxt-community/nuxt-tailwindcss '@nuxtjs/tailwindcss', ], tailwindcss: { configPath: '~/tailwind.config.js', cssPath: '~/assets/main.styl' },

其中 main.styl,包含了 tailwind 的 css 工具的引入,内容如下

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

这样基础配置和实现完成后,就可以打包发布,执行

yarn build

所有开发工作完成,接下来就是正式上线部署。详细内容请见运维篇。

,