In early November, I set myself the goal of learning about Vue3 and then doing a small project
Among them, Vue3 is learned early, and then also wrote two summary or experience, many of which are in the project to step out of the pit, so you can take a look, to avoid the development of the encounter:
- Quick access to Vue3’s latest 15 commonly used apis (400+ 👍)
- A supplement to Vue3’s getCurrentInstance method to get the current component instance (30+ 👍)
The Vue3 project was also conceived by me, because there were not many projects on the Internet at that time, or most of them were mall projects. I had written many similar projects before, so I decided to write one by myself. I called it nav-URL, which is a navigation bar for the website as the name implies. The project is already online and used by myself and my friends around me. Here is a preview link 👇👇
Click to preview the 👉 project preview link
Put on the project source address 👉 : project source link (welcome to all stars)
Let me introduce my project in more detail
designed
Now I am also a senior student who is not majoring in computer science. I usually study on my own, so from the beginning to now, I have mostly learned by watching videos on Baijiao online, buying books or borrowing books from the library, browsing technology blogs and so on. During this period, I saw a lot of useful tool websites or some interesting websites, AND I always saved them in case I could not find them later, but as time went on, my browser favorites became more and more like this
These are my favorites a long time ago, if according to this momentum, my favorites within half a year will be full, when the site is not convenient to find, so I want to do a website navigation bar of my own, not high requirements: simple and generous, convenient and fast
Hence the current project, as shown below:
Project functions && Features
After all, it’s a url navigation bar, so it’s pretty simple, but then I’ll try to improve some of the additional features of the project as much as possible
Functions of the project:
✅ Add, modify, and delete labels
Add, modify and delete ✅
✅ Search function
✅ Import and export configurations
Features of the project:
⭐ is developed based on Vue3
⭐ page simple and generous
⭐ Provides the interface for obtaining the website icon and name
The ⭐ TAB bar supports multiple icon choices
⭐ The database is stored through localStorage and does not need to be configured
⭐ encapsulates the Message, Dialog, Button, Input, and popover components of the Element UI with Vue3
⭐ Use Vuex 4 to manage the status
⭐ page scrolling animation
⭐ Supports one-click data export and one-click data import
Project file structure
The main files for the entire project are in the SRC folder, structured as follows:
├ ─ ─ the SRC ├ ─ ─ assets// Store static resources├ ─ ─ the components// Various components│ ├ ─ ─ the main// The main content of the page related components│ ├ ─ ─ tabs// Label bar related components│ └ ─ ─ the public// Global public component├ ─ ─ the network// Network request├ ─ ─ store// Vuex├ ─ ─ utils// Store your own encapsulated tools├ ─ ─ APP. Vue └ ─ ─ main. JSSSCopy the code
Emphasis on the
For the logical code of the project, you can directly view my source code, which is all written in Vue3 syntax
When I first started this project, I had not found a suitable Vue3 component library, so I encapsulated five components such as Message, Dialog, Input, Button and Popover according to my own needs. Let’s focus on Message and Dialog. Another highlight of the project: configuration imports and exports
Dilog components
First is the component content:
// 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">The & # 10006;</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">determine</lp-button>
<lp-button type="default" class="lp-confirm-btn lp-confirm-btn-cancel" @_click="closeConfirm">cancel</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: 'tip'
},
content: {
type: String.default: 'Are you sure? '}},setup() {
const status = ref(-1) // Store the status of the user point, -1: not clicked; 0: Cancel. 1: make sure
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>
/* See source code, omit */ here
</style>
Copy the code
Here I set a component’s status variable in the Dialog component to confirm the user’s clicks
Let’s look at the handler code for the component:
// 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 : {}
// Generate component instances
const instance = createVNode(
confirmConstructor,
options
)
// Render the mount component
const container = document.createElement('div')
render(instance, container)
document.querySelector('#app').appendChild(instance.el)
// Initialize component parameters
const props = instance.component.props
Object.keys(options).forEach(key= > {
props[key] = options[key]
})
// Get the component's status variable
const status = toRef(instance.component.setupState, 'status')
// Return promise to facilitate external calls
return new Promise((resolve, reject) = > {
// Listen for button clicks of the component
watch(status, (now) = > {
if(now == 0) reject();
else if(now == 1) resolve()
})
})
}
Copy the code
Next, register dialog globally as a method, which I put in the app.vue file and expose globally via Vue3’s provide method
<template>
<div id="app"></div>
</template>
<script>
import { provide } from 'vue'
import createDialog from './components/public/lp-dialog/lp-dialog.js'
export default {
setup() {
// Globally expose the method that created the Dialog component
provide('confirm', createDialog)
}
}
</script>
Copy the code
Then use the Dialog component in another component
<template>
<div class="tabs" @click="btnConfirm"></div>
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
// Receive the method to create the dialog component
let $confirm = inject('confirm')
btnConfirm() {
// Call the method
$confirm({
title: 'tip'.// Confirm the title of the box
content: 'Are you sure to close? '.// Message content
})
.then(() = > {
console.log('confirm')
})
.catch(() = > {
console.log('cancel')})}return { btnConfirm }
}
}
</script>
Copy the code
This creates a chain of promise-based calls that can set the processing code after the user clicks confirm or cancel
The Message component
First is the component content:
// 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: 'This is a message.'
},
isShow: {
type: Boolean.default: false
},
isLeave: {
type: Boolean.default: false
},
isEnter: {
type: Boolean.default: false
},
seed: {
type: Number.default: 0}}}</script>
<style scoped>
/* See source code, omit */ here
</style>
Copy the code
Then there is the handler code for the component:
// 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
)
/ / a mount
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
// Initialize parameters
Object.keys(options).forEach(key= > {
props[key] = options[key]
})
// Add to instances for management
instances.push(instance)
// The message box appears
setTimeout(() = > {
props.isShow = true
props.isEnter = true
}, 200)
// The message box leaves
setTimeout(() = > {
props.isEnter = false
props.isShow = false
props.isLeave = true
}, props.lastTime)
// Remove the message box
setTimeout(() = > {
close(el)
}, props.lastTime + 200)}// Close a popup
const close = (el) = > {
instances.shift()
instances.forEach((v) = > {
v.component.props.seed -= 1
})
document.querySelector('#app').removeChild(el)
}
Copy the code
This mimics the idea of Element-UI, where all message capabilities are managed in an array
Then we need to register it globally as a method, which I put in the app.vue file and expose it globally via Vue3’s provide method
<template>
<div id="app"></div>
</template>
<script>
import { provide } from 'vue'
import createMessage from './components/public/lp-message/lp-message.js'
export default {
setup() {
// Globally expose the method that created the Message component
provide('message', createMessage)
}
}
</script>
Copy the code
Use the message component and use the inject method
<template>
<div class="main"></div>
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
// Receive the method that created the Message component
let $message = inject('message')
// Call the method
$message({
type: 'success'./ / the type of message box, optional: info | success | err | warning
content: 'This is a success story.'.// Message content
lastTime: 5000 // The duration of the message box}}})</script>
Copy the code
Popover components
I didn’t imitate the Element-UI because I didn’t like the way it was called, so I designed it with my own whimsiness: Since this component is a bubble box, there must be an element to determine where the bubble box appears, so I want to make this component called by the custom v-popover directive
Let’s take a look at my design process
The first is the content of the component:
// 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: 'I am the title'
},
content: {
type: String.default: 'I am a piece of content'
},
position: { / / the position, the top | | bottom left | right
type: String.default: 'bottom'
},
type: { / / trigger mode, 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() // Get the page location information and size of the target element
let { height: cHeight, width: cWidth } = el.getBoundingClientRect() // Get the width and height of the bubble frame
// Set the position of the bubble box
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;
}
// Set the trigger mode for the bubble box
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>
/* Component style omitted, see source */ for details
</style>
Copy the code
Main idea is a good location according to the position of bubble box position relative to the parent element, support the position of a total of 4 kinds, namely the top | | bottom left | right, at the same time, according to the type of dealing with the trigger box show bubbles method, there are two types of trigger, The hover | click
Then let’s see how the custom instruction is written
// lp-popover.js
import lpPopover from './lp-popover.vue'
import {defineComponent, createVNode, render, toRaw} from 'vue'
// Define the component
const popoverConstructor = defineComponent(lpPopover)
export default function createPopover(app) {
// Register the custom instruction v-popover globally
app.directive('popover', {
// Called after the element has been mounted
mounted (el, binding) {
// Get the value of the instruction passed in. For example, v-popover="data"
let { value } = binding
let options = toRaw(value)
// Check whether the passed parameter is an object
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
// Initialize the component with the parameters we pass in
Object.keys(options).forEach(v= > {
props[v] = options[v]
})
}
})
}
Copy the code
Then we register our custom directive in the main.js file
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)
// Register the custom instruction v-popver
vPopover(app)
app.use(vuex).mount('#app')
Copy the code
Now let’s see how it’s used
<template>
<div id="app" v-popover="infos">
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const infos = reactive({
title: 'remind'.content: 'Here's a reminder.'.position: 'left'.type: 'click'
})
return { infos }
}
}
</script>
<style scoped>
</style>
Copy the code
This makes it easy to call the bubble box component, which also has HTML support for Content
But overall, the performance of this component may not be as good as that of the Element-UI because I’m directly manipulating the DOM, and I may need to improve it later
SaveConfig
Before I cover configuration exports and imports, LET me introduce the data store for this project
I uphold a can not to the server will not use the server, can not use the database database principle, think of the localStorage can be used as a local database, each change the browser or device, only need to import the data in the localStorage again on the good, So I call this data Config.
First we need to have the configuration, so we need to have a one-click export and save the data in localStorage as a file
Web API — url.createObjecturl ()
Here’s how I did it:
// The encapsulated download data function
function downLoadFile(fileName, content) {
var aTag = document.createElement('a'); // Get the a element
var blob = new Blob([content]); // Store data in bloB objects
aTag.download = fileName; // Set the name of the saved file
aTag.href = URL.createObjectURL(blob); // Save the data in the href attribute
aTag.click(); // Click on element A to download
URL.revokeObjectURL(blob); // Delete the data of the bloB object from memory
}
// Call the download interface
function saveConfig() {
downLoadFile('nav.config.json'.window.localStorage.navInfos)
}
Copy the code
Try clicking on it to see what it looks like 😁 :
ImportConfig
Now that you have the configuration file in your hand, you are not afraid to go anywhere ~ the next thing to do is to import the configuration file to localStorage
This method is referred to the MDN documentation, you can check out: Web API – FilerReader
Here’s how I did it:
// Import the configuration
function importConfig() {
let reader = new FileReader() // Create a FileReader object
let files = document.querySelector('.file').value.files // Obtain the information about the uploaded files
reader.readAsText(files[0]) // Read the contents of the file
reader.onload = function() { // The handler that reads the completed operation
let data = this.result // Get the file read result
window.localStorage.navInfos = data // Store the file data to localStorage
location.reload() // Refresh the page}}Copy the code
Then let’s import the json configuration file we exported and saved to see what it looks like:
Right, right, right, right, right
Scroll Animation
Because all of our urls are in one page, and with the sidebar button to jump to the label, that is, click on the left of the tag, the right of the content to jump to the same tag. At first I did it with anchor points, but then I found the jump to be too stiff, so I simply implemented the jump animation myself
It works like this: Each tag in the right side of the content has an ID, and each button on the left has its own ID, so when the button is clicked, the element EL with the corresponding ID is fetched first, and the distance between el and the top of the scroll page is fetched, that is, el. ScrollTop. Then get the distance between the current position and the top of the scrolling page, as shown below:
So our jump distance is location-current in the figure
Here’s how I did it:
// Jump to the specified label
function toID(id) {
const content = document.getElementById('content') // Get the scrolling page element
const el = document.getElementById(`${id}`) // Get the tag element corresponding to the ID
let start = content.scrollTop // Get the current page distance from the top
let end = el.offsetTop - 80 // Get the distance from the top of the target element (80 here is the height of my top message bar minus, you can forget it)
let each = start > end ? -1 * Math.abs(start - end) / 20 : Math.abs(start - end) / 20 // Consider the direction of the roll and calculate the total distance to be rolled. Divide the distance into 20 parts
let count = 0 // Record the scrolling times
let timer = setInterval(() = > { // Set timer to roll 20 times
if(count < 20) {
content.scrollTop += each
count ++
} else {
clearInterval(timer)
}
}, 10)}Copy the code
Let’s see how the scrolling works, shall we
I feel that the roll is still quite silky 🤔 if you have a more simple and convenient, better performance method can recommend to me
Get Icons Interface
As I have said before, in line with the principle of no server or database, there is no way to automatically obtain the page icon function. It is almost impossible to access other people’s web pages in the browser and get the icon URL, because of the cross-domain problem. So I exposed an interface on my server to get the icon address of the target web page
I will not put the code here, because it is relatively simple, is to visit the target web page, get the HTML document content, filter the address of icon and return it, to see the code can be viewed in the project source code app.js
It should also be noted that although I provided an interface to automatically retrieve the ICONS of the other web pages, some web pages did not respond to the requests from the outside, such as returning a 403 Forbiden that rejected my request, so some ICONS were not available or could not be loaded. I always use a default icon to replace it. Although I have been working as a crawler for a long time, I have tried to deal with the user-agent, referer and other request headers, but it still doesn’t work. If you have a good idea, you can also provide me with a try
Then give you a simple demonstration of how to use the ~
There seems to be some blur or style changes in this GIF, due to the GIF recorder
other
As for this project, since it has only been out for less than half a month, there must be some improvements to be made. I have also listed the new functions that need to be followed up:
URL
Drag, arrange- Page account information storage function
- Provide more urls
icon
The choice of - More……
What is the meaning of the first function? It is not supported in my current project to reorder the URL after adding it, but I think this function must have, and I will add it later. I am going to try to make a function that can complete the arrangement by dragging and dropping in the editing state
The purpose of the second function is that for many websites, you may have different accounts and passwords, but now the most troublesome is that I can’t remember my account or password of this website, so I have to try many times or retrieve my password each time, which is particularly troublesome. So I want to do a mouse moved to the corresponding url, there is a view of this url corresponding to my account password function
The third function is for those websites that cannot get the icon, the icon displayed in our navigation bar is the default icon, which is ugly, so we can support you to choose your favorite icon by yourself
Please make suggestions for more features
The last
Some friends ask, why not do an account login url navigation bar, so that you do not have to take the configuration file, only need to remember the account password. I want to emphasize the choice of this project, can not use the server is not the server, can not use the database is not the database, use your own local localStorage as a database storage, you are not more assured, such as you collect some strange websites, anyway, only you know, I certainly do not know 😏 and careful friends have found that I do not even use the static page of their own server, directly deployed in the code cloud
I have taught myself front-end for so long, before I have been doing other people’s projects or imitating some websites to do a project, to count a few: Taobao home static page, mogujie mobile terminal APP, Node community, elementUi components and component document display, and so on, this project also belongs to my own, and for me is a very practical small tool, I hope you can give me a lot of support ~ give me suggestions, if you can click star 🤞
Again put the project source address: project source
If you have any questions or problems with this project, please let me know. Vx: Lpyexplore333
Pay attention to the public number: front-end impression, get more front-end information, we can also learn to exchange front-end technology, share development experience
See here, you do not point 👍 to like before walk the heart, finally thank you for your patience to watch