从零开始的web前端学习-Vue
这篇文章仅仅是分享 Vue 的部分内容,并没有完整地对其进行学习总结,想要完整地学习 Vue,请移至:https://cn.vuejs.org/guide/essentials/template-syntax.html
1初始化
npm create vite@latest(npm 安装 vite,通过 vite 对 vue 项目进行构建)
在通过 vite 构建好 vue 项目文件夹后,输入命令行指令对 vue 项目进行初始化
也可以直接通过 npm create vue@latest 命令构建,vue 默认使用 vite 进行管理
当然,如果是简单的项目,vue 也支持 cdn 方式引入
2响应式数据与插值表达式
<!-- Basic.vue -->
<script>
export default {
data() {
return {
name: "Jack",
}
},
methods: {
output() {
return `this student's name is Jack `
}
}
}
</script>
<template>
<p>{{ name }}</p>
<p>{{ 1 + 2 + 3 + 4 }}</p>
<p>{{ 1 > 2 ? "right" : "wrong" }}</p>
<p>{{ output() }}</p>
</template>
vue 内置 data 方法可以通过 return 返回数据,methods 对象包含着需要定义的方法,插值表达式 {{ }},可以将 data 方法的返回值进行渲染,也可以内部进行一些逻辑的运算并对结果进行呈现
3计算属性
计算属性与 methods 对象类似,都是方法的集合,但是计算属性具有缓存性,在响应式变量没有改变时,不会重复计算,而是输出缓存结果
<!-- Computed.vue -->
<script>
export default {
data() {
return {
number: 2,
}
},
computed: {
compare() {
console.log('compare function run')
return this.number > 4 ? `${this.number} is over 4` : `${this.number} is not over 4`
}
}
}
</script>
<template>
<p>{{ number }}</p>
<p>{{ compare }}</p>
<p>{{ compare }}</p>
</template>
注意,上述确实会生成两个具有相同内容的 p 标签,但是控制台输出仅有一次,即没有重复计算,并且,计算属性内部虽然写成函数形式,但是外部调用是不能加括号的
4侦听器
侦听器的主要功能是在响应式变量更新后,渲染页面的同时执行一些其它操作
<!-- Listener.vue -->
<script>
export default {
data() {
return {
isTrue: false
}
},
methods: {
change() {
this.isTrue = true
}
},
watch: {
isTrue(newValue, oldValue) {
console.log(`(new, old): (${newValue}, ${oldValue})`)
}
}
}
</script>
<template>
<p>{{ change() }}</p>
</template>
5指令
| 指令 | 说明 |
|---|---|
| v-text | 渲染文本,无法对标签进行渲染 |
| v-html | 可渲染标签 |
| v-for | 循环渲染 |
| v-if | 选择性渲染,不渲染时会被移除 |
| v-show | 选择性隐藏 |
| v-bind | 属性绑定响应式变量 |
| v-on | 事件绑定 |
| v-model | 绑定表单元素,实现双向数据绑定 |
<!-- Directives.vue -->
<script>
export default {
data() {
return {
text: "<span>text</span>",
html: "<span>html</span>",
name: "Jack",
status: "pending",
tasks: ["task one", "task two", "task three"],
link: "https://cn.vuejs.org/guide/essentials/template-syntax.html",
newTask: "",
person: {
age: 18,
sex: "male",
isStudent: true
}
}
},
methods: {
toggleStatus() {
if (this.status === "active") {
this.status = "pending"
} else if (this.status === "pending") {
this.status = "inactive"
} else {
this.status = "active"
}
},
addTask() {
if (this.newTask.trim() !== "") {
this.tasks.push(this.newTask)
this.newTask = ""
}
},
removeTask(index) {
this.tasks.splice(index, 1)
}
}
}
</script>
<template>
<p v-text="text"></p>
<p v-html="html"></p>
<br>
<p v-if="status === 'active'">{{ name }} is active</p>
<p v-else-if="status === 'pending'">{{ name }} is pending</p>
<p v-else>{{ name }} is inactive</p>
<br>
<p v-show="false">hidden content</p>
<form @submit.prevent="addTask">
<input type="text" name="newTask" id="newTask" placeholder="add task" v-model="newTask">
<button type="submit">add task</button>
</form>
<br>
<h2>cycle</h2>
<p v-for="i in 5">{{ i }}</p>
<br>
<h2>list of tasks</h2>
<ul>
<li v-for="(task, index) in tasks" :key="index"><span>{{ task }} </span><button @click="removeTask(index)">x</button></li>
</ul>
<br>
<h2>object cycle</h2>
<p v-for="(item, key, index) in person">item: {{ item }}, key: {{ key }}, index: {{ index }}</p>
<br>
<a v-bind:href="link">go to vue documnet</a>
<!-- <a :href="link">go to vue documnet</a> -->
<br>
<button v-on:click="toggleStatus">change status</button>
<!-- <button @click="toggleStatus">change status</button> -->
</template>
在 addTask 函数中,我们利用 JavaScript 中的 trim 函数实现空格的清除,实际上我们也可以在 v-model 后加入修饰符,如:v-model.trim,即可实现同样效果
6组件通信
- 父传子
vue 父传子组件通信与 react 类似,同样通过 props 机制进行数据传输
<!-- Comunication.vue -->
<script>
export default {
props: {
name: String,
age: {
type: [Number, String],
default: 18,
// required: true,
},
isStudent: Boolean
}
}
</script>
<template>
<p>{{ name }} is {{ age }} years old, isStudent: {{ isStudent }}</p>
</template>
type 代表该数据的类型(可为多类型),default 为该数据默认值,required 代表该数据父组件必须传递
<!-- App.vue -->
<script>
export default {
components: {
Communication
}
}
import Communication from './components/Communication.vue';
</script>
<template>
<h1>Vue Demo</h1>
<Communication name="Jack" v-bind:is-student="parentIsStudent"/>
</template>
- 子传父
vue 子传父组件通信则需要通过自定义事件进行实现
<!-- Comunication.vue -->
<script>
export default {
data() {
return {
childCount: 0
}
},
emits: ["childCountAdd"],
methods: {
addCount() {
this.childCount++
this.$emit("childCountAdd", this.childCount)
}
}
}
</script>
<template>
<button v-on:click="addCount">add</button>
</template>
注意,在 vue3 中需要先在 emits 中声明自定义函数
<!-- App.vue -->
<script>
export default {
data() {
return {
childData: 0
}
},
methods: {
getChildCount(childCount) {
this.childData = childCount
}
},
components: {
Communication
}
}
import Communication from './components/Communication.vue';
</script>
<template>
<h1>Vue Demo</h1>
<Communication @childCountAdd="getChildCount"/>
<p>{{ childData }}</p>
</template>
7组件插槽
组件插槽,简单来说,就是标签内容,通过双标签实现差异化,不仅如此,插槽也可以实现父组件“调用”子组件变量
<!-- Slot.vue -->
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
<template>
<br>
<slot>default content</slot>
<br>
<slot name="footer">default footer</slot>
<br>
<slot name="dataOne" v-bind:count="count">data content one</slot>
<br>
<slot name="dataTwo" v-bind:count="count">data content two</slot>
<br>
</template>
<!-- App.vue -->
<script>
export default {
data() {
return { }
},
methods: { },
components: {
Slot
}
}
import Slot from './components/Slot.vue';
</script>
<template>
<h1>Vue Demo</h1>
<Slot></Slot>
<Slot>content one</Slot>
<Slot>content two<template v-slot:footer>input footer two</template></Slot>
<Slot>content three<template #dataOne="dataObj">{{ dataObj.count }}</template></Slot>
<Slot>content three<template #dataTwo="{ count }">{{ count }}</template></Slot>
</template>
注意,父组件“调用”子组件变量,是因为变量本质是定义在子组件上,然后插槽的实质也是在子组件内部调用,然后该调用的返回值是一个对象,可以通过对象属性访问或者解构赋值的方式访问变量
8Vue Router
安装 vue-router 包:npm install vue-router@4,vite 项目构建默认已安装 vue-router 插件
在 src 文件夹下创建 views 文件夹,该文件夹主要存储页面文件
<!-- Home.vue -->
<script>
export default { }
</script>
<template>
<h1>Home Page</h1>
<p>This is home page.</p>
<a href="./#/About">goto</a>
</template>
<!-- About.vue -->
<script>
export default { }
</script>
<template>
<h1>About Page</h1>
<p>This is about page.</p>
<a href="./#/">goto</a>
</template>
在 src 文件夹下创建 router 文件夹,并创建 index.js 文件
// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: About }
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
注意:
| 属性 | 说明 |
|---|---|
| path | 实际 url 地址 |
| name | 逻辑 url 地址,映射实际地址 |
| component | 对应 url 地址显示组件 |
对于一些初始不用渲染的组件,我们可以使用懒加载进行优化,仅在使用时加载,如 About 组件,可以进行以下的懒加载优化
const About = () => import('@/views/About.vue')
以上,我们的简易 router 搭建完毕,然后我们需要引入 router
// src/main.js
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
接着我们在 App.vue 中添加 <router-view></router-view> 标签即可显示页面内容,也可以加入类似 <router-link to="/about">About</router-link> 的 nav 链接组实现各页面切换
动态路由:简单来讲就是为 url 添加查询参数实现不同的页面内容
<!-- Number.vue -->
<script>
export default {
props : ['number']
}
</script>
<template>
<p>Number: {{ number }}</p>
</template>
{ path: '/number/:number', name: 'Number', component: Number, props: true }(/router/index.js 中 routes 对象添加路径信息)
<!-- App.vue -->
<script>
export default { }
</script>
<template>
<nav>
<router-link to="/number/2">Number2</router-link> |
<router-link :to="{ name: 'Number', params: { number: 4 } }">Number4</router-link>
</nav>
<router-view></router-view>
</template>
注意,我们对于路径的设置存在两种方式,已在动态路由示例中演示
嵌套路由:简单来讲,即为原 url 路径添加子路径,与动态路由类似,但是嵌套路由是固定的不同路由,而动态路由则是根据某一查询参数变化而动态变化的路由
<!-- ProductComment.vue -->
<script>
export default { }
</script>
<template>
<h1>Comment Page</h1>
<p>This page provides comments of some products.</p>
</template>
<!-- Product.vue -->
<script>
export default { }
</script>
<template>
<h1>Product Page</h1>
<p>This is product page.</p>
<nav>
<router-link :to="{ name: 'product-comment'}">comment</router-link>
</nav>
<router-view></router-view>
</template>
{ path: '/product', name: 'Product', component: Product, children: [{ path: 'comment', name: 'product-comment', component: ProductComment }] }(/router/index.js 中 routes 对象添加路径信息)
<router-link to="/product">Product</router-link>(App.vue 中添加链接)
编程式导航:在某些情况下,实现自动跳转到某一路径
<!-- BackHome.vue -->
<script>
export default {
data() {
return {
timer: null
}
},
created() {
this.timer = setTimeout(() => {
this.$router.push({ name: 'Home' })
}, 3000)
},
beforeDestroy() {
if (this.timer) {
clearTimeout(this.timer)
}
}
}
</script>
<template>
<h1>Back Home Page</h1>
<p>It will go home after 3 seconds.</p>
</template>
this.$router 能够指向 router 中设定的路由,上述代码实现 3 秒后跳转 home 页面
路由传参:各个 url 路径间传递数据
<!-- Deliver.vue -->
<script>
export default {
data() {
return {
timer: null
}
},
created() {
this.timer = setTimeout(() => {
this.$router.push({ name: 'product-comment', query: { comment: 'the product is good!' } })
}, 3000)
},
beforeDestroy() {
if (this.timer) {
clearTimeout(this.timer)
}
}
}
</script>
<template>
<p>It will send a good commnet after 3 seconds in console.</p>
</template>
<!-- Product.vue -->
<script>
export default {
created() {
console.log(this.$route.query)
}
}
</script>
<template>
<h1>Product Page</h1>
<p>This is product page.</p>
<nav>
<router-link :to="{ name: 'product-comment' }">comment</router-link> |
<router-link :to="{ name: 'back-home' }">back</router-link> |
<router-link :to="{ name: 'deliver-comment' }">deliver</router-link>
</nav>
<router-view></router-view>
</template>
注意此处的 this.$route 与之前的 this.$router 不同,之前的 router 是对路由进行一些操作,而 route 则是对路由数据进行一些操作
上述代码在由 Deliver 页面跳转至 ProductComment 页面后,会在控制台输出一条评论信息
导航守卫:对所有导航做统一设置
// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '@/views/Home.vue';
const About = () => import('@/views/About.vue')
const Number = () => import('@/views/Number.vue')
const Product = () => import('@/views/Product.vue')
const ProductComment = () => import('@/views/product/ProductComment.vue')
const BackHome = () => import('@/views/product/BackHome.vue')
const Deliver = () => import('@/views/product/Deliver.vue')
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: About },
{ path: '/number/:number', name: 'Number', component: Number, props: true },
{ path: '/product', name: 'Product', component: Product, children: [{ path: 'comment', name: 'product-comment', component: ProductComment }, { path: 'back', name: 'back-home', component: BackHome }, { path: 'deliver', name: 'deliver-comment', component: Deliver}] }
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
router.beforeEach((to, from, next) => {
console.log('router run')
next()
})
export default router
其中,router.beforeEach 是导航守卫函数之一,to 代表着前往路由,from 代表出发路由,next 代表切换路由函数,若不执行该函数,则页面不会相应更新
9Vuex
Vuex 是组件状态管理工具,安装 vuex 包:npm install vuex,vite 项目构建默认已安装 vuex 插件
在 src 文件夹下创建 store 文件夹,并创建 index.js 文件
// src/store/index.js
import { createStore } from "vuex";
const store = createStore({
state() {},
getters: {},
mutations: {},
actions: {},
modules: {}
})
export default store
然后,需要导入 store
// src/main.js
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
state:简单来讲,是全局数据存储,所有组件皆可使用 state 中的数据
// src/store/index.js
import { createStore } from "vuex";
const store = createStore({
state() {
return {
isLogin: true
}
}
})
export default store
<!-- TestStore.vue -->
<script>
export default {
data() {
return {
isLogin: this.$store.state.isLogin
}
}
}
</script>
<template>
{{ isLogin }}
</template>
注意,通过 this.$store.state 访问 state 中的数据
实际上,可以直接通过赋值的方式修改 state 中的数据,但是这会破坏全局数据的一致性,所以并不建议这么做
通过 data 访问全局数据会更加规范,但是直接访问也是可行的
mutations:提供修改 state 中数据的途径
// src/store/index.js
import { createStore } from "vuex";
const store = createStore({
state() {
return {
count: 0
}
},
mutations: {
addCount(state, num) {
state.count += num
}
}
})
export default store
<!-- TestStore.vue -->
<script>
export default {
methods: {
add() {
this.$store.commit('addCount', 1)
}
}
}
</script>
<template>
{{ this.$store.state.count }}
<button v-on:click="add">add</button>
</template>
注意访问 mutations 方法的方式是 this.$store.commit 请求,而非 this.$store.mutations 属性,当然也可以直接使用 mutations 方法,但是通过 methods 方法的方式使用 mutations 方法会更加规范
actions:mutations 是同步操作,对于异步操作,只会记录最终结果,而 actions 可以理解为异步的 mutations,但是注意,修改依旧依赖于 mutations 方法,actions 仅仅是添加异步操作
// src/store/index.js
import { createStore } from "vuex";
const store = createStore({
state() {
return {
count: 0,
timer: null
}
},
mutations: {
addCount(state, num) {
state.count += num
},
setDelayTimer(state, timer) {
state.timer = timer
},
clearDelayTimer(state) {
state.timer = null
}
},
actions: {
delayMinus(store, num) {
if (store.state.timer) {
clearTimeout(store.state.timer)
}
const timer = setTimeout(() => {
store.commit('addCount', -num)
store.commit('clearDelayTimer')
}, 3000)
store.commit('setDelayTimer', timer)
}
}
})
export default store
<!-- TestStore.vue -->
<script>
export default {
methods: {
delayMinus() {
this.$store.dispatch('delayMinus', 1)
}
}
}
</script>
<template>
{{ this.$store.state.count }}
<button @click="delayMinus">delay minus</button>
</template>
上述代码实现延迟 3 秒减一操作,若不进行删除定时器操作,效果类似,但是定时器冗余,可能会降低执行效率
注意,优先同步操作,后异步操作
getters:可以理解为全局的计算属性
// src/store/index.js
import { createStore } from "vuex";
const store = createStore({
state() {
return {
count: 0
}
},
getters: {
compare(state) {
console.log('compare run')
return state.count > -1
}
}
modules() {}
})
export default store
<!-- TestStore.vue -->
<script>
export default {
methods: {
compare() {
console.log(this.$store.getters.compare)
}
}
}
</script>
<template>
<button @click="compare">compare</button>
</template>
modules:划分模块,各模块间不互相依赖,或者可以理解为封装,将针对某一部分用户的变量、方法等进行封装
// src/store/index.js
import { createStore } from "vuex";
const store = createStore({
modules: {
student: {
state() {
return {
id: '001'
}
},
getters: {},
mutations: {},
actions: {}
}
}
})
export default store
对此的访问也需要稍微改变: this.$store.state.student.id
当然,你也可以真正将 modules 中的对象封装为单独的 JavaScript 文件
// src/store/module/worker.js
const worker = {
state() {
return {
job: 'cleaner'
}
},
getters: {},
mutations: {},
actions: {}
}
export default worker
// src/store/index.js
import { createStore } from "vuex";
import worker from "./module/worker";
const store = createStore({
modules: {
student: {
state() {
return {
id: '001'
}
},
getters: {},
mutations: {},
actions: {}
},
worker: worker
}
})
export default store
10vue3
vue3 是当前 vue 的默认版本,与 vue2 存在一些差别,但是大部分内容是相同的
vue 现在存在选项式与组合式两种代码风格,vue2 语法多为选项式,而 vue3 则提供一种新的写法,即组合式 API
选项式 API 会导致某一功能的代码较为分散,组合式 API 则能够将同一功能的实现集中起来,会更加类似于 JavaScript
在组合式 API 中,直接通过 const 或者 let 声明的变量都不是响应式的,对其进行修改并不能生效,需要通过相应的函数创建响应式数据
响应式数据-reactive:reactive 函数能够创建响应式数据,但是仅能创建对象、数组、映射、元组等对象类型数据
<!-- Reactive.vue -->
<script>
import { reactive } from 'vue';
export default {
setup() {
const numArray = reactive([1, 2, 3])
function pushNumber() {
numArray.push(4)
}
return {
numArray,
pushNumber
}
}
}
</script>
<template>
<p>{{ numArray }}</p>
<button @click="pushNumber">push</button>
</template>
在代码中,也能够看出组合式 API 的一种实现方式,即 setup 函数
reactive 函数创建的变量的各个维度都是响应式的,无论维度是深层次还是浅层次,如果不希望如此,可以使用 sallowReactive 函数,该函数仅仅会对数据的浅层次进行响应式处理
响应式数据-ref:ref 函数的使用范围会更加广泛,它对基本类型以及对象类型的数据都适用,但是 ref 创建的变量不能直接修改,而是应该对其 value 属性进行修改
<!-- Ref.vue -->
<script setup>
import { ref } from 'vue'
const name = ref("Jack")
const status = ref("active")
const tasks = ref(["task one", "task two", "task three"])
const toggleStatus = () => {
if (status.value === "active") {
status.value = "pending"
} else if (status.value === "pending") {
status.value = "inactive"
} else {
status.value = "active"
}
}
</script>
<template>
<p v-if="status === 'active'">{{ name }} is active</p>
<p v-else-if="status === 'pending'">{{ name }} is pending</p>
<p v-else>{{ name }} is inactive</p>
<h2>list of tasks</h2>
<ul>
<li v-for="task in tasks" :key="task">{{ task }}</li>
</ul>
<button @click="toggleStatus">change status</button>
</template>
上述代码还给出组合式 API 实现的另一种方式,即 setup 属性,该方式并不需要 return 返回逻辑
只读数据-readonly:readonly 函数创建只读数据,该数据仅读不能进行写操作,所以是非响应式的
<!-- ReadOnly.vue -->
<script setup>
import { readonly } from 'vue';
const readData = readonly({
name: 'Jack',
age: 18,
isStudent: true
})
</script>
<template>
<p>{{ readData.name }} is {{ readData.age }} years old.</p>
<p>Is he a student? <br> {{ readData.isStudent }}</p>
</template>
如果希望数据浅层次只读,数据深层次可写,则可以使用类似的 shallowReadonly 函数,该函数对于数据深层次是可写的,但是数据仍然是非响应式的,需要自己手动刷新查看变动
计算属性-computed:与选项是 API 的计算属性类似
<!-- ComputedFun.vue -->
<script setup>
import { computed, ref } from 'vue';
const content = ref('test')
const getLen = computed(() => {
console.log('getLen run')
return content.value.length
})
</script>
<template>
{{ getLen }}
{{ getLen }}
</template>
侦听器-watch:和组合式 API 类似,但是会存在一些特殊情况
<!-- Watch.vue -->
<script setup>
import { reactive, ref, watch } from 'vue';
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(oldValue, newValue)
})
function addCount() {
count.value++
}
const student = reactive({
name: 'Jack',
age: 18,
friends: ['Tom', 'Aily']
})
watch(() => student.age, (newValue, oldValue) => {
console.log(oldValue, newValue)
})
function beOlder() {
student.age++
}
</script>
<template>
<p>{{ count }}</p>
<button @click="addCount">add</button>
<p>{{ student }}</p>
<button @click="beOlder">old</button>
</template>
侦听器能够直接监听 reactive 和 ref 对象,但是对于对象类型的某一具体属性的监听,需要通过函数的形式实现
watchEffect:类似于 react 框架中的 useEffect,但是 watchEffect 会更加智能,会自动监听使用变量的值是否发生变化
<!-- WatchEffect.vue -->
<script setup>
import { ref, watchEffect } from 'vue';
const count = ref(0)
const judge = ref(true)
watchEffect(() => {
console.log(`count: ${count.value}, judge: ${judge.value}`)
})
function addCount() {
count.value++
}
function toggleJudge() {
judge.value = !judge.value
}
</script>
<template>
<p>{{ count }}</p>
<button @click="addCount">add</button>
<p>{{ judge }}</p>
<button @click="toggleJudge">toggle</button>
</template>
11vue3状态管理-pinia
在项目构建时,如果没有勾选 pinia,需要通过 npm install pinia 安装并导入
// src/main.js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
然后,在 src/store 文件夹下创建 counter.js 文件
如果在构建时已经勾选 pinia,那么 counter.js 文件内容如下
// src/store/counter.js
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return {
count,
doubleCount,
increment
}
})
实际上,在 pinia 中进行状态管理,并没有特别之处,使用的基本语法与 vue3 中的语法几乎相同,ref 会被类比为 vuex 中的 state,computed 会被类比为 getters,pinia 没有 mutations 的概念,而 function 会被类比为 actions + mutations,最后在 pinia 中的模块概念实际就是该 JavaScript 文件,如 counter.js 文件,而 defineStore 就创建了 'counter' 这个模块
<!-- TestPinia.vue -->
<script setup>
import { useCounterStore } from '@/store/counter';
const counterStore = useCounterStore()
</script>
<template>
<p>{{ counterStore.count }}</p>
<p>{{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">add</button>
</template>

浙公网安备 33010602011771号