├── .gitignore ├── README.md ├── assets ├── fonts │ ├── google │ │ ├── bbr.woff2 │ │ ├── ir.woff2 │ │ ├── jsr.woff2 │ │ ├── lobster.woff2 │ │ ├── ml.woff2 │ │ └── sarpanch.woff2 │ ├── icomoon │ │ └── Icon.woff │ └── iconfont │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 └── images │ └── bg.png ├── blog ├── 404 │ └── index.md ├── .vuepress │ ├── __tests__ │ │ ├── snippet-with-region.js │ │ └── snippet.js │ ├── config.js │ ├── config │ │ └── themeConfig.js │ ├── enhanceApp.js │ └── public │ │ ├── blogImages │ │ ├── BuildFail.png │ │ └── buildSuccess.png │ │ ├── images │ │ ├── demand.png │ │ ├── friend.jpg │ │ ├── git.png │ │ ├── interview.jpg │ │ ├── screenshot-1.png │ │ ├── screenshot-2.png │ │ ├── screenshot-3.png │ │ └── screenshot.webp │ │ └── logo.jpg ├── _post │ ├── directory.md │ ├── icon.md │ ├── lastUpdatedConfig.md │ ├── maker-basic.md │ ├── maker-page.md │ ├── md5.md │ ├── npmRunBuildError.md │ ├── shell-tree.md │ ├── theme-learning-archive.md │ ├── theme-learning-category.md │ ├── theme-learning-concept.md │ ├── theme-learning-directory.md │ ├── theme-learning-footerbar.md │ ├── theme-learning-guide.md │ ├── theme-learning-home.md │ ├── theme-learning-nav.md │ ├── theme-learning-post.md │ ├── theme-learning-subnav.md │ ├── theme-learning-tag.md │ ├── theme-learning-template.md │ ├── theme-learning.md │ ├── theme-showcase.md │ └── timeIsOutOfOrder.md ├── git │ └── index.md ├── interview │ └── index.md ├── personalInformation │ └── index.md ├── programdemand │ └── index.md ├── programdifficulty │ ├── htmlcss │ │ ├── Nth-child.md │ │ ├── bodyBgiCover.md │ │ ├── cssWeight.md │ │ ├── fixedPositionCover.md │ │ ├── heightSubsidence.md │ │ ├── iconfontShowBug.md │ │ ├── image403.md │ │ └── spriteImageUseNegativeValue.md │ ├── js │ │ ├── LogicWithMisunderstanding.md │ │ ├── arrowFunction.md │ │ ├── awaitBlocking.md │ │ ├── focusAndonfocusDifference.md │ │ ├── localstrorage.md │ │ ├── objectCantEqualObject.md │ │ ├── offsetAndStyleDifference.md │ │ ├── printStar.md │ │ ├── replaceAndRegex.md │ │ ├── switch.md │ │ ├── throttle.md │ │ └── usingDomEvent.md │ ├── react │ │ ├── InterfaceNotUpdate.md │ │ └── proxyConfig.md │ ├── ts │ │ └── test.md │ ├── uniapp │ │ └── a.md │ └── vue │ │ ├── TableDataChange.md │ │ ├── VueRouterNextFalse.md │ │ ├── aTagSkip.md │ │ ├── autoOpen.md │ │ ├── axiosParamsLose.md │ │ ├── deconstMissResponsiveness.md │ │ ├── deliverEventParam.md │ │ ├── dynamicClass.md │ │ ├── ellipsisThis.md │ │ ├── fullScreenAndPrerender.md │ │ ├── ifElseOptimization.md │ │ ├── interfaceTest.md │ │ ├── keepAlive.md │ │ ├── listShortSlideEnterDetail.md │ │ ├── location.md │ │ ├── lotsOfFormsValidate.md │ │ ├── mock.md │ │ ├── npmITypeShaError.md │ │ ├── productItemFresh.md │ │ ├── publicAndAssets.md │ │ ├── rewritePushAndReplace.md │ │ ├── sonRouterPath.md │ │ ├── swiperLoopError.md │ │ ├── vIfAddKey.md │ │ ├── validateAndCallback.md │ │ ├── vue3ImportIcon.md │ │ └── vuexUndefined.md └── studyprogress │ ├── htmlcss │ ├── cssWriteOrder.md │ ├── elementDeifference.md │ ├── flex1Width0.md │ ├── floatAndInlineBlockDifference.md │ ├── linesEllipsis.md │ ├── normalFlowAndFloat.md │ ├── overContentEllipsis.md │ ├── pseudoElementUsing.md │ └── textIndent-999px.md │ ├── js │ ├── Dom.md │ ├── ShortCircuitOperator.md │ ├── asyncAndAwait.md │ ├── constCatch.md │ ├── continue.md │ ├── createOuterJsFile.md │ ├── eventProxy.md │ ├── exclusiveThoughts.md │ ├── export.md │ ├── fetch.md │ ├── forin.md │ ├── functionParamsPlaceholder.md │ ├── js.md │ ├── judgeObjectValue.md │ ├── operator.md │ ├── prototype.md │ ├── reduce.md │ ├── redueceResolveArrayToRreeData.md │ ├── regexp.md │ └── replace(reg).md │ ├── react │ └── a.md │ ├── ts │ └── declareGlobalTypes.md │ ├── uniapp │ └── a.md │ └── vue │ ├── guarantees.md │ ├── lifecycleHooksFunction.md │ ├── methods--Computed-Watch.md │ ├── moduleImport.md │ ├── package.md │ ├── routerRequestWays.md │ └── sync.md ├── components ├── Archive.vue ├── Category.vue ├── CategoryItem.vue ├── ColorScheme.vue ├── Comments.vue ├── DarkMode.vue ├── DropdownLink.vue ├── DropdownTransition.vue ├── FooterBar.vue ├── FriendLink.vue ├── Home.vue ├── NavLink.vue ├── NavLinks.vue ├── Navbar.vue ├── Post.vue ├── PostMeta.vue ├── PostNav.vue ├── PostTag.vue ├── Reward.vue ├── SettingPanel.vue ├── SideBar.vue ├── Sticker.vue ├── SubNav.vue ├── SvgSprite.vue ├── Tag.vue ├── TagItem.vue ├── Toc.vue └── Valine.vue ├── deploy.sh ├── docs └── .vuepress │ ├── .cache │ └── deps │ │ ├── @vue_devtools-api.js │ │ ├── @vue_devtools-api.js.map │ │ ├── @vuepress_shared.js │ │ ├── @vuepress_shared.js.map │ │ ├── _metadata.json │ │ ├── chunk-AWA6B2ZS.js │ │ ├── chunk-AWA6B2ZS.js.map │ │ ├── chunk-JXWQMH7G.js │ │ ├── chunk-JXWQMH7G.js.map │ │ ├── chunk-RFQTXRIF.js │ │ ├── chunk-RFQTXRIF.js.map │ │ ├── package.json │ │ ├── vue-router.js │ │ ├── vue-router.js.map │ │ ├── vue.js │ │ └── vue.js.map │ └── .temp │ ├── internal │ ├── clientConfigs.js │ ├── pagesComponents.js │ ├── pagesData.js │ ├── pagesRoutes.js │ ├── siteData.js │ └── themeData.js │ ├── pages │ ├── 404.html.js │ └── 404.html.vue │ ├── styles │ ├── index.scss │ └── palette.scss │ └── vite-root │ └── index.html ├── global-components ├── Badge.vue ├── Icon.vue ├── RelatedPosts.vue └── ThemeSWUpdatePopup.vue ├── index.js ├── layouts ├── 404.vue └── Layout.vue ├── mixins └── index.js ├── package-lock.json ├── package.json ├── plugin ├── demo-code │ ├── DemoCode.vue │ ├── enhanceAppFile.js │ └── index.js ├── float-menu │ ├── FloatMenu.vue │ ├── Search.vue │ ├── enhanceAppFile.js │ └── index.js ├── theme-palette │ ├── ThemePalette.vue │ ├── enhanceAppFile.js │ └── index.js └── theme-utils │ └── index.js ├── styles ├── code.styl ├── color_scheme.styl ├── custom_block.styl ├── fonts.styl ├── index.styl ├── mobile.styl └── palette.styl ├── templates ├── dev.html └── ssr.html ├── util ├── index.js ├── node.js ├── pathList.js └── pidList.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode 3 | blog/.vuepress/dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XiaoChen's Blog 2 | 3 | A flat and clean Blog for VuePress site. inspired by [Vuepress-theme-maker](https://github.com/80maker/vuepress-theme-maker) 4 | 5 | ## Features 6 | 7 | - Build with Vue.js 8 | - 在VuePress默认主题基础之上修改,保留了内置搜索,tag分类等绝大多数特性 9 | - 基于官方博客插件`@vuepress/plugin-blog`制作 10 | - 扩展了Markdown语法,支持`footnote` `mark` `abbr` `task-lists`, `Demo Code` 11 | - 支持文章评论 12 | - 文章打赏模块 13 | - 内置友情链接页 14 | - 文章阅读时长估算 15 | - 文章阅读计数(依赖valine评论) 16 | - rss订阅及sitemap 17 | - 访客自定义主题 18 | - 导航多级菜单 19 | - PWA支持 20 | - seo配置 21 | - 代码复制 22 | - 暗黑模式 23 | - 响应式主题 24 | 25 | ## Install 26 | 27 | ``` 28 | npm i vuepress-theme-maker -D 29 | # OR yarn add vuepress-theme-maker -D 30 | ``` 31 | 32 | ## Usage 33 | 34 | ``` 35 | // .vuepress/config.js 36 | module.exports = { 37 | theme: 'vuepress-theme-maker', 38 | themeConfig: { 39 | // Please head documentation to see the available options. 40 | } 41 | } 42 | ``` 43 | 44 | For more details, see [Theme Doc](https://codelove9.github.io/myBlog/) 45 | 46 | ## Blog Online 47 | 48 | [github-pages](https://codelove9.github.io/myBlog/) 49 | 50 | ## Screenshot 51 | 58 | 59 | 60 | [![screenshot-1.png](https://i.postimg.cc/zvJ9B4nY/screenshot-1.png)](https://postimg.cc/WFH9YWW5) 61 | [![screenshot-2.png](https://i.postimg.cc/FHgyTfWT/screenshot-2.png)](https://postimg.cc/hXvQjGqm) 62 | [![screenshot-3.png](https://i.postimg.cc/t4K1Yg7t/screenshot-3.png)](https://postimg.cc/QFqNzhQH) 63 | 64 | ## Deploy to Github Pages 65 | 66 | - 根目录下运行命令: `bash deploy.sh` 67 | 68 | - 在vsCode终端或者cmd中运行会抛出错误 69 | 70 | - 需要在powerShell或者gitBash中运行 71 | 72 | ## Thanks to 73 | 74 | - [VuePress](https://vuepress.vuejs.org/) 75 | - [@vuepress/plugin-blog](https://github.com/vuepress/vuepress-plugin-blog) 76 | - [@vuepress/theme-blog](https://github.com/vuepress/vuepress-theme-blog) 77 | -------------------------------------------------------------------------------- /assets/fonts/google/bbr.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/google/bbr.woff2 -------------------------------------------------------------------------------- /assets/fonts/google/ir.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/google/ir.woff2 -------------------------------------------------------------------------------- /assets/fonts/google/jsr.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/google/jsr.woff2 -------------------------------------------------------------------------------- /assets/fonts/google/lobster.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/google/lobster.woff2 -------------------------------------------------------------------------------- /assets/fonts/google/ml.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/google/ml.woff2 -------------------------------------------------------------------------------- /assets/fonts/google/sarpanch.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/google/sarpanch.woff2 -------------------------------------------------------------------------------- /assets/fonts/icomoon/Icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/icomoon/Icon.woff -------------------------------------------------------------------------------- /assets/fonts/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /assets/fonts/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/iconfont/iconfont.woff -------------------------------------------------------------------------------- /assets/fonts/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/fonts/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /assets/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/assets/images/bg.png -------------------------------------------------------------------------------- /blog/.vuepress/__tests__/snippet-with-region.js: -------------------------------------------------------------------------------- 1 | // #region snippet 2 | function foo () { 3 | return ({ 4 | dest: '../../vuepress', 5 | locales: { 6 | '/': { 7 | lang: 'en-US', 8 | title: 'VuePress', 9 | description: 'Vue-powered Static Site Generator' 10 | }, 11 | '/zh/': { 12 | lang: 'zh-CN', 13 | title: 'VuePress', 14 | description: 'Vue 驱动的静态网站生成器' 15 | } 16 | }, 17 | head: [ 18 | ['link', { rel: 'icon', href: `/logo.png` }], 19 | ['link', { rel: 'manifest', href: '/manifest.json' }], 20 | ['meta', { name: 'theme-color', content: '#3eaf7c' }], 21 | ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }], 22 | ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }], 23 | ['link', { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` }], 24 | ['link', { rel: 'mask-icon', href: '/icons/safari-pinned-tab.svg', color: '#3eaf7c' }], 25 | ['meta', { name: 'msapplication-TileImage', content: '/icons/msapplication-icon-144x144.png' }], 26 | ['meta', { name: 'msapplication-TileColor', content: '#000000' }] 27 | ] 28 | }) 29 | } 30 | // #endregion snippet 31 | 32 | export default foo -------------------------------------------------------------------------------- /blog/.vuepress/__tests__/snippet.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | // .. 3 | } -------------------------------------------------------------------------------- /blog/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "XiaoChen's Blog", 3 | description: 'A front-end programmer born in 1999', 4 | port: 8088, 5 | base: '/myBlog/', 6 | markdown: { 7 | lineNumbers: true, 8 | extractHeaders: ['h2', 'h3', 'h4'], 9 | plugins: { 10 | 'markdown-it-mark': true, 11 | 'markdown-it-footnote': true, 12 | 'markdown-it-abbr': true, 13 | 'markdown-it-task-lists': true 14 | } 15 | }, 16 | theme: require.resolve('../../index'), // 使用本地主题 17 | themeConfig: require('./config/themeConfig') 18 | } -------------------------------------------------------------------------------- /blog/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | // 使用异步函数也是可以的 2 | export default ({ 3 | Vue, // VuePress 正在使用的 Vue 构造函数 4 | options, // 附加到根实例的一些选项 5 | router, // 当前应用的路由实例 6 | siteData, // 站点元数据 7 | isServer // 当前应用配置是处于 服务端渲染 或 客户端 8 | }) => { 9 | // ...做一些其他的应用级别的优化 10 | } -------------------------------------------------------------------------------- /blog/.vuepress/public/blogImages/BuildFail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/blogImages/BuildFail.png -------------------------------------------------------------------------------- /blog/.vuepress/public/blogImages/buildSuccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/blogImages/buildSuccess.png -------------------------------------------------------------------------------- /blog/.vuepress/public/images/demand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/demand.png -------------------------------------------------------------------------------- /blog/.vuepress/public/images/friend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/friend.jpg -------------------------------------------------------------------------------- /blog/.vuepress/public/images/git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/git.png -------------------------------------------------------------------------------- /blog/.vuepress/public/images/interview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/interview.jpg -------------------------------------------------------------------------------- /blog/.vuepress/public/images/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/screenshot-1.png -------------------------------------------------------------------------------- /blog/.vuepress/public/images/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/screenshot-2.png -------------------------------------------------------------------------------- /blog/.vuepress/public/images/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/screenshot-3.png -------------------------------------------------------------------------------- /blog/.vuepress/public/images/screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/images/screenshot.webp -------------------------------------------------------------------------------- /blog/.vuepress/public/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/.vuepress/public/logo.jpg -------------------------------------------------------------------------------- /blog/404/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeLove9/myBlog/2a0a079828b08390630230c0a24d15d842b1ea79/blog/404/index.md -------------------------------------------------------------------------------- /blog/_post/directory.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-07-10 3 | title: markdown中的正文不能用一级标题,否则目录出不来 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | ## 背景 10 | 11 | 1. 最开始的时候我是下载了markdownLint插件,来规范markdown编写的格式。 12 | 2. 我发现每当我在正文中用一个#写一级标题的时候,markdownLint总会报错`MD025/single-title/single-h1: Multiple top-level headings in the same document`。 13 | 3. 我觉得这个报错非常不合理,凭啥不要我在正文中写一级标题,于是我在vscode里的settings.json文件中增加了对markdown格式的忽略设置,具体代码如下: 14 | 15 | ```json 16 | // markdownLint忽略设置 17 | "markdownlint.config": { 18 | "default": true, 19 | "MD025": false, // 同一片文档中不允许存在多个一级标题 20 | } 21 | ``` 22 | 23 | 4. 设置后果然可以随意的写一级标题了。 24 | 25 | ## 发现异常 26 | 27 | 过了段时间,当我打开之后写的几篇用一级标题的文章,点击悬浮菜单打开目录时,发现目录竟然是空白。 28 | 29 | ## 解决方案 30 | 31 | 1. 最开始一直找不到解决方案,只看到获取不到$site.headings这个属性。 32 | 2. 按道理这个属性是由VuePress底层已经封装好自行抛出的,可是查看了相关组件却始终无法定位到问题点。 33 | 3. 于是我打开有目录的和无目录的正文进行比较,发现有目录的正文标题级别都第一点,而无目录的普遍标题级别高,并且这是一个共性。 34 | 4. 于是我试着更改无目录文档中正文的标题级别,把一级标题改成二级标题。 35 | 5. 再次运行项目,目录成功展示出来,问题解决。 36 | -------------------------------------------------------------------------------- /blog/_post/lastUpdatedConfig.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-04-12 3 | title: VuePress内置计算属性this.$page.lastUpdated由于没有git commit记录在npm run serve时报错 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | > 写了新博客却出不来页面,调试工具丢出报错找不到this.$page.lastUpdated,这是为什么呢 10 | 11 | 12 | 13 | ## 问题 14 | 15 | 昨天写了一个新的md文件,却发现获取不到this.$page.lastUpdated,且出不来页面 16 | 17 | ## 解决方案 18 | 19 | 我在npm中搜索相关的包@vuepress/plugin-last-updated,并查看了源码,以下是代码段 20 | 21 | ```js 22 | // index.js 23 | 24 | const path = require('path') 25 | const spawn = require('cross-spawn') 26 | 27 | /** 28 | * @type {import('@vuepress/types').Plugin} 29 | */ 30 | module.exports = (options = {}, context) => ({ 31 | extendPageData ($page) { 32 | const { transformer, dateOptions } = options 33 | const timestamp = getGitLastUpdatedTimeStamp($page._filePath) 34 | const $lang = $page._computed.$lang 35 | if (timestamp) { 36 | const lastUpdated = typeof transformer === 'function' 37 | ? transformer(timestamp, $lang) 38 | : defaultTransformer(timestamp, $lang, dateOptions) 39 | $page.lastUpdated = lastUpdated 40 | $page.lastUpdatedTimestamp = timestamp 41 | } 42 | } 43 | }) 44 | 45 | function defaultTransformer (timestamp, lang, dateOptions) { 46 | return new Date(timestamp).toLocaleString(lang, dateOptions) 47 | } 48 | 49 | function getGitLastUpdatedTimeStamp (filePath) { 50 | let lastUpdated 51 | try { 52 | lastUpdated = parseInt(spawn.sync( 53 | 'git', 54 | ['log', '-1', '--format=%at', path.basename(filePath)], 55 | { cwd: path.dirname(filePath) } 56 | ).stdout.toString('utf-8')) * 1000 57 | } catch (e) { /* do not handle for now */ } 58 | return lastUpdated 59 | } 60 | ``` 61 | 62 | 这是对这段代码的解释: 63 | 64 | 这是一个使用JavaScript编写的VuePress插件脚本,用于获取页面源文件的最后一次Git提交时间戳,并将其添加到页面元数据中。以下是代码的概述: 65 | 66 | 1. 它从Node.js导入了两个模块:path和cross-spawn包中的spawn。 67 | 68 | 2. 它导出一个函数,该函数接受两个参数:options和context,并返回包含名为extendPageData的方法的对象。 69 | 70 | 3. extendPageData方法检索当前页面源文件的Git时间戳,并将其作为lastUpdated和lastUpdatedTimestamp属性添加到$page对象中。 71 | 72 | 4. 如果提供了options.transformer函数,则使用它来将时间戳格式化为字符串表示形式;否则使用脚本中定义的defaultTransformer函数。 73 | 74 | 5. getGitLastUpdatedTimeStamp函数获取页面的文件路径,并使用spawn模块运行Git命令以检索修改文件的最后一次提交时间戳。然后解析并返回自Unix纪元以来的时间戳值(毫秒)。 75 | 76 | 并找到了相关issue, 77 | 78 | issue详情:'@vuepress/last-updated' relies on git history, but the document doesn't mention this at all. 79 | At first, I did not initialize git and commit, so the lastUpdated was not displayed. Finally, I had to look at the source code to solve the problem. 80 | It wasted a lot of my time to debug, consumed a lot of my enthusiasm and goodwill, 81 | Your documentation should clearly indicate that you are relying on git and that you must commit to detect new changes 82 | 83 | [document issue '@vuepress/last-updated' 链接](https://github.com/vuejs/vuepress/issues/1291) 84 | 85 | ## 总结 86 | 87 | this.$page.lastUpdated计算属性依赖于git提交时间(本地commit),否则丢出报错 88 | -------------------------------------------------------------------------------- /blog/_post/maker-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Maker主题配置--页面配置 3 | date: 2021-01-25 4 | category: maker 5 | layout: SpecialLayout 6 | --- 7 | 8 | ## 文章标题 - Title 9 | 10 | `Maker主题的限制, title设置后会自动解析为h1标签` 11 | 12 | ``` 13 | --- 14 | title: 页面title 15 | --- 16 | ``` 17 | 18 | ## 文章日期 - Date 19 | 20 | `Maker主题的限制,文档归档依赖该字段,发布文章需要显示的填写date字段` 21 | 22 | ``` 23 | --- 24 | date: 2021-01-25 25 | --- 26 | ``` 27 | 28 | ## 设置文章封面 - Cover 29 | 30 | ``` 31 | --- 32 | cover: path-of-cover 33 | --- 34 | ``` 35 | 36 | ## 设置文章分类、标签 37 | 38 | 可以是数组、也可以是单项 39 | 40 | ``` 41 | --- 42 | category: maker 43 | categories: 44 | - maker 45 | - theme 46 | tag: maker 47 | tags: 48 | - VuePress 49 | - tag-2 50 | --- 51 | ``` 52 | 53 | ## 手动设置不在关联文章列表中显示 54 | 55 | ``` 56 | --- 57 | excludeRelatedPost: true 58 | --- 59 | ``` 60 | 61 | ## 禁用当前文章的打赏 62 | 63 | ``` 64 | reward: false 65 | ``` 66 | 67 | ## 自定义页面类 68 | 69 | 有时候你可能需要为特定页面添加一个 CSS 类名,以方便针对该页面添加一些专门的 CSS。这种情况下你可以在该页面的 YAML front matter 中声明一个 pageClass: 70 | 71 | ``` 72 | --- 73 | pageClass: custom-page-class 74 | --- 75 | ``` 76 | 77 | 然后你就可以写专门针对该页面的 CSS 了: 78 | 79 | ``` 80 | /* .vuepress/override.styl */ 81 | 82 | .theme-container.custom-page-class { 83 | /* 特定页面的 CSS */ 84 | } 85 | ``` 86 | 87 | ## 特定页面的自定义布局 88 | 89 | 默认情况下,每个 `*.md` 文件将会被渲染在一个 `
` 容器中,同时还有侧边栏,以及上 / 下一篇文章的链接。如果你想要使用一个完全自定义的组件来代替当前的页面,你可以使用 `YAML front matter` 来指定这个Layout组件。 90 | 91 | ``` yaml 92 | --- 93 | layout: SpecialLayout 94 | --- 95 | ``` 96 | 97 | 这将会为当前的页面渲染 `.vuepress/components/SpecialLayout.vue` 布局。 98 | 99 | ## See also 100 | 101 | -------------------------------------------------------------------------------- /blog/_post/md5.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-01-26 3 | category: frontend 4 | title: 浏览器端不用第三方包也可以获取md5值 5 | --- 6 | 7 | > 利益于JS的同构特性,crypto虽然是Node.js的自带模块,但同时也能用于Web浏览器端。 8 | 9 | 10 | ## 双主角 11 | 12 | ### crypto 13 | 14 | ::: warning 15 | 这家伙是nodejs自带模块,测试了下在浏览器端也能正常使用 16 | ::: 17 | 18 | ### FileReader 19 | 20 | ## 隐藏的孪生兄弟 21 | 22 | ### atob和btoa 23 | 24 | ## 关键代码 25 | 26 | ``` js {5} 27 | // 将reader方法封装成Promisify 28 | async readFileData(fileReader, file) { 29 | return new Promise((resolve, reject) => { 30 | fileReader.addEventListener('load', (ev) => { 31 | resolve(ev.target.result); 32 | }); 33 | fileReader.readAsArrayBuffer(file); 34 | }); 35 | } 36 | 37 | // 代码调用 38 | const reader = new FileReader(); 39 | const crypto = require('crypto'); 40 | const md5 = crypto.createHash('md5'); 41 | let fileBuff = await that.readFileData(reader, file); 42 | fileBuff = Buffer.from(fileBuff); 43 | md5Str = md5.update(fileBuff).digest('hex'); 44 | ``` 45 | -------------------------------------------------------------------------------- /blog/_post/npmRunBuildError.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-04-15 3 | title: 由于获取不到内置计算属性this.$page.lastUpdated引起的npm run build打包报错 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | > npm run build打包报`Error in render: "TypeError: Cannot destructure property 'time' of 'this.$page.lastUpdated' as it is undefined."` 10 | 11 | 12 | 13 | ## 引言 14 | 15 | 今天写好新的md文件时,npm run serve运行起来没有问题,于是我commit到本地后npm run build打包,可是却报错了,报错图片如下 16 | 17 | ![image](/myBlog/blogImages/BuildFail.png) 18 | 19 | 提示`Error in render: "TypeError: Cannot destructure property 'time' of 'this.$page.lastUpdated' as it is undefined."`,然后让我在Post组件里找 20 | 21 | 我猜想是因为run serve运行起来没有问题,那就不是写法的问题,在run build打包的适合提示找不到`this.$page.lastUpdated`属性,就是打包时获取不到这个计算属性,那应该就与异步加载有关联了,于是我打开Post组件查找跟`this.$page.lastUpdated`有关的代码,具体代码如下 22 | 23 | ```vue 24 | computed: { 25 | // 最近更新时间 26 | lastUpdated() { 27 | const { time } = this.$page.lastUpdated 28 | return time 29 | }, 30 | // 距离现在的时间 31 | fromNow() { 32 | const { fromNow } = this.$page.lastUpdated 33 | return fromNow 34 | } 35 | } 36 | ``` 37 | 38 | 可以看到是在计算属性里获取`this.$page.lastUpdated`,计算属性里是同步的,于是我打算试试用别的方案获取 39 | 40 | ## 解决方案 41 | 42 | 众所周知,Vue里有一个生命周期钩子`mounted`,作用是在组件开始渲染模板时调用,于是我试想如果在这个钩子里获取的话,会不会就解决了这个问题 43 | 44 | 我开始用自己的思路修改代码,具体代码如下: 45 | 46 | ```vue 47 | // 首先在data里定义这两个用于渲染模板里的响应式数据 48 | data() { 49 | return { 50 | lastUpdated: '', 51 | fromNow: '' 52 | }, 53 | }, 54 | // 生命周期钩子中获取并赋值 55 | mounted() { 56 | const { time, fromNow } = this.$page.lastUpdated 57 | this.lastUpdated = time 58 | this.fromNow = fromNow 59 | } 60 | ``` 61 | 62 | 问题解决! 成功截图如下: 63 | 64 | ![image](/myBlog/blogImages/buildSuccess.png) 65 | -------------------------------------------------------------------------------- /blog/_post/shell-tree.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2021-01-22 3 | title: "【常用Shell命令】- tree" 4 | category: shell 5 | pageClass: page-shell-tree 6 | --- 7 | 8 | 浏览博客文章时经常会看到一些很漂亮的目录结构,就像下面所示 9 | 10 | ``` sh 11 | assets 12 | ├── fonts 13 | │ ├── google 14 | │ │ ├── bbr.woff2 15 | │ │ └── sarpanch.woff2 16 | │ └── icomoon 17 | │ └── Icon.woff 18 | └── images 19 | └── bg.png 20 | ``` 21 | 22 | 23 | 24 | 以前不知道还以为是XX高级编辑器插件, 其实系统自带的tree命令就可以帮助我们搞定这些高大上的目录结构. 25 | 26 | ## tree 命令生成目录树 27 | 28 | 语法格式:tree [参数] 29 | 30 | 常用参数: 31 | 32 | 38 | 39 | | 参数名 | 说明 | 40 | |-------------| ----------------------------------------- | 41 | | -L level | 限制目录显示层级 | 42 | | -C | 显示目录和文件的色彩区分 | 43 | | -d | -d 只显示目录不显示内容。 | 44 | | -f | 在每个文件或目录之前,显示完整的相对路径名称 | 45 | | --dirsfirst | 优先显示目录 | 46 | | -p | 列出权限标示 | 47 | | -s | 列出文件或目录大小 | 48 | 49 | 50 | ### 示例 51 | 52 | `tree blog -L 2 -C --dirsfirst` 53 | 54 | ``` sh 55 | blog 56 | ├── _post 57 | │ ├── maker-icon.md 58 | │ ├── maker.md 59 | │ ├── md5.md 60 | │ ├── shell-tree.md 61 | │ ├── theme-learning-0.md 62 | │ ├── theme-learning-concept.md 63 | │ └── theme-showcase.md 64 | ├── archives 65 | │ └── index.md 66 | ├── friend-links 67 | │ └── index.md 68 | └── README.md 69 | ``` 70 | 71 | ### mac下可通过homebrew安装tree命令 72 | 73 | 74 | ``` sh 75 | $ brew install tree 76 | ``` -------------------------------------------------------------------------------- /blog/_post/theme-learning-directory.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2021-02-24 3 | title: VuePress 主题目录结构 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | > 接上一篇,我们已经有了个能运行的简易站点,主题目录下仅包含了一个`Layout.vue`文件,从现在开始我们需要往里面添置东西让主题变得功能完备起来,以下是推荐的目录结构。 10 | 11 | 12 | ## 约定目录 13 | 14 | ``` 15 | theme 16 | ├── assets (资源文件) 17 | │ ├── images 18 | │ └── fonts 19 | ├── global-components 20 | │ └── xxx.vue 21 | ├── components 22 | │ └── xxx.vue 23 | ├── layouts 24 | │ ├── Layout.vue (必要的) 25 | │ └── 404.vue 26 | ├── styles 27 | │ ├── index.styl 28 | │ └── palette.styl 29 | ├── templates 30 | │ ├── dev.html 31 | │ └── ssr.html 32 | ├── utils (工具函数) 33 | ├── plugin (私有插件) 34 | ├── index.js (入口文件) 35 | ├── enhanceApp.js 36 | └── package.json 37 | ``` 38 | 39 | - `theme/global-components`: 该目录下的组件都会被自动注册为全局组件。想了解更多,请参考 [@vuepress/plugin-register-components](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/plugin-register-components)。 40 | - `theme/components`: Vue 组件。 41 | - `theme/layouts`: 布局组件,其中 `Layout.vue` 是必需的。 42 | - `theme/styles`: 全局的样式和调色板。 43 | - `theme/templates`: 修改默认的模板文件。 44 | - `theme/index.js`: 主题文件的入口文件。 45 | - `theme/enhanceApp.js`: 主题水平的客户端增强文件。 46 | 47 | ### 资源文件 48 | 49 | 存放字体、图片等主题中用到的资源文件 50 | 51 | ### 全局组件 52 | 53 | 如果你想在 主题外部 使用组件而不需要额外注册,那么组件就可以放在该目录下 54 | *[主题外部]: 这里指文档目录 55 | 56 | ### 主题自带组件 57 | 这个和 主题外部 的components目录有所区别,需要单独注册后方可使用。 58 | 59 | ``` js 60 | // 复用主题的Layout组件 61 | import Layout from 'vuepress-theme-maker/layouts/Layout'; 62 | ``` 63 | 64 | ### 主题入口文件 65 | 66 | ``` js 67 | // .vuepress/theme/index.js 68 | module.exports = (themeConfig, ctx) => { 69 | return { 70 | // ... 71 | } 72 | } 73 | ``` 74 | 75 | 包含了主题本身的一个配置,`themeConfig`是用户对主题的配置,这里作为参数接收,开发自定义主题的时候可以利用该参数做一些配置的预处理。其实这个和`VuePress`的设计理念相关,到后面讲插件的时候会做更详细介绍, 我个人在开发主题的过程中觉得这块还是比较容易被混淆的。 76 | 77 | ### 布局组件 78 | 79 | 所有的页面将会默认使用 `Layout.vue` 作为布局组件,对于那些匹配不到的路由将会使用 404.vue 80 | 如果你想要在某一个页面中使用 `AnotherLayout.vue` 作为布局组件,那么你只需要在layouts目录下创建对应组件, 并在具体的页面中 `frontmatter` 显示的设置布局组件名称即可。 81 | 82 | ``` md 83 | --- 84 | layout: AnotherLayout 85 | --- 86 | ``` 87 | 88 | ### 样式 89 | 90 | `vuepress` 默认的是 `stylus` 作为css预处理器, 样式一般来说只需写在两个文件下。 91 | 92 | `.vuepress/styles/palette.styl` 用来定义一系列变量: 93 | 94 | ``` stylus 95 | // 颜色 96 | $color1 = xxx 97 | 98 | // 布局 99 | $sidebarWidth = 20rem 100 | 101 | 102 | // 响应式变化点 103 | $MQNarrow = 975px 104 | $MQMobile = 675px 105 | $MQMobileNarrow = 359px 106 | ``` 107 | 108 | `.vuepress/styles/index.styl` 用来编写主题样式 109 | 110 | ::: tip 提示 111 | 如果你习惯把样式拆分得更细,当然没问题, `vuepress`并没有限制。你可以单独编写其它模块,最终引入到`index.styl`。 112 | ::: 113 | 114 | ### templates 115 | 116 | 顾名思义, `dev.html`是用于开发环境下的 webpack dev server 的模板文件, `ssr.html`用于最终生成静态页面的模板文件。介绍 117 | 118 | ### 客户端增强文件 119 | 120 | 自定义主题也可以通过主题根目录下的 enhanceApp.js 文件来对 VuePress 应用进行拓展配置。这个文件应当 export default 一个钩子函数,并接受一个包含了一些应用级别属性的对象作为参数。你可以使用这个钩子来安装一些附加的 Vue 插件、注册全局组件,或者增加额外的路由钩子等: 121 | 122 | ``` js 123 | export default ({ 124 | Vue, // VuePress 正在使用的 Vue 构造函数 125 | options, // 附加到根实例的一些选项 126 | router, // 当前应用的路由实例 127 | siteData // 站点元数据 128 | }) => { 129 | // ...做一些其他的应用级别的优化 130 | } 131 | ``` 132 | 133 | ### 最后 134 | 135 | 至此,我们对主题目录下的内容已经有了整体概念,主题女神面纱下的面庞已慢慢变得清晰。 -------------------------------------------------------------------------------- /blog/_post/theme-learning-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2021-02-23 3 | title: VuePress 主题介绍 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | ## 什么是VuePress主题 10 | 11 | 按官网的描述, VuePress 由两部分组成:第一部分是一个极简静态网站生成器,它包含由 Vue 驱动的主题系统和插件 API,另一个部分是为书写技术文档而优化的默认主题。 12 | 我们这里要说的VuePress主题指的是自定义主题, 和默认主题一样, 自定义主题也构建在主题系统之上。 13 | 14 | 当在配置项中如下指定自定义主题时: 15 | 16 | ``` 17 | // .vuepress/config.js 18 | module.exports = { 19 | theme: 'vuepress-theme-xx' // 已发布成npm包的主题 20 | theme: 'theme-index-path' // 本地主题 21 | } 22 | ``` 23 | 24 | 自定义主题会接管默认主题完成整个站点的页面渲染。 25 | 26 | ## 快速开始 27 | 28 | 1. 创建并进入一个新目录 29 | 30 | ``` sh 31 | mkdir vuepress-starter && cd vuepress-starter 32 | ``` 33 | 2. 使用你喜欢的包管理器进行初始化 34 | 35 | ``` sh 36 | yarn init # npm init 37 | ``` 38 | 39 | 3. 将 VuePress 安装为本地依赖 40 | 41 | ``` sh 42 | yarn add -D vuepress # npm install -D vuepress 43 | ``` 44 | 45 | 4. 创建你的第一篇文档 46 | 47 | ``` sh 48 | mkdir docs && echo '# Hello VuePress' > docs/README.md 49 | ``` 50 | 51 | 5. 在 package.json 中添加一些 scripts [可选] 52 | 53 | ``` 54 | { 55 | "scripts": { 56 | "docs:dev": "vuepress dev docs", 57 | "docs:build": "vuepress build docs" 58 | } 59 | } 60 | ``` 61 | 62 | 6. 创建一个layout.vue文件 63 | 64 | ``` 65 | . 66 | └─ .vuepress 67 | └─ theme 68 | └─ Layout.vue 69 | ``` 70 | 71 | ``` vue 72 | 77 | ``` 78 | 79 | 没错,一个最简单的主题可以仅由一个layout文件构成。layout是一个vue组件,该文件会告诉主题中所有的元素如何去布局展示。 80 | 81 | 7. 在本地启动服务器 82 | 83 | ``` sh 84 | yarn docs:dev # npm run docs:dev 85 | ``` 86 | 87 | VuePress 会在 [http://localhost:8080](http://localhost:8080)启动一个热重载的开发服务器。 88 | 89 | :tada: :100: 到此, 我们已经有了一个完整的可运行的VuePress应用,并且包含了一个最简单的主题。剩下的只需要不断的添加和扩展我们的主题功能。 -------------------------------------------------------------------------------- /blog/_post/theme-learning-subnav.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2021-03-11 3 | title: VuePress主题教程 --SubNav模板编写 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | > SideBar是一个复合模板, `FooterBar`,`NavBar`, `SubNav`均包含在里面, 我们已经完成了`NavBar`, 剩下的`SubNav`是对站点的分类、标签及文章的简易统计和快速入口,`FooterBar`是用来放一些社交媒体链接及版权信息。 10 | 11 | 12 | 13 | ## SubNav 14 | 15 | 我们先来编写`SubNav`模板,想下可以从哪里获取这些统计数据,没错,还是`blog`插件君. 16 | 17 | 在他的加持下,只需简单的三步便可完成我们想要的功能。 18 | 19 | 1. 添加插件配置: 20 | 21 | ``` js {20-41} 22 | // .vuepress/theme/index.js 23 | module.exports = (themeConfig, ctx) => { 24 | return { 25 | // ... 26 | plugins: [ 27 | [ 28 | '@vuepress/blog', 29 | { 30 | directories: [ 31 | { 32 | id: 'post', 33 | dirname: '_post', 34 | path: '/' 35 | } 36 | ], 37 | globalPagination: { 38 | lengthPerPage: 5, 39 | }, 40 | frontmatters: [ 41 | { 42 | id: "tag", 43 | keys: ['tag', 'tags'], 44 | path: '/tags/', 45 | frontmatter: { title: 'Tag' }, 46 | pagination: { 47 | lengthPerPage: 10 48 | } 49 | }, 50 | { 51 | id: "category", 52 | keys: ['category', 'categories'], 53 | path: '/categories/', 54 | frontmatter: { title: 'Category' }, 55 | pagination: { 56 | lengthPerPage: 10, 57 | prevText: '', 58 | nextText: '' 59 | } 60 | } 61 | ] 62 | } 63 | ] 64 | ] 65 | } 66 | } 67 | ``` 68 | 69 | 2. 给文章添加frontmatter字段: 70 | 71 | ``` md {4-8} 72 | --- 73 | date: 2021-03-11 74 | title: two 75 | category: theme 76 | tags: 77 | - course 78 | - vuepress 79 | --- 80 | ## two 81 | ``` 82 | 83 | ``` md {3-6} 84 | --- 85 | date: 2021-03-03 86 | categories: 87 | - theme 88 | tags: 89 | - vuepress 90 | --- 91 | ## five 92 | ``` 93 | 94 | `tags`和`categories`与`blog`插件配置中相对应。 95 | 96 | 3. 编写.vue文件 97 | 98 | ``` vue 99 | // docs/.vuepress/theme/components/SubNav.vue 100 | 107 | 108 | 120 | 121 | 124 | ``` 125 | 126 | `$category`、`$tag`均为`blog`插件暴露到全局的计算属性,通过它俩我们很容易就得到了分类和标签的统计;文章总数则是通过默认的全局计算属性`$site`拿到全部的文档,然后再过滤id为post的文章进行计数。 127 | 128 | 运行结果如下: 129 | 130 | ![截图](https://80shuo.com/images/learning/subnav.png) 131 | 132 | ## 最后 133 | 134 | -------------------------------------------------------------------------------- /blog/_post/theme-learning-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2021-02-25 3 | title: VuePress 模板文件和布局 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | > 在 `VuePress` 中 `Markdown` 文件是元数据(页面内容、配置等)的提供者,而布局组件负责消费他们。VuePress中的模板文件指的就是普通的`Vue组件`,它可以配合布局组件共同处理这些元数据,渲染出我们的主题页面。从本篇开始,假设你已具备Vue应用的开发基础, 如果没有接触过Vue开发请先移步[Vue官网](https://cn.vuejs.org/v2/guide/)。 10 | 11 | 12 | 13 | ## 开始之前 14 | 15 | 为了方便演示,我们在先前基础上添加以下一些内容: 16 | 17 | 1. 给主题添加一个入口文件 18 | 19 | ``` js 20 | // .vuepress/theme/index.js 21 | module.exports = (themeConfig, ctx) => { 22 | return { 23 | // ... 24 | } 25 | } 26 | ``` 27 | 2. 给文档添加配置文件 28 | 29 | ``` js 30 | module.exports = { 31 | title: 'hello vuepress', 32 | description: '我的第一个vupress站点', 33 | port: 8088, 34 | theme: require('./theme/index'), 35 | themeConfig: { 36 | copyright: `© ${new Date().getFullYear()} ❤️ Neil Chen` 37 | } 38 | } 39 | ``` 40 | 41 | 添加的配置是为了给主题里的模板文件和布局组件提供数据源,经过VuePress内部处理,会映射为若干`全局的计算属性`[^first] 42 | 43 | ## 以首页为例 44 | 45 | ![layout](https://80shuo.com/images/learning/layout.svg) 46 | 47 | 上图是模板文件和布局的示意图, 可以看出首页由3个模板文件`SideBar.vue`、`FooterBar.vue`、 `Home.vue` 和默认的 `Layout.vue` 布局组件 共同构成。 48 | 49 | ### SideBar组件 50 | 51 | ``` vue 52 | 58 | 66 | ``` 67 | ### Home组件 68 | 69 | ``` vue 70 | 73 | ``` 74 | ### FooterBar组件 75 | 76 | ``` vue 77 | 82 | ``` 83 | 84 | ### Layout组件 85 | 86 | ``` vue 87 | 93 | 103 | ``` 104 | 105 | ## 最终渲染出来的首页HTML 106 | 107 | ``` html 108 |
109 |
110 | SideBar hello vuepress 111 |
FooterBar 112 |
113 |
114 |
Home
115 |
116 | ``` 117 | 118 | ## 最后 119 | 120 | 到此,一个首页的框架结构就完成了,其它页面也是一样的原理。我们发现编写VuePress主题的时候,和开发Vue应用是非常相似的,因为本质上vuepress就是一个vue的同构应用。VuePress给了我们很大的自由,在多数情景下可以掌控到DOM元素的颗粒度,让我们在写主题样式时可以大刀阔斧。 121 | 122 | [^first]: **全局的计算属性** 123 | 124 | 在 VuePress 中,内置了一些核心的计算属性,以供默认主题 或自定义主题使用 125 | 126 | 127 | -------------------------------------------------------------------------------- /blog/_post/theme-learning.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2020-01-26 3 | title: 从零开始制作VuePress主题 4 | category: theme 5 | cover: /images/screenshot.webp 6 | tags: 7 | - VuePress 8 | --- 9 | ## 缘起 10 | 11 | 一直以来我都想自己搭建一个博客作为自己总结归纳学习历程的一个平台,在一个周末,我开始试着搭建自己的博客,刚开始不熟悉,过程也是有点曲折,不断的看文档,所幸最终坚持下来了。 12 | 我自己是一名前端开发,技术栈是Vue.js.现在比较流行静态博客,不需要运行后台,恰好看到Vue官方推出过VuePress。 13 | 不过VuePress自带的主题较为简单,生态的主题相对比较少,没找到即好看又适合自己的,所幸看到了Neil Chen的开源主题Maker,于是我拉下源码在此主题的基础上,结合着主题文档开始了自己的创作。 14 | 15 | ## VuePress介绍 16 | 17 | 你现在看到的博客站点就是基于的这个主题。用的github pages部署,这是github提供的一项免费服务,我们可以在上边放些静态HTML页面组成一个站点。VuePress就是方便我们维护和生成这个静态站点的工具,类似的还有`Hugo`、`hexo`、`jekyll`等等 闻如其名,VuePress是由Vue.js驱动的,而且出自Vue官方团队之手。它的诞生初衷便是为了支持 Vue 及其子项目的文档需求,拥有完善的Markdown支持,并自带了一个专门为技术文档而优化的`默认主题`. 18 | 19 | ## VuePress背后的工作机制 20 | 事实上,一个开发阶段的VuePress站点是一个由Vue、Vue Router、Webpack共同驱动的单页应用,如果你熟悉用Vue-cli脚手架开发web前端应用,你会觉得这并无二样,甚至你仍然可以用Vue DevTools去调试你的自定义VuePress主题.在主题开发过程中这会给我们带来极大的方便--比如`计算属性`和`routes`。当构建发布站点时,VuePress会创建一个服务端渲染(ssr)版本,类似Nuxt,生成所有对应的HTML页面。 21 | 22 | ## 创建VuePress主题所需的工具和准备工作 23 | 24 | * [Node.js](https://nodejs.org/en/)>= 8.6 25 | * [Github Pages](https://pages.github.com/) 其它部署平台可参考 [官方部署文档](https://vuepress.vuejs.org/zh/guide/deploy.html) 26 | * 顺手的文本编辑器. e.g [VSCode](https://code.visualstudio.com/) 27 | 28 | ## 主题源码下载 29 | - 完整的主题源码都放在GitHub上了,可以随时Clone下来做为参照. 30 | 31 | - [`VuePress-theme-maker`](https://github.com/80maker) 32 | 33 | --- 34 | 35 | ## 开启VuePress主题开发之旅 36 | 37 | --- 38 | 39 | * :bear: [**常用术语**](/post/2021/01/01/theme-learning-concept.html) 40 | * :sheep: **主题目录结构** 41 | * :elephant: **模板文件和布局** 42 | * :koala: **主题继承和插件** 43 | * :hamster: **Header模板** 44 | * :eagle: **内置搜索** 45 | * :frog: **NavBar导航模板** 46 | * :whale: **SideBar模板** 47 | * :shark: **Archive文章归档页** 48 | * :cow2: **Category分类页** 49 | * :dragon_face: **Tag标签页** 50 | * :unicorn: **Post文章详情页** 51 | * :turtle: **Toc文章目录组件** 52 | * :snail: **FriendLink友情链接页** 53 | * :octopus: **主题插件开发--悬浮球菜单** 54 | * :tropical_fish: **响应式主题** 55 | * :bat: **主题暗黑模式** 56 | * :dolphin: **主题优化篇** -------------------------------------------------------------------------------- /blog/_post/timeIsOutOfOrder.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2021-06-12 3 | title: md中文档开头一定要写Frontmatter,否则会导致Archieves模块时间排序错乱 4 | category: theme 5 | tags: 6 | - VuePress 7 | --- 8 | 9 | > 创建新的md文档时文档头一定要定义Frontmatter,要么就删除md文档;不能创建了md文档不写Frontmatter 10 | 11 | ## 背景 12 | 13 | 我创建了一个新的test.md文档占坑时,准备以后补充内容,文档中什么都没写。 14 | 15 | 当我blog:dev启动项目时,点击Archieves模块查看自己的文档时,发现刚刚的test.md不见了,更离谱的是月份位置也错乱了。 16 | 17 | ## 原因 18 | 19 | 于是我开始排查原因,在Archieves组件中我发现以下代码: 20 | 21 | ```vue 22 | computed: { 23 | archiveList() { 24 | let res = {}; 25 | let tmp = []; 26 | let list = this.$site.pages.filter(item => { 27 | // return item.pid === 'post' || item.pid === 'studyprogresshtmlcss'; 28 | return ~pidList.indexOf(item.pid) 29 | }); 30 | list = list.sort((a,b) => { 31 | let time1 = new Date(a.frontmatter.date); 32 | let time2 = new Date(b.frontmatter.date); 33 | return time2 - time1; 34 | }) 35 | list.map(item => { 36 | const date = new Date(item.frontmatter.date) 37 | const year = date.getFullYear(); 38 | const month = date.getMonth(); 39 | const monthKey = DATE_MAP[month] 40 | const day = `${date.getDate()}`; 41 | res[year] || (res[year] = {}); 42 | res[year][monthKey] || (res[year][monthKey] = []); 43 | item.date = `${`${month + 1}`.padStart(2, 0)}-${day.padStart(2, 0)}`; 44 | res[year][monthKey].push(item); 45 | }) 46 | for (let [key, item] of Object.entries(res)) { 47 | tmp.push({ 48 | year: +key, 49 | list: item 50 | }); 51 | } 52 | tmp.sort((a, b) => { 53 | return b.year - a.year; 54 | }) 55 | return tmp; 56 | } 57 | } 58 | ``` 59 | 60 | 可以看到,使用过list.frontmatter.date进行比较的,如果不给md文档写frontmatter,那取值就是undefined。 61 | 62 | undefined和另外一个时间戳做减法,返回值自然就会导致错乱了。 63 | -------------------------------------------------------------------------------- /blog/interview/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-04-14 3 | title: 面试总结 4 | cover: /images/interview.jpg 5 | category: Interview 6 | --- 7 | 8 | ## 面试遇到有价值的问题总结,并带着自己的一些思考记录下来 9 | 10 | ### JS原型和原型链 11 | 12 | ```js 13 | // 创建构造函数 14 | function Person(name, age) { 15 | this.name = name; 16 | this.age = age; 17 | } 18 | 19 | Person.prototype.say = function (word) { 20 | console.log(`${this.name}说:${word}`); 21 | } 22 | 23 | // 生成实例 24 | const p = new Person('zhangsan', 18); 25 | 26 | console(p.name, p.age) //log-----> 'zhangsan', 18 27 | p.hasOwnProperty('say') // false 说明不是定义在其本身上的 28 | p.say('hello world'); // 调用公共方法 打印:张三说:hello world 29 | ``` 30 | 31 | 可以看到new一个构造函数得到一个实例p,p调用say方法,p本身没有,于是顺着原型链__proto__向上找到构造函数的原型prototype,得以调用这个say方法 32 | 33 | 所有构造函数的原型链则是又指向了function Object,再往上查找原型链就是null了 34 | 35 | 具体看链接[JS原型和原型链详解](https://codelove9.github.io/myBlog/studyprogress/js/2023/04/14/prototype.html) 36 | 37 | 我个人理解是像const arr = [1, 2, 3]或者是const obj = { name: 'chen', age: 24 }也是同理,它们只是new了构造函数Array、Object的一个语法糖 38 | 39 | 所以mdn上的那些方法本质上都是构造函数原型上的方法,如下面代码 40 | 41 | ```js 42 | // 底层做了arr = new Array(1, 2, 3)的操作 43 | const arr = [1, 2, 3] 44 | // arr这个实例本身没有forEach方法,于是向上原型链查找构造函数Array上的方法,找到了forEach这个方法,代码得以成功执行 45 | arr.forEach(item => console.log(item)) 46 | ``` 47 | 48 | 有一点不明白的是为什么String.prototype得到的数据类型是字符串,Array.prototype得到的数据类型是数组,Object.prototype得到的数据类型是对象,待弄明白后补充 49 | 50 | ### 项目中遇到的难题 51 | 52 | 场景: 53 | 54 | 1. 宿主组件在created中触发了bus.emit(updateInformation)事件,依赖项目在mounted生命周期中用bus.on(updateInformation)接收了该事件并修改了data响应式值。可在依赖项目中console.log时,data值并没有被改变。 55 | 56 | 2. 通过打印双方的生命周期执行顺序时,发现并没有按照父子组件的执行顺序父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted->父beforeUpdate->子beforeUpdate->子updated->父updated->父beforeDestroy->子beforeDestroy->子destroyed->父destroyed这个顺序执行,而是父组件比在子组件created生命周期触发前,就执行完了mounted。 57 | 58 | 原因: 59 | 60 | 1. 依赖项目是一个低代码的项目,是通过父组件用插槽slot的形式异步导入,这就导致当父组件执行完了created和mounted生命周期,子组件才开始执行created生命周期。 61 | 62 | 2. 而eventbus事件总线的执行顺序是先执行on,才能触发emit事件,而子组件on的执行时机总是在父子间emit之后,这就导致了挂载失败,自然就无法触发事件监听机制了。 63 | 64 | 解决方案: 65 | 66 | 1. 在父组件bus.js文件中的created生命周期中就bus.on挂载事件总线,这就能使事件总线机制成功执行,并把接收到参数更新到bus的data响应式数据中,直接存在bus中,相当于不仅仅把bus当成事件监听传值的作用,而是看成一个组件去使用。 67 | 68 | 2. 在子组件中导入bus,直接去取bus上的data响应式数据,可以成功获取,然后再更新到子组件的data中,问题解决。 69 | -------------------------------------------------------------------------------- /blog/personalInformation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-03-25 3 | title: It is always a pleasure to greet a friend from afar ~ 4 | cover: /images/friend.jpg 5 | category: MyInformation 6 | --- 7 | 8 | ## This is XiaoChen's blog 9 | 10 | ### Introduce 11 | 12 | 这是我用来**记录学习过程**的博客,欢迎大家观看:blush: 13 | 14 | ### My information 15 | 16 | name: XiaoChen 17 | age: 24 18 | gender: male 19 | hometown: HuBei 20 | profession: Web Front-end development programmer 21 | ability: **Vue2, Vue3 ,Vuex, Pinia, Vue-router, React, Redux,. React-router** 22 | -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/Nth-child.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-03-26 3 | title: 使用nth-child()时发现的bug 4 | category: frontend 5 | tags: 6 | - HTML/CSS 7 | --- 8 | 9 | ## 项目场景: 10 | 使用li:nth-child(4n)选择第4个第8个li盒子清除右侧外边距 11 | 12 | *** 13 | 14 | ## 问题描述 15 | 16 | 当我在box盒子中左边创建了一个left盒子,右边创建了多个li盒子时,想用nth-child(4)选择第四个li盒子时,发现选择的是第三个li盒子。 17 | 18 | * * * 19 | 20 | ## 原因分析: 21 | 22 | > 我发现当右边右很多li盒子,左边有个盒子的情况时,尽管是用li:nth-child(4n)选择所有li盒子中的4的倍数盒子,也会出现选中错误的bug,网页会把前面的left盒子也算进li的里面来计算。 23 | 24 | * * * 25 | 26 | ## 解决方案: 27 | 28 | > 所有li盒子外面需要包一个大盒子right,在right盒子中选li盒子,网页就能正确选中了。 29 | > 30 | > ```css 31 | >.box .right li:nth-child(4n) { 32 | > margin-left=0 33 | >} 34 | > ``` 35 | -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/bodyBgiCover.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-03-26 3 | title: body设置背景图却不铺满浏览器的解决方法 4 | category: frontend 5 | tags: 6 | - HTML/CSS 7 | --- 8 | 9 | 10 | 11 | ## 项目场景: 12 | 13 | 当我用background-image选择一张与浏览器宽高不匹配的背景图时,需要铺满整个浏览器。 14 | 15 | ```css 16 | body { 17 | background-image: url(../../全民出游季/images/f1_1.jpg) ; 18 | background-repeat: no-repeat ; 19 | /* 缩放背景图 */ 20 | background-size: cover; 21 | background-position: center 0; 22 | } 23 | ``` 24 | 25 | * * * 26 | 27 | ## 问题描述 28 | 29 | 当我使用了background-repeat代码串选择不重复图片,再用background-size: cover代码串让图片填充覆盖整个浏览器,运行后发现并没有相应的效果,并且body的高度始终为0。 30 | 31 | * * * 32 | 33 | # 原因分析: 34 | 35 | > 我知道一个事实:一个块级元素没有主动为其设置宽度和高度,浏览器会为其分配可使用的最大宽度(比如全屏宽度),但是不负责分配高度,块级元素的高度是由子元素堆砌撑起来的。那么,html和body标签的高度也都是由子级元素堆砌撑起来的。 36 | > 还有,元素高度百分比需要向上遍历父标签要找到一个定值高度才能起作用,如果中途有个height为auto或是没有设置height属性,则高度百分比不起作用,此时的情况是父元素高度依赖子元素堆砌撑高,而子元素依赖父元素的定高起作用,互相依赖,却都无法依赖,死循环了。 37 | 38 | * * * 39 | 40 | # 解决方案: 41 | 42 | > 可以让子元素先定高,这样是可以解决;但是如果子元素一定要依赖父元素高度呢? 43 | 44 | > ``` 45 | >html { 46 | > height: 100%; 47 | >} 48 | > 49 | >body { 50 | > height: 100%; 51 | > background-image: url(../../全民出游季/images/f1_1.jpg) ; 52 | > background-repeat: no-repeat ; 53 | > /* 缩放背景图 */ 54 | > background-size: cover; 55 | > background-position: center 0; 56 | >} 57 | > ``` 58 | > 59 | > 上面的html代码可以看出,body的父元素是html,通过height:100%层层向上,找到顶级获取定高。 60 | > 61 | > 所以出现了html和body同时设置height:100%,那html的上级是谁呢? 62 | > 63 | > 通过上面的事实知道,浏览器负责分配块级元素宽度,那么浏览器也一定可以分配高度(只是没有做),那么浏览器本身是有宽度和高度的,设置html的height:100%,就可以获取浏览器的定高了,后面的body和其他块级元素也就有了依赖。 -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/cssWeight.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS写了后控制台查看元素样式中存在,但是被删除线删掉了不生效(涉及css权重) 3 | date: 2023-09-23 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - HTML/CSS 8 | --- 9 | 10 | ## 背景 11 | 12 | 今天在写项目时,需求涉及到改一个元素的样式,可当我改了后,没有生效。当我打开控制台时,看见新写的样式已经在元素里了,但是上面有删除线,导致不生效。 13 | 14 | ## 原因 15 | 16 | 经过排查发现,这个样式由于是在封装的ui组件中,外层不尽有多个css选择器,本身还有!important,导致它的权重非常高。而我写的样式虽然后面也加了!important,但是没有外层的选择器,导致权重没有ui组件中的高,因此没有生效。 17 | 18 | ## 解决方案 19 | 20 | 在自己写的样式选择器外多套几层,权重比ui组件中的高后,样式生效,代码如下: 21 | 22 | ```css 23 | // 原来不生效的css代码 24 | .a { 25 | color: xxx!important 26 | background: xxxx!important 27 | } 28 | 29 | // 优化后的代码 30 | // a选择器样式外面加了两层后样式生效 31 | .c { 32 | .b { 33 | .a { 34 | color: xxx!important 35 | background: xxxx!important 36 | } 37 | } 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/fixedPositionCover.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS解决固定定位无法覆盖的层叠问题 3 | date: 2023-03-30 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - HTML/CSS 8 | --- 9 | 10 | ## 元素重叠(层级问题) 11 | 12 | 使用了定位,元素会提升一个层级(如果与别的元素发生重叠,会在别的元素上面)如果多个元素同时开启了定位.层级都一样的情况下.如果发生重叠.则后面的元素会盖住前面的元素。如果想调整层级 可以**使用z-index:来调整层级,需要注意的是,没有开启定位的元素不可以使用[z-index属性](https://so.csdn.net/so/search?q=z-index%E5%B1%9E%E6%80%A7&spm=1001.2101.3001.7020)** 13 | 14 | 所以需要给固定定位的盒子添加z-index和深色背景颜色。 15 | 16 | ```css 17 | header { 18 | position: fixed; 19 | left: 0; 20 | top: 0; 21 | width: 100%; 22 | height: (84 / @vw); 23 | background-color: #fff; 24 | z-index: 1; 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/heightSubsidence.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 高度塌陷:父级没有设置高度且子级含有浮动属性 3 | date: 2023-03-27 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - HTML/CSS 8 | --- 9 | 10 | 在开发的过程中,我们往往会使父级的高度自适应(没有固定高度,高度由子级撑开),但是有时需要子级浮动(float),这时的子级会脱离文档流,子级不会再把父级的高度撑开了。这就是高度坍塌形成的原因。 11 | 12 | 结果如下: 13 | ![](https://img-blog.csdnimg.cn/f94a22a2756d45ae887aa72c7b3a8bd7.png) 14 | 15 | 解决方法: 16 | 17 | 1.方法:在父级上添加高度 18 | 19 | 优点:好理解 20 | 21 | 缺点:固定的布局,不能做到自适应 22 | 23 | 2.给父级添加` overflow:hidden` 24 | 25 | 原理:触发了**BFC** 26 | 27 | (块级格式化上下文)--  将浮动元素的高度参与计算 28 | 29 | 优点:简单,代码少 30 | 31 | 缺点:子元素含有定位属性,那么子级元素超出的部分会被隐藏 32 | 33 | 3.在最后一个浮动元素同级添加一个div,给div添加css声明 34 | 35 | `div{ clear:both;}  clear:left / right / both ` 36 | 37 | 原理:表示清除上方预留出来的空间 38 | 39 | 优点:清除方便 40 | 41 | 缺点:代码多,HTML结构,代码冗余,造成排版影响 42 | 43 | 4.万能清除法 44 | 45 | 原理:表示清除上方预留出来的空间 46 | 47 | 方法:a.给含有高度塌陷的父级盒子添加类名**clear-fix** 48 | ``` 49 |   .clear-fix:after{ 50 | 51 |          content:"  "; 52 | 53 |          clear:both; 54 | 55 |          height:0; 56 | 57 |          width:100%; 58 | 59 |          overflow:hidden; 60 | 61 |          display:block; 62 | 63 |          visibility: hidden; 64 | 65 | } 66 | 67 | .clear-fix{zoom:1;/\*兼容IE\*/} 68 | ``` -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/iconfontShowBug.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: iconfont通过font class引入显示小方块bug 3 | date: 2023-04-09 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - HTML/CSS 8 | --- 9 | 10 | 官网显示[iconfont](https://so.csdn.net/so/search?q=iconfont&spm=1001.2101.3001.7020)通过font class引入有两种方式: 11 | 12 | 1.直接查看链接用link引入cdn链接 `//at.alicdn.com/t/c/font\_3680524\_qjgj55gtmj.css`,这个方法没有问题, 注意前面要加http:变成`http://at.alicdn.com/t/c/font\_3680524\_qjgj55gtmj.css`再引入 13 | 14 | 如图:![img](https://img-blog.csdnimg.cn/fa1fe763a8a64f0297e0d2f5b7ae2330.png) 15 | 16 | 2.通过下载至本地引入css文件,可是当我引入文件时,图标却显示不出来,显示的是一个小方块,使我备受困惑。 17 | 18 | 经过我不断地排错后发现,当iconfont下载完后是一个文件夹,里面有html、css、js、json等很多文件,如果只把css文件放入到项目中,就会显示错误,应该放至少除了css文件还有别的文件,这样就不会显示错误了。 19 | 20 | iconfont.css中有这样一个代码段: 21 | 22 | ```css 23 | @font-face { 24 | font-family: "iconfont"; 25 | /* Project id 3680524 */ 26 | src: url('iconfont.woff2?t=1664438813020') format('woff2'), 27 | url('iconfont.woff?t=1664438813020') format('woff'), 28 | url('iconfont.ttf?t=1664438813020') format('truetype'); 29 | } 30 | ``` 31 | 32 | 由此代码段可见,css文件中也有两个woff和[ttf](https://so.csdn.net/so/search?q=ttf&spm=1001.2101.3001.7020)文件的引入,所以至少要打包四个文件到项目中,就不会报错了!如图所示四个文件: 33 | 34 | ![img](https://img-blog.csdnimg.cn/dea7bf35190d4b22b5d605d91e580f72.png) 35 | -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/image403.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GET请求图片出现403 防盗链解决方式 3 | date: 2023-03-27 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - HTML/CSS 8 | --- 9 | 10 | http请求头中有一个referrer字段,用来表示发起http请求的源地址信息 11 | 12 | 服务器端在拿到这个referrer值后判断请求是否来自本站 13 | 14 | 若不是则返回403,从而实现图片的防盗链。上面出现403就是因为,请求的是别人服务器上的资源,但把自己的referrer信息带过去了,被对方服务器拦截返回了403 15 | 16 | ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/54a48c7dd8da48e3a9269b98d214f17b~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?) 17 | 18 | 解决方法: 19 | 20 | 在index.html中的head中添加: 21 | 22 | ``` 23 | 24 | 25 | ``` 26 | 27 | 在前端可以通过meta来设置referrer policy(来源策略),referrer设置成`no-referrer`,发送请求不会带上referrer信息,对方服务器也就无法拦截了 。 -------------------------------------------------------------------------------- /blog/programdifficulty/htmlcss/spriteImageUseNegativeValue.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-03-26 3 | title: CSS精灵图坐标取负值原因 4 | category: frontend 5 | tags: 6 | - HTML/CSS 7 | --- 8 | 9 | * 精灵图主要针对小的背景图片使用 10 | * 精灵图的显示主要借助于背景位置来实现——background-position 11 | * 网页中的坐标:x轴右边是正值,左边是负值,y轴下边是正值,上边是负值 12 | * 一般情况下精灵图都是负值,因为盒子是固定的,固定在精灵图的左上角顶点处,想要取精灵图除了第一个图形,只能把精灵图往左上角移动,对应坐标就是 -(具体移动像素值)px,-(具体移动像素值)px -------------------------------------------------------------------------------- /blog/programdifficulty/js/LogicWithMisunderstanding.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-10-31 3 | title: 逻辑与和三元表达式一起用的错误代码运行理解 4 | category: frontend 5 | tags: 6 | - JS 7 | --- 8 | 9 | ## 背景 10 | 11 | 今天在写项目时,逻辑代码涉及到一个逻辑与和三元表达式一起用的一行代码,当组长说这么改时,我一直理解不了。 12 | 13 | 下面是示范代码,为了简化把变量都简化一下: 14 | 15 | ```js 16 | 0 && 1? true: false 17 | ``` 18 | 19 | 我一直错误地认为最好的输出结果会是0,但是组长一直说是false,使我百思不得其解。 20 | 21 | ## 探究 22 | 23 | 我通过F12打开浏览器控制台,准备验证一下,输入了下面一行代码: 24 | 25 | ```js 26 | 0 && 1? true: false 27 | ``` 28 | 29 | 控制台打印结果为false,我看了一会于是立马悟到,我之前理解的代码执行顺序一直是错误的,**把`1? true: false`看出了一个整体**,认为js会先计算出这个三元表达式的值拿到false,再和`0 && false`这个短路运算符做计算。 30 | 31 | 其实**正确的执行顺序是把`0 && 1`当作一个整体**,直接执行为false,得到结果false。 32 | 33 | ## 总结 34 | 35 | 当逻辑与和三元表达式一起用的时候,会直接计算前面的逻辑关系,得到最后三元的结果值。 36 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/arrowFunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-04-18 3 | title: ES6箭头函数后面加不加花括号{}问题 4 | category: frontend 5 | tags: 6 | - JS 7 | --- 8 | 9 | ### 前言 10 | 11 | 本人在做项目的时候,使用到了every方法并使用[箭头函数](https://so.csdn.net/so/search?q=%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0&spm=1001.2101.3001.7020),我在箭头函数中加了花括号但是却无法返回正确的结果,经过查阅才知道,箭头函数后加花括号和不加是有区别的。 12 | 13 | 一、箭头函数后加花括号 14 | 如下所示,array是一个数组,用every方法看数组中的每一项是否满足所对应的条件,如果都满足的话,则会返回true,不满足返回false。根据every方法返回的结果再进行判断。我刚开始写的错误代码如下: 15 | 16 | ```js 17 | this.cartInfoList.every(item => { 18 | item.isChecked == 1 19 | }) 20 | ``` 21 | 22 | 结果发现当array数组中的所有数字都是0的时候还是会返回1,其原因在于当箭头函数加上花括号的时候是需要加上return的。否则返回的就是就是空。因此加上花括号正确的代码如下: 23 | 24 | ```js 25 | this.cartInfoList.every(item => { 26 | return item.isChecked == 1 27 | }) 28 | ``` 29 | 30 | 二、箭头函数不加花括号 31 | 此时是默认有return的,因此以上代码直接这样写: 32 | 33 | ```js 34 | this.cartInfoList.every(item => item.isChecked == 1) 35 | ``` 36 | 37 | 总结 38 | 记住一句话就可以了:箭头函数加上花括号需要写return,不加花括号不需要写,默认就有return。 39 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/awaitBlocking.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: await阻塞后面的代码(微任务) 3 | date: 2023-05-14 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | ## 背景 11 | 12 | 今天在公司开发时遇到一个有趣的现象,一下是源代码 13 | 14 | ```js 15 | const { groupNo } = await ABSplitResult(); 16 | groupNo = 1? .........: ........; 17 | do something..... 18 | ``` 19 | 20 | ABSplitResult这个钩子是从服务组件里引得,用的是app原生方法,只能在app中才跑的通 21 | 22 | 我在h5环境里测试时,await后面的groupNo那些代码竟然不执行了,当我把await删掉的时候,奇迹发生了,又出来了!! 23 | 24 | ## 探究原因 25 | 26 | 当时我就想到了可能跟js的机制 event loop有关系。之前学习的时候看到文档说await后面代码会被压入到微任务中,等await执行后再拿出来执行 27 | 28 | 然后我就百度了一下,以下是答案: 29 | 30 | 解释器看到该函数被声明为异步,这意味着它将始终返回一个promise。 31 | 32 | **当遇到await关键字时,它会暂停该函数的进一步执行,直到正在等待的promise被解决 才会去向下执行。** 33 | 34 | await的阻塞方式并不是阻塞同步代码所在的主线程,await其实是阻塞的当前异步函数的异步线程。 35 | 虽然await会阻塞async异步函数,但是并没有阻塞同步代码的主线程,同步和异步之间仍然相互不阻塞。 36 | 虽然await阻塞异步函数向后执行,看起来像是同步的,但是它本质还是异步的,我们同样可以并行执行其他的不关联的异步操作,而同步函数不能并行执行。 37 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/focusAndonfocusDifference.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: focus和onfocus区别 3 | date: 2023-04-02 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | focus() 是方法,表示是去触发使其获得焦点。 11 | 12 | [onfocus](https://so.csdn.net/so/search?q=onfocus&spm=1001.2101.3001.7020) 是事件属性, 表示获得焦点时去执行什么。 13 | 14 | 同理,如 click 和 onclick 等区别也一样。 15 | 16 | 案例:当键盘按下ASCII码83(对应键盘s),鼠标光标会定到输入框内。 17 | 18 | ```js 19 | 20 | 21 | 30 | 31 | ``` 32 | 33 | 另外,如果监听函数中的监听方法是 'keydown',那么按下s的时候,鼠标光标在定位到文本框的同时,也会输入s,因为按下了s光标定到输入框 和 输入s是同时进行的。 34 | 35 | 如果把监听方法是 '[keyup](https://so.csdn.net/so/search?q=keyup&spm=1001.2101.3001.7020)',表示弹起的时候再定光标,就不会再有这种情况。 36 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/localstrorage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 关于localStorage为什么要存储json数据 3 | date: 2023-04-30 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | 11 | 今天做项目的时候疑惑,为什么localStorage存储数据还要转换json数据 12 | 13 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/76edf9deb4a343b1a762b18c7d784973.png) 14 | 15 | 首先解释一下,**`JSON.stringify()`** 方法将一个 JavaScript 对象或值转换为 JSON 字符串,那么,为什么要将JavaScript 对象或值转换为 JSON 字符串呢?是因为localStorage.[setItem](https://so.csdn.net/so/search?q=setItem&spm=1001.2101.3001.7020)()只能存储字符串数据,而它不会自动将JavaScript 对象或值转成字符串形式。我们可以来试一下,去掉JSON.stringify() 16 | 17 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/d71fd82111794852b3e091252849e9dd.png) 18 | 19 | 然后我们查看一下浏览器, 20 | 21 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/4b1975b0a8824f0191205a1af26111bb.png) 22 | 23 |  啥也不是,,,,我们继续 24 | 所以我们要把他转换成字符串再存进去,把JSON.stringify()它加上看看效果。 25 | 26 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/90caeef38c594fab9ee4fce5c38e1157.png) 27 | 28 |  这样我们就可以拿到json数据,等到用的时候再用JSON.parse()方法把他转为对象就可以了。 29 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/objectCantEqualObject.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS中对象不能等于对象(大坑) 3 | date: 2023-04-23 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | > 在js中,object(对象),是一个复杂数据类型,两者比较的是存储地址,所以一个对象怎么都不可能等于另外一个对象,应该比较对象的key 11 | 12 | 13 | 14 | ## 场景 15 | 16 | 用Vue写面包屑功能的时候,传入一个对象给vuex的mutations,然后枚举state里的一个数组,数组里是一个对象, 让数组里的对象等于传入的对象后实现对应功能,却一直实现不了。 17 | 18 | ![img](https://img-blog.csdnimg.cn/c7b1b20c846045f6bdda0b4afc6d4d02.png) 19 | 20 | ![img](https://img-blog.csdnimg.cn/0abe620cf3504676a4edeeb48e578c30.png) 21 | 22 | ![img](https://img-blog.csdnimg.cn/d134750fc45a498894b364acc5a46e37.jpeg) 23 | 24 | 我当时认为,如果两个对象有相同的属性,以及它们的属性有相同的值,那么这两个对象就相等。 25 | 26 | 后来一步步实践发现是对象不能等于对象,无论使用"=="还是"===",都返回false。 27 | 28 | ![img](https://img-blog.csdnimg.cn/6bce882a7dee41bf93ff123326135fb9.png) 29 | 30 | 主要原因是基本类型string,number通过值来比较,而对象(Date,Array)及普通对象通过指针指向的内存中的地址来做比较。 31 | 32 | ## 解决方案 33 | 34 | 所以如果判断两个对象的值是否相等,就得看对象属性对应的值是否相同。 35 | 36 | ![img](https://img-blog.csdnimg.cn/92c77e09a31b49eabbcdcbb1bf49a4f2.png) 37 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/offsetAndStyleDifference.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS中offset与style区别 3 | date: 2023-04-02 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | |\\|offset|style| 11 | |:---:|:----:|:----:| 12 | |限制| offset可以得到任意[样式表](https://so.csdn.net/so/search?q=%E6%A0%B7%E5%BC%8F%E8%A1%A8&spm=1001.2101.3001.7020)中样式值|style只能得到行内样式表中的样式值| 13 | |单位| offset系列获得的数值是没有单位的|style.width获得的是带有单位的字符串| 14 | |值| [offsetWidth](https://so.csdn.net/so/search?q=offsetWidth&spm=1001.2101.3001.7020)包含padding+border+width|style获得不包含padding和border的值| 15 | |读写性| offset Width等属性是只读属性,只能获取不能复制|style.width是可读属性,可以获取也可以赋值| 16 | 17 | 总结:想要获取元素大小位置,用offset更合适;想要给元素更改值,则需要用style改变 18 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/printStar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS中打印星星或是99乘法表定义str为数值或者字符串的原因 3 | date: 2023-03-31 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | 因为在内循环i <= n中打印时,每一个打印的元素都需要在一次内循环中拼接起来,所以要设置str。**我们可以吧str看做成sum,星星看做成1,str = str + 星星,等价于 sum = sum +1,所以必须设置一个变量str去逐一保存打印的星星。** 11 | 12 | 因为打印的星星属于字符串,想要打印出来全部都是字符串星星,需要考虑给变量定义什么。我们可以想到,**如果给变量定义一个数字0,那么打印出来就是0☆☆☆☆☆,所以我们也需要给变量定义一个字符串,即 str = ‘’;,这样再打印出来就是一个空的字符串加上五个星星,即☆☆☆☆☆,**具体验证看以下代码串: 13 | 14 | 假如给str定义为0: 15 | 16 | ```js 17 | var str = 0; 18 | for (i = 1; i <= 5; i++) { 19 | for (j = 1; j <= 5; j++) { 20 | str = str + '☆'; 21 | } 22 | str = str + '\n'; 23 | 24 | } 25 | console.log(str); 26 | ``` 27 | 28 | 打印结果是: ![](https://img-blog.csdnimg.cn/5710e56473eb4acf81768bcb8817adbf.png) 29 | 30 |   可以看到,控制台打印结果有个0。 31 | 32 |   假如给str定义为字符串‘’: 33 | 34 | ```js 35 | var str = ''; 36 | for (i = 1; i <= 5; i++) { 37 | for (j = 1; j <= 5; j++) { 38 | str = str + '☆'; 39 | } 40 | str = str + '\n'; 41 | } 42 | console.log(str); 43 | ``` 44 | 45 | 打印结果是:![](https://img-blog.csdnimg.cn/7de9f0bff31441aa8c947f917631113d.png) 46 | 47 |         此时打印结果正确 。 48 | 49 | 同理,打印99[乘法表](https://so.csdn.net/so/search?q=%E4%B9%98%E6%B3%95%E8%A1%A8&spm=1001.2101.3001.7020)也一样,定义str为0或是字符串都可以。因为 str + i,不论是0或是字符串,都不影响最终结果。 50 | 51 | ```js 52 | // 这里str即可以赋值0,也可以赋值‘’,结果不变。 53 | var i, j , n, str = 0; 54 | for (i = 1; i <= 9; i++) { 55 | for (j = 1; j <= i; j++) { 56 | n = i * j ; 57 | str = str + i + '×' + j + '=' + n + '\t'; 58 | } 59 | str = str +'\n'; 60 | } 61 | console.log(str); 62 | ``` 63 | 64 | 总结:定义str为字符串还是数字,取决于内循环打印出来的效果,可以打断点看。 -------------------------------------------------------------------------------- /blog/programdifficulty/js/replaceAndRegex.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: js中replace方法与正则结合用法 3 | date: 2024-10-08 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | > js中replace加正则在某些场景在可以简化字符串截取操作,事半功倍。 11 | 12 | ## 背景 13 | 14 | 在对服务端返回到报错话术ResponseMsg:‘ERROR0391,您的登陆信息已失效’这段话术进行过滤,需要删除掉报错码,前端反显‘您的登陆信息已失效’。 15 | 16 | 我的做法是用indexOf方法查询ResponseMsg是否包含ResponseCode‘ERROR0391’,如果存在再用字符串截取方法slice截取掉前10位,只留话术,审核代码的时候却被告知这样比较麻烦。 17 | 18 | ## 优化方案 19 | 20 | 已知报错码值固定9位,前五位固定大写ERROR,后四位数字,那么使用`ResponseMsg.replace(/^[A-Z]{5}[0-9]{4},/, '')`即可,**^表示开头,[A-Z]{5}表示5位A-Z的大写字母,[0-9]{4},表示0-9的4位数字以及后面的逗号**,如果匹配到了则换成空串。 21 | 22 | ```console 23 | 'ERROR0391,您的登陆信息已失效'.replace(/^[A-Z]{5}[0-9]{4},/, '') // 输入代码 24 | '您的登陆信息已失效' // 打印信息 25 | ``` 26 | 27 | ## 总结 28 | 29 | 很多检索再截取的数据方法场景,如果数据存在规律,可以直接使用**replace方法,第一个参数传入正则表达式,第二个参数为被替换的内容**从而一步到位。 30 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/switch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 当用switch结构判断范围值时问题 3 | date: 2023-03-31 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | 当判断范围值时,我们一般用if else结构,switch结构更多的用于判断定值。 11 | 如果要用switch结构判断范围值问题时,**switch后不要写变量,否则会一直打印default的结果。** 12 | 如以下代码: 13 | 14 | ```js 15 | var money = parseInt(prompt('班长口袋里的钱数:')); 16 | //money 和 money >= 2000不是全等关系,所以不成立;switch后要跟case条件后的返回值 17 | switch (money) { 18 | case (money >= 2000): 19 | alert('请大家吃西餐'); 20 | break; 21 | case money >= 1500 && money <2000: 22 | alert('请大家吃快餐'); 23 | break; 24 | case money >= 1000 && money <1500: 25 | alert('请大家喝饮料'); 26 | break; 27 | case money >=500 && money <1000: 28 | alert('请大家吃棒棒糖'); 29 | break; 30 | default: 31 | alert('下次带够钱'); 32 | } 33 | ``` 34 | 35 | 在switch结构中,switch后括号中的表达式或者值要和case后括号中的表达式或者值全等,否则就会不成立。而比如我输入了4000,4000 已经 大于等于了2000,这个表达式的结果是true,可以得出正确代码: 36 | 37 | ```js 38 | var money = parseInt(prompt('班长口袋里的钱数:')); 39 | //当改成true后,运行顺利 40 | switch (true) { 41 | case (money >= 2000): 42 | alert('请大家吃西餐'); 43 | break; 44 | case money >= 1500 && money <2000: 45 | alert('请大家吃快餐'); 46 | break; 47 | case money >= 1000 && money <1500: 48 | alert('请大家喝饮料'); 49 | break; 50 | case money >=500 && money <1000: 51 | alert('请大家吃棒棒糖'); 52 | break; 53 | default: 54 | alert('下次带够钱'); 55 | } 56 | ``` -------------------------------------------------------------------------------- /blog/programdifficulty/js/throttle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS节流阀 3 | date: 2023-04-04 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | > 节流阀:可以减少一段时间内事件触发的频率,指定时间内只能触发一次 11 | 12 | 13 | 14 | ## 核心实现思路 15 | 16 | 开始设置一个变量var flag = true; 17 | 18 | `if (flag) { flag = false; do something }` // 关闭水龙头 19 | 20 | 利用回调函数等动画执行完毕,flag = true; //打开水龙头 21 | 22 | ## 实现代码段 23 | 24 | ```js 25 | // flag 节流阀 26 | var flag = true; 27 | arrow_r.addEventListener('click', function() { 28 | if (flag) { 29 | flag = false; // 关闭节流阀 30 | // 如果走到了最后复制的一张图片,此时 我们的ul 要快速复原 left 改为 0 31 | if (num == ul.children.length - 1) { 32 | ul.style.left = 0; 33 | num = 0; 34 | } 35 | num++; 36 | animate(ul, -num * focusWidth, function() { 37 | flag = true; // 打开节流阀 38 | }); 39 | // 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放 40 | circle++; 41 | // 如果circle == 4 说明走到最后我们克隆的这张图片了 我们就复原 42 | if (circle == ol.children.length) { 43 | circle = 0; 44 | } 45 | // 调用函数 46 | circleChange(); 47 | } 48 | }); 49 | ``` 50 | -------------------------------------------------------------------------------- /blog/programdifficulty/js/usingDomEvent.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS之手动调用dom事件触发后的回调函数 3 | date: 2023-04-04 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | > 在js中我们经常给一个dom绑定一个事件监听器,监听某种行为,触发后执行回调函数。那怎么在别的地方直接调用这个回调函数呢? 11 | 12 | 13 | 14 | ## 回调函数代码段 15 | 16 | ```js 17 | rightBtn.addEventListener('click', function() { 18 | if(num == ul.children.length - 1) { 19 | ul.style.left = 0; 20 | num = 0; 21 | } 22 | num++; 23 | animate(ul, -num * focusWidth); 24 | circle++; 25 | if(circle == 4) { 26 | circle = 0; 27 | } 28 | change(); 29 | }) 30 | ``` 31 | 32 | ## 手动调用右箭头按钮点击事件 33 | 34 | ```js 35 | var timer = setInterval(function() { 36 | rightBtn.click(); //手动调用右按钮点击事件 37 | }, 1000) 38 | ``` 39 | -------------------------------------------------------------------------------- /blog/programdifficulty/react/InterfaceNotUpdate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React中响应式数据变了界面却未更新 3 | date: 2023-05-02 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - React 8 | --- 9 | 10 | ## 背景 11 | 12 | 点击按钮,数组push新的数据,但是界面却未更新 13 | 14 | 数组是引用类型直接赋值再修改,会导致引用未变 15 | 16 | ```react 17 | let [testArr, setTestArr] = useState([]) 18 | 19 | let _arr = testArr; 20 | _arr.push('test') 21 | setTestArr(_arr)` 22 | ``` 23 | 24 | [useState](https://so.csdn.net/so/search?q=useState&spm=1001.2101.3001.7020)修改后,useEffect其实未监听到 25 | 26 | ## 解决方案 27 | 28 | 所以要重新创建一个新数组,对原数组序列化或者解构 29 | 30 | ```react 31 | let [testArr, setTestArr] = useState([]) 32 | 33 | let _arr = JSON.parse(JSON.stringify(testArr)); 34 | // 或者 35 | let _arr = [...testArr]; 36 | _arr.push('test') 37 | setTestArr(_arr) 38 | ``` 39 | 40 | [useEffect](https://so.csdn.net/so/search?q=useEffect&spm=1001.2101.3001.7020)监听数据变化时,只有在数组元素类型为基本数据类型时可以起到作用。但对于复杂数据类型如:对象,数组和函数来说,`React`会使用`referential equality`来对比前后是否有不同。 41 | 42 | `React`会检查当前渲染下的这个对象和上一次渲染下的对象的内存地址是否一致。两个对象必须是同一个对象`useEffect`才会跳过执行`effect`。所以,即使内容完全相同,内存地址不同的话,`useEffect`还是会执行`effect`。 43 | -------------------------------------------------------------------------------- /blog/programdifficulty/react/proxyConfig.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React配置代理(proxy) 3 | date: 2023-05-14 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - React 8 | --- 9 | 10 | 使用axios进行请求,而配置代理过程 11 | 12 | ## 第一种 13 | 14 | 在package.json中,添加`proxy`配置项,之后所有的请求都会指向该地址 15 | 但这种方法只能配置一次,也只有一个 16 | 17 | 示例: 18 | 19 | ```react 20 | "proxy":"https://localhost:5000" 21 | ``` 22 | 23 | 添加后,重启项目!!!让配置文件加载生效 24 | 然后就可以进行请求了 25 | 比如请求地址是 `http://localhost:5000/api/index/index` 26 | 那就可以写 27 | 28 | ```react 29 | axios.get("/api/index/index").then( 30 | response => {console.log('成功了',response.data);}, 31 | error => {console.log('失败了',error);} 32 | ) 33 | ``` 34 | 35 | ## 第二种 36 | 37 | 在`src`中,新建`setupProxy.js`(必须是这个名字,react脚手架会识别),在文件中写以下配置内容(最近的项目要使用高版本这个,不然会导致项目无法启动): 38 | http-proxy-middleware高版本(2以上): 39 | 40 | ```react 41 | const proxy = require('http-proxy-middleware')//引入http-proxy-middleware,react脚手架已经安装 42 | 43 | module.exports = function(app){ 44 | app.use( 45 | proxy.createProxyMiddleware('/api',{ //遇见/api1前缀的请求,就会触发该代理配置 46 | target:'http://localhost:5000', //请求转发给谁 47 | changeOrigin:true, //控制服务器收到的请求头中Host的值 48 | pathRewrite:{'^/api':''} //重写请求路径,下面有示例解释 49 | }), 50 | proxy.createProxyMiddleware('/api2',{ 51 | target:'http://localhost:5001', 52 | changeOrigin:true, 53 | pathRewrite:{'^/api2':''} 54 | }), 55 | ) 56 | } 57 | ``` 58 | 59 | 写好以后,重启项目!!! 60 | 然后进行请求 61 | 62 | 假设地址是 `http://localhost:5000/api/index/index` 63 | 64 | ```react 65 | //没有开启重写路径 66 | axios.get("/api/index/index").then( 67 | response => {console.log('成功了',response.data);}, 68 | error => {console.log('失败了',error);} 69 | ) 70 | //开启重写路径 71 | axios.get("/api/api/index/index").then( 72 | response => {console.log('成功了',response.data);}, 73 | error => {console.log('失败了',error);} 74 | ) 75 | ``` 76 | -------------------------------------------------------------------------------- /blog/programdifficulty/ts/test.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TSxxxx 3 | date: 2023-06-01 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - TS 8 | --- 9 | 10 | 待定 11 | -------------------------------------------------------------------------------- /blog/programdifficulty/uniapp/a.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 待定 3 | date: 2023-01-01 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Uniapp 8 | --- -------------------------------------------------------------------------------- /blog/programdifficulty/vue/TableDataChange.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Element UI在表单Form组件中修改数据,表格Table组件的数据也跟着修改的问题 3 | date: 2023-04-30 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Element UI 8 | --- 9 | 10 | ## 场景 11 | 12 | 最近遇到一个问题,在表单中修改数据,表格的数据也跟着修改,且不管是不是按确定或取消按钮,表格的数据还是被修改了,部分代码如下: 13 | 14 | ```vue 15 | editUser(row) { 16 | this.operateType = 'edit' 17 | this.isShow = true 18 | this.dialog.operateForm = row 19 | } 20 | ``` 21 | 22 | ## 解决方案 23 | 24 | 刚开始还以为是table的传值传错了,最后发现就是上面的代码的最后一行错了。 25 | 26 | `this.dialog.operateForm = row` 27 | 28 | 因为row是Object对象类型,如果直接赋值的话,就变成了浅拷贝,复制的是地址,导致在表单中改变值的时候table中的数据也跟着改变,所以要进行深拷贝,利用json就可以了,改成下面就行了。 29 | 30 | `this.dialog.operateForm = JSON.parse(JSON.stringify(row))` 31 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/VueRouterNextFalse.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue-Router中next(false)的含义: 从哪儿来,就回到哪儿去 3 | date: 2023-04-23 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | ```vue 11 | { 12 | path: '/trade', 13 | component: Trade, 14 | meta: { showFooter: true }, 15 | beforeEnter: (to, from, next) => { 16 | if(from.path == '/shopcart') { 17 | next() 18 | } else { 19 | next(false) 20 | } 21 | } 22 | }, 23 | ``` 24 | 25 | 路由独享守卫,表示如果要去trade页的话,只有从shopcart页才能去,否则从哪个页想跳转到trade页,就回到那个初始页 26 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/aTagSkip.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue利用a标签跳转页面 出现 `http://xxxxxx/about#/about` 问题原因和解决方法 3 | date: 2023-04-22 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | > Vue项目中跳转内部链接一般都是`About`标签,其实它的底层是``标签,当我用a标签跳转时,跳转的链接却是非常奇怪 11 | 12 | 13 | 14 | ## START 15 | 16 | * 前段时间在vue项目中,路由跳转的时候,使用a标签去跳转路由,遇到跳转不生效,路径还是奇怪的`http://xxxxxx/about#/about`,记录一下这个问题。 17 | 18 | ### 1.问题 19 | 20 | #### 1.1 编写的代码 21 | 22 | `我是a标签,点击我跳转到关于页面` 23 | 24 | #### 1.2 问题描述 25 | 26 | * 点击a标签页面跳转不生效; 27 | * 点击之后路径从`http://localhost:8080#/about`变化为`http://localhost:8080/about#/about`; 28 | 29 | ### 2.问题分析与解决 30 | 31 | 1. 背景我说一下,首先我们知道vue[单页面应用](https://so.csdn.net/so/search?q=%E5%8D%95%E9%A1%B5%E9%9D%A2%E5%BA%94%E7%94%A8&spm=1001.2101.3001.7020),有两种路由模式。一个叫`hash`,一个叫`history`。不清楚[路由模式](https://so.csdn.net/so/search?q=%E8%B7%AF%E7%94%B1%E6%A8%A1%E5%BC%8F&spm=1001.2101.3001.7020)可以看我写的博客:[我想理解 hash history 两种前端路由](https://blog.csdn.net/wswq2505655377/article/details/124482841?spm=1001.2014.3001.5501)。 32 | 33 | 2. 跳转前的路径存在`#`号,可以很明显的得知,当前的项目的路由模式是使用的[hash](https://so.csdn.net/so/search?q=hash&spm=1001.2101.3001.7020)模式。 34 | 35 | 3. 我们一般路由跳转,创建的例如 `About`,其实底层就是[a标签](https://so.csdn.net/so/search?q=a%E6%A0%87%E7%AD%BE&spm=1001.2101.3001.7020)。 36 | 37 | > ![在这里插入图片描述](https://img-blog.csdnimg.cn/b0bc996c44a74800a22df3bdd203f328.png) 38 | > | 模式 | 代码 | 页面展示 | 39 | > | --- | --- | --- | 40 | > | `history` | `About` | `About` | 41 | > | `hash` | `About` | `About` | 42 | 43 | `解决答案:` 44 | **写到这里答案就呼之欲出了,其实本质``也是利用a标签切换路由的,我们写原生的a标签也是没问题的.但是我们a标签的路径需要和我们路由模式匹配。** 45 | 46 | * 如果是`hash`路由,使用`href="#/xxxx"`进行跳转; 47 | * 如果是`history`路由,使用`href="/xxxx"`进行跳转; 48 | 49 | > `xxxx`可替换为你想要跳转的路径 50 | 51 | ## 其他 52 | 53 | ### a标签基本介绍 54 | 55 | 首先看看我们的a标签``的[W3C基本介绍](https://www.w3cschool.cn/htmltags/att-a-href.html): 56 | 57 | * ``标签定义超链接,用于从一个页面链接到另一个页面; 58 | * ``元素最重要的属性是 `href` 属性,它指定链接的目标 59 | 60 | `href`属性可以设置的URL: 61 | 62 | * `绝对 URL` \- 指向另一个站点(比如 href=“” rel=“external nofollow” target=“\_blank” ) 63 | * `相对 URL` - 指向站点内的某个文件(href=“index.html”) 64 | * `锚 URL` - 指向页面中的锚(href=“#top”) 65 | 66 | ## END 67 | 68 | * 其实是一个很简单的问题,一一对应即可。 69 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/autoOpen.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue-cli 运行vue项目,设置vue-cli-service serve --open自动打开浏览器,跳转到http://0.0.0.0:8081 解决办法 3 | date: 2023-04-09 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | ## 错误示例 11 | 12 | 尝试在 package.json 里面设置自动打开浏览器 13 | 14 | ```package.json 15 | module.exports = defineConfig({ 16 | devServer: { 17 | host: 'localhost', 18 | port: 8080, 19 | https: false, 20 | hot: false, 21 | proxy: null 22 | } 23 | }) 24 | ``` 25 | 26 | 运行之后 跳转到  27 | 28 | ![image](https://img-blog.csdnimg.cn/img_convert/e3caa2f97f1cb598edfb65406f1731d7.jpeg) 29 | 30 | ## 解决办法 31 | 32 | 在 项目文件夹下的 vue.config.js 里面添加这段代码 33 | 34 | ```vue.config.js 35 | module.exports = defineConfig({ 36 | devServer: { 37 | host: 'localhost', 38 | port: 8080, 39 | https: false, 40 | hot: false, 41 | proxy: null 42 | } 43 | }) 44 | ``` 45 | 46 | 就可以跑起来自动打开了! 47 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/axiosParamsLose.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ajax请求地址携带不上参数 3 | date: 2023-04-30 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Ajax 8 | --- 9 | 10 | ## get 11 | 12 | get接口: 13 | 14 | ![img](https://img-blog.csdnimg.cn/55d4ae948f644a1f9564dc446a954089.png) 15 | 16 | get请求中传入的params其实是一个对象,即`{params: params}`,因为key, value一致,所以可以简写 17 | 18 | 错误示范:getUser(this.pageData), 这样传的话请求携带参数只会是`this.pageData`,而不是`{params: this.pageData}`,故无法将参数带到请求url中 19 | 20 | 正确示范:getUser({ params: this.pageData }) 21 | 22 | 总结:Ajax请求地址携带不上参数的get请求如果存在params参数,携带参数必须是一个key是params的对象,不能是一个值或简单对象; 23 | 24 | ## post 25 | 26 | post接口: 27 | 28 | ![img](https://img-blog.csdnimg.cn/e4f39478fa3a4deab0a9830648d7b5c8.png) 29 | 30 | 正确示范: getMenu(this.form), post请求则可以直接填入一个普通对象 31 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/deconstMissResponsiveness.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue3解构对象丢失响应式,如props,torefs, pinia响应式核心 3 | date: 2023-06-03 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue3 8 | --- 9 | 10 | ## 项目场景 11 | 12 | 这段时间在利用vue3开发一个项目,在vue的使用过程中,我处于用到什么学什么的状态,一方面是学业,一方面是技术,项目的影响,导致我一直没有静下心来研究透彻vue核心知识,这么,一个关于组件开发时响应式的问题面对了我,我做的是一个组件嵌套,分别有三个组件,一个负责页面框架,一个负责核心业务,一个负责文件处理,这三个组件需要互相通信,我为了代码简洁没有定义过多的变量,而是充分利用vue的组件通信来完成响应式组件和业务。 13 | 14 | * * * 15 | 16 | ## 问题描述: 17 | 18 | 项目中我用的是pinia状态管理库,我是希望通过中间库来使通信更容易,这个过程就产生了响应式问题。总结如下: 19 | 20 | 1. pinia仓库失去响应式 21 | 22 | ```pinia 23 | const system = useSystem(); 24 | const { isNewProject, isOpenEdit } = toRefs(system) 25 | ``` 26 | 27 | 2. props响应式异常 28 | 29 | ```vue 30 | const props = defineProps({ 31 | isNewProject: Boolean, 32 | isOpenEdit: Boolean 33 | }) 34 | 35 | const { isNewProject, isOpenEdit } = props; 36 | ``` 37 | 38 | * * * 39 | 40 | ## 原因分析: 41 | 42 | 上面我放了俩段代码,是为了更好说明问题; 43 | 44 | ############ 问题1分析 :我之所以会用那样的解构方式,是因为我在用pinia之前就没有完整的读完文档,这一点我深刻反思,其实原因很简单,官方文档说的很清楚`store 是一个用reactive 包裹的对象,像setup 中的props 一样,我们不能对其进行解构`这是官方原话,如果需要响应式跟踪`Store 中提取属性同时保持其响应式,您需要使用storeToRefs()。 它将为任何响应式属性创建 refs。` 45 | 46 | ############ 问题2分析 :props响应式异常,一个常识是vue的组件中props数据流是由父到子单向的,那么这个数据流可以是响应式的也可以是非响应式的,非响应式的不会产生我们这里的问题,关键在于响应式,什么情况下会失去响应式,于是我带着问题开始了探索,先是读了一个[大佬的文章](https://blog.csdn.net/lunahaijiao/article/details/125863270?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-125863270-blog-115365262.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-125863270-blog-115365262.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=1), `ES6 解构,不能随意使用。会破坏他的响应式特性!`对就是这句话,那么我上面写出来的代码就是被这句话说中了. 47 | 48 | * * * 49 | 50 | ## 解决方案: 51 | 52 | 第一个问题我们用`storeToRefs` 来解决,或者就不进行解构,不结构的话是有一点书写麻烦 53 | 54 | ```pinia 55 | import { storeToRefs } from "pinia"; 56 | 57 | const system = useSystem(); 58 | const { isNewProject, isOpenEdit } = storeToRefs(system) 59 | ``` 60 | 61 | 第二个问题要想解决,那就是不要试图去解构props, 这里可以扩展一个知识点,就是用`toRefs`来解构,被它解构完的属性会具有ref特性,也具备了响应式,但是会使props的属性与父组件的响应式断开连接,所以在搞清楚这个问题以后,我们在组件通信上或许就有了更笃定的想法。 62 | 63 | ## Tip 64 | 65 | 最后推荐有时间的同学能去读下[这位大神的文章](https://blog.csdn.net/lunahaijiao/article/details/125863270?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-125863270-blog-115365262.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-125863270-blog-115365262.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=1) 66 | 67 | 以上仅代表个人学习过程观点,如有错误希望能够进一步交流,感谢这个社区。 68 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/deliverEventParam.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue中自定义事件的 $event传参问题 3 | date: 2023-04-08 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | ## 详解 11 | 12 | 1. $event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event 13 | 14 | 2. 在原生事件中,$event是事件对象 可以点出来属性 15 | 16 | 3. 在原生事件中,$event是事件对象,在自定义事件中,$event是传递过来的数据(参数) 17 | 18 | 4. 1在自定义事件中,$event是传递过来的数据 19 | 20 | ## 代码示范 21 | 22 | ## 原生vue里的$event 23 | 24 | ```vue 25 | // 原生vue里的$event 26 | 27 | 28 | 29 | 30 | 55 | ``` 56 | 57 | ## 自定义事件里的$event 58 | 59 | ```vue 60 | // 子组件传值 61 | export default { 62 | methods: { 63 | customEvent() { 64 | this.$emit(custom-event, value) 65 | } 66 | } 67 | } 68 | 69 | //父组件接收自定义事件 70 | 76 | 77 | //父组件自定义事件方法中接收$event 78 | export default { 79 | methods: { 80 | // e就是接收过来的$event 现在他就是子组件传过来的值 不再是 对象事件 81 | customEvent(index, e) { 82 | console.log(e) // some value 83 | } 84 | } 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/dynamicClass.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-04-13 3 | title: Vue动态class为什么v-bind:class的第二个class需要加引号 4 | category: frontend 5 | tags: 6 | - Vue 7 | --- 8 | 9 | ## 问题 10 | 11 | 问题是关于vue.js框架中动态class绑定的带‘-’的类名为什么要加引号 12 | 13 | ## 代码 14 | 15 | 下面是中文官网给出的示例代码,英文官网也是相同的(我加了test内容,方便debug) 16 | 17 | [https://cn.vuejs.org/v2/guide...](https://v2.cn.vuejs.org/v2/guide/class-and-style.html) 18 | 19 | ```vue 20 |
test 22 |
23 | ``` 24 | 25 | 我的问题就是为什么了第2个class是有引号的。 26 | 27 | 如果我去了引号,下面的就不能生效 28 | 29 | ```vue 30 |
test 32 |
33 | ``` 34 | 35 | ## 原因 36 | 37 | 因为 **{ } 内的代码是要拿去当js解析的,js中变量是没有用 ' - '号连接** 38 | 39 | 就像 css中 font-size 是用 - 号连接 40 | 41 | 到了js中 就必须用驼峰的写法 42 | 43 | 此处,`v-bind`后面的值为表达式,表达式写法和JS基本一样,但是所有的`this`都被省略。 44 | 45 | JS里面,键名如果有`-`符号,也是需要加引号。JS中 46 | 47 | ```vue 48 | { 49 | active: isActive, 50 | text-danger: hasError 51 | } 52 | ``` 53 | 54 | 是无效的,而 55 | 56 | ```vue 57 | { 58 | 'active': isActive, 59 | 'text-danger': hasError 60 | } 61 | ``` 62 | 63 | 是有效的 64 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/ellipsisThis.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-04-19 3 | title: Vue中省略this方案 4 | category: frontend 5 | tags: 6 | - Vue 7 | --- 8 | 9 | > 在Vue的methods中的某个方法中,如果用到了响应式数据,那么就要用this.xxx来调用,数据量小还好,如果方法里用到了十几个甚至几十个响应式数据,此时就不得不请出**ES6中的对象解构**了 10 | 11 | 12 | 13 | ## 正常写法 14 | 15 | ```vue 16 | methods: { 17 | getCode() { 18 | this.$store.dispatch('getCode', this.phone) 19 | } 20 | } 21 | ``` 22 | 23 | 问题:如果代码量大了,每一次使用Vue中的变量都需要加上前缀this 24 | 25 | 分析:我们可以在控制台中尝试打印this,可以看到:this打印出来是一个VueComponent的对象,包括了phone这个属性 26 | 27 | ```vue 28 | methods: { 29 | getCode() { 30 | console.log(this) 31 | this.$store.dispatch('getCode', this.phone) 32 | } 33 | } 34 | ``` 35 | 36 | 打印结果如图: 37 | 38 | ![img](https://img-blog.csdnimg.cn/fcda88b271c24498b44a3277d205162b.png) 39 | 40 | 可以看出,打印出的的对象包含了phone这个属性,我已经用红线标注出来 41 | 42 | ## 解决方案 43 | 44 | 所以可以用es6的[解构赋值](https://so.csdn.net/so/search?q=%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC&spm=1001.2101.3001.7020)把自己想要的属性解构出来,就可以直接用这个变量而不加this前缀了 45 | 46 | ```vue 47 | methods: { 48 | getCode() { 49 | const { phone } = this //this是一个对象,包含了phone属性,左边解构出来给phone 50 | this.$store.dispatch('getCode', phone) //可以直接用变量phone了 51 | } 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/fullScreenAndPrerender.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 沉浸式改造下预渲染前后闪屏问题 3 | date: 2024-10-10 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | > 预渲染是服务端数据返回前页面的默认渲染,数据返回后完成真实渲染。 11 | 12 | ## 背景 13 | 14 | 在App大于13.0.0时,各个页面头部做沉浸式改造,优化用户体验。 15 | 16 | 在做安卓沉浸式改造时,通过`window.user.navigator`获取状态栏的高度,再给body下追加一个--top属性,值即为状态栏的高度。Vue组件中通过dom原生方法拿到标签下--top属性的值,即状态栏的高度值,再追加给Header组件的margin值即可纵向布局不变。 17 | 18 | 因为ios自带沉浸式而安卓有黑色状态栏,为了保证视觉高度一致,安卓系统时头部高度值相应减少了一些。由于安卓做沉浸式后,没有了黑色状态栏的高度,盒子贴到了屏幕顶端,视觉效果高度比ios矮了,所以需要增加一些高度值。 19 | 20 | 所以用获取系统版本的钩子判断了一下是否是沉浸式版本,如果是给头部增加高度,否则默认不变。开发完成后,功能实现,但是后来用户发现页面竟然偶尔会出现短暂的屏幕抖动。 21 | 22 | ## 原因 23 | 24 | 录屏进入页面并慢放发现,进入页面初始化的一瞬间会走入预渲染,都是兜底数据,但是预渲染完成后屏幕抖动一下(偶尔复现,加载快的时候),然后头部高度增加了,于是推测该问题肯定跟预渲染有关。 25 | 26 | 经排查发现:用getVersion钩子判断是否是沉浸式版本,从而给头部组件不同的高度值。但是仔细慢放发现,在沉浸式版本预渲染时,头部高度一直都是非预渲染的高度,也就是交易返回false时拿到的高度值。
27 | 于是猜测预渲染时是一瞬间直接读取打包后的html和css,此时发不通任何服务端接口交易或者交易还没有返回,相当于判断App版本的钩子根本未执行,直接默认走的兜底值false即非沉浸式的高度。等预渲染完成后,交易突然发通了钩子返回true走了沉浸式的高度,高度值突然增加,导致前后高度不一致,从而出现屏幕抖动。 28 | 29 | 以下是大致代码(简洁化): 30 | 31 | ```js 32 | headerTop() { 33 | return `${OS_TYPE} === 'ios' 34 | ? 'ios' 35 | : this.getVersion > 13.0.0 ? 36 | 'android-full' 37 | : 'android' 38 | }-header-adv` 39 | } 40 | ``` 41 | 42 | ```css 43 | .ios-header-adv{ // ios高度 44 | height: 400px 45 | } 46 | .android-full-header-adv{ // 安卓沉浸式高度 47 | height: 400px 48 | } 49 | .android-adv{ // 安卓非沉浸式高度 50 | height: 400px 51 | } 52 | ``` 53 | 54 | ## 解决方案 55 | 56 | 最初方案:继续发交易,给预渲染时的钩子返回false的高度值给成沉浸式的高度值,这样就能保证沉浸式版本预渲染前后高度一致,页面不会抖动了。
57 | 弊端是非沉浸式版本可能会存在问题,高度值由高变低了。但是这个方案被业务否了,他给我看了一张用户版本统计图表,显示版本小于13.0.0的非沉浸式用户也有不少,这样做相当于牺牲了一部分用户的使用体验去保障另外一部分的体验,于是我就思考换一种方案保证两个版本都不会抖动。 58 | 59 | 最终方案:我想着既然屏幕抖动是因为预渲染前交易发不通导致的,那么我就不再判断是否是沉浸式版本了,直接给预渲染后的高度,从而不存在预渲染前后高度值变化的过程了,保证预渲染前后一样的高度,问题解决。
60 | 此方案也有一定的弊端,就是版本低的非沉浸式页面,头部高度会增加30px(不影响功能和布局),但是相较于屏幕抖动的bug,已经是最好的方案了,因为以后版本只会越来越高,用户总会升级上去,沉浸式版本才是主流,甚至可以做一个版本控制强制更新来保证,最终该方案成功上线并受到肯定。 61 | 62 | ## 总结 63 | 64 | 预渲染工具的基本原理是:构建打包之后,插件会在本地启动express静态服务,serve打包好的静态资源。然后再启动一个无头浏览器(例如Puppeteer),浏览器从服务器请求网页,网页运行时候会请求首屏接口,用拿到的数据渲染出包含内容的首屏后,无头浏览器截屏并替换掉原来的html。 65 | 66 | **此时服务端的任何接口交易是发不通的(或者还没有数据返回),开发时一定要注意这个点。** 67 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/ifElseOptimization.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: if、else逻辑的优化 3 | date: 2023-04-11 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | > 当Vue项目中methods中的一个方法,if、else中的逻辑相同,只是数据源不一样,该怎么优化呢? 11 | 12 | 13 | 14 | ## 背景 15 | 16 | 今天在公司做code review时,因为if、else中逻辑相同没有做封装被打了回来,下面是大致的代码段 17 | 18 | ```vue 19 | data() { 20 | retren { 21 | isNoData: false, 22 | list: [], 23 | topList: [] 24 | } 25 | }, 26 | 27 | methods:{ 28 | updateCollections() { 29 | if(this.isNoData) { 30 | this.list.forEach(item => { 31 | xxxxxxxxx 32 | }) 33 | } else { 34 | this.topList.forEach(item => { 35 | xxxxxxxxx 36 | }) 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | 然后因为相同的逻辑写了两遍,果不其然被打回来了。。。 43 | 44 | 我最初想的是在methods中再封装一个公共逻辑,把forEach后面的逻辑给封装起来,没想到经过审阅老师的指导,打开了一种新思路,于是我在新思路下写了一版优雅的代码,可以说是满满的成就感,下面放出示例代码 45 | 46 | ## 示例代码 47 | 48 | ```vue 49 | data() { 50 | retren { 51 | isNoData: false, 52 | list: [], 53 | topList: [] 54 | } 55 | }, 56 | 57 | methods:{ 58 | updateCollections() { 59 | let showData = this.isNoData? [...this.topList] : [...this.list] 60 | showData.forEach(item => { 61 | xxxxxxxxx 62 | }) 63 | } 64 | } 65 | ``` 66 | 67 | 这种思路无非就是先把判断逻辑放到前面,交给一个统一的数据源,再用这个数据源做逻辑处理,这样就可以只写一便核心逻辑了,可以说是学到了啊!! 68 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/interfaceTest.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 发起请求返回异常情况时,要在控制台测试一下是否会报typeError 3 | date: 2023-05-27 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Ajax 8 | --- 9 | 10 | ## 背景 11 | 12 | 今天在公司给老大做代码review时,因为没有测试异常情况,被老大批评了一顿,说再一再二不再三,我也该长长记性了 13 | 14 | 下面是大致的代码段: 15 | 16 | ```vue 17 | async created() { 18 | const [{ a }, b] = await Promise.all([getData().catch(e => ({})),getFinanceList()].catch(e => '')) 19 | } 20 | ``` 21 | 22 | 当我沾沾自喜觉得自己写了优秀的代码,把两个请求用Promise.all统一处理还不忘做了捕获异常,信心满满的给老大看时 23 | 24 | 下面是他的原话:‘你考虑到两个请求发生异常的情况没?’ 我说:‘啊?好像没有’,顿时心里慌得一笔。 25 | 26 | 他于是批评道:‘**那如果你第一个请求发生异常捕获了空对象,如果a拿不到值产生了typeError报错,导致页面卡死,你能负的起这个责吗?再一再二不再三啊,下次注意!**’ 27 | 28 | 解决方案: 29 | 30 | 所以每次在捕获异常之后,要在浏览器调试工具中的控制台打印一下要取到的各个值,模拟一下异常情况会不会导致卡死,如下图所示 31 | 32 | [![interface-Test.png](https://i.postimg.cc/fLzspndZ/interface-Test.png)](https://postimg.cc/NyzZ2nbP) 33 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/keepAlive.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-10-20 3 | title: 路由懒加载和原进原出 4 | category: frontend 5 | tags: 6 | - JS 7 | --- 8 | 9 | ## 方案 10 | 11 | 1. 给需要懒加载的路由的meta配置项加上`keepAlive`标识,标注这是一个懒加载路由。 12 | 13 | ```js 14 | { 15 | title: '列表页', 16 | path: 'list', 17 | name: 'list', 18 | meta: { 19 | keepAlive: true // 懒加载路由 20 | }, 21 | component: () => 22 | import('@/views/List') 23 | }, 24 | { 25 | title: '详情页', 26 | path: 'detail', 27 | name: 'detail', 28 | component: () => 29 | import('@/views/Detail') 30 | } 31 | ..... 32 | ``` 33 | 34 | 2. 路由组件中判断是否是懒加载组件,如果是给外层包上keep-alive标签开启懒加载 35 | 36 | ```js 37 | 47 | ``` 48 | 49 | 3. 列表页中:
50 | 设置响应式数据isRefreshData作为是否重新发接口的标识,初始化为false。
51 | 创建init初始化函数,发接口获取返回数据并把isRefreshData设置成false。
52 | activated懒加载激活生命周期中,如果isRefreshData是true,调用init函数。
53 | 当从列表页点击返回按钮进入到首页时,把isRefreshData设置成true。
54 | 点击卡片进入详情页时,获取当前页面的卷动值并存储在localStorage中。
55 | 设置路由前置守卫,如果前置页面是后置页面详情页返回的,就获取localStorage中的页面卷动值并滚动到对应位置。
56 | 57 | 4. 进入页面的触发顺序: 58 | 首页**第一次**进入列表页时:路由守卫直接放行,**mounted**里触发init函数,发交易获取数据并把isRefreshData置成true,数据正常展示。 59 | 60 | 列表页进入详情页时再返回列表页时:点击列表,存入当前卷动值并跳转,再从详情页返回,此时路由守卫检测到是从详情页返回,拿到存储里的卷动值并滚动到对应位置,激活activated生命周期判断isRefreshData为false,不做任何处理展示懒加载数据。 61 | 62 | 列表页先返回首页再进入列表页时:点击返回按钮,isRefreshData置成true并返回到首页,再次进入列表页,路由前置守卫直接放行,激活activated生命周期判断isRefreshData为true,执行init函数再次发接口刷新页面数据,并把isRefreshData值置为false。
63 | ..... 64 | 以下是简洁代码: 65 | 66 | ```js 67 | data() { 68 | list: [], 69 | isRefreshData: false 70 | }, 71 | mounted() { 72 | this.init() 73 | }, 74 | // 懒加载激活生命周期 75 | activated() { 76 | isRefreshData && await this.init() 77 | }, 78 | methods:{ 79 | async init() { 80 | this.list = await getListData().catch(e => []) 81 | }, 82 | // 跳转详情页 83 | goDetail() { 84 | // 获取页面卷动值并存储到sessionstorage 85 | const scrollTop = document.documentElement.scrollTop || document.pageYoffset || document.body.scrollTop || 0 86 | sessionstorage.setItem('listscrollTop', scrollTop) 87 | this.$router.push(name: 'Detail') 88 | }, 89 | // 返回到首页 90 | back() { 91 | this.isRefreshData = true 92 | this.$router.push(name: 'Home') 93 | } 94 | }, 95 | beforeRouteEnter(to, from ,next) { 96 | // 从详情页来 97 | if(from.path === 'detail') { 98 | // 放行的回调:拿到sessionstorage中的卷动值并滚动到这个高度 99 | next(vm =>{ 100 | const listScrollTop = sessionstorage.getItem('listscrollTop', scrollTop) 101 | vm.$nextTick(() => { 102 | scrollT(0, listScrollTop || 0) 103 | }) 104 | }) 105 | } 106 | next() 107 | } 108 | ``` 109 | 110 | ## 总结 111 | 112 | 只有**第一次**进入页面时才会触发**mounted生命周期**,后来**再进入**都是触发的**activated懒加载激活生命周期。** 113 | 可以根据这个特性,业务逻辑设置成只有从首页进入列表才刷新页面,详情页返回用懒加载的缓存数据不做刷新,这样就可以实现数据流正常以及原进原出,并且提升应用性能。 114 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/listShortSlideEnterDetail.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Android环境下列表短距离滑动进入详情页bug 3 | date: 2024-01-04 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - 兼容性适配 8 | --- 9 | 10 | ## 问题 11 | 12 | 在列表短距离滑动结束后会触发点击事件跳转至详情页,经复现,ios无此问题,安卓存在该问题。 13 | 14 | 原因:出现该问题的罪魁祸首是fastclick。 15 | 16 | ```js 17 | // console.log日志 18 | touchstart 19 | (3次)touchmove 20 | touchend 21 | list点击事件触发 22 | 外部被点击 23 | ``` 24 | 25 | 上方的打印日志为一次短距离快速滑动后触发的整个流程,由外部touchstart -> touchmove -> touchend ->列表click-外层click。应用项目均依赖了@quasar/fastclick,从而猜刻 fastclick对touchend和click事件做了处理。
26 | fastclick 的思路是,利用touch来模拟 tap触摸,如果判断为一次有效的tap,则在 touchend触发完成后立刻模拟一次click事件分发。 27 | 28 | ```js 29 | //fastclick.lib.js 30 | this.touchBoundary = options.touchBoundary || 10; 31 | 32 | FastClick.prototype.touchHasMoved = function(event) { 33 | var touch = event.changedTouches[0], boundary = this.touchBoundary; 34 | // 如果移动距离大于10px才判定为移动了 35 | if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) { 36 | return true; 37 | } 38 | 39 | return false; 40 | }; 41 | 42 | FastClick.prototype.onTouchMove = function(event) { 43 | if (!this.trackingClick) { 44 | return true; 45 | } 46 | 47 | // If the touch has moved, cancel the click tracking 48 | if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) { 49 | this.trackingClick = false; 50 | this.targetElement = null; 51 | } 52 | 53 | return true; 54 | }; 55 | 56 | Fastclick.prototype.onTouchEnd = function(event) { 57 | if(!this.trackingClick) { // 判断是否是点击标记,下面会讲 58 | return true 59 | } 60 | } 61 | // ... 62 | 63 | if(!this.needsClick(targetElement)) { 64 | event.preventDefault() 65 | this.sendClick(targetElement, event) // 此处触发点击 66 | } 67 | ``` 68 | 69 | 通过打印日志发现,只要触发了列表元素的click事件,一定会触发sendClick。然而在onTouchMove中有这样一段注释: 70 | `// If the touch has moved,cancel the click tracking 即只要触发了滑动,点击标记就会置为false` 71 | 72 | 所以问题出现在这个判断错误了,安卓的短滑动滑动误判为没有滑动点击标记,并没有置反。源码中,判断此处的代码,有一部分是比较touch.pageY的移动值,并设定了一个界限10(10像素)。发现在安卓上,短距离的滑动,有概率出现小于10的情况,而ios几乎都是大于10,可能是ios像素分辨率特别高?所以安卓出现了误判,从而进入sendClick中触发点击事件。 73 | 通过用不同的机型,打开开发者模式中的纵向像素值发现,分辨率越低,同样的10像素,手指滑动距离越大。 74 | 故猜测安卓手机分辨率比苹果低,所以同样的10像素,安卓滑动一小段距离就能达到,故而触发了点击时间误判,而苹果则没有这个问题。 75 | 76 | ## 解决方案 77 | 78 | 1. 根据源码和官网提示,当不想意外的触发模拟的点击事件,而交给原生页面去做,则在点击事件的元素上,添加`class="needClick"`事件,在范围内取消fastclick,此时滑动和点击事件均正常触发。 79 | 80 | 2. this.touchBoundary数值不再设置成10px,再设置大一些即可。 81 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/location.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: location.search失效 3 | date: 2024-06-15 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | ## 背景 11 | 12 | 使用浏览器原生方法location.search获取路由参数时,不能正常获取。 13 | 14 | ## 原因 15 | 16 | 我通过F12打开浏览器控制台,输入location.search准备验证一下 17 | 18 | 当浏览器地址是`https://www.bilibili.com/video/BV1Sa4y1Z7B1/?p=12时` 19 | 20 | ```js 21 | '?p=12' 打印内容 22 | ``` 23 | 24 | 可以看到成功获取到了?后的参数 25 | 26 | 当浏览器地址是`https://www.bilibili.com/video/#BV1Sa4y1Z7B1/?p=12`时 27 | 28 | ```js 29 | '' // 打印内容 30 | ``` 31 | 32 | ## 总结 33 | 34 | location.search能不能获取到浏览器链接中?后的参数还是看项目中设置的**路由模式,如果是history模式,可以成功获取到;如果是hash模式,则获取不到。** 35 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/lotsOfFormsValidate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 多个表单同步校验,且表单间数据联动 3 | date: 2025-06-12 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | > 通常一个页面都是一个表单,如果需要多个表单的时候,该怎么处理呢? 11 | 12 | 13 | 14 | ## 背景 15 | 16 | 在项目中一个录入页面中需要实现多个Form表单,分别叫交易信息表单、付款信息表单、其他信息表单,并在页面提交时需要对多个Form表单进行校验,多个表单都校验成功时才能提交。 17 | 18 | ## 分析 19 | 20 | 1. 每个表单里的逻辑代码都较多,所有把三个表单都封装成一个单独的Form,每个表单上都设置单独的model和ref,不能同时使用,否则每个表单上的校验提示会失效。 21 | 22 | ```vue 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | 43 | 2. 存入与读取:三个表单内数据联动,如果联动字段较少,直接把字段存入到vuex中,每个表单都可以使用。如果联动字段很多,则要把整个form存进去,同时引入其他两个表单,在formA中引入formB和formC,在formB中引入formA和formC等。
44 | 修改:在vue中定义一个方法:`updateFormData`,payload为一个对象`{form: xxxForm, value: xxx}`,`state[payload[form]] = payload[value]`,每个表单修改时也同步传入表单名和值的对象,三个表单只用调这一个方法就能改变vuex里的各表单值。 45 | 46 | 3. 提交时先获取各个表单的dom,调用表单的validated校验方法,由于validate方法是一个Promise,同步校验就可以使用Promise.all方法。 47 | 48 | ```vue 49 | 61 | 62 | 92 | ``` 93 | 94 | ## 总结 95 | 96 | 联动方式就是存入整个表单,引入其他两个表单,更新也是更新整个表单。
97 | 同步校验时获取各个表单组件最里层form的validate校验方法后,用Promise.all方法一起提交校验。 98 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/mock.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mock拦截携带参数的url地址 3 | date: 2023-04-30 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Mock 8 | --- 9 | 10 | mock只能拦截确定的url地址,拦截到不到带参数的url,如果一定要传参数则要加个正则进行匹配,一共有两种方式: 11 | 12 | 1.Mock.mock(/\/api\/user\/getUser/, user.getUserList) 13 | 14 | 需要注意的是, 是把url转化成正则表达式,不能在url外再加‘’包裹 15 | 16 | 2.Mock.mock(RegExp('/api/user/getUser' + '.*'), user.getUserList) 17 | 18 | RegExp()方法中的url则需要加引号了,另外拼接'.*' 19 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/npmITypeShaError.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'npm i 报错 sha1-xxxx= integrity checksum failed when using sha1: wanted sha1-xxxx but got sha512-xxx' 3 | date: 2023-04-26 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Webpack 8 | --- 9 | 10 | > 升级了服务组件后,npm i时一直报错 11 | 12 | 13 | 14 | ## 场景 15 | 16 | 今天在公司开发时,因为任务涉及到了servic-plugin服务组件的升级,我在package.json中替换版本后,npm i一直报错 17 | 18 | 报错信息如下: 19 | 20 | ```Terminal 21 | npm ERR! code EINTEGRITY 22 | npm ERR! Verification failed while extracting eslint-plugin-react@7.20.4: 23 | npm ERR! Verification failed while extracting eslint-plugin-react@7.20.4: 24 | npm ERR! sha1-wU0mMSIexpTd2EVX1xUvRLZuSqA= integrity checksum failed when using sha1: wanted sha1-wU0mMSIexpTd2EVX1xUvRLZuSqA= but got sha512-txbo090buDeyV0ugF3YMWrzLIUqpYTsWSDZV9xLSmExE1P/Kmgg9++PD931r+KEWS66O1c9R4srLVVHmeHpoAg== sha1-BZBSXn64OJDOcfc8LPg2KErYwvE=. (141152 bytes) 25 | 26 | npm ERR! A complete log of this run can be found in: 27 | npm ERR! D:\nodejs\node_cache\_logs\2022-07-25T05_53_42_186Z-debug.log 28 | ``` 29 | 30 | 询问同时后告诉我因为此次安装的版本的测试版本,测试版本没有拆包版本会报错 31 | 32 | ## 解决方案 33 | 34 | 到package-lock.json 搜索 sha1 `-wU0mMSIexpTd2EVX1xUvRLZuSqA=` 报错地方 35 | 36 | ![img](https://img-blog.csdnimg.cn/32ef9862061a415999817db9bdbafddc.png) 37 | 38 |  更改成报错地方的值 39 | 40 | ![img](https://img-blog.csdnimg.cn/3e5f8cfae23b411fb318f0274c49474d.png) 41 | 42 |  然后重新install 43 | 44 | ![img](https://img-blog.csdnimg.cn/e8eda1cc602d4b47a077c109ad21f040.png) 45 | 46 |  成功! 47 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/productItemFresh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 返回原页面数据不刷新问题 3 | date: 2023-03-31 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - JS 8 | --- 9 | 10 | > 为什么从一个页面点击进入另一个页面更新状态再返回原页面后不刷新? 11 | 12 | ## 问题 13 | 14 | 当在矩阵列表点击产品进入产品详情页时,在详情页中添加收藏,返回矩阵页后,矩阵列表产品收藏状态没有更新 15 | 16 | ## 解决方案 17 | 18 | 1. ProductItem组件中点击收藏按钮时,调用自定义事件`this.$emit('updateCollectionList')` 19 | 20 | 2. ProductList组件中在data中创建一个`collectionList`的数组用来存储收藏列表 21 | 22 | 3. ProductList组件接收自定义事件并在methods中定义一个自定义事件的回调函数,函数中调用查询收藏列表的接口,返回值赋值给`collectionList` 23 | 24 | 4. watch中监听collectionList的变化,一但变化了就拿collectionList的productId和存储所有产品的listData中的productId作比较,如果全等了,修改收藏值为true,反之则为false 25 | 26 | 5. 在Matrix组件中给ProductList组件打上ref,在created钩子中使用封装的ViewWillAppear方法,在返回页面的时候直接调用`this.refs.productList.updateCollectionList`来刷新响应值 27 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/publicAndAssets.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue 中加载图片 public 和 src/assets 文件夹下的区别 3 | date: 2023-05-01 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Webpack 8 | --- 9 | 10 | 11 | 12 | 遇坑:今天在组件中加载放在public文件夹下的图片时,加载不出来 13 | 14 | 错误示例: 15 | 16 | ```template 17 | 18 | ``` 19 | 20 | 经过我查阅资料尝试后发现: 21 | 22 | ### 一、public 文件夹 23 | 24 | * 路径设置时无需添加 public/,默认加载 public 文件夹下的图片。 25 | * 打包构建后,直接加载 26 | * 系统编译后,可替换图片; 27 | 28 | ```template 29 | 30 | ``` 31 | 32 | ### 二、src/assets 文件夹 33 | 34 | * 系统编译后,不能替换,因为被内部编译管理 35 | * 打包构建后,会被编译成js文件系统编译后,不能替换,因为被内部编译管理 36 | 37 | ```template 38 | 39 | ``` 40 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/rewritePushAndReplace.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 重写VueRouter的push/replace方法,解决路由跳转相同路径抛出NavigationDuplicated的警告错误 3 | date: 2023-04-09 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | > 编程式导航路由跳转到当前路由(参数不变), 多次执行会抛出NavigationDuplicated的警告错误? 11 | 12 | ## 问题 13 | 14 | 编程式导航[路由跳转](https://so.csdn.net/so/search?q=%E8%B7%AF%E7%94%B1%E8%B7%B3%E8%BD%AC&spm=1001.2101.3001.7020)到当前路由(参数不变), 多次执行会抛出NavigationDuplicated的警告错误? 15 | 16 | 注意:编程式导航(push|replace)才会有这种情况的异常,声明式导航是没有这种问题,因为声明式导航内部已经解决这种问题。 17 | 18 | ![img](https://img-blog.csdnimg.cn/e6dd718055b94ffa931bd7c01faa5387.png) 19 | 20 | 这种异常,对于程序运行没有任何影响。 21 | 22 | 为什么会出现这种现象: 23 | 24 | 由于[vue-router](https://so.csdn.net/so/search?q=vue-router&spm=1001.2101.3001.7020)最新版本3.5.2,引入了promise,push、replace方法会返回一个Promise。当传递参数多次且重复,或是没有写成功或失败的回调。会抛出异常,因此出现上面现象 25 | 26 | ## 解决方案 27 | 28 | 第一种解决方案:是给push和replace方法,传入相应的成功的回调与失败的回调 29 | 30 | ```vue 31 | this.$router.push({name:"search",params:{keyword:this.keyword},query:{this.keyword.toUpperCase()}},()=>{},()=>{}) 32 | ``` 33 | 34 | 第一种解决方案可以暂时解决当前问题,治标不治本,但是以后再用push|replace还是会出现类似现象,因此我们需要从‘根’治病; 35 | 36 | 第二种解决方案:重写$router的push和replace方法 37 | 38 | 首先我们要清楚,在组件中: 39 | 40 | this:表示当前组件实例对象(search组件,实质是Vuecomponent实例对象) 41 | 42 | this.$router属性:表示的是[VueRouter](https://so.csdn.net/so/search?q=VueRouter&spm=1001.2101.3001.7020)的一个实例。在入口文件main.js注册路由时,给每个组件身上都加了$route|$router属性 43 | 44 | this.$router.push()方法:实际上是VueRouter这个构造函数的原型对象身上的方法(即VueRouter.prototype的方法) 45 | 46 | 我们使用this.$router.push()方法时,方法内部代码执行的上下文为VueRouter的一个实例(即用this.$router.push()和VueRouter.prototype.push()时,函数体内的this均指向VueRouter的一个实例,故重写push|replace方法时需要将this重新指向VueRouter实例) 47 | 48 | ```vueRouter.js 49 | // 重写push|replace方法 50 | //先把VueRouter的push和replace方法保存一份 51 | let originPush = VueRouter.prototype.push; 52 | let originReplace = VueRouter.prototype.replace; 53 | VueRouter.prototype.push = function (location, resolve, reject) { 54 | // 此函数上下文(this指向)为VueRouter的一个实例 55 | if (resolve && reject) { //如果我们自己指定了成功/失败的回调,则自己传入 56 | originPush.call(this, location, resolve, reject) 57 | //若此时直接使用originPush()方法,则函数内的this指向window(内部代码将无法执行)。故应用call或apply方法修改this指向 58 | } else { //如果我们没有指定成功/失败的回调,则自动帮我们生成,防止报错 59 | originPush.call(this, location, () => { }, () => { }) 60 | } 61 | } 62 | ``` 63 | 64 | ```vueRouter.js 65 | // replace方法同理 66 | VueRouter.prototype.replace = function (location, resolve, reject) { 67 | if (resolve && reject) { 68 | originReplace.call(this, location, resolve, reject) 69 | } else { 70 | originReplace.call(this, location, () => { }, () => { }) 71 | } 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/sonRouterPath.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue之路由嵌套(子路由)注意“/“斜杆问题 3 | date: 2023-04-08 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | > 在Vue项目中配置嵌套多级路由时很大可能会因为子路由路径问题抛出报错 11 | 12 | ## 说明 13 | 14 | 如果 路由组件 是一个另一个 路由组件 的 子路由组件 的话: 15 | 16 | 在配置路由规则是,path 路径,前面不能添加 '/', 它代表根路径;加上他就不会拼接上父路由组件的 path 路径的,如: 17 | 18 | ## 代码段 19 | 20 | ```vue 21 | import Home from '../views/Home.vue' 22 | import New from '../views/New.vue' 23 | import Message from '../views/Message.vue' 24 | 25 | const router = new VueRouter({ 26 | routers:[ 27 | { 28 | path: '/home', 29 | component: Home, 30 | children:[ 31 | // 要么全写 32 | { path: '/home/new', component: New }, 33 | 34 | // 简写前面不能加'/',不然加载不出来 35 | ------ { path: '/message', component: Message }, ---- //错误示例!!! 36 | 37 | { path: 'message', component: Message }, 38 | ] 39 | } 40 | ] 41 | }) 42 | ``` 43 | 44 | ```vue 45 | view > Home.vue 46 | 47 | 58 | ``` 59 | 60 | ![image example](https://img-blog.csdnimg.cn/20201104112343176.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4Zl9DTg==,size_16,color_FFFFFF,t_70#pic_center) 61 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/swiperLoopError.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: swiper轮播图loop循环失效bug解决(watch+nextTick) 3 | date: 2023-04-09 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | ## 示例 11 | 12 | template渲染页代码如下: 13 | 14 | ```vue 15 | 16 | 31 | ``` 32 | 33 | ## 分析 34 | 35 | 可以看到,轮播图中的数据是由后台拿到的。 36 | 37 | 一开始的bug:我在mounted里创建了[swiper](https://so.csdn.net/so/search?q=swiper&spm=1001.2101.3001.7020)实例,改变dom,无法循环! 38 | 39 | 尝试解决:我最开始先尝试着在mounted钩子中加了this.$nextTick,this.$nextTick中创建swiper实例,但问题并没有解决。我去了官网搜索了mounted API,发现官网中写着**该钩子在服务器端渲染期间不被调用。** 40 | 又搜索了为什么mounted+this.$nexttick解决不了,答案是**代码运行到mounted钩子函数时,页面元素还没加载,dom还没有渲染完成,所以在nextTick里获取不到dom元素,一般nextTick是和watch一起用的。** 41 | 42 | 原因分析:swiper中的loop是要复制第一张和最后一张图片的,也就是说要拿到服务器端返回的图片数据,而**v-for循环拿到数据渲染模板引擎是异步的,不会等到list里有了数据再去遍历**。也就是说此时数据还没有存储到本地就开始遍历了,遍历了一个空数组。 43 | 44 | 解决方案:把swiper实例放在了watch监听里,加上this.$nextTick,问题解决。监听到了list有了数据再去创建实例,可以保证此时v-for遍历的是有数据的数组,自然可以解决没有那两张复制图片,loop循环失效的bug。 45 | 46 | 代码如下: 47 | 48 | ```vue 49 | 88 | ``` 89 | 90 | 总结: 91 | v-for循环渲染是异步的,发请求也是异步的,而new一个swiper实例是需要改变dom的,也就是需要数据,否则就会失败! 92 | 93 | mounted钩子:该钩子函数在服务器端渲染期间不被调用! 94 | 95 | vm.$nextTick:回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。 96 | 97 | mounted中调用vm.$nextTick:代码运行到mounted钩子函数时,页面元素还没加载,dom还没有渲染完成,所以在nextTick里获取不到dom元素,一般 98 | vm.$nextTick是和watch一起用的。 99 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/validateAndCallback.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: elementUI自定义表单校验规则的两个大坑(callback和return) 3 | date: 2025-06-15 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | > elementUI表单校验时拿不到校验结果是为什么? 11 | 12 | 13 | 14 | 正常情况下,我们使用elementUI的时候会采用自定义表单校验,然后会自定义校验规则。 15 | 16 | 但是在自定义规则校验实践过程中,发现会有两个大坑!!!下面会详细解释 17 | 18 | + **自定义校验规则中不能直接写return,比如`if(!value)return;`必须返回一个回调函数callback()(返回callback的入参为空代表校验通过规则,返回含new Error(‘自定义提示’)入参代表校验不通过规则。)** 19 | + **必须保证自定义校验规则的每个分支都调用了callback方法,否则会导致el-form组件的validate方法无法进入回调函数。** 20 | 21 | **重点来了,特别注意自定义规则这个方法的内容!!!!** 22 | 23 | ```vue 24 | 25 | 26 | 27 | 28 | // 自定义校验规则!!! 29 | var checkAge = (rule, value, callback) => { 30 | if (!value) { 31 | return callback(new Error('年龄不能为空')); 32 | } 33 | if (value < 18) { 34 | callback(new Error('必须年满18岁')); 35 | // 注意,如果这里把else分支去掉,也就是没有callback()回调,会发现,保存不成功! 36 | } else { 37 | callback(); 38 | } 39 | }; 40 | // 提交表单,进行保存校验 41 | saveAndSubmit: function() { 42 | let loading = this.$loading() 43 | console.log("进入校验前") 44 | this.$refs['model'].validate((valid) => { 45 | console.log("准备校验") 46 | if (valid) 47 | .......省略代码 48 | }) 49 | } 50 | ``` 51 | 52 | ##### 第一坑: 53 | 54 | 比如`if(!value) return callback(new Error('年龄不能为空'));` 55 | 56 | 一定要写callback,如果你想写成`if(!value) { alert("年龄不能为空"); return ; }`这种方式是不行的! 57 | 58 | ##### 第二坑: 59 | 60 | 自定义校验方法每个分支必须要调用callback方法,不然会导致el-form组件的validate方法无法进入回调函数!! 61 | 62 | 比如将下面的else去掉,会发现在提交表单的时候,不会进入回调函数,**也就是下面的`console.log("准备校验")`不会输出!!!** 63 | 64 | ```vue 65 | if (value < 18) { 66 | callback(new Error('必须年满18岁')); 67 | // 注意,如果这里把else分支去掉,也就是没有callback()回调,会发现,保存不成功! 68 | } else { 69 | callback(); 70 | } 71 | 72 | saveAndSubmit: function() { 73 | let loading = this.$loading() 74 | console.log("进入校验前") 75 | this.$refs['model'].validate((valid) => { 76 | console.log("准备校验") 77 | if (valid) 78 | .......省略代码 79 | }) 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /blog/programdifficulty/vue/vue3ImportIcon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue3动态引入Element-plus icon图标 3 | date: 2023-05-14 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - Vue 8 | --- 9 | 10 | 直接贴代码 11 | 12 | list数据 13 | 14 | ```vue3 15 | const list = reactive([ 16 | { 17 | path: "/user", 18 | name: "user", 19 | label: "用户管理", 20 | icon: "user", 21 | url: "UserManage/UserManage" 22 | }, 23 | { 24 | label: "其他", 25 | icon: "location", 26 | path: "/other", 27 | children: [ 28 | { 29 | path: "/page1", 30 | name: "page1", 31 | label: "页面1", 32 | icon: "setting", 33 | url: "Other/PageOne" 34 | }, 35 | { 36 | path: "/page2", 37 | name: "page2", 38 | label: "页面2", 39 | icon: "setting", 40 | url: "Other/PageTwo" 41 | } 42 | ] 43 | } 44 | ]) 45 | ``` 46 | 47 | 动态引入icon图标: el-icon包裹(不包裹的话图标会很大,需要设置样式),用动态组件的方式引入图标 48 | 49 | ```vue 50 | 51 | 52 | 53 | 54 | {{ item.label }} 55 | 56 | ``` 57 | -------------------------------------------------------------------------------- /blog/studyprogress/htmlcss/cssWriteOrder.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-03-26 3 | title: CSS属性书写顺序 4 | category: frontend 5 | tags: 6 | - HTML/CSS 7 | --- 8 | 9 | 1 .布局定位属性:display / position / float / clear / visibility / overflow ( 建议 display 第一个写,毕竟关系到模式) 10 | 2 .自身属性:width / height/ margin / padding / border / background 11 | 3 .文本属性:color / font / text-decoration/ text-align/ vertical-align/ white- space / break-word 12 | 4 .其他属性(CSS3 ) : content/cursor / border-radius/ box-shadow / text-shadow/ background:linear-gradient… 13 | 14 | 如: 15 | 16 | ```css 17 | .example { 18 | display: block 19 | position: relative 20 | float: left 21 | width: 100px 22 | height: 100px 23 | margin: 0 auto 24 | padding: 10px 25 | font-family: Arial, Helvetica, sans-serif 26 | color: #333 27 | background: rgba(0, 0, 0, 5) 28 | border-radius: 10px 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /blog/studyprogress/htmlcss/elementDeifference.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: HTML之块元素、行内块元素、行内元素的认识和区别 3 | date: 2023-03-23 4 | author: XiaoChen 5 | category: frontend 6 | tags: 7 | - HTML/CSS 8 | --- 9 | 10 | ## 1.**块元素** 11 | 12 | * 总是在新行上开始; 13 | 14 | * 高度,行高以及外边距和内边距都可控制; 15 | 16 | * 宽度默认是它的父级的100%,除非设定一个宽度。 17 | 18 | * 它可以容纳内联元素和其他块元素 19 | 20 | 21 | 常见的块级元素有:`

~

,

,

,