从零开始使用Vue.js实现中国三级城市查询

470 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

从零开始使用Vue.js实现中国三级城市查询

一 简介

项目需求是实现一个展示和查询中国三级城市(省->市->县(区))的功能。

主要界面如下:

image-20220527130514216.png

image-20220527144225560.png

image-20220527144251265.png

二 开发环境部署

2.1 建立vue.js工程项目

我们这里采用HbuilderX开发工具,来直接创建一个Vue.js 2.x的项目,省去安装node.js及npm下载的过程。

菜单栏:文件->新建->项目,然后选择普通项目中的vue项目(2.6.10),并给项目起一个名字:“myApp”,然后点击创建,等待HbuilderX下载相关依赖。

image-20220527131200492.png

项目构建完成后,项目目录如下图,其中src是源码目录,也就是我们的主要开发工作都在此目录里进行。

image-20220527131846951.png

image-20220527132003132.png

2.2 创建页面及组件

2.2.1 创建主页面

在src目录下,创建pages目录,用于存放vue页面,并在其目录下,创建首页index、省级页province和市级页city,最后我们再创建一个找不到页面时转向的404页面。

image-20220527134347770.png

2.2.2 创建组件页面

在src目录下的components目录中,创建Header顶部组件和Serach搜索组件。

image-20220527134515062.png

2.3 安装Vue工具及插件

2.3.1 Axios - ajax工具

Axios是vue官方推荐的一个基于promise的ajax库,通过它我们来与数据接口进行请求,获得响应数据。我们利用Hbuilder自带的终端及npm工具来进行下载:

  1. Axios 安装

    选择项目目录,右键弹出菜单中,选择使用命令行窗口打开所在目录。或者直接使用快捷键“ALT+C”,来打开底部终端,其本质是调用了windows的PowerShell工具。

image-20220527132345792.png

在终端里,我们执行npm安装Axios命令:

 npm i axios

0. Axios 配置

在src目录下,创建axios目录,并创建index.js。

```
 // index.js
 import axios from 'axios'
 // 定义一个全局的路径
 axios.defaults.baseURL = 'https://www.mxnzp.com/api/'; // api接口地址
 // 定义了一个全局请求参数 app_id及app_secret 自行到https://www.mxnzp.com申请
 axios.defaults.params = {
     app_id: 'xx',
     app_secret: 'xxxx',
 }
 export default axios
```

在src目录下的main.js文件中,加入代码:

```
 import Vue from 'vue'
 import App from './App.vue'
 import axios from '@/axios'
 // vue 原型链增加axios  在项目中可用this.axios
 Vue.prototype.axios = axios
 ​
 new Vue({
     render: h => h(App),
 }).$mount('#app')
```

2.3.2 Vue-Router - 路由

  1. Vue-Router 安装

    在终端里,我们执行npm安装vue-router 3.x命令。

     npm i vue-router@3  
    
  2. Vue-Router 配置

    在src目录下,创建router目录,并创建index.js,这里我们定义了4个路由规则,并指向了pages目录下的vue页面,

     // index.js
     import Vue from 'vue'
     import VueRouter from 'vue-router'
     Vue.use(VueRouter)
     // 解决 vue 重复点击一个路由报错的问题
     const originalPush = VueRouter.prototype.push
     VueRouter.prototype.push = function push(location) {
         return originalPush.call(this, location).catch(err => err)
     }
     // 定义路由规则
     const routes = [{
             name: 'index',
             path: '/',
             component: () => import("@/pages/index")
         },
         {
             name: 'province',
             path: '/province',
             component: () => import("@/pages/province")
         },
         {
             name: 'city',
             path: '/city',
             component: () => import("@/pages/city")
         },
         {
             name: '404',
             path: '*',
             component: () => import("@/pages/404")
         }
     ]
     ​
     // 实例化vuerouter
     ​
     const router = new VueRouter({
         routes
     })
     ​
     export default router
     ​
    

    在src目录下的main.js文件中,加入代码:

     import Vue from 'vue'
     import App from './App.vue'
     import axios from '@/axios'
     ​
     // 导入router
     import router from '@/router'
     ​
     Vue.prototype.axios = axios
     ​
     Vue.config.productionTip = false
     ​
     new Vue({
         router,  // 使用router
         render: h => h(App),
     }).$mount('#app')
     ​
    

2.3.3 ElementUI

  1. ElementUI安装

     npm i element-ui
    
  2. 在main.js 导入 ElementUI

     ​
     import Vue from 'vue'
     import App from './App.vue'
     import axios from '@/axios'
     import ElementUI from 'element-ui'
     Vue.use(ElementUI)
     import 'element-ui/lib/theme-chalk/index.css';
     import 'element-ui/lib/theme-chalk/display.css';
     ​
     ​
     import router from '@/router'
     Vue.prototype.axios = axios
     ​
     Vue.config.productionTip = false
     ​
     new Vue({
         router,
         render: h => h(App),
     }).$mount('#app')
     ​
    

三 数据接口

3.1 全国城市列表

请求参数说明:

名称类型说明

返回参数说明:

名称类型说明
code整形省/市/区编号
name字符串省/市/区名称
pchildsArray市列表
cchildsArray区列表

返回示例:

 {
     "code":1,
     "msg":"数据返回成功",
     "data":[
         {
             "code":"130000",
             "name":"河北省",
             "pchilds":[
                 {
                     "code":"130100",
                     "name":"石家庄市",
                     "cchilds":[
                         {
                             "code":"130101",
                             "name":"市辖区"
                         },
                         {
                             "code":"130102",
                             "name":"长安区"
                         },
                         ...这里只显示了两个区...
                     ]
                 },
                 {
                     "code":"130200",
                     "name":"唐山市",
                     "cchilds":[
                         {
                             "code":"130201",
                             "name":"市辖区"
                         },
                         {
                             "code":"130202",
                             "name":"路南区"
                         },
                         ...这里只显示了两个区...
                     ]
                 },
                 ...这里只显示了两个市...
             ]
         }
         ...这里只显示了一个省...
     ]
 }

3.2 搜索全国城市列表

请求参数说明:

名称类型说明
type整形类型 0-查询省份 1-查询城市
value字符串被查询的省份或者城市名称

返回参数说明:

名称类型说明
code整形省/市/区编号
name字符串省/市/区名称
pchildsArray市列表
cchildsArray区列表

返回示例:

 {
     "code": 1,
     "msg": "数据返回成功",
     "data": [
         {
             "code": "440000",
             "name": "广东省",
             "pchilds": [
                 {
                     "code": "440300",
                     "name": "深圳市",
                     "cchilds": [
                         {
                             "code": "440301",
                             "name": "市辖区"
                         },
                         {
                             "code": "440303",
                             "name": "罗湖区"
                         },
                         ...这里只显示了两个区...
                     ]
                 }
             ]
         }
     ]
 }

四 代码实现

4.1 主要页面

4.1.1 首页 index.vue

 <template>
     <div>
         <el-row>
             <el-col :xs="12" :sm="6" :md="6" :lg="6" :xl="6" :gutter="20" v-for="item,index in provinceList">
                 <div class="grid-content" @click="goProvince(item.name)">{{ item.name }}</div>
             </el-col>
         </el-row>
     </div>
 </template>
 ​
 <script>
     export default {
         data() {
             return {
                 provinceList: []
             }
         },
         created() {
             this.getList()
         },
         methods: {
             getList() {
                 this.axios.get('address/list').then((res) => {
                     this.provinceList = res.data.data
                     // console.log(res);
                 })
             },
             goProvince(provinceName) {
                 this.$router.push('/province?value=' + provinceName)
             }
 ​
         },
     }
 </script>
 ​
 <style>
 ​
 </style>
 ​

4.1.2 省级页 privince.vue

 <template>
     <div>
 ​
         <el-row>
             <div class="title">
                 {{provinceName}}
             </div>
             <el-col :xs="12" :sm="6" :md="6" :lg="6" :xl="6" :gutter="20" v-for="item,index in cityList">
                 <div class="grid-content" @click="goCity(item.name)">{{ item.name }}</div>
             </el-col>
         </el-row>
     </div>
 </template>
 ​
 <script>
     /**
      * 使用test方法实现模糊查询
      * @param  {Array}  list     原数组
      * @param  {String} keyWord  查询的关键词
      * @return {Array}           查询的结果
      */
     function fuzzyQuery(list, keyWord) {
         var reg = new RegExp(keyWord);
         var arr = [];
         for (var i = 0; i < list.length; i++) {
             if (reg.test(list[i])) {
                 arr.push(list[i]);
             }
         }
         if (arr.length > 0) {
             return true
         } else {
             return false
         }
     }
     export default {
         data() {
             return {
                 provinceName:'',
                 cityList: [],
                 // 直辖市或特别自治区: 只有直辖市/地区->县区 两级的地方
                 specialProvinces: ['北京市', '天津市', '重庆市', '上海市', '中国台湾', '中国香港', '澳门']
             }
         },
         created() {
             this.getList()
         },
         watch: {
             // 观察$route发生变化时,重新加载dom
             '$route.query.value'(to, from) {
                 this.getList()
             }
         },
         methods: {
             getList() {
 ​
                 this.axios.get('address/search', {
                     params: {
                         type: 1,
                         value: this.$route.query.value
                     }
                 }).then((res) => {
                     console.log(res);
                     
                     // 无省份信息时,跳转404
                     if (res.data.data.length === 0) {
                         this.$router.replace('/404')
                     }
                     this.provinceName = res.data.data[0].name
                     // 判断特殊地区 处理
                     fuzzyQuery(this.specialProvinces, this.$route.query.value) ?
                         this.cityList = res.data.data[0].pchilds[0].cchilds :
                         this.cityList = res.data.data[0].pchilds
                 })
             },
             goCity(cityName) {
                 // 判断特殊地区 处理
                 fuzzyQuery(this.specialProvinces, this.$route.query.value) ?
                     this.$message.error('直辖市或特别自治区,没有往下一级了') :
                     this.$router.push('/city?value=' + cityName)
             }
 ​
 ​
         },
     }
 </script>
 ​
 <style>
     .title {
         background: #E6A23C;
         text-align: center;
         line-height: 36px;
         margin: 10px auto;
         width: 200px;
         padding: 10px;
         border-radius: 4px;
         min-height: 36px;
         color: white;
     }
 </style>
 ​

4.1.3 市级页 city.vue

 <template>
     <div>
         <div class="title">
             当前市:{{this.$route.query.value}}
         </div>
         <el-row>
             <el-col :xs="12" :sm="6" :md="6" :lg="6" :xl="6" :gutter="20" v-for="item,index in countyList">
                 <div class="grid-content" @click="giveNotice()">{{ item.name }}</div>
             </el-col>
         </el-row>
         
     </div>
 </template>
 ​
 <script>
     export default {
         data() {
             return {
                 countyList: []
             }
         },
         created() {
             this.getList()
         },
         methods: {
             getList() {
                 this.axios.get('address/search',{
                     params:{
                         type:1,
                         value:this.$route.query.value
                     }
                 }).then((res) => {
                     this.countyList = res.data.data[0].pchilds[0].cchilds
                     this.countyList.shift()
                     console.log(this.countyList);
                 })
             },
             giveNotice(){
                 this.$message.error('没有往下一级了')
             }
         },
     }
 </script>
 ​
 <style>
     .title {
         background: #E6A23C;
         text-align: center;
         line-height: 36px;
         margin: 10px auto;
         width: 200px;
         padding: 10px;
         border-radius: 4px;
         min-height: 36px;
         color: white;
     }
 </style>

4.1.4 404页 404.vue

 <template>
     <div>
         <router-link to="/"> 
             <img src="../assets/images/404.png" width="100%" alt="">
         </router-link>
     </div>
 </template>
 ​
 <script>
 </script>
 ​
 <style>
 </style>

4.2 子组件页面

4.2.1 顶部组件 Header.vue

 <template>
     <el-row>
         <div class="header">
             <el-col :xs="{span:24,offset:0}" :sm="{span:16,offset:4}" :md="{span:12,offset:6}">
                 <el-row style="margin-bottom: 0;">
                     <el-col :span="8">
                         <div v-if="$route.name !== 'index'" class="back"><i class="el-icon-arrow-left"
                                 @click="$router.back()"></i></div>
                         <div v-if="$route.name == 'index'">&nbsp;</div>
                     </el-col>
                     <el-col :span="8" style="text-align: center; over">
                         <router-link to="/" class="webtitle">
                             中国城市查询
                         </router-link>
                     </el-col>
                     <el-col :span="8">
                         <search-com class="search-com"></search-com>
                     </el-col>
                 </el-row>
             </el-col>
         </div>
 ​
     </el-row>
 </template>
 ​
 <script>
     export default {
 ​
         components: {
             'search-com': () => import('@/components/Search')
         },
     }
 </script>
 ​
 <style scoped>
     .header {
         width: 100%;
         position: fixed;
         top: 0;
         background: rgb(41, 175, 276);
         height: 70px;
         line-height: 70px;
         color: white;
         z-index: 9;
         
     }
 ​
     .webtitle {
         font-size: 1.5rem;
         color: white;
         text-decoration: none;
     }
     
     .back {
         font-size: 1.5rem;
         cursor: pointer;
         margin-left: 10px;
     }
     .search-com {
         float: right;
         margin-right: 10px;
     }
 ​
     @media screen and (max-width: 600px) {
         .header {
             height: 50px;
             line-height: 50px;
         }
         .webtitle {
             font-size: 0.8rem;
         }
         .back {
             font-size: 1.2rem;
         }
     }
 ​
     
 </style>
 ​

4.2.2 搜索组件 Serach.vue

 <template>
     <div>
         <el-input @keydown.native.enter="goProvince(provinceName)" size="mini" prefix-icon="el-icon-search"
             v-model.trim="provinceName" @focus="focusIt" @blur="focusIt" placeholder="请输入省份名称,可模糊查询"
             :style="isFocus?'width:100%':'width:5rem'">
         </el-input>
     </div>
 </template>
 ​
 <script>
     /**
      * 使用test方法实现模糊查询
      * @param  {Array}  list     原数组
      * @param  {String} keyWord  查询的关键词
      * @return {Array}           查询的结果
      */
     function fuzzyQuery(list, keyWord) {
         var reg = new RegExp(keyWord);
         var arr = [];
         for (var i = 0; i < list.length; i++) {
             if (reg.test(list[i].name)) {
                 arr.push(list[i]);
             }
         }
         if (arr.length > 0) {
             return true
         } else {
             return false
         }
     }
     export default {
         data() {
             return {
                 provinceName: '',
                 isFocus: false
             }
         },
         methods: {
             goProvince(provinceName) {
                 if (!provinceName.length) {
                     return this.$message.error('请输入省份名称')
                 }
                 if (provinceName.length<2) {
                     return this.$message.error('请输入两个字以上')
                 }
                 this.axios.get('address/list').then((res) => {
                     this.provinceList = res.data.data
                     let find = fuzzyQuery(this.provinceList, provinceName)
                     if (find) {
                         this.$router.push('/province?value=' + provinceName)
                     } else {
                         this.$message.error('找不到您所输入的省份')
                     }
                 })
             },
             focusIt() {
                 this.isFocus = !this.isFocus
             }
         },
     }
 </script>
 ​
 <style scoped>
     @media screen and (max-width: 600px) {
         .el-input {
             width: 140px;
         }
     }
 </style>
 ​

4.3 其他资源

4.3.1 main.css

 * {
     padding: 0;
     margin: 0;
 }
 ​
 #app {
     height: 100%;
 }
 ​
 .content {
     margin-top: 70px;
 }
 ​
 .el-row {
     margin-bottom: 20px;
 ​
     &:last-child {
         margin-bottom: 0;
     }
 }
 ​
 .el-col {
     border-radius: 4px;
 }
 ​
 .bg-purple-dark {
     background: #99a9bf;
 }
 ​
 .bg-purple {
     background: #d3dce6;
 }
 ​
 .bg-purple-light {
     background: #e5e9f2;
 }
 ​
 .grid-content {
     background: #909399;
     text-align: center;
     line-height: 36px;
     margin: 10px;
     padding: 10px;
     border-radius: 4px;
     min-height: 36px;
     color: white;
 }
 ​
 .grid-content:hover {
     background: #E6A23C;
     text-align: center;
     line-height: 36px;
     margin: 10px;
     padding: 10px;
     border-radius: 4px;
     min-height: 36px;
     color: white;
 }
 ​
 .row-bg {
     padding: 10px 0;
     background-color: #f9fafc;
 }
 ​

4.3.2 404图

image-20220527143812276.png

五、项目发布:

我们在菜单栏运行->运行到终端-> npm run build 打包生成html代码,打包完成后,会在项目的dist目录中,将该目录下的所有文件及文件夹上传到服务器,即可运行。

预览地址:area.0315e.com/#/