Практический проект Vue3, разработанный вами (включая идеи реализации и представление основных моментов проекта)

Vue.js
Практический проект Vue3, разработанный вами (включая идеи реализации и представление основных моментов проекта)

В начале ноября я поставил себеЦель: полныйVue3, а затем сделать небольшой проект

в,Vue3Я уже давно его изучил, а потом еще написал два резюме или опыта, многие из них - ямы, на которые наступили в проекте, так что можете посмотреть на них, чтобы не столкнуться в дальнейшей разработке:

тогда делайVue3Проект тоже был задуман мной, т. к. онлайн-проектов было не так много или большинство из них были проектами торговых центров, а подобных проектов я писал до этого много, поэтому я все же планировал написать один сам, и назвал его какnav-url, как следует из названия, являетсяПанель навигации по URL-адресу, когда я писал эту статью, проект уже был в сети и использовался мной и моими друзьями.Ссылка на превью ниже 👇👇

Нажмите, чтобы просмотреть 👉Ссылка на предварительный просмотр проекта

Затем укажите адрес исходного кода проекта 👉:Ссылка на источник проекта(Добро пожаловатьstar)

Далее я подробно расскажу о своем проекте

Первоначальный замысел дизайна

Я также старший некомпьютерный специалист в школе.Я обычно изучаю интерфейс самостоятельно, поэтому с самого начала и до сих пор я в основном использую видео в Интернете, покупаю книги или беру книги в библиотеке и просматриваю технические блоги. знания и тд. За это время я увижу много сайтов с полезными инструментами или какие-то интересные сайты и добавлю их в закладки, опасаясь, что потом не смогу их найти.Возможно, клип получился таким

Это все мои любимые давным-давно, если я буду следовать этой тенденции, мои избранные заполнятся в течение полугода, а найти сайт в это время будет неудобно, поэтому я хочу сделать свою панель навигации по сайту. высоко :Простой и щедрый,Удобный

Итак, есть текущий проект, как показано на следующем рисунке:

Особенности проекта && Особенности

В конце концов, это панель навигации по URL-адресу, поэтому функция очень проста, но я постараюсь улучшить некоторые дополнительные функции проекта.

Особенности проекта:

✅ Добавлять, изменять, удалять теги

✅Добавлять, изменять, удалять URL-адреса

✅ Функция поиска

✅ Импорт и экспорт конфигурации

Особенности проекта:

⭐ На основеVue3развивать

⭐ Простая и элегантная страница

⭐ Предоставить сайтзначок,имяполучить интерфейс

⭐ Панель вкладок поддерживает различныеiconвыберите

⭐ ПройденоlocalStorageхранилище, нет необходимости настраивать базу данных

⭐ сVue3упакованныйElement UIизmessage,dialog,button,input,popoverкомпоненты

⭐ ПройденоVuex 4Заниматься управлением состоянием

⭐ Анимация прокрутки страницы

⭐ ПоддержкаСохранение и экспорт данных в один клик,Импорт данных одним щелчком мыши

Структура файла проекта

Основные файлы всего проекта находятся вsrcПод папкой структура каталога выглядит следующим образом:

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

Основные моменты

Для логического кода проекта вы можете напрямую просмотреть мой исходный код, все из которых используютсяVue3написано по грамматике

На момент первоначальной работы над этим проектом не было подходящегоVue3Библиотека компонентов, поэтому я инкапсулировал ее в соответствии со своими потребностями.message,dialog,input,button,popoverТакие пять компонентов, которые сосредоточены наmessageа такжеdialogКроме того, есть также основной момент этого проекта: импорт конфигурации и экспорт

Дилоговый компонент

Во-первых, это содержимое компонента:

// 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">&#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">确定</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файл, черезVue3изprovideметод выставлен глобально

<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Цепной вызов , вы можете настроить пользователя на нажатиеподтверждатьилиОтменаКод обработки после

Компонент сообщения

Во-первых, это содержимое компонента:

// 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файл, черезVue3изprovideметод выставлен глобально

<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>

Компонент всплывающего окна

Я не имитировал этот компонент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Просто снова импортируйте в него данные, поэтому я называю эти данные какКонфигурация (Конфиг)

Сначала у нас должна быть конфигурация, поэтому нам нужно иметьlocalStorageЭкспорт данных одним щелчком мыши и сохранение в виде файла

Я ссылаюсь на документ MDN для этой функции, Если вам интересно, вы можете узнать об этом:Веб-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, вы можете понять:Веб-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-agent,refererОжидание обработки заголовков запроса, но все безрезультатно. Если у вас есть хорошее решение, вы также можете дать мне попробовать.

Тогда я покажу вам, как его использовать.

Кажется, в этой анимации есть некоторые размытия или изменения стиля, и все из-за устройства записи gif.

разное

Для этого проекта, поскольку он только что был выпущен менее чем за полмесяца, должны быть возможности для улучшения.Я также перечислил новые функции, которые необходимо реализовать в будущем:

  1. URLперетащить
  2. Функция хранения информации об учетной записи страницы
  3. предоставить больше URL-адресовiconвыбор
  4. более

Что означает первая функция, то есть мой текущий проект не поддерживает ее добавлениеURLПереупорядочил, но думаю эта функция обязательна, и она будет добавлена ​​позже.Планирую найти способ сделать функцию, которую можно было бы сортировать перетаскиванием в состоянии редактирования.

Цель второй функции заключается в том, что для многих веб-сайтов у вас могут быть разные номера учетных записей и пароли, но сейчас самое неприятное заключается в том, что я не могу вспомнить, какой номер моей учетной записи или пароль для этого веб-сайта. попробуйте или восстановите пароль много раз; поэтому я хочу навести указатель мыши на соответствующий URL-адрес, и есть функция для просмотра URL-адреса, соответствующего паролю моей учетной записи.

Третья функция предназначена для тех, кто не может получитьiconна веб-сайте s значки, отображаемые на панели навигации, являются значками по умолчанию, которые довольно уродливы, поэтому мы можем помочь каждому выбрать свои любимые значки в то время.

Для получения дополнительных функций, пожалуйста, дайте больше предложений ~

наконец

Некоторые друзья спрашивали, почему бы не сделать панель навигации по URL для входа в учетную запись, чтобы не нужно было никуда нести файл конфигурации, нужно было только запомнить пароль от учетной записи. Хочу еще раз подчеркнуть выбор данного проекта.Если можно обойтись без сервера - сервер вам не нужен.Если можно обойтись без базы данных - вам не нужна база данных.Используйте свою локальнуюlocalStorageВ качестве хранилища базы данных вы не чувствуете себя более непринужденно.Например, у вас есть какие-то странные веб-сайты в вашей коллекции.В любом случае, только вы знаете о них.Я все равно не должен знать.Используйте свой собственный сервер и разверните его прямо на облако кода

Самоучка в области внешнего интерфейса так долго, что я работал над проектами других людей или имитировал некоторые веб-сайты для выполнения проекта, вот некоторые из них: статическая страница домашней страницы Taobao, мобильное приложение Mogujie, сообщество узлов, компоненты elementUi и документация по компонентам Показать , и т. д. На этот раз этот проект также является моим собственным, и это очень полезный небольшой инструмент для меня. Надеюсь, вы можете меня сильно поддержать ~ Дайте мне свое мнение и нажмите, если можете.star🤞

Снова введите адрес исходного кода проекта:Исходный код проекта

Если у вас есть какие-либо вопросы по этому проекту или если есть проблемы с проектом, вы можете сказать мне, vx: Lpyexplore333

Обратите внимание на общедоступный номер:фронтное впечатление, чтобы получить больше передовых материалов, мы также можем изучать и обмениваться передовыми технологиями и делиться опытом разработки вместе.

Смотрите здесь, не заказывайте 👍отличныйПродолжайте, наберитесь терпения и, наконец, спасибо за ваше терпение смотреть