滁州市重点工程建设管理局网站,万网网站备案系统,迅速百度网站自然排名,黄骅贴吧二手房day-121-one-hundred-and-twenty-one-20230726-vue3项目实战-知乎日报第3天-TS-简历
vue3项目实战-知乎日报第3天
封装按钮组件
jsx函数式组件
只能做静态页面#xff0c;内部没有方法让它自动更新。
封装第三方按钮-非计算属性版
封装第三方按钮-不使用计算属性
src/c…day-121-one-hundred-and-twenty-one-20230726-vue3项目实战-知乎日报第3天-TS-简历
vue3项目实战-知乎日报第3天
封装按钮组件
jsx函数式组件
只能做静态页面内部没有方法让它自动更新。
封装第三方按钮-非计算属性版
封装第三方按钮-不使用计算属性
src/components/ButtonAgain.jsx
import { Button } from vant
import { ref, useAttrs, useSlots } from vue// 把传递的属性去除特殊的其余的都赋值给Vant内部的组件
const filter (attrs) {let props {}Reflect.ownKeys(attrs).forEach((key) {if (key loading || key onClick) returnprops[key] attrs[key]})return props
}const ButtonAgain {inheritAttrs: false,setup() {const attrs useAttrs(),slots useSlots()// 自己控制loading效果const loading ref(false)const handle async (ev) {loading.value truetry {await attrs.onClick(ev)} catch (_) {}loading.value false}console.log(1- 非计算属性版)return () {console.log(2- 非计算属性版)let props filter(useAttrs())return (Button {...props} loading{loading.value} onClick{handle}{slots.default()}/Button)}}
}
export default ButtonAgain封装第三方按钮计算属性版
封装第三方按钮-使用计算属性。
src/components/ButtonAgain.jsx
import { Button } from vant
import { ref, useAttrs, useSlots, computed } from vueconst ButtonAgain {inheritAttrs: false,setup() {const attrs useAttrs(),slots useSlots()const props computed(() {let attrs useAttrs()let props {}Reflect.ownKeys(attrs).forEach((key) {if (key loading || key onClick) returnprops[key] attrs[key]})return props})// 自己控制loading效果const loading ref(false)const handle async (ev) {loading.value truetry {await attrs.onClick(ev)} catch (_) {}loading.value false}console.log(计算属性版)return () {return (Button {...props.value} loading{loading.value} onClick{handle}{slots.default()}/Button)}}
}
export default ButtonAgain函数式调用组件的处理优化
src/components/overlay/Index.vue
script setup/script
templatevan-overlay showvan-loading color#0094ff vertical 努力加载中请稍后 /van-loading/van-overlay
/template
style langless scoped
.van-overlay {display: flex;align-items: center;justify-content: center;
}
/stylesrc/components/overlay/index.js
import { createVNode, render } from vue
import Index from ./Index.vueexport default function showOverlayLoading() {// 创建虚拟DOM。let vnode createVNode(Index)// 渲染虚拟DOM// console.log(vnode--, vnode);const frag document.createDocumentFragment()render(vnode, frag)document.body.appendChild(vnode.el, frag)return function hiddenOverlayLoading() {if (vnode?.el) {document.body.removeChild(vnode.el)vnode null}// render(null, frag)}
}登录页
script setup
import useBaseStore from /stores/base
import useAutoImport from /useAutoImport
const { reactive, ref, onUnmounted, API, router, route, showSuccessToast, showFailToast, utils } useAutoImport()const baseStore useBaseStore()
console.log(baseStore--, baseStore)/* 定义状态 */
const formIns ref(null)
const state reactive({phone: ,code: ,btn: {disabled: false,text: 发送验证码}
})/* 发送验证码 */
let timer null,count 30
const handleSendCode async () {try {// 先对手机号进行校验await formIns.value.validate(phone)// 向服务器发送请求let { code } await API.userSendCode(state.phone)if (code 0) {// 开启倒计时state.btn.disabled truestate.btn.text 30s后重发timer setInterval(() {if (count 1) {clearInterval(timer)count 30state.btn.disabled falsestate.btn.text 发送验证码return}count--state.btn.text ${count}s后重发}, 1000)return}showFailToast(发送失败稍后再试)} catch (_) {}
}
onUnmounted(() clearInterval(timer))/* 登录提交 */
const submit async () {try {await formIns.value.validate()let { code, token } await API.userLogin(state.phone, state.code)if (code ! 0) {showFailToast(登录失败请稍后再试)return}// 登录成功存储Token、获取登录者信息、提示、跳转utils.storage.set(TK, token)await baseStore.queryProfile()showSuccessToast(登录成功)router.push(/)} catch (_) {}
}
/scripttemplatenav-back title登录/注册 /van-form refformIns validate-firstvan-cell-group insetvan-fieldcenterlabel手机号label-width50pxnamephonev-model.trimstate.phone:rules[{ required: true, message: 手机号是必填项 },{ pattern: /^(?:(?:\|00)86)?1\d{10}$/, message: 手机号格式不正确 }]template #buttonbutton-againclassform-btnsizesmalltypeprimaryloading-text处理中:disabledstate.btn.disabledclickhandleSendCode{{ state.btn.text }}/button-again/template/van-fieldvan-fieldlabel验证码label-width50pxnamecodev-model.trimstate.code:rules[{ required: true, message: 验证码是必填项 },{ pattern: /^\d{6}$/, message: 验证码格式不正确 }]//van-cell-groupdiv stylemargin: 20px 40pxButtonAgain round block typeprimary loading-text正在处理中... clicksubmit立即登录/注册/ButtonAgain/div/van-form
/templatestyle langless scoped
.van-form {margin-top: 30px;.form-btn {width: 78px;}
}
/style提交表单信息
对表单进行校验。发送请求。登录成功存储token、进行提示。获取登录者信息、进行页面的跳转。
获取登录者信息
从服务器获取登录者信息。 一般是在pinia中创建出来的。 src/stores/base.js import { defineStore } from pinia
import { ref } from vue
import API from /apiconst useBaseStore defineStore(base, () {// 定义公共状态。const profile ref(null)// 修改公共状态。// const queryProfile async () {let info nulltry {let { code, data } await API.userInfo()if (code 0) {info dataprofile.value info}} catch (error) {console.log(error:--, error)}return info}// 暴露给外面用。return {profile,queryProfile}
})
export default useBaseStore登录态校验 src/router/index.js import { createRouter, createWebHashHistory } from vue-router
import routes from ./routes
import useBaseStore from /stores/base
import { showFailToast } from vantconst router createRouter({history: createWebHashHistory(),routes
})// 全局前置守卫登录态校验
const checkList [/person, /store, /update]//用于判断那些页面需要登录态校验。
router.beforeEach(async (to, from, next) {const base useBaseStore()//用于拿到个人信息。let profile base.profileif (checkList.includes(to.path) !profile?.value) {let info await base.queryProfile()if (!info) {// 真的没登录过。showFailToast(您还未登录请先登录)next({path: /login,query: {target: to.fullPath}})return}}next()
})
// 全局后置守卫
router.beforeEach((to, from) {})
export default routersrc/views/Login.vue script setup
import useBaseStore from /stores/base
import useAutoImport from /useAutoImport
const { reactive, ref, onUnmounted, API, router, route, showSuccessToast, showFailToast, utils } useAutoImport()const baseStore useBaseStore()
console.log(baseStore--, baseStore)/* 定义状态 */
const formIns ref(null)
const state reactive({phone: ,code: ,btn: {disabled: false,text: 发送验证码}
})
/* 登录提交 */
const submit async () {try {await formIns.value.validate()let { code, token } await API.userLogin(state.phone, state.code)if (code ! 0) {showFailToast(登录失败请稍后再试)return}// 登录成功存储Token、获取登录者信息、提示、跳转utils.storage.set(TK, token)await baseStore.queryProfile()showSuccessToast(登录成功)let target route.query.targettarget ? router.replace(target) : router.push(/)} catch (_) {}
}
/scripttemplatediv stylemargin: 20px 40pxButtonAgain round block typeprimary loading-text正在处理中... clicksubmit立即登录/注册/ButtonAgain/div/van-form
/templatescript setup
/* 登录提交 */
const submit async () {try {showSuccessToast(登录成功)let target route.query.targettarget ? router.replace(target) : router.push(/)} catch (_) {}
}
/script 会有一个问题-路由错乱的问题。
登录页的跳转
让登录页中可以直接跳转回来源页面。
src/views/Login.vue
script setup
/* 登录提交 */
const submit async () {try {showSuccessToast(登录成功)let target route.query.targettarget ? router.replace(target) : router.push(/)} catch (_) {}
}
/scriptscript setup
import useBaseStore from /stores/base
import useAutoImport from /useAutoImport
const { reactive, ref, onUnmounted, API, router, route, showSuccessToast, showFailToast, utils } useAutoImport()const baseStore useBaseStore()
console.log(baseStore--, baseStore)/* 定义状态 */
const formIns ref(null)
const state reactive({phone: ,code: ,btn: {disabled: false,text: 发送验证码}
})
/* 登录提交 */
const submit async () {try {await formIns.value.validate()let { code, token } await API.userLogin(state.phone, state.code)if (code ! 0) {showFailToast(登录失败请稍后再试)return}// 登录成功存储Token、获取登录者信息、提示、跳转utils.storage.set(TK, token)await baseStore.queryProfile()showSuccessToast(登录成功)let target route.query.targettarget ? router.replace(target) : router.push(/)} catch (_) {}
}
/scripttemplatediv stylemargin: 20px 40pxButtonAgain round block typeprimary loading-text正在处理中... clicksubmit立即登录/注册/ButtonAgain/div/van-form
/template返回上一页功能
单独做一个组件专门来处理返回逻辑。
src/components/NavBack.vue
script setup
import useAutoImport from /useAutoImportconst { router, route } useAutoImport()const back () {router.go(-1)
}
/scripttemplatevan-nav-bar title个人中心 left-text返回 left-arrow click-leftback /
/templatestyle langless scoped
:deep(.van-icon),
:deep(.van-nav-bar__text) {color: #000;
}
/style函数式调用组件的封装 先写一个主组件。主组件可以用模板组件也可以用jsx组件 src/components/overlay/Index.vue 模板组件 script setup/script
templatevan-overlay showvan-loading color#0094ff vertical 努力加载中请稍后 /van-loading/van-overlay
/template
style langless scoped
.van-overlay {display: flex;align-items: center;justify-content: center;
}
/stylesrc/App.vue 在根视图中先看模板组件效果 script setup
import OverlayVue from /components/overlay/Index.vue
/scripttemplateOverlayVue/OverlayVuerouter-view v-slot{ Component }keep-alive includeHomecomponent :isComponent //keep-alive/router-view
/template写一个js函数用于在全局中渲染组件和移除主组件。 src/components/overlay/Index.vue 主组件 script setup/script
templatevan-overlay showvan-loading color#0094ff vertical 努力加载中请稍后 /van-loading/van-overlay
/template
style langless scoped
.van-overlay {display: flex;align-items: center;justify-content: center;
}
/stylesrc/components/overlay/index.js 在js文件中用js方式来在全局中插件入调用。 import { createVNode, render } from vue
import Index from ./Index.vueexport default function showOverlayLoading() {// 创建虚拟DOM。const vnode createVNode(Index)// 渲染虚拟DOMconsole.log(vnode--, vnode);const frag document.createDocumentFragment()render(vnode, frag)document.body.appendChild(vnode.el, frag)return function hiddenOverlayLoading() {render(null, frag)}
}src/App.vue 根组件中尝试调用 script setup
import showOverlayLoading from /components/overlay
let hiddenOverlayLoading showOverlayLoading()
setTimeout(() {console.log(根视图组件移除)hiddenOverlayLoading?.()
}, 3000)
/scripttemplaterouter-view v-slot{ Component }keep-alive includeHomecomponent :isComponent //keep-alive/router-view
/templatestyle langless
import ./assets/reset.min.css;.van-button {border-radius: 0 !important;
}html,
body,
#app {min-height: 100vh;overflow-x: hidden;background: #f4f4f4;
}#app {margin: 0 auto;background: CR_W;
}.van-skeleton {padding: 30px 15px;
}
/style路由中进行loading
src/components/overlay/Index.vue 全局loading模板组件
script setup/script
templatevan-overlay showvan-loading color#0094ff vertical 努力加载中请稍后 /van-loading/van-overlay
/template
style langless scoped
.van-overlay {display: flex;align-items: center;justify-content: center;
}
/stylesrc/components/overlay/index.js 函数式调用全局loading模板组件的方法
import { createVNode, render } from vue
import Index from ./Index.vueexport default function showOverlayLoading() {// 创建虚拟DOM。const vnode createVNode(Index)// 渲染虚拟DOMconsole.log(vnode--, vnode);const frag document.createDocumentFragment()render(vnode, frag)document.body.appendChild(vnode.el, frag)return function hiddenOverlayLoading() {render(null, frag)}
}src/router/index.js
import showOverlayLoading from /components/overlay// 全局前置守卫登录态校验
const checkList [/person, /store, /update]//用于判断那些页面需要登录态校验。
let hiddenOverlayLoading null//用于遮罩层
router.beforeEach(async (to, from, next) {if (需要进行登录但没个人信息时) {hiddenOverlayLoading showOverlayLoading()//开启遮罩层let info await base.queryProfile()//异步用token拿到个人信息。if (!info) {// 真的没登录过。showFailToast(您还未登录请先登录)next({path: /login,query: {target: to.fullPath}})hiddenOverlayLoading?.()//移除遮罩层-用户真的没登录时。return}}next()
})
// 全局后置守卫
router.beforeEach((to, from) {hiddenOverlayLoading?.()//移除遮罩层-其它情况如用户已登录或者是无需个人信息页的情况。
})
export default routerimport { createRouter, createWebHashHistory } from vue-router
import routes from ./routes
import useBaseStore from /stores/base
import { showFailToast } from vant
import showOverlayLoading from /components/overlayconst router createRouter({history: createWebHashHistory(),routes
})// 全局前置守卫登录态校验
const checkList [/person, /store, /update]//用于判断那些页面需要登录态校验。
let hiddenOverlayLoading null//用于遮罩层
router.beforeEach(async (to, from, next) {const base useBaseStore()//用于拿到个人信息。let profile base.profileif (checkList.includes(to.path) !profile) {hiddenOverlayLoading showOverlayLoading()//开启遮罩层let info await base.queryProfile()if (!info) {// 真的没登录过。showFailToast(您还未登录请先登录)next({path: /login,query: {target: to.fullPath}})hiddenOverlayLoading?.()//移除遮罩层return}}next()
})
// 全局后置守卫
router.beforeEach((to, from) {hiddenOverlayLoading?.()//移除遮罩层let title to.meta?.titledocument.title !title ? 知乎日报 : ${title} - 知乎日报
})
export default router路由跳转时修改标签页标题 src/router/index.js import { createRouter, createWebHashHistory } from vue-router
import routes from ./routesconst router createRouter({history: createWebHashHistory(),routes
})
// 全局后置守卫
router.beforeEach((to, from) {let title to.meta?.titledocument.title !title ? 知乎日报 : ${title} - 知乎日报
})
export default routerimport { createRouter, createWebHashHistory } from vue-router
import routes from ./routes
import useBaseStore from /stores/base
import { showFailToast } from vant
import showOverlayLoading from /components/overlayconst router createRouter({history: createWebHashHistory(),routes
})// 全局前置守卫登录态校验
const checkList [/person, /store, /update]//用于判断那些页面需要登录态校验。
let hiddenOverlayLoading null//用于遮罩层
router.beforeEach(async (to, from, next) {const base useBaseStore()//用于拿到个人信息。let profile base.profileif (checkList.includes(to.path) !profile?.value) {hiddenOverlayLoading showOverlayLoading()//开启遮罩层let info await base.queryProfile()if (!info) {// 真的没登录过。showFailToast(您还未登录请先登录)next({path: /login,query: {target: to.fullPath}})hiddenOverlayLoading?.()//移除遮罩层return}}next()
})
// 全局后置守卫
router.beforeEach((to, from) {hiddenOverlayLoading?.()//移除遮罩层let title to.meta?.titledocument.title !title ? 知乎日报 : ${title} - 知乎日报
})
export default routersrc/router/routes.js import Home from /views/Home.vue
const routes [{path: /,name: home,meta: { title: 首页 },component: Home
}, {path: /detail/:id,name: detail,meta: { title: 详情页 },component: () import(/views/Detail.vue)
}, {path: /login,name: login,meta: { title: 登录/注册页 },component: () import(/views/Login.vue)
}, {path: /person,name: person,meta: { title: 个人中心 },component: () import(/views/Person.vue)
}, {path: /store,name: store,meta: { title: 我的收藏 },component: () import(/views/Store.vue)
}, {path: /update,name: update,meta: { title: 更改信息 },component: () import(/views/Update.vue)
}, {path: /:pathMatch(.*)*,redirect: /
}]
export default routes详情页收藏按钮
不用传统的登录态校验但一些区域或功能需要用到个人信息。所以需要优化个人信息的处理。所有的涉及收藏的状态及操作和前后端数据交互都放在全局公共状态里。在需要用到收藏相关的状态及操作都要调用全局公共状态方法。
优化个人信息的处理
src/router/index.js
import { createRouter, createWebHashHistory } from vue-router
import routes from ./routes
import useBaseStore from /stores/base
import { showFailToast } from vant
import showOverlayLoading from /components/overlayconst router createRouter({history: createWebHashHistory(),routes
})// 全局前置守卫登录态校验
const checkList [/person, /store, /update]//用于判断那些页面需要登录态校验。
let hiddenOverlayLoading null//用于遮罩层
router.beforeEach(async (to, from, next) {const base useBaseStore()//用于拿到个人信息。let profile base.profile//个人信息。// 除登录页之外其余所有页面在没有存储登录者信息的情况下都需要从服务器获取登录者信息进行存储。if (!profile to.path ! /login) {hiddenOverlayLoading showOverlayLoading()//开启遮罩层let info await base.queryProfile()// 如果是需要登录态校验的三个页面再进行登录校验和跳转。if (checkList.includes(to.path) !info) {// 真的没登录过。showFailToast(您还未登录请先登录)next({path: /login,query: {target: to.fullPath}})hiddenOverlayLoading?.()//移除遮罩层return}}next()
})
// 全局后置守卫
router.beforeEach((to, from) {hiddenOverlayLoading?.()//移除遮罩层let title to.meta?.titledocument.title !title ? 知乎日报 : ${title} - 知乎日报
})
export default router收藏功能
基础pinia模板
src/stores/collect.js
import { defineStore } from pinia
import { ref } from vue
import API from /apiconst useCollectStore defineStore(collect, () {// 定义公共状态。// 派发的方法。// 暴露给外面用。return {}
})
export default useCollectStoreimport { defineStore } from pinia
import { ref } from vue
import API from /apiconst useCollectStore defineStore(collect, () {// 定义公共状态。const collectList ref(null)// 派发的方法。const queryCollectList async () {}const removeCollectList async () {}// 暴露给外面用。return {collectList,queryCollectList,removeCollectList,}
})
export default useCollectStore收藏模块全局状态 示例代码 src/stores/collect.js 收藏相关的接口都用来源于这里的文件。 import { defineStore } from pinia
import { ref } from vue
import API from /api
import { showFailToast, showSuccessToast } from vantconst useCollectStore defineStore(collect, () {// 定义公共状态。const collectList ref(null)//用于保存收藏列表。// 派发的方法。// 查询收藏列表。const queryCollectList async () {let list nulltry {let { code, data } await API.storeList()if (code 0) {list datacollectList.value list}} catch (error) {console.log(error:--, error)}return list}// 删除收藏。// id为收藏id。const removeCollectList async (id) {if (!collectList?.value) {return}try {let { code } await API.storeRemove(id)if (code ! 0) {showFailToast(移除收藏失败)return}showSuccessToast(移除收藏成功)collectList.value collectList.value.filter(item {return item.id ! id})} catch (error) {console.log(error:--, error)}}// 新增收藏。const insertCollectList async (newsId) {try {let { code } await API.storeAdd(newsId)if (code ! 0) {showFailToast(收藏失败)return}await queryCollectList()showSuccessToast(收藏成功)} catch (error) {console.log(error:--, error)}}// 暴露给外面用。return {collectList,queryCollectList,removeCollectList,insertCollectList,}
})
export default useCollectStoresrc/views/Detail.vue 详情页 由于没有登录而进入到登录页不能直接用push()因为会添加一条记录导致登录成功后重新跳转回详情页之后会新增一条详情记录。此时登录成功后点击返回依旧是在详情页。所以这里只能使用replace()进登录页用target字段标识则在登录成功后退回到详情页。 这个在登录页中做特殊处理如果有target字段标识则在登录成功后跳转到target字段对应的路径中。 而用replace()也会丢失历史记录。在登录页中点击我们写的后退组件不是返回详情页而是回到详情页的上一条历史记录。即在详情页用replace()进登录页之后从登录页点击后退会跳转回首页。 这个需要在我们写的后退组件中做特殊处理。 script setup
import useCollectStore from /stores/collect
import useBaseStore from /stores/base
import useAutoImport from /useAutoImport
const { reactive, onBeforeMount, onUnmounted, nextTick, router, route, API } useAutoImport()
const { computed, showFailToast } useAutoImport()const base useBaseStore()
const collect useCollectStore()/* 定义状态 */
const newsId route.params.id
const state reactive({info: null,extra: null
})/* 第一次渲染之前从服务器获取新闻详情和额外的信息 */
let link null
const handleInfoStyle () {let css state.info?.css?.[0]if (!css) returnlink document.createElement(link)link.rel stylesheetlink.href cssdocument.head.appendChild(link)
}
const handleHeaderImage () {const holderBox document.querySelector(.img-place-holder)if (!holderBox) returnconst imgTemp new Image()imgTemp.src state.info.imageimgTemp.onload () holderBox.appendChild(imgTemp)imgTemp.onerror () {const p holderBox.parentNodep.parentNode.removeChild(p)}
}
onBeforeMount(async () {try {let data await API.queryNewsInfo(newsId)state.info Object.freeze(data)// 处理样式无需等待视图更新完毕handleInfoStyle()// 处理头图需要等待组件更新完毕nextTick(handleHeaderImage)} catch (_) {}
})
onBeforeMount(async () {try {let data await API.queryStoryExtra(newsId)state.extra Object.freeze(data)} catch (_) {}
})/* 组件销毁后把创建的样式移除掉 */
onUnmounted(() {if (link) document.head.removeChild(link)
})// ----------------------------------
// 第一次渲染页面之前如果用户登录了且没有收藏记录则需要获取。
onBeforeMount(() {if (base.profile !collect.collectList) {collect.queryCollectList()}
})
// 根据收藏记录来计算此文章用户是否收藏过。
const collectItem computed(() {let collectList collect.collectList || []return collectList.find((item) {return String(item.news.id) String(newsId)})
})
// 收藏的相关操作
const handleCollect () {if (!base.profile) {showFailToast(请你先登录)router.replace({path: /login,query: {target: route.fullPath}})return}if (collectItem.value) {// 当前是已收藏则移除收藏collect.removeCollectList(collectItem.value.id)return}// 当前是未收藏则进行收藏。collect.insertCollectList(newsId)
}
/scripttemplatevan-skeleton title :row5 v-if!state.info /div classcontentMy v-else v-htmlstate.info.body/divdiv classnav-boxvan-icon namearrow-left clickrouter.go(-1)/van-icontemplate v-ifstate.extravan-icon namecomment-o :badgestate.extra.comments/van-iconvan-icon namegood-job-o :badgestate.extra.popularity/van-iconvan-iconnamestar-o:colorcollectItem ? #1989fa : clickhandleCollect/van-iconvan-icon nameshare-o color#ccc/van-icon/template/div
/templatestyle langless scoped
.contentMy {background: CR_W;padding-bottom: 50px;margin: 0;:deep(.img-place-holder) {height: 375px;overflow: hidden;img {display: block;margin: 0;width: 100%;min-height: 100%;}}
}.van-skeleton {padding: 30px 15px;
}.nav-box {position: fixed;bottom: 0;left: 0;display: flex;justify-content: space-between;align-items: center;box-sizing: border-box;padding: 0 15px;width: 100%;height: 50px;background: #f4f4f4;font-size: 22px;.van-icon:nth-child(1) {position: relative;::after {position: absolute;top: -10%;right: -15px;content: ;width: 1px;height: 120%;background: #d5d5d5;}}:deep(.van-badge) {background-color: transparent;border: none;color: #000;right: -5px;}
}
/stylesrc/components/NavBack.vue script setup
import useAutoImport from /useAutoImportconst { router, route } useAutoImport()const back () {// 特殊情况当前是登录页而且来源是详情页需要基于replace的方式回到详情页。if (route.path /login) {let target route.query.target || if (/^\/detail\//.test(target)) {router.replace(target)return}}router.go(-1)
}
/scripttemplatevan-nav-bar title个人中心 left-text返回 left-arrow click-leftback /
/templatestyle langless scoped
:deep(.van-icon),
:deep(.van-nav-bar__text) {color: #000;
}
/stylesrc/views/Login.vue script setup
import useBaseStore from /stores/base
import useAutoImport from /useAutoImport
const { reactive, ref, onUnmounted, API, router, route, showSuccessToast, showFailToast, utils } useAutoImport()const baseStore useBaseStore()
console.log(baseStore--, baseStore)/* 定义状态 */
const formIns ref(null)
const state reactive({phone: ,code: ,btn: {disabled: false,text: 发送验证码}
})/* 发送验证码 */
let timer null,count 30
const handleSendCode async () {try {// 先对手机号进行校验await formIns.value.validate(phone)// 向服务器发送请求let { code } await API.userSendCode(state.phone)if (code 0) {// 开启倒计时state.btn.disabled truestate.btn.text 30s后重发timer setInterval(() {if (count 1) {clearInterval(timer)count 30state.btn.disabled falsestate.btn.text 发送验证码return}count--state.btn.text ${count}s后重发}, 1000)return}showFailToast(发送失败稍后再试)} catch (_) {}
}
onUnmounted(() clearInterval(timer))/* 登录提交 */
const submit async () {try {await formIns.value.validate()let { code, token } await API.userLogin(state.phone, state.code)if (code ! 0) {showFailToast(登录失败请稍后再试)return}// 登录成功存储Token、获取登录者信息、提示、跳转utils.storage.set(TK, token)await baseStore.queryProfile()showSuccessToast(登录成功)let target route.query.targettarget ? router.replace(target) : router.push(/)} catch (_) {}
}
/scripttemplatenav-back title登录/注册 /van-form refformIns validate-firstvan-cell-group insetvan-fieldcenterlabel手机号label-width50pxnamephonev-model.trimstate.phone:rules[{ required: true, message: 手机号是必填项 },{ pattern: /^(?:(?:\|00)86)?1\d{10}$/, message: 手机号格式不正确 }]template #buttonbutton-againclassform-btnsizesmalltypeprimaryloading-text处理中:disabledstate.btn.disabledclickhandleSendCode{{ state.btn.text }}/button-again/template/van-fieldvan-fieldlabel验证码label-width50pxnamecodev-model.trimstate.code:rules[{ required: true, message: 验证码是必填项 },{ pattern: /^\d{6}$/, message: 验证码格式不正确 }]//van-cell-groupdiv stylemargin: 20px 40pxButtonAgain round block typeprimary loading-text正在处理中... clicksubmit立即登录/注册/ButtonAgain/div/van-form
/templatestyle langless scoped
.van-form {margin-top: 30px;.form-btn {width: 78px;}
}
/style关于没登录跳转到登录页的核心处理代码 src/views/Detail.vue 详情页 由于没有登录而进入到登录页不能直接用push()因为会添加一条记录导致登录成功后重新跳转回详情页之后会新增一条详情记录。此时登录成功后点击返回依旧是在详情页。所以这里只能使用replace()进登录页用target字段标识则在登录成功后退回到详情页。 这个在登录页中做特殊处理如果有target字段标识则在登录成功后跳转到target字段对应的路径中。 而用replace()也会丢失历史记录。在登录页中点击我们写的后退组件不是返回详情页而是回到详情页的上一条历史记录。即在详情页用replace()进登录页之后从登录页点击后退会跳转回首页。 这个需要在我们写的后退组件中做特殊处理。 script setup
import useCollectStore from /stores/collect
import useBaseStore from /stores/base
import useAutoImport from /useAutoImport
const { reactive, onBeforeMount, onUnmounted, nextTick, router, route, API } useAutoImport()
const { computed, showFailToast } useAutoImport()const base useBaseStore()
const collect useCollectStore()
// 收藏的相关操作
const handleCollect () {if (!base.profile) {showFailToast(请你先登录)router.replace({path: /login,query: {target: route.fullPath}})return}if (collectItem.value) {// 当前是已收藏则移除收藏collect.removeCollectList(collectItem.value.id)return}// 当前是未收藏则进行收藏。collect.insertCollectList(newsId)
}
/scripttemplatediv classnav-boxtemplate v-ifstate.extravan-iconnamestar-o:colorcollectItem ? #1989fa : clickhandleCollect/van-icon/template/div
/templatesrc/components/NavBack.vue script setup
import useAutoImport from /useAutoImportconst { router, route } useAutoImport()const back () {// 特殊情况当前是登录页而且来源是详情页需要基于replace的方式回到详情页。if (route.path /login) {let target route.query.target || if (/^\/detail\//.test(target)) {router.replace(target)return}}router.go(-1)
}
/scripttemplatevan-nav-bar title个人中心 left-text返回 left-arrow click-leftback /
/templatesrc/views/Login.vue script setup/* 登录提交 */
const submit async () {try {//...showSuccessToast(登录成功)let target route.query.targettarget ? router.replace(target) : router.push(/)} catch (_) {}
}
/scripttemplatediv stylemargin: 20px 40pxButtonAgain round block typeprimary loading-text正在处理中... clicksubmit立即登录/注册/ButtonAgain/div/van-form
/template打包
vite按需导入插件vite-plugin-imp与vant4的按需导入插件有冲突会导致vant4中的函数调用式组件会导入与实际vant组件用到的样式文件地址不同的路径。 示例 vite.config.js import { fileURLToPath, URL } from node:url
import { defineConfig } from vite
import vue from vitejs/plugin-vue
import vueJsx from vitejs/plugin-vue-jsx
import viteImp from vite-plugin-imp
import Components from unplugin-vue-components/vite
import { VantResolver } from unplugin-vue-components/resolvers
import pxtorem from postcss-pxtorem/* https://vitejs.dev/config/ */
export default defineConfig({base: ./,plugins: [vue(),vueJsx(),/* // 按需导入插件 https://github.com/onebay/vite-plugin-imp// 与vant4的按需导入有冲突。viteImp({libList: [{libName: lodash,libDirectory: ,camel2DashComponentName: false}]}), */// vant4的按需导入Components({resolvers: [VantResolver()]})],resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}},/* 服务配置 */server: {host: 127.0.0.1,proxy: {/api: {target: http://127.0.0.1:7100,changeOrigin: true,rewrite: path path.replace(/^\/api/, )}}},/* 生产环境 */build: {assetsInlineLimit: 1024 * 10,minify: terser,terserOptions: {compress: {drop_console: true,drop_debugger: true}},rollupOptions: {external: []}},/* CSS样式 */css: {postcss: {plugins: [pxtorem({rootValue: 37.5,propList: [*]})]},preprocessorOptions: {less: {additionalData: import /assets/var.less;}}}
})核心 vite.config.js import viteImp from vite-plugin-imp
export default defineConfig({plugins: [// 按需导入插件 https://github.com/onebay/vite-plugin-imp// 与vant4的按需导入有冲突。viteImp({libList: [{libName: lodash,libDirectory: ,camel2DashComponentName: false}]}),],
})不兼容 import Components from unplugin-vue-components/vite
import { VantResolver } from unplugin-vue-components/resolvers
export default defineConfig({plugins: [Components({resolvers: [VantResolver()]})],
})TS
主要就是为了开发时限定类型让代码更严谨。 开发时用ts代替js用tsx代替jsx。
类型 对各种变量/值进行类型限制类型断言在函数中使用各种声明和限制在类中的处理 public/private/protected
与es5及es6的关系
类型的限定
对各种变量/值进行类型限制
常见类型
/*let/const 变量类型限定 值 变量不能是已经被 lib.dom.d.ts 声明的例如name但可以把当前文件变为一个模块 “ 加入 export 导出 ”这样在这里声明的变量都是私有的了 类型限定可以是小写和大写 一般用小写 大写类型可以描述实例 大写的 Object 不用因为所有值都是其实例想要笼统表示对象类型需要用 object 数组的限定let arr:number/string[]let arr:(number|string)[]let arr:Arraystring 泛型... TS中的元祖类型和长度固定let tuple:[string, number] [abc, 10]可基于数组的方法操作元祖 TS中的枚举enum USER_ROLE {ADMIN,USER} null 和 undefined 只能赋予本身的值 void 用于函数的返回function fn():void{ ... }function fn():void | null{ ... } never 不可能出现的值「任何类型的子类型」function fn():never{ // 报错 OR 死循环 等} any 任意类型*/类型断言
一定小心使用相关于程序员用人格保证了就是不是ts编译器也会把该值当成是断言的类型。
/*类型断言1 声明变量没有给类型限定没有赋值的时候默认类型是any2 如果最开始声明的时候赋值了则会按照此时值的类型自动推导3 联合类型let name:string | number 在没有赋值之前只能使用联合类型规定的类型进行相关的操作 不能在变量赋值之前调用其方法 !. 断言变量一定有值 as 认定是啥类型的值(name! as number).toFixed()4 字面量类型let direction:top|right|down|left 赋的值只能是这四个中的一个{限定了值}可以基于 type 类型别名优化let Direction top|right|down|leftlet direction:Direction ...*/函数类型
在函数中使用各种声明和限制。
/*函数的玩法普通函数声明参数和返回值类型function fn(x:number,y:number):number{...}函数表达式在普通函数基础上对赋值的函数做类型校验 type Fn (x:number,y?:number) numberlet fn:Fn function(x,y){...}*/类的类型
高级类型与联合类型
接口
接口与type
泛型
ts的应用
在vue/cli中使用在vite中使用
在项目根目录中配置 Vue3进阶/knowledge/env.d.ts 这个很重要要不在.vue后缀类型文件中会有报错。 /// reference typesvite/client /// 声明导入 .vue 文件的类型「防止波浪线报错」
declare module *.vue {import type { DefineComponent } from vueconst component: DefineComponent{}, {}, anyexport default component
}Vue3进阶/knowledge/tsconfig.app.json {extends: vue/tsconfig/tsconfig.dom.json,include: [env.d.ts,src/**/*,src/**/*.vue],exclude: [src/**/__tests__/*],compilerOptions: {composite: true,baseUrl: .,paths: {/*: [./src/*]}}
}Vue3进阶/knowledge/tsconfig.json 看对应的pdf文档。 {files: [],references: [{path: ./tsconfig.node.json},{path: ./tsconfig.app.json}]
}Vue3进阶/knowledge/tsconfig.node.json {extends: tsconfig/node18/tsconfig.json,include: [vite.config.*,vitest.config.*,cypress.config.*,nightwatch.conf.*,playwright.config.*],compilerOptions: {composite: true,module: ESNext,types: [node]}
}简历
注意细节
先有面试再说后面的事。先有word版写好后面再复制到网站的模板上。先全员海投有面试机会再看具体信息。无脑投在BOSS直聘一天100个左右其它投到上限。用一个小时左右投先看到有的后面去试可以准备给朋友。可以记录下要面试的题。面试时一般就说在我之前的项目中…而不要八股文。个人真实就好了带上笔和本-面试遇到不会的问题是面试的开始而不是结束。 当上不会的题或东西当着面试官的面来记再说后面再查晚上回去再查。这个也要真查因为提到的可能就是新的主流东西。 再问对方写代码了多少年夸奖面试官。多少年之后比自己少的夸对方厉害。比自己多不愧是xx年工作经验的。
招聘平台
招聘平台 BOSS直聘主要 基于聊天去投递简历要准备好简历和聊天用语。 51job前程无忧拉钩猎聘网…
投递时间 投的时间周一到周六每天9:30开始、下午14:00开始不要睡懒觉。 剩下的时间要复习。整理好css、js之后是vue和react并写页面。 进阶学一些算法。 面试之后要录音电脑面试也要录音而现场面试时进公司就录音。面试之后要再整理面试题如果记不清则要听录音。同时再总结出最佳的面试题回答。早睡早起早上不要晚于8:30、晚上不要晚于12:00、在此期间不要玩游戏。 老家或北京之类的都投。
个人预期
学习完ts和uniapp。
职业规划和离职原因 职业规划 随意一些按个人真实的来。走技术学习全栈。 学会后端知识点如node。学习uniapp。学习taro。看vue3源码和react源码和UI框架源码如 element-ui源码。antd源码。vant源码。 学习前端算法。 走管理熟悉公司的业务会培训带领新人写文档。会和后端进行交互 离职原因 不要说上家公司坏话如技术栈不新、公司抠门、领导差之类的。尽量多写客观原因 可以说公司业绩不太好-公司暗示要解散项目组。 公司倒闭了但压了自己的工资老大那边压力也大后面帮做最后一个项目里结束最后一个业务后就结束了。 要结婚之类的。
进阶参考
ts中文网