零基础快速搭建vue3移动端框架

684

一 、所需技术栈

vue3官方文档:

v3.cn.vuejs.org/

项目构建工具(推荐第二个,启动、打包速度都快):

cli.vuejs.org/zh/guide/

vitejs.cn/

适用于vue3的移动端ui组件库Vant3:

vant-contrib.gitee.io/vant/ 

一般用vw做长度单位做移动端适配:

www.jianshu.com/p/4913ad4d2…

css预处理器:(随便选,都差不多,就是语法不一样)

less.bootcss.com/

www.sass.hk/

状态管理工具(推荐第二个,算是vuex的新版本):

vuex.vuejs.org/ vuex

pinia.vuejs.org/ pinia(vuex5)

路由:

router.vuejs.org/zh/guide/

http库:

www.axios-js.com/

vscode的vue3 插件 volar(格式化,代码提示,代码高亮等)

vscode左侧商店搜索volar 下载,可能需要重启编辑器生效

image.png

浏览器VueDevtools插件

谷歌浏览器的VueDevtools插件需要翻墙到谷歌应用商店下载

Edge的VueDevtools插件不用翻墙就能进商店下载

image.png

二、搭建步骤

1、node安装并配置node环境变量

blog.csdn.net/Ninewu/arti…

2、使用vite初始化项目

image.png

image.png

3、安装依赖包,启动项目

control + shift + ~ 在vscode中打开项目根目录的终端

npm install 安装项目所需的包:安装成功后项目中会多一个node_modules的文件夹里面是项目所需的包

image.png

终端中 npm run dev 启动项目

image.png

打开http://localhost:3000/查看初始页面:

image.png

4、vw适配

mdn上 1vw就是视口宽度的1%

image.png

这个包就是为了将项目中的px都编译成vw

配置前项目中app.vue的 里 margin-top:60px; 到浏览器中也是60px

image.png

image.png

安装

npm install postcss-px-to-viewport --save-dev

然后在项目根目录新建 postcss.config.js文件

// postcss.config.js
module.exports = {
  plugins: {
    "postcss-px-to-viewport": {
      unitToConvert: "px", // 要转化的单位
      viewportWidth: 375, // UI设计稿的宽度 750
      unitPrecision: 6, // 转换后的精度,即小数点位数
      propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
      selectorBlackList: ["ignore-"], // 指定不转换为视窗单位的类名,
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      // exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配,设置忽略node_modules会导致测试时,vant组件无法转换单位
      exclude: [],
      landscape: false, // 是否处理横屏情况
      landscapeUnit: 'vw',
      landscapeWidth: 568,
    }
  }
};

image.png

配置成功后浏览器的60px 变成了16vw ( 60 / 375 )*100

5、集成css预处理器less

npm install less

安装成功后在每个.vue文件的style标签里加属性 lang='less' 就可以在里面以less的语法写样式了

image.png

image.png

6、集成vue-router

npm install vue-router@4

项目根目录中新建router文件夹专门配置路由:

// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    redirect: '/routerTest',
  },
  {
    path: '/routerTest',
    component: import('../views/demo/routerTest.vue'),
  },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;

main.js(项目入口文件)中引入router.js

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

createApp(App).use(router).mount('#app');

项目根目录中新建views文件夹专门写页面:

// src/views/demo/routerTest.vue
<template>
  vue-router集成过来了没有
</template>

<script setup>

</script>

<style lang="less" scoped>
</style>

app.vue中 写入一下代码:

<router-view class="router_view" v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" />
  </keep-alive>
</router-view>

image.png

测试效果:

image.png

vue-router将routerTest.vue的模板内容编译到了app.vue的router-view标签内。

7、集成ui框架vant

npm install vant

入口文件main.js中全局引入(也可以局部引入,局部引入打包后体积更小,首屏加载更快)

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import Vant from 'vant';
import 'vant/lib/index.css';

createApp(App).use(router).use(Vant).mount('#app');

app.vue中写vant组件:

image.png

效果:

image.png

8、集成axios,并设置请求拦截器

axios 就是 用promise的写法封装了ajax ,用起来更方便

npm install axios

src/api文件夹内新建request.js 封装axios请求 request.js可以参考:

gitee.com/panjiachen/…

根目录新建一个api文件夹专门写各个接口函数

// src/api/request.js
import request from './request'

export const login = params => request({
  url: '/sm/auth/login',
  params,
  loading: false,
  method: 'POST'
})

login.vue页面中发请求

// src/views/login/index.vue
<template>
姓名<input v-model="form.username"></input>
密码<input v-model="form.password" type="password"></input>
<button @click="handleLogin">登录</button>
</template>
<script setup>
import { reactive } from 'vue'
import { login } from '../../api/auth.js'
const form = reactive({
  username: '',
  password: ''
})
const handleLogin = async () => {
  let res = login(form)
  console.log(res) // {code: 200,message: 'success'}
}
</script>

9、集成状态管理工具pinia

npm install pinia

入口文件main.js中引入pinia 并用app.use()来注册

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createPinia } from 'pinia';

createApp(App).use(router).use(createPinia())mount('#app');

新建一个store文件夹专门存储应用的状态

以下是一个登录存储用户信息的demo:

// src/store/user.js
import { defineStore } from 'pinia';
import { getSession, setSession, removeSession } from '@/utils/session';

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: getSession('userInfo') || null,
    token: getSession('token') || '',
  }),
  actions: {
    setUserInfo(userInfo) {
      this.userInfo = userInfo;
      setSession('userInfo', userInfo);
    },
    setToken(token) {
      this.token = token;
      setSession('token', token);
    },
    clearLogInfo() {
      this.userInfo = null;
      this.token = '';
      removeSession('userInfo');
      removeSession('token');
    },
  },
});

新建一个文件夹utils

// src/utils/session.js
export const getSession = (key) => {
  return JSON.parse(sessionStorage.getItem(key)) || '';
};
export const setSession = (key, data) => {
  sessionStorage.setItem(key, JSON.stringify(data));
};
export const removeSession = (key) => {
  sessionStorage.removeItem(key);
};

登陆界面:

<template>
  <div class="login">
    <img width="100" src="@/assets/images/drawable-xxhdpi/logo_main.png" alt="">
    <van-form class="login_form" @submit="onSubmit" @failed="onFailed" :show-error-message="false">
      <van-field v-model="form.username" left-icon="contact" placeholder="请输入用户名"
        :rules="[{ required: true, message: '请填写用户名' }]" clearable />
      <van-field v-model="form.password" type="password" left-icon="goods-collect-o" placeholder="请输入密码"
        :rules="[{ required: true, message: '请输入密码' }]" clearable />
      <van-checkbox v-model="isRemenber">
        记住用户名
        <template #icon="props">
          <img width="18" class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
        </template>
      </van-checkbox>
      <div style="margin: 16px;">
        <van-button class="submit_btn" round block type="primary" native-type="submit">
          登录
        </van-button>
      </div>
    </van-form>
    <img style="visibility: hidden;" width="100" src="@/assets/images/drawable-xxhdpi/logo_main.png" alt="">
  </div>
</template>
<script setup>
import { ref } from 'vue';
import activeIcon from '@/assets/images/drawable-hdpi/username_check.png'
import inactiveIcon from '@/assets/images/drawable-hdpi/username_no_check.png'
import Cookies from 'js-cookie'
import CryptoJS from 'crypto-js'
import { guid } from '@/utils/index'
import api from '@/api/auth.js'
import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@/store/user'
import { Toast } from 'vant'

const router = useRouter()
const { setUserInfo, setToken } = useUserStore()
const SECRETKEY = 'glens123!'

const form = ref({
  username: '',
  password: '',
  captchaId: '',
  validateCode: '',
  phoneId: '',
  sysCode: ''
})

/** 记住密码 */
const isRemenber = ref(true)
const RemenderPwd = () => {
  // 登录成功后 判断是否选择了勾选密码
  if (isRemenber) {
    //添加cookie
    Cookies.set('xzusername', form.value.username, {
      expires: 30
    })
    //使用crypto-js进行加密(需要npm加载后引入) 并存储到cookie中 此处glens123! 为秘钥 
    try {
      Cookies.set('xzpassword', CryptoJS.AES.encrypt(form.value.password, SECRETKEY), {
        expires: 30 // 存储30天
      })
    } catch (error) {
      console.log(error)
    }
  } else {
    // 删除cookie
    Cookies.remove('xzusername')
    Cookies.remove('xzpassword')
  }
}

/** 账号密码回显 */
const oldUsername = Cookies.get('xzusername')
const oldPassword = Cookies.get('xzpassword')
if (oldUsername && oldPassword) {
  form.value.username = oldUsername
  form.value.password = CryptoJS.AES.decrypt(oldPassword, SECRETKEY).toString(CryptoJS.enc.Utf8)
}

/** 登录 */
form.value.captchaId = guid(10)
const route = useRoute()

const login = async () => {
  const sgccUserId = route.query.sgccUserId
  if (sgccUserId) {
    return await api.loginAndBind({ ...form.value, sgccUserId })
  } else {
    return await api.login(form.value)
  }
}

const onSubmit = async () => {
  const userInfo = await login()
  setUserInfo(userInfo)
  setToken(userInfo.token)
  RemenderPwd()
  router.push('/task')
}

const onFailed = ({ errors }) => {
  console.log(errors)
  Toast(errors[0].message)
}

</script>
<style lang="less" scoped>
.login {
  background: url('@/assets/images/drawable-xhdpi/login_bg.png') no-repeat center;
  background-size: cover;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-around;
  padding: 10px;

  .login_form {
    width: 100%;

    .submit_btn {
      margin-top: 30px;
      background: linear-gradient(to right, #83D1FF, #D4C5FE);
      border: none;
      font-size: 16px;
    }

    :deep(.van-cell) {
      background: transparent;
      padding: 10px 40px;
      color: 20px;

      .van-field__control {
        color: #fff;
        font-size: 16px;

        &::placeholder {
          color: #fff;
        }
      }

      .van-field__left-icon {
        color: #fff;
        margin-right: 15px;

        .van-icon {
          font-size: 20px;
        }
      }
    }

    :deep(.van-checkbox) {
      margin: 15px;
      padding-left: 20px;

      .van-checkbox__label {
        color: #fff;
      }
    }
  }
}
</style>