购优偿WebApp
项目实机展示
欢迎/注册/登录页
主页/加购物车/设置地址
地址增删改
提交订单/所有订单列表
其他(我的,地图,个人信息及获取手机联系人,视频及弹幕,分类页)
项目流程简要记录
知识点:
1. 选项式/组合式的父子组件互相引入;函数式(组合式)编程没有this;
2. 推导类型/显示类型
3. 定义计算属性/监视属性
4. 全局的接口;vite-env.ts ;
1. 首先全局处理 declare module '*.vue'{} //typescript语法
2. Interface User{}
1. swagger 接口网站
1. 开发的APP类型
1. WebApp -- 在手机浏览器上运行的app
2. HybirdApp -- 混合app
1. Cordova
2. Hbuilder 和 ApiCloud 以上两类开发模式都是有一个原生壳,然后编写html、js、css开发一个项目,运行在壳里边
3. ReactNative、Weex -- 编写类似于小程序的方式来实现代码编写和开发(类似鸿蒙)
4. flutter
3. NativeApp -- 纯原生开发;Android是用的是Java、koltin;iOS Object-c、swift
2. 架构分析
|—— project
|—— src
|—— main.ts 入口文件
|—— pages
|—— index.vue 项目启动文件
|—— login
|—— index.vue 项目登陆页面
|—— router
|—— index.ts 路由的主要注册文件
|—— interceptor.ts 路由拦截器
|—— store
|—— index.ts 实现状态管理器的管理
|—— style
|—— index.less 项目的全局样式
|—— common.less 全局定义的变量、方法
|—— images 项目的图片目录
|—— config 项目的常量定义
|—— apis 实现接口请求的方法
|—— components 实现全局组件
知识点:
1. 安装vite框架/依赖
1. yarn create vite
2. cd mingcheng
3. yarn install
4. yarn dev
2. 处理框架 路由以及挂载
1. 创建删除文件
2. vue-router插件的下载引入
router/index.ts 中import{createRouter} from 'vue-router';
main.ts 中vue2/vue3 的挂载与注入路由对象
3. 创建基础的页面
1. pages文件下创建;
2. routes路径的写入;
3. component Login/home的组件书写;
4. 渲染到pages/index.vue 页面;
4. 拦截器
安装准备
- node -v 18.16.1
- 下载/运行/删除不必要
- 创建pages/index.vue;
- 创建src/main.ts;- main.ts 引入vue,引入app盒子,挂载app到根目录;
- yarn create vite> 开始 | Vite 官方中文文档 (vitejs.dev)
- 购优偿app youchang-app;1. vue2. ts
- cd youchang-app
- yarn // 安装依赖;
启动/挂载
- yarn dev // 运行项目1. 切换logo /// iconfont 电商; 128尺寸,png,项目src-images内2. png 传ico // 输出96 或 723. 删除原有public/ ***.svg4. 电商app为空(虽然不知道为什么不要,那就不要好嘞)5. 删除文件6. 创建images 与 pages文件 与 main.ts 文件挂载;1. main.ts中的挂载app需要修改为新建的pages下的index.vue;核对根元素是否为app或root;7. 创建基础文件vite-env.d.ts
/// declare module "*.vue" {'#root'}
- 可能出现的问题:2.3为解决步骤1. 2. tsconfig.json修改为bundler==>node1. src 下的vite-env.d.ts添加2. 当 “–moduleResolution” 为 “node16” 或 “nodenext” 时,相对导入路径需要 EcmaScript 导入中的显式文件扩展名。请考虑将扩展名添加到导入路径中。ts(2834)
路由vue-router*使用
- yarn add vue-router
- 创建router/index.ts
- 路由模式 history(做App统一用hash)
- routes 的路径 1. 重定向2. 组件的import 引入以及注册
- 渲染已经注册完成的components组件到pages/index.vue (挂载的盒子) 1. < router-view >< /router-view >
- 容易遗漏的: 1. router/index.ts中使用hash与Router是需要引用的,否则无法正常渲染;2. main.ts中的createApp 中router 的引入与挂载;
路由实现页面的跳转
- import 引入
- router.push跳转
- 跳转不动记得检查interceptor拦截器中next()使用否;
pinia状态管理器*
1. pinia 状态管理器(继承状态管理器)类似与vuex
2. 用户注册interface
3. pinia插件的开发与注册
4. login/home 实现数仓持久化 以及数仓内值的取用
- 安装yarn add pinia
- 全局注入
- pinia和vuex的区别 1. vuex遵循的是全局只能有一个数仓(只能有一个store实例new Vuex.Store({})),而pinia不一样
2. vuex集中管理,pinia松散式管理
- 创建store/useUserStore.ts (用户仓库)1. 引入;import{defineStore} from ‘pinia’;2. 导出;export default useUserStore3. 作用;全局多次需要直接获取到用户的信息
import{ defineStore }from'pinia'// 为什么使用defineStore?// 创建接口const useUserStore =defineStore('user',{state():UserStore{return{ token:'', userInfo:({}as UserInfo)}},// 修改数据 -- pinia插件修改数据用action,取代了mutations; actions:{setToken(_newToken:string){this.token = _newToken;console.log('setToken被调用了');// 设置函数方法更新token值},setUserInfo(_newUserInfo:UserInfo){this.userInfo = _newUserInfo;// 设置函数方法更新userInfo值},}, getters:{tokenInfo():string{returnthis.token },myUserInfo():UserInfo{returnthis.userInfo }}})exportdefault useUserStore // 导出;
用户注册接口
- vite-env.d.ts1. 创建interface 接口;(承接上文pinia中使用的UserStore,UserInfo两个接口)
- 使用pinia的store数仓(两个页面之间数仓的修改与接收)1. 引入useUserStore2. 实例化调用store3. login./index.vue等页面使用;4. home./index.vue接收login修改的token值
特点:可获取修改后的值,但是页面刷新后数值就消失
参考pinia中文文档,解决问题;
pinia 插件的开发;
- main.ts1. 实例化pinia /将原本直接挂载的createPinia() 切换为pinia注册上去2. 开发plugin3. plugin插件注册到pinia
- 取出plugin中的store,使用解构赋值获取,并调用$subscribe订阅数据的函数;1. 可获取到store数据仓发生改变后的newState值,2. 利用sessionStorage存储到缓存内;3. 加入判断条件cache(之前存入的本地缓存)存在时,将store.某值取出来并赋予> 每次页面刷新时会执行该plugin插件,数仓会再次取出数值,持久性完成。
login/home页持久化测试
- 将token值的获取拓展开userInfo
- login页面修改数仓内数值token,userInfo
- 此时调用main.ts中plugin ;- 缓存去到上次刷新前取出的值后,Json.parse转为对象后遍历对象,将其存储回数仓store;
- 页面跳转到home页面;- home页面设置数仓中userInfo内取出的常量name值,显示到页面- 40706050822452.png&pos_id=img-IUOrUU2u-1729446020194)
1. pinia结合路由实现用户登录认证
1. 拦截器,识别用户登陆状态
2. meta路由的固定传参,决定路由是否需要背拦截;
3. meta/params/query 路由传参的三种方式
token校验正确性与时效性;
- token-info信息由token值与当前时间构成
- 间隔开一同加密后存储进入数仓中;
- 存入多个位置后比对,以验证正确性;store/sessionStorage
- interceptor.ts 完善token信息除开存在与否并验证是否正确;
URLENCODED 与form-data的区别;
后端生成JWT;
- 安装插件1. 加密生成token
npm install jsonwebtoken
2. 解析jwtnpm install express-jwt
- 引入jwt,设置加密对象加密算法有效时间
/* 登录请求 通常放在登录或注册时*/const jwt = require("jsonwebtoken");router.get("/", function (req, res, next) { let token = jwt.sign({ username: "zhangsan" }, "test123456", { expiresIn: "360s", algorithm: "HS256", }); console.log(req.query); res.json({ code: 1, msg: "注册成功", token, });});
- 使用apipost 软件测试登录- 此时所有的路由都需要header传入token值,方可正常访问否则报错401- 无header内Authorization 传递值时- 正常做法:
Authorization:Bearer xxxxxxxx// 务必bearer后面加上空格,除非后端设置过并且特别注明;
封装解密加密工具
- 使用CryptoJS库AES与enc。封装encodeApi、decodeApi加密解密工具类。
- 开发tools或utils助手工具(便于多次加密/解密)1. 安装:yarn add crypto-js2. 引入:crypto-js3. AES.encrypt(变量,‘加密设置字符串’).toString // 注释中内容4. AES.decrypt(变量,‘加密设置字符串’)
- 创建utils/index.ts
- 将main.ts中使用AES的位置切换为自己开发的工具encodeApi/decodeApi
- 可能会报的错误 4. 引入报错1,直接去全局定义 5. 引入报错2,tsconfig.node.json修改// 但一般不影响运行;
- 解决思路:- key设置为16/32/48位数- 关闭项目重新启动尝试- 字符串清空换行符;
import {AES,enc} from 'crypto-js'const key = '' // 16/32/48 ;bit,不能有空格换行等;export function encodeApi(value:string){ return AES.encrypt(value,key).toString()}export function decodeApi(msg:string){ return AES.decrypt(msg,key).toString(enc,UTF8) //}
封装拦截路由
- 封装拦截路由。验证除欢迎登录注册等页面的接收到token正确性、时效性。
//1. 创建store;const userStore =({'user',state():userStore{// 这里函数return{ token:''; userInfo:({}as userInfo)}}, getters:{tokenInfo(v){returnthis.token;},userInfo(v){returnthis.userInfo;}}, actions:{setToken(v){this.token = v },setInfo(v){this.userInfo = v;}}})exportdefault userStore;//2. 创建router;meta:{needAuth:true}import{createRouter,Router,createWebHashHistory}from'vue-router'const router:Router =createRouter({ history:createWebHashHistory(), routes:[{path:'/',redirect:'/login'},{path:'/login',redirect:'/login'},{path:'/home',redirect:'/home',meta:{needAuth:true,}}]})//3. 路由守卫router.beforeEach(interceptor)router.beforeEach(intercepter)exportdefault router;//4. 路由拦截器的封装;import userStore from'../store/userStore'import{decodeApi,encodeApi}from'../utils/index.ts'functioninterceptor(to,from,next):{if(to.meta.needAuth){let token =userStore().token;let _token = sessionStorage.getItem('tokenInfo')if(!_token){next('/login')// 去登录}else{ _token =decodeApi(_token).split('--')[0],let time =decodeApi(_token).split('--')[1]if(Date.now()- time>=2*60*60*1000){next('/login')// 超时去登录}else{if(token === _token){next()}else{next('/login')}}}}else{next()}}// 由于设置了重定向路由,next('/') === next('/login')
免登录思路
- 登录页与主页免登录,Pinia开发数据持久化插件。
// pinia版本数据持久化;// 0. 安装引入piniayarn add pinia;import{createPinia}from'pinia';import App from'./pages/index.vue';//引入容器import{createApp}from'vue'//引入vuelet pinia =createPinia();cretateApp(App).use(pinia).mount('#root')// 0.1 创建数仓;const userStore =defineStore({'user',state(){return{ userInfo, userToken }}, getters:{getInfo(){},getToken(){}}, actions:{}})// 1. 登录页填写信息;<button @click='saveEvt'>functionsaveEvt(){// 存入数仓; store.setInfo();}// 2. 主页从数仓获取内容;{{uname}}{{uage}}import userStore from'../store/userStore';const store =userStore()let uname:string = store.userInfo.uname;// 3. 开发pinia的plugin插件实现数据持久化;functionplugin({store}:any){// 3.2解构数仓log(arguments)// 3.1了解到数仓内有目标内容store,$subscribe发布方法;let cathe = sessionStorage.getItem('userInfo')//3.4 查询本地有无实现持久化的数据if(cathe){let info : any =JSON.parse(decodeApi(cache));for(let key in info){ store[key]= info[key]// 将本地缓存的数据依次存入store数仓 end至此完成持久化数据封装}} store.$subscribe(function(_,newState)){// 3.3 调用发布方法,将数仓内的数据缓存到sessionb本地,防止刷新丢失数据,实现数据持久化;let str =encodeApi(JSON.stringify(newState)); sessionStorage.setItem('userInfo',str)}}pinia.use(plugin)// 插件注册到pinia库,便于使用;// vuex 版本数据持久化;createApp(App).use(pinia)
vant4 快速搭建页面,文件上传组件使用
- Vant4组件搭建注册页。包括头像图片文件地址等用户信息上传。
1. van-uploader 组件<van-field name="uploader" label="用户头像"><!-- 这里的#input表示v-slot的简写;等价于slot="input"--><!-- formData对象将json数据转化为表单对象 --><template #input><van-uploader v-model="userInfo.photo":after-read="filePicker" multiple :max-count="1"/></template></van-field>2. fd 实例 functionfilePicker(obj:any){// console.log(arguments); //就可直接拿到文件对象;let fd =newFormData();// console.log(fd);// 当前元素节点后面插入元素element; fd.append('file',obj.file)// 调用useApi.ts中定义的接口uploadFileApi(fd).then((d:any)=>{// console.log(d)if(d.code ==1){// console.log(d.data);// if(d.code == 200){ userInfo.photo =[{url:'/apis/'+d.data[0].path}]// console.log(userInfo.photo[0].url);// userInfo.photo =[{url:'apis'+d.data[2].pimg}]// console.log(d.data[2].pimg);}})}// 3. 后端;let express =require('express')let router = express.Router();const multer =require('multer');const fs =require('fs');let{Upload}=require('../models/index.js')// 文件上传的存储目录// var imgupload = multer({dest:'www/imgupload'});// 单个 可行postapi 网页可行// 多个: postapi可 网页不可router.post('/complate',multer({ dest:'www/imgupload/'}).array('file',9),function(req,res,next){ console.log(req.files);const files = req.files;const fileList =[]; files.forEach(v=>{let file = v; fs.renameSync(file.path,`www/imgupload/${file.originalname}`) file.path =`www/imgupload/${file.originalname}` fileList.push(file) Upload.create(file)});// res.send(fileList) res.json({ code:1, data:fileList, msg:'上传成功'})})// 设置静态文件//必须设置在use文件前方!!!app.use('/www',express.static('www'));//将文件设置成静态,才能公开访问该文件夹下面的图片,真的很关键;app.set('views', path.join(__dirname,'views'));app.set('view engine','ejs');
主页列表滚动实现节流思路
functiongetList(){homeRecomendApi({pagesize:4,pagenum:pagenum.value})//一次请求数量,请求的页面.then(d=>{//遍历data的list
list.value()})
loading.value =false// 调用api获取一组列表完成}functionscrollEvt(e:PointerEvent){// 指针事件,鼠标事件类似将鼠标,触摸,触控笔三种事件的整合let div: HTMLDivElement = e.target as HTMLDivElement;
console.log(div.clientHeight,div.scrollTop,div.scrollHeight,div);let hasToBotton: boolean = div.scrollHeight -80<(div.clientHeight + div.scrollTop)
console.log(hasToBotton);//false // 整个除开头与导航的高度-尾部提示文字 < 内容可视高度 + 顶部滚过的高度;if(hasMore.value &&!loading.value && hasToBotton){//当前page内容数据库加载完成,调用api接口完成修改true,此页滚到底部为true,// 此处开启节流;
loading.value =true;// 加载完成;
pagenum.value +=1;
console.log(pagenum.value+'当前页数节流');// 加载数据getList();}}
- 搭建购物车、地址页,增删商品、地址、编辑信息;
// 地址弹出层放在大盒子外侧;// 遇事不决arguments和events事件对象;总体不难,按规则写
- 百度地图接口的使用。https://lbsyun.baidu.com/cms/jsapi/reference/jsapi_reference.html
- Node.JS与Mongodb搭建后端,使用jsonwebtoken、express-jwt分别加密、解析token。
// 1. 加密token--login页面;
let jwt = require('jsonwebtoken')
const key =''
let token = jwt.sign({phone:req.body.phone},
key,
{expiresIn:'3600s', //1h有效期
algorithm:'HS256'})
// 2. 解密token
// 排除哪些不需要token
var {expressjwt} = require('express-jwt');
// var { key } = require("./routes/index.js");
// 下面的方法拦截必须写在静态资源值后,否则无法访问,页面不可正常加载;
app.use(
expressjwt({
secret:key, // 解密的密码
algorithms:["HS256"], // 解密的算法
}).unless({ // 排除:定义:默认所有的接口都需要传入token,在这里排除不需要的token接口
path:[
// 可使用正则表达式
]
})
)
本文转载自: https://blog.csdn.net/m0_69364468/article/details/143102023
版权归原作者 不是周末 所有, 如有侵权,请联系我们删除。
版权归原作者 不是周末 所有, 如有侵权,请联系我们删除。