零基础快速搭建vue3移动端框架
一 、所需技术栈
vue3官方文档:
项目构建工具(推荐第二个,启动、打包速度都快):
适用于vue3的移动端ui组件库Vant3:
一般用vw做长度单位做移动端适配:
css预处理器:(随便选,都差不多,就是语法不一样)
状态管理工具(推荐第二个,算是vuex的新版本):
vuex.vuejs.org/ vuex
pinia.vuejs.org/ pinia(vuex5)
路由:
http库:
vscode的vue3 插件 volar(格式化,代码提示,代码高亮等)
vscode左侧商店搜索volar 下载,可能需要重启编辑器生效
浏览器VueDevtools插件
谷歌浏览器的VueDevtools插件需要翻墙到谷歌应用商店下载
Edge的VueDevtools插件不用翻墙就能进商店下载
二、搭建步骤
1、node安装并配置node环境变量
2、使用vite初始化项目
3、安装依赖包,启动项目
control + shift + ~ 在vscode中打开项目根目录的终端
npm install 安装项目所需的包:安装成功后项目中会多一个node_modules的文件夹里面是项目所需的包
终端中 npm run dev 启动项目
打开http://localhost:3000/查看初始页面:
4、vw适配
mdn上 1vw就是视口宽度的1%
这个包就是为了将项目中的px都编译成vw
配置前项目中app.vue的 里 margin-top:60px; 到浏览器中也是60px
安装
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,
}
}
};
配置成功后浏览器的60px 变成了16vw ( 60 / 375 )*100
5、集成css预处理器less
npm install less
安装成功后在每个.vue文件的style标签里加属性 lang='less' 就可以在里面以less的语法写样式了
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>
测试效果:
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组件:
效果:
8、集成axios,并设置请求拦截器
axios 就是 用promise的写法封装了ajax ,用起来更方便
npm install axios
src/api文件夹内新建request.js 封装axios请求 request.js可以参考:
根目录新建一个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>
转载自:https://juejin.cn/post/7220607468699156537