专栏首页前端印象自己设计的Vue3的实用项目(内含对项目亮点的实现思路与介绍)

自己设计的Vue3的实用项目(内含对项目亮点的实现思路与介绍)

在11月初的时候,我给自己定的目标:了解完 Vue3,然后做一个小项目

其中,Vue3 是早就学完了的,然后也写了两篇总结或是心得吧,其中有很多都是在做项目中踩出来的坑,所以大家可以看一下,避免之后开发中遇到:

然后做的 Vue3 项目也是我自己构思出来,因为当时网上的项目也不多或是大部分都是商城项目,之前也写过很多类似的了,所以就还是打算自己写一个,我给它取名叫做 nav-url,顾名思义就是一个网址导航栏,在我写这篇文章时,项目是已经上线并被我自己以及身边的小伙伴使用了的,下面放上预览链接 ????????

点击即可预览 ???? 项目预览链接

再放上项目源码地址 ????:项目源码链接(欢迎各位 star

接下来就详细地介绍一下我的项目

设计初衷

我现在也是个非计算机专业的大四在校生,平时前端都是自学的,所以从初学到现在基本上都是通过白嫖网上的视频、买书或从图书馆借书看、逛技术博客长长见识等等。这期间我会看到很多实用的工具网站或一些有趣的网站,我都会把他们收藏下来,生怕之后找不到了,但是随着时间的推移,收藏的网站越来越多,我的浏览器收藏夹可能变成了这样

这些都是我很久之前收藏夹收藏的,要是按照这个势头,我的收藏夹不出半年就爆满了,到时候找网站都不方便,所以我就想做一个我自己的网站导航栏,要求不高 : 简单大方方便快捷

于是就有了现在这个项目,如下图所示:

项目功能 && 特色

毕竟是个网址导航栏,所以功能非常的简单,但之后我会尽可能地去完善该项目的一些额外的功能

项目的功能:

✅ 标签的添加、修改、删除

✅ 网址的添加、修改、删除

✅ 搜索功能

✅ 配置的导入、导出

项目的特色:

⭐ 基于 Vue3 开发

⭐ 页面简单大方

⭐ 提供网站图标名称的获取接口

⭐ 标签栏支持多种 icon 选择

⭐ 通过 localStorage 存储,无需配置数据库

⭐ 用 Vue3 封装了 Element UImessagedialogbuttoninputpopover 组件

⭐ 通过 Vuex 4 进行状态管理

⭐ 页面的滚动动画

⭐ 支持一键保存导出数据一键导入数据

项目文件结构

整个项目主要的文件都在 src 文件夹下,结构目录如下:

├── src 
     ├── assets      // 存放静态资源
     ├── components  // 各种组件
     │   ├── main    // 页面主要内容相关组件
     │   ├── tabs    // 标签栏相关组件
     │   └── public  // 全局公共组件
     ├── network     // 网络请求
     ├── store       // Vuex
     ├── utils       // 存放自己封装的工具
     ├── APP.vue
     └── main.jsss

重点介绍

对于项目的逻辑代码,你们可以直接查看我的源码,全部都是用的 Vue3 语法写的

在最初做这个项目时,还没找到合适的 Vue3 组件库,所以我就根据自己的需求,封装了 messagedialoginputbuttonpopover 这样五个组件,其中重点讲一下 messagedialog 吧,另外还有这个项目的亮点:配置导入与导出

Dilog组件

首先是组件内容:

// lp-dialog.vue
<template>
  <div class="lp-confirm-container" ref="lpConfirmAlert">
      <div class="lp-confirm-box">
          <div class="lp-confirm-title">
              <span class="lp-confirm-title-txt">{{ title }}span>
              <span class="lp-confirm-title-close" @click="closeConfirm">✖span>
          div>
          <div class="lp-confirm-content">
              <span class="lp-confirm-content-txt">{{ content }}span>
          div>
          <div class="lp-confirm-btn-groups">
              <lp-button type="primary" class="lp-confirm-btn" @_click="sureConfirm">确定lp-button>
              <lp-button type="default" class="lp-confirm-btn lp-confirm-btn-cancel" @_click="closeConfirm">取消lp-button>
          div>
      div>
  div>
template>

<script>
import lpButton from '../lp-button/lp-button'
import {ref} from 'vue'
export default {
    components: {
        lpButton
    },
    props: {
        title: {
            type: String,
            default: '提示'
        },
        content: {
            type: String,
            default: '确定关闭吗?'
        }
    },
    setup() {
        const status = ref(-1)       // 存储用户点的状态,-1:未点击;0:取消;1:确定
        const lpConfirmAlert = ref(null)

        function removeElement() {     
            lpConfirmAlert.value.parentNode.removeChild(lpConfirmAlert.value)
        }
        
        function closeConfirm() {
            status.value = 0
            removeElement()
        }

        function sureConfirm() {
            status.value = 1
            removeElement()
        }

        return {removeElement, closeConfirm, sureConfirm, status, lpConfirmAlert}
    }
}
script>

<style scoped>
	/* 样式见源码,此处省略 */
style>

这里我在 dialog 组件内设定了一个组件的状态变量 status,用于确认用户的点击情况

再来看看组件的处理代码:

// lp-dialog.js
import lp_dialog from './lp-dialog.vue'
import {defineComponent, createVNode, render, toRef, watch} from 'vue'

const confirmConstructor = defineComponent(lp_dialog)

export const createDialog = (options) => {
    if(!Object.prototype.toString.call(options) === '[Object Object]') {
        console.error('Please enter an object as a parameter');
    }

    options = options ? options : {}
	
    // 生成组件实例
    const instance = createVNode(
        confirmConstructor,
        options
    )
	
    // 渲染挂载组件
    const container = document.createElement('div')
    render(instance, container)
    document.querySelector('#app').appendChild(instance.el)
	
    // 初始化组件参数
    const props = instance.component.props
    Object.keys(options).forEach(key => {
        props[key] = options[key]
    })
    // 获取组件的 status 状态变量
    const status = toRef(instance.component.setupState, 'status')
	
    // 返回 promise,方便外部调用
    return new Promise((resolve, reject) => {
    	// 监听组件的按钮点击情况
        watch(status, (now) => {
            if(now == 0) reject();
            else if(now == 1) resolve()
        })
    })   
}

接下来把 dialog 作为一个方法注册到全局中,这个我就把它放在了 App.vue 文件中,通过 Vue3provide 方法暴露在全局

<template>
    <div id="app">div>
template>

<script>
import { provide } from 'vue'
import createDialog from './components/public/lp-dialog/lp-dialog.js'
export default {
    setup() {
    	// 全局暴露创建 dialog 组件的方法
    	provide('confirm', createDialog)
    }
}
script>

然后在别的组件中使用 dialog 组件

<template>
    <div class="tabs" @click="btnConfirm">div>
template>

<script>
import { inject } from 'vue'
export default {
    setup() {
    	// 接收创建 dialog 组件的方法
    	let $confirm = inject('confirm')
        
        btnConfirm() {
            // 调用方法
            $confirm({
                title: '提示',     // 确认框的标题
                content: '确认关闭吗?',  // 消息内容
            })
            .then(() => {
                console.log('确认')
            })
            .catch(() => {
                console.log('取消')
            })
        }
        
        return { btnConfirm }
    }
}
script>

这样就实现了一个基于 promise 的链式调用,可以设定用户点击了 确认取消 之后的处理代码

Message组件

首先是组件内容:

// lp-message.vue
<template>
    <div class="message_container"
         :class="[
            {'show': isShow},
            {'hide': !isShow},
            {'enter': isEnter},
            {'leave': isLeave},
            type
         ]" 
         :style="{
             'top': `${seed * 70}px`
         }">
        <div class="content">
            <i :class="[
                    `lp-message-${type}`, 
                    'icon', 
                    'fa', 
                    {'fa-info-circle': type == 'info'},
                    {'fa-check-circle': type == 'success'},
                    {'fa-times-circle': type == 'err'},
                    {'fa-exclamation-triangle': type == 'warning'},
                ]"/>
            <div class="txt"
                 :class="[`txt_${type}`]">
                {{content}}
            div>
        div>
    div>
template>

<script>
    export default {
        name: "lp-message",
        props: {
            type: {
                type: String,
                default: 'info'
            },
            lastTime: {
                type: Number,
                default: 2500
            },
            content: {
                type: String,
                default: '这是一条提示信息'
            },
            isShow: {
                type: Boolean,
                default: false
            },
            isLeave: {
                type: Boolean,
                default: false
            },
            isEnter: {
                type: Boolean,
                default: false
            },
            seed: {
                type: Number,
                default: 0
            }
        }
    }
script>

<style scoped>
	/* 样式见源码,此处省略 */
style>

然后是组件的处理代码:

// lp-message.js
import lp_message from "./lp-message.vue"
import { defineComponent, createVNode, render } from 'vue'

let MessageConstructor = defineComponent(lp_message)
let instance;
const instances = []

export const createMessage = (options) => {

    if(!Object.prototype.toString.call(options) === '[object Object]') {
        console.error('Please enter an object as a parameter')
    }

    options = options ? options : {}

    instance = createVNode(
        MessageConstructor,
        options
    )

    //挂载
    const container = document.createElement('div')
    render(instance, container)

    document.querySelector('#app').appendChild(instance.el)

    const cpn = instance.component
    const el = instance.el
    const props = cpn.props  
    props.seed = instances.length
    
    // 初始化参数
    Object.keys(options).forEach(key => {
        props[key] = options[key]
    })

    // 加入到instances中管理
    instances.push(instance)
    
    // 消息框出现
    setTimeout(() => {
        props.isShow = true
        props.isEnter = true
    }, 200)
    
    // 消息框离开
    setTimeout(() => {
        props.isEnter = false
        props.isShow = false
        props.isLeave = true
    }, props.lastTime)

    // 移除消息框
    setTimeout(() => {
        close(el)
    }, props.lastTime + 200)
    
}

// 关闭某个弹框
const close = (el) => {
    instances.shift()
    instances.forEach((v) => {
        v.component.props.seed -= 1
    })
    document.querySelector('#app').removeChild(el)
}

这里模仿了 element-ui 的思想,把所有的 message 实力管理在一个数组中

然后我们要把其作为一个方法注册到全局中,这个我就把它放在了 App.vue 文件中,通过 Vue3provide 方法暴露在全局

<template>
    <div id="app">div>
template>

<script>
import { provide } from 'vue'
import createMessage from './components/public/lp-message/lp-message.js'
export default {
    setup() {
    	// 全局暴露创建 message 组件的方法
    	provide('message', createMessage)
    }
}
script>

使用 message 组件,通过 inject 方法获取即可

<template>
    <div class="main">div>
template>

<script>
import { inject } from 'vue'
export default {
    setup() {
    	// 接收创建 message 组件的方法
    	let $message = inject('message')
        
        // 调用方法
        $message({
            type: 'success',  // 消息框的类型,可选:info | success | err | warning
            content: '这是一条成功的消息',  // 消息内容
            lastTime: 5000          // 消息框持续的时间
        })
    }
}
script>

Popover组件

这个组件我没有模仿 element-ui ,因为我不太喜欢它的那种调用方式,所以我就根据自己的奇思妙想设计了一下这个组件:既然这个组件是一个气泡框,那么必然需要一个元素来确定这个气泡框的出现位置,因此我想把这个组件做成通过自定义指令 v-popover 来调用

接下来看下我的设计过程哈

首先是组件的内容:

// lp-popover.vue
<template>
  <div ref="popover"
       :class="['lp-popover-container', position]"
       :style="{
           'top': `${top}px`,
           'left': `${left}px`,
        }">
      <div class="container-proxy">
          <div class="lp-popover-title" v-html="title">div>
          <div class="lp-popover-content" v-html="content">div>
      div> 
  div>
template>

<script>
import {ref, onMounted, reactive, toRefs} from 'vue'
export default {
    props: {
        title: {   
            type: String,
            default: '我是标题'
        },
        content: {
            type: String,
            default: '我是一段内容'
        },
        position: {  // 出现的位置, top | bottom | left | right
            type: String,
            default: 'bottom'
        },
        type: {    // 触发方式, hover | click
            type: String,
            default: 'hover'
        }
    },
    setup({ position, type }) {
        const popover = ref(null)
        const site = reactive({
            top: 0,
            left: 0,
        })

        onMounted(() => {
            const el = popover.value
            let { top, left, height: pHeight, widht: pWidth } = el.parentNode.getBoundingClientRect()  // 获取目标元素的页面位置信息与尺寸大小
            let { height: cHeight, width: cWidth } = el.getBoundingClientRect()  // 获取气泡框的宽高
            // 设置气泡框的位置
            switch(position) {
                case 'top': 
                    site['left'] = left
                    site['top'] = top - cHeight - 25
                    break;
                case 'bottom':
                    site['left'] = left
                    site['top'] = top + pHeight + 25
                    break;
                case 'left':
                    site['top'] = top
                    site['left'] = left - cWidth - 25 
                    break;
                case 'right':
                    site['top'] = top
                    site['left'] = left + pWidth + 25
                    break;            
            }

            // 为气泡框设置触发方式
            switch(type) {
                case 'hover':
                    el.parentNode.addEventListener('mouseover', function() {
                        el.style.visibility = 'visible'
                        el.style.opacity = '1'
                    })
                    el.parentNode.addEventListener('mouseout', function() {
                        el.style.visibility = 'hidden'
                        el.style.opacity = '0'
                    })
                    break;
                case 'click':
                    el.parentNode.addEventListener('click', function() {
                        if(el.style.visibility == 'hidden' || el.style.visibility == '') {
                            el.style.visibility = 'visible'
                            el.style.opacity = '1'
                        } else {
                            el.style.visibility = 'hidden'
                            el.style.opacity = '2'
                        }
                    })
                    break;
            }        
        })

        return {
            ...toRefs(site),
            popover
        }
    }
}
script>

<style scoped>
	/* 组件样式省略,详情见源码 */
style>

主要思路就是根据 position 定位好气泡框相对于其父元素的位置,支持的位置一共有四种,即 top | bottom | left | right ,同时根据 type 处理触发展示气泡框的方法,一共有两种触发方式,即 hover | click

然后再来看一下自定义指令是如何写的

// lp-popover.js
import lpPopover from './lp-popover.vue'
import {defineComponent, createVNode, render, toRaw} from 'vue'

// 定义组件
const popoverConstructor = defineComponent(lpPopover)

export default function createPopover(app) {
    // 在全局上注册自定义指令v-popover
    app.directive('popover', {
    	// 在元素挂载后调用
        mounted (el, binding) {
            // 获取外界传入的指令的值,例如v-popover="data",value获取的就是data对应的值
            let { value } = binding

            let options = toRaw(value)
            // 判断传入的参数是否为对象
            if(!Object.prototype.toString.call(options) === '[Object Object]') {
                console.error('Please enter an object as a parameter');
            }
          
            options = options ? options : {}
        
            const popoverInstance = createVNode(
                popoverConstructor,
                options
            )
            const container = document.createElement('div')
            render(popoverInstance, container)
            el.appendChild(popoverInstance.el)
            const props = popoverInstance.component.props
            // 通过我们传入的参数对组件进行数据的初始化
            Object.keys(options).forEach(v => {
                props[v] = options[v]
            })         
        }
    })  
}

然后我们再在 main.js 文件中注册一下自定义指令

import { createApp } from 'vue';
import App from './App.vue'
import vuex from './store/index'
import vPopover from './components/public/lp-popover/lp-popover'

const app = createApp(App)

// 注册自定义指令 v-popver
vPopover(app)

app.use(vuex).mount('#app')

再来看一下使用方式

<template>
  <div id="app" v-popover="infos">
    
  div>
template>

<script>
import { reactive } from 'vue'
export default {
    setup() {
        const infos = reactive({
            title: '提醒',
            content: '这是一条提醒内容',
            position: 'left',
            type: 'click'
        })
        
        return { infos }
    }
}
script>

<style scoped>

style>

这样就简单地实现了气泡框组件的调用,当然其中 content 也是支持 html

但总的来说,这个组件的性能可能没 element-ui 好,因为我是直接对DOM进行了操作,也许后期还需要进行改善

SaveConfig

在介绍配置的导出与导入之前, 我先来介绍一下这个项目的数据存储

我秉承着一种能不用到服务器就不用服务器,能不用数据库就不用数据库的原则,想到了 localStorage 可以作为一个本地的数据库使用,每次换浏览器或设备时,只需要将 localStorage 里的数据再导入一次就好啦,因此我把这个数据称为配置(Config)

首先我们得拥有配置,所以需要有一个把 localStorage 里数据一键导出保存为一个文件的功能

该功能我是参考的 MDN 文档,你们有兴趣可以了解一下:Web API——URL.createObjectURL()

我大致是这样实现的:

// 封装的下载数据函数
function downLoadFile(fileName, content) {
    var aTag = document.createElement('a');   // 获取 a 元素
    var blob = new Blob([content]);           // 将数据保存在 blob 对象中
    aTag.download = fileName;                 // 设置保存的文件名称
    aTag.href = URL.createObjectURL(blob);    // 将数据保存在 href 属性中
    aTag.click();                             // 模拟点击 a 元素,进行下载
    URL.revokeObjectURL(blob);                // 删除内存中的 blob 对象的数据
}

// 调用下载接口
function saveConfig() {
    downLoadFile('nav.config.json', window.localStorage.navInfos)
}

试着点击一下看看效果 ????:

ImportConfig

既然已经手握配置文件,那么走到哪里都不怕了~ 接下来要做的就是将配置文件导入 localStorage

该方法是参考了 MDN 文档了的,大家可以前去了解一下: Web API——FilerReader

我大致是这样实现的:

// 导入配置
function importConfig() {
  let reader = new FileReader()           // 创建 FileReader 对象
  let files = document.querySelector('.file').value.files  // 获取已上传的文件信息
  reader.readAsText(files[0])             // 读取文件内容
  reader.onload = function() {            // 读取操作完成的处理函数
    let data = this.result                // 获取文件读取结果
    window.localStorage.navInfos = data   // 将文件数据存入 localStorage
    location.reload()                     // 刷新页面
  } 
}

然后我们把刚才导出保存的 json 配置文件重新导入看看效果:

哈哈哈,这样就成功导入文件啦~ ✔

Scroll Animation

因为我们所有的 URL 都是在一个页面内的,并且搭配着侧边栏中的按钮进行标签的跳转,即在左侧点哪个标签,右侧的内容就跳到哪个标签。刚开始我是用锚点实现的,但后来发现这样的跳转太生硬了,所以就自己简单地实现了一下跳转动画

实现原理大概是这样:右侧内容中每个标签都有带有一个 id,并且左侧的每个按钮也是对应着各自的 id 的,所以当点击了按钮时,先获取到对应 id 的元素 el,并获取 el 离滚动页面顶部的距离,即 el.scrollTop,然后同时获取一下当前位置离滚动页面离顶部的距离,如下图所示:

那么我们的跳转距离就是图中的 Location - Current

我大致是这样实现的:

// 跳转到指定标签
function toID(id) {
    const content = document.getElementById('content')  // 获取滚动页面元素
    const el = document.getElementById(`${id}`)         // 获取对应id的标签元素
    let start = content.scrollTop                       // 获取当前页面离顶部的距离
    let end = el.offsetTop - 80                         // 获取目标元素离顶部的距离(这里的80是减去了我顶部消息栏的高度,大家可以不用管)
    let each = start > end ? -1 * Math.abs(start - end) / 20 : Math.abs(start - end) / 20   // 考虑滚动方向并计算总共需要滚动的距离,同时将距离平分成20份
    let count = 0       // 记录滚动次数
    let timer = setInterval(() => {  // 设置定时器,滚动20次
        if(count < 20) {
            content.scrollTop += each
            count ++
        } else {
            clearInterval(timer)
        }
    }, 10) 
}

我们来看看滚动的效果如何吧~

我感觉滚动还是挺丝滑的 ???? 如果大家有更简单方便、性能更好的方法可以推荐给我

Get Icons Interface

我前面一直说,本着能不用服务器就不用服务器,能不用数据库就不用数据库的原则,但是自动获取页面图标这个功能真的没有办法了,要在浏览器端访问别人的网页还要得到 icon URL,几乎是不可能的,因为存在跨域问题,所以我就拿自己的服务器暴露了个接口出来用于获取目标网页的 icon 地址

代码这里我就不放上了,因为也比较简单,就是访问目标网页,得到 html 文档内容,从中筛选出 icon 的地址再返回就好了,要看代码的可以在 项目源码 中的 app.js 中去查看

这里还要强调的是,虽然我提供了一个接口用于自动获取对方网页的图标,但是有些网页对外部来路不明的请求都做了处理,例如返回一个 403 Forbiden 把我的请求给拒绝了,因此一些无法获得的图标或者无法加载的图标,我都是用一个默认图标统一替代,虽然之前我做过挺久的爬虫,想办法对 user-agentreferer等请求头都做了处理了,但还是无济于事,大家如果有好的办法也可以提供给我尝试

然后给大家简单演示一下如何使用的吧~

这个动图上好像有些模糊或者是样式的变动,都是因为 gif录制器的原因哈

其它

对于这个项目,因为刚出来半个月不到嘛,肯定还有需要改进的地方,我也已经列出了之后需要继续跟进的新功能:

  1. URL 的拖拽、排列
  2. 页面账号信息存储功能
  3. 提供更多的网址 icon 的选择
  4. more ……

第一个功能什么意思呢,就是我现在的项目中是不支持添加好后的 URL 重新排序的,但我觉得这个功能是一定要有的,之后会加上,打算想办法做一个在编辑状态下拖拽即可完成排列的功能

第二个功能的目的是因为对于很多个网站,你也许会有不同的账号和密码,但现在最令人头疼的就是,总是记不住这个网站我的账号或密码是啥,导致每次都要多次尝试或找回密码,特别的麻烦;所以我想做一个鼠标移到对应网址上,有一个查看此网址对应我的账号密码的功能

第三个功能就是为了针对那些无法获取 icon 的网站导致我们导航栏中显示的图标为默认图标,比较丑,所以到时候可以支持大家自行选择喜欢的图标

更多的功能还请大家多提建议啦~

最后

有些小伙伴问,为啥不做一个账号登录的网址导航栏,这样到哪都不用带着配置文件了,只需要记住账号密码就可以了。我又要强调本项目的选择了,能不用服务器就不用服务器,能不用数据库就不用数据库,用你自己的本地的 localStorage 作为数据库存储,你不是更放心嘛,比如你收藏了一些奇奇怪怪的网站,反正就只有你知道,我反正肯定是不知道的 ???? 而且细心的小伙伴有没有发现,我连静态页面都不是用的自己的服务器,直接部署在码云上的

自学前端这么久了,之前一直做着别人的项目或是模仿一些网站做一个项目,细数一下有这么几个:淘宝首页静态页面、蘑菇街移动端APP、node社区、elementUi组件以及组件文档展示等等,这次这个项目也算属于我自己的了,而且对于我来说是非常实用的一个小工具了,希望大家多多支持~ 给我提提意见,可以的话点个 star ????

再放一次项目源码地址:项目源码

对于这个项目有什么疑问或是项目出现问题的小伙伴可以告知我,vx:Lpyexplore333

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 自己设计的Vue3的实用项目(内含对项目亮点的实现思路与介绍)

    其中,Vue3 是早就学完了的,然后也写了两篇总结或是心得吧,其中有很多都是在做项目中踩出来的坑,所以大家可以看一下,避免之后开发中遇到:

    @零一
  • 迟来的2020年度总结,顺带附上被鸽了很久的自我介绍

    大家好,我是小澎,一个热爱前端的2021届的应届毕业生,大学专业是安全工程,不,不是网络安全,而是工程安全,所以我是非科班。今天呢,想跟大家介绍介绍我自己

    @零一
  • 写给初中级前端的高级进阶指南(JS、TS、Vue、React、性能、学习规划)

    我曾经一度很迷茫,在学了Vue、React的实战开发和应用以后,好像遇到了一些瓶颈,不知道该怎样继续深入下去。相信这也是很多一两年经验的前端工程师所遇到共同问题...

    ssh_晨曦时梦见兮
  • 双非同学,自学编程,毕业一年逆袭百度!

    大家好,我是鱼皮,最近秋招快到了,我就想着给大家找一些优秀的程序员面经分享。希望可以帮助准备求职的同学更稳地上岸,同时帮助学编程的小伙伴们更好地明确学习路线和方...

    程序员鱼皮
  • Vue3 + Vite2 项目实战复盘总结(干货!)

    •背景•vue3 项目开发 get 到的知识•让我惊讶的 vite•项目中遇到的困难•总结•项目技术栈•资料推荐

    Nealyang
  • 9 个值得推荐的 VUE3 UI 框架

    本文推荐几个比较流行的VUE3 UI框架,同时提供出色的开发人员体验,合理利用,又或者学习借鉴都是不错的选择,排名不分先后。

    网罗开发
  • Vue3 Composition-Api + TypeScript + 新型状态管理模式探索。

    一时间大家都觉得Redux很low,都在研究各种各样配合hook实现的新形状态管理模式。

    ssh_晨曦时梦见兮
  • 前端技术观察第 31 期

    ConardLi
  • Taro 支持使用 Vue3 开发小程序

    9 月 19 日凌晨,Vue3 在经过多个开发版本的迭代后,终于迎来了它的正式版本,"One Piece" 的代号也昭示了其开拓伟大航路的野心。

    一只图雀
  • 每天学一点 Vue3(一) CND方式的安装以及简单使用 js脚本的引用方式数据绑定和UI库的使用Vuex状态管理的简单使用路由的简单使用

    感觉vue3的新特性很舒服,这样才是写软件的感觉嘛。打算用Vue实现自己的一些想法。

    用户1174620
  • 做个开源博客学习Vite2 + Vue3 (一)搭建项目

    不会 webpack,遇到报错就一头雾水,完全不知道怎么办,而且体积还大速度还慢。 所以尤雨溪做了 vite 后就很向往,只是知道自己水平有限还是等大佬先趟趟...

    用户1174620
  • 紧跟尤大的脚步提前体验Vue3新特性,你不会还没了解过Vue3吧

    随着前端框架的快速更新迭代,现在的主流前端框架之一Vue.js迎来了它的新版本3.0。在今年2020的5月28日,Vue.js的作者尤雨溪公布了Vue3的整个设...

    @零一
  • char-dust 一个图片转字符画的 npm 包与示例站点

    那么乍看似乎已经有些被玩烂的东西,为啥又要重复造轮子呢?(当然其实最重要的原因在后话里。)

    云游君
  • 如何充分利用Composition API对Vue3项目进行代码抽离

    在2020年,Vue3的学习一直被我鸽到了11月份,在学完以后,我自己做了一个Vue3的小项目nav-url,也整理了我对于如何快速上手Vue3的几篇博客,很高...

    @零一
  • 如何充分利用Composition API对Vue3项目进行代码抽离

    在2020年,Vue3的学习一直被我鸽到了11月份,在学完以后,我自己做了一个Vue3的小项目nav-url,也整理了我对于如何快速上手Vue3的几篇博客,很高...

    @零一
  • 一个基于 Vue3 的开源项目,3个月时间 star 终于破千!

    关于 newbee-mall-vue3-app 这个开源项目的开发背景和详细介绍之前都已经介绍过,可以在上述两篇文章中查看。

    程序员十三
  • Vue3 深度解析

    距离尤雨溪首次公开 Vue3 (vue-next)源码有一个多月了。青笔观察到,刚发布国庆期间,出现不少解读 Vue3 源码的文章。当然不少有追风蹭热之嫌,文章...

    我是一条小青蛇
  • 在后台框架同质化的今天,我是如何思考并做出差异化的

    首先先安利一波,由我开发并维护的后台框架 Fantastic-admin 正式发布 Vue3 版本了,虽迟但到,欢迎大家访问链接体验。

    胡尐睿丶
  • 如何从 vue-element-admin 迁移到 Fantastic-admin

    如果你还不知道 Fantastic-admin 是什么,那么我先用几张预览图给大家了解一番。

    胡尐睿丶

扫码关注云+社区

领取腾讯云代金券