前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自己设计的Vue3的实用项目(内含对项目亮点的实现思路与介绍)

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

作者头像
@零一
发布2021-05-14 17:43:53
1.5K0
发布2021-05-14 17:43:53
举报
文章被收录于专栏:前端印象前端印象

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

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

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

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

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

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

设计初衷

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

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

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

项目功能 && 特色

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

项目的功能:

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

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

✅ 搜索功能

✅ 配置的导入、导出

项目的特色:

⭐ 基于 Vue3 开发

⭐ 页面简单大方

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

⭐ 标签栏支持多种 icon 选择

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

⭐ 用 Vue3 封装了 Element UImessagedialogbuttoninputpopover 组件

⭐ 通过 Vuex 4 进行状态管理

⭐ 页面的滚动动画

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

项目文件结构

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

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

重点介绍

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

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

Dilog组件

首先是组件内容:

代码语言:javascript
复制
// 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,用于确认用户的点击情况

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

代码语言:javascript
复制
// 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 方法暴露在全局

代码语言:javascript
复制
<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 组件

代码语言:javascript
复制
<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组件

首先是组件内容:

代码语言:javascript
复制
// 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>

然后是组件的处理代码:

代码语言:javascript
复制
// 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 方法暴露在全局

代码语言:javascript
复制
<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 方法获取即可

代码语言:javascript
复制
<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 来调用

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

首先是组件的内容:

代码语言:javascript
复制
// 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

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

代码语言:javascript
复制
// 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 文件中注册一下自定义指令

代码语言:javascript
复制
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')

再来看一下使用方式

代码语言:javascript
复制
<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()

我大致是这样实现的:

代码语言:javascript
复制
// 封装的下载数据函数
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

我大致是这样实现的:

代码语言:javascript
复制
// 导入配置
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

我大致是这样实现的:

代码语言:javascript
复制
// 跳转到指定标签
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-12-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 设计初衷
  • 项目功能 && 特色
    • 项目的功能:
      • 项目的特色:
        • Dilog组件
        • Message组件
        • Popover组件
        • SaveConfig
        • ImportConfig
        • Scroll Animation
    • 项目文件结构
    • 重点介绍
    • Get Icons Interface
    • 其它
    • 最后
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档