├── .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 | [](https://postimg.cc/WFH9YWW5)
61 | [](https://postimg.cc/hXvQjGqm)
62 | [](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 | 
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 | 
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 |
73 |
74 |
75 |
76 |
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 |
101 |
102 | 文章总数:{{count}}
103 | 分类:{{$category.length}}
104 | 标签: {{$tag.length}}
105 |
106 |
107 |
108 |
120 |
121 |
124 | ```
125 |
126 | `$category`、`$tag`均为`blog`插件暴露到全局的计算属性,通过它俩我们很容易就得到了分类和标签的统计;文章总数则是通过默认的全局计算属性`$site`拿到全部的文档,然后再过滤id为post的文章进行计数。
127 |
128 | 运行结果如下:
129 |
130 | 
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 | 
46 |
47 | 上图是模板文件和布局的示意图, 可以看出首页由3个模板文件`SideBar.vue`、`FooterBar.vue`、 `Home.vue` 和默认的 `Layout.vue` 布局组件 共同构成。
48 |
49 | ### SideBar组件
50 |
51 | ``` vue
52 |
53 |
54 | SideBar {{$site.title}}
55 |
56 |
57 |
58 |
66 | ```
67 | ### Home组件
68 |
69 | ``` vue
70 |
71 | Home
72 |
73 | ```
74 | ### FooterBar组件
75 |
76 | ``` vue
77 |
78 |
81 |
82 | ```
83 |
84 | ### Layout组件
85 |
86 | ``` vue
87 |
88 |
89 |
90 |
91 |
92 |
93 |
103 | ```
104 |
105 | ## 最终渲染出来的首页HTML
106 |
107 | ``` html
108 |
109 |
110 | SideBar hello vuepress
111 |
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 | 
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 | 如图:
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 | 
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 | 
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 | 
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 | 
18 |
19 | 然后我们查看一下浏览器,
20 |
21 | 
22 |
23 | 啥也不是,,,,我们继续
24 | 所以我们要把他转换成字符串再存进去,把JSON.stringify()它加上看看效果。
25 |
26 | 
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 | 
19 |
20 | 
21 |
22 | 
23 |
24 | 我当时认为,如果两个对象有相同的属性,以及它们的属性有相同的值,那么这两个对象就相等。
25 |
26 | 后来一步步实践发现是对象不能等于对象,无论使用"=="还是"===",都返回false。
27 |
28 | 
29 |
30 | 主要原因是基本类型string,number通过值来比较,而对象(Date,Array)及普通对象通过指针指向的内存中的地址来做比较。
31 |
32 | ## 解决方案
33 |
34 | 所以如果判断两个对象的值是否相等,就得看对象属性对应的值是否相同。
35 |
36 | 
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 | 打印结果是: 
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 | 打印结果是:
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 | > 
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 | 
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 | 
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 | 
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 |
71 |
72 |
73 |
74 |
75 |
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 | 
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 | [](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 |
38 |
39 | // 如果路由meta配置项中有keepAlive,即表示开启了路由懒加载
40 |
41 |
42 |
43 | // 没有路由懒加载
44 |
45 |
46 |
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 |