[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fIsMLz_iA6HHOcur4pbz0zkIY_T8kZg9CC5R8_8rKnD4":3,"donations-sidebar":9,"sidebar-data":10,"posts-{\"page\":1,\"pageSize\":10,\"categoryId\":\"9ed9827c-9cbb-42da-80e4-d04c7fdba886\"}":353},{"id":4,"name":5,"slug":5,"description":5,"sortOrder":6,"createdAt":7,"updatedAt":8},"9ed9827c-9cbb-42da-80e4-d04c7fdba886","开发工具",9,"2022-06-21T03:35:05.000Z","2023-02-08T02:49:14.000Z",[],{"categories":11,"tags":95,"postCount":279,"tagCount":280,"hotPosts":281},[12,20,26,32,38,45,52,56,63,67,68,74,80,85,89],{"id":13,"name":14,"slug":15,"description":16,"sortOrder":17,"createdAt":18,"updatedAt":18,"postCount":19},"e8d0bd45-d10c-46d3-8afb-0c072df7f8a7","技术","tech","技术文章",0,"2026-06-27T04:18:37.371Z",1,{"id":21,"name":22,"slug":23,"description":22,"sortOrder":19,"createdAt":24,"updatedAt":8,"postCount":25},"15ac46ad-edf7-4435-9a64-ff78117d58c5","Vue3 生态","vue3-生态","2022-05-21T08:05:39.000Z",6,{"id":27,"name":28,"slug":29,"description":28,"sortOrder":30,"createdAt":31,"updatedAt":8,"postCount":6},"11d4d397-685c-4180-a7b3-9b0e3a1e411e","Css","css",2,"2022-05-23T07:19:37.000Z",{"id":33,"name":34,"slug":34,"description":34,"sortOrder":35,"createdAt":36,"updatedAt":8,"postCount":37},"d10456a5-e649-4741-a38f-f07f266ce5f2","开发环境",3,"2022-05-24T01:52:41.000Z",13,{"id":39,"name":40,"slug":41,"description":40,"sortOrder":42,"createdAt":43,"updatedAt":8,"postCount":44},"5ed5cc62-43ea-49a2-b0b2-38bc7aae52a0","Vue3","vue3",4,"2022-05-24T01:55:05.000Z",8,{"id":46,"name":47,"slug":48,"description":47,"sortOrder":49,"createdAt":50,"updatedAt":8,"postCount":51},"da130ba9-d4f4-49f3-aa0f-149078097ef0","JavaScript","javascript",5,"2022-05-24T02:22:57.000Z",18,{"id":53,"name":54,"slug":54,"description":54,"sortOrder":25,"createdAt":55,"updatedAt":8,"postCount":19},"d8cbe380-54b3-4a61-a12d-5438c2918574","限时优惠","2022-05-25T07:18:03.000Z",{"id":57,"name":58,"slug":59,"description":58,"sortOrder":60,"createdAt":61,"updatedAt":8,"postCount":62},"e0f3b8d8-cfe7-41fb-802b-a79699d95968","JavaScript插件","javascript插件",7,"2022-06-01T14:08:31.000Z",16,{"id":64,"name":65,"slug":65,"description":65,"sortOrder":44,"createdAt":66,"updatedAt":8,"postCount":25},"4ea3d8af-9cc3-49bb-a9cd-34dbcdc3bd85","构建工具","2022-06-02T07:28:13.000Z",{"id":4,"name":5,"slug":5,"description":5,"sortOrder":6,"createdAt":7,"updatedAt":8,"postCount":44},{"id":69,"name":70,"slug":71,"description":70,"sortOrder":72,"createdAt":73,"updatedAt":8,"postCount":49},"6b9179c3-17b2-43ff-a431-a03d6eb32d89","Vue2 生态","vue2-生态",10,"2022-07-16T13:14:29.000Z",{"id":75,"name":76,"slug":77,"description":76,"sortOrder":78,"createdAt":79,"updatedAt":8,"postCount":49},"73a5f62c-3c47-45b9-9ae2-f29953ae8dc0","Node","node",11,"2022-07-16T13:15:39.000Z",{"id":81,"name":82,"slug":82,"description":82,"sortOrder":83,"createdAt":84,"updatedAt":8,"postCount":30},"2b696c16-48ef-403b-a88b-6e57cfc79596","开发问题",12,"2022-07-16T14:06:54.000Z",{"id":86,"name":87,"slug":87,"description":87,"sortOrder":37,"createdAt":88,"updatedAt":8,"postCount":19},"c0f0561e-a47a-4ecd-8caa-cc1df2315d57","算法","2022-07-16T14:22:34.000Z",{"id":90,"name":91,"slug":92,"description":91,"sortOrder":93,"createdAt":94,"updatedAt":8,"postCount":35},"a629c1f7-29f1-439e-be3c-29670b17ba20","Vue2","vue2",15,"2022-07-16T14:41:51.000Z",[96,102,107,110,115,120,124,128,133,138,143,148,151,156,160,164,169,174,179,184,188,192,196,198,202,207,212,215,218,221,224,227,231,234,236,238,241,245,248,252,256,259,262,264,267,269,273,276],{"id":97,"name":98,"slug":99,"createdAt":100,"updatedAt":101},"076bd8b9-293e-45cb-9dc3-e162007ca474","Axios","axios","2022-06-05T07:41:56.000Z","2025-12-30T07:26:21.000Z",{"id":103,"name":104,"slug":105,"createdAt":106,"updatedAt":8},"2aa7f6d0-1fac-4ed1-b9bb-f3afc813f42c","Axure","axure","2022-06-21T03:35:15.000Z",{"id":108,"name":28,"slug":29,"createdAt":109,"updatedAt":8},"b084ddd8-09be-4e57-98f0-cf4e376aecd7","2022-05-21T09:59:55.000Z",{"id":111,"name":112,"slug":113,"createdAt":114,"updatedAt":8},"78a62bff-ff77-4878-8c25-3e6aae18c668","Docker","docker","2022-07-16T14:34:37.000Z",{"id":116,"name":117,"slug":118,"createdAt":119,"updatedAt":8},"2de16806-ef3f-4e54-a259-d1e1e182468c","Git","git","2022-07-16T14:25:15.000Z",{"id":121,"name":122,"slug":123,"createdAt":109,"updatedAt":8},"994cc226-578b-4a72-a57e-a47a63d2793e","JavaScript生态","javascript生态",{"id":125,"name":126,"slug":127,"createdAt":109,"updatedAt":8},"5086e93c-23b9-43d3-9643-cc87f0e9ee94","JenKins","jenkins",{"id":129,"name":130,"slug":131,"createdAt":132,"updatedAt":8},"b73007a8-bb5c-42a8-9fd9-163033a5b45d","Linux","linux","2022-07-16T14:40:17.000Z",{"id":134,"name":135,"slug":136,"createdAt":137,"updatedAt":8},"0b658b92-dd6b-4db3-a398-9f6d69950a02","Markdown","markdown","2022-07-16T14:39:25.000Z",{"id":139,"name":140,"slug":141,"createdAt":142,"updatedAt":8},"ab034d3a-6e5b-4db5-a2dc-faf4ccbb63f5","Nest","nest","2022-07-16T13:15:49.000Z",{"id":144,"name":145,"slug":146,"createdAt":147,"updatedAt":8},"52c41978-da06-4962-9636-45bbaeedda80","Nginx","nginx","2022-05-21T09:59:56.000Z",{"id":149,"name":150,"slug":150,"createdAt":147,"updatedAt":8},"0f1cc678-40e4-44b1-b2cf-a6fd8a1c867a","npm",{"id":152,"name":153,"slug":154,"createdAt":155,"updatedAt":8},"a4370d78-70e1-4073-a8f6-3dc5d81fd8fd","Nuxt","nuxt","2022-06-01T13:07:07.000Z",{"id":157,"name":158,"slug":159,"createdAt":109,"updatedAt":8},"d232e01f-048e-4151-8a0a-fff9561f946f","Pinia","pinia",{"id":161,"name":162,"slug":163,"createdAt":147,"updatedAt":8},"14e9ab02-b0bb-4c85-8604-fe6f1f0f33cd","Pnpm","pnpm",{"id":165,"name":166,"slug":167,"createdAt":168,"updatedAt":168},"399d1d38-cc0d-43ce-8baf-c769447a2ebd","React生态","react生态","2023-02-21T02:03:09.000Z",{"id":170,"name":171,"slug":172,"createdAt":173,"updatedAt":8},"c95bbe84-bdd0-410a-86a9-e87958c55f4f","Redis","redis","2022-10-05T05:14:14.000Z",{"id":175,"name":176,"slug":177,"createdAt":178,"updatedAt":8},"6d05f9df-e116-450f-af57-85ed710c4870","Swiper","swiper","2022-06-01T14:08:46.000Z",{"id":180,"name":181,"slug":182,"createdAt":183,"updatedAt":8},"66f3aeb0-84ef-45f6-a43a-944eefc9895a","Vite","vite","2022-06-02T07:28:24.000Z",{"id":185,"name":186,"slug":187,"createdAt":109,"updatedAt":8},"bf5b94d3-090b-4098-a03c-4bc69781fb2d","Vue","vue",{"id":189,"name":190,"slug":191,"createdAt":147,"updatedAt":8},"2f7fb1be-b9c5-4606-b54f-e9f66f2653b2","Vue-Router","vue-router",{"id":193,"name":194,"slug":195,"createdAt":109,"updatedAt":8},"2fef3b91-1c1c-4ae8-b2c1-0e04b4f9b3a2","Vue2生态","vue2生态",{"id":197,"name":40,"slug":41,"createdAt":109,"updatedAt":8},"62b94c93-724f-488d-8fc5-0449971d9204",{"id":199,"name":200,"slug":201,"createdAt":109,"updatedAt":8},"20bff9cd-7848-4c16-8775-42cf12b44b30","Vue3生态","vue3生态",{"id":203,"name":204,"slug":205,"createdAt":206,"updatedAt":8},"c807b2c6-cb12-4409-a1f1-6bea9f330a6b","Vuex","vuex","2022-07-16T13:14:59.000Z",{"id":208,"name":209,"slug":210,"createdAt":211,"updatedAt":8},"5782dff5-2ea2-4427-9696-d4363a7fd5bc","Webpack","webpack","2022-07-16T14:33:41.000Z",{"id":213,"name":214,"slug":214,"createdAt":109,"updatedAt":8},"d0aa41f4-68f8-48d4-a4ed-3a503ea90451","下载",{"id":216,"name":217,"slug":217,"createdAt":109,"updatedAt":8},"a046060c-39ef-474a-8c85-2546aca0e2e5","代码片段",{"id":219,"name":220,"slug":220,"createdAt":109,"updatedAt":8},"fee73435-b2be-4b55-85b1-d133ea96aea4","伪元素",{"id":222,"name":223,"slug":223,"createdAt":109,"updatedAt":8},"436bd369-8c57-4869-8827-e88e50e5e0ab","伪类",{"id":225,"name":226,"slug":226,"createdAt":109,"updatedAt":8},"4c6be544-8a00-4445-92a3-e3dcbaf6142e","动画",{"id":228,"name":229,"slug":229,"createdAt":230,"updatedAt":8},"9321a12e-ea72-49a9-a32d-5566149f812f","图片压缩","2022-08-02T00:37:47.000Z",{"id":232,"name":233,"slug":233,"createdAt":147,"updatedAt":8},"512b16fb-576a-4397-a7c5-dd20e6a8f9ca","布局",{"id":235,"name":5,"slug":5,"createdAt":109,"updatedAt":8},"f32faa96-f2ec-45c6-9a17-2c76062edcb0",{"id":237,"name":34,"slug":34,"createdAt":109,"updatedAt":8},"3c46ed3f-6d6b-4f91-bcb3-af5112860bf5",{"id":239,"name":240,"slug":240,"createdAt":109,"updatedAt":8},"dbfc086a-73a6-4560-814d-593acb61cf98","性能优化",{"id":242,"name":243,"slug":243,"createdAt":244,"updatedAt":8},"1831cd06-0d6b-48f7-94fa-324782fe23cb","拖拽","2022-07-28T12:39:13.000Z",{"id":246,"name":247,"slug":247,"createdAt":147,"updatedAt":8},"9a74300d-06f7-46d0-80d9-8fe67ec0539b","数组",{"id":249,"name":250,"slug":250,"createdAt":251,"updatedAt":8},"19ac8998-7e0a-459b-9702-bb1adca70e8c","文本复制","2022-07-17T01:54:45.000Z",{"id":253,"name":254,"slug":254,"createdAt":255,"updatedAt":8},"5ff33473-71a4-4e02-8c82-f9ea369a768f","时间","2022-07-17T01:51:12.000Z",{"id":257,"name":258,"slug":258,"createdAt":109,"updatedAt":8},"aa47ca4d-d3f6-4cac-b495-2c67c9592c36","最新优惠",{"id":260,"name":261,"slug":261,"createdAt":147,"updatedAt":8},"f6766d54-54fc-405e-932d-b7d550559125","服务器",{"id":263,"name":65,"slug":65,"createdAt":109,"updatedAt":8},"d856559a-03ff-40b4-980d-3f272b998c3c",{"id":265,"name":266,"slug":266,"createdAt":109,"updatedAt":8},"692d5d68-b188-4e5c-aca8-65d0229399a1","渐变",{"id":268,"name":87,"slug":87,"createdAt":109,"updatedAt":8},"38e1fd6b-d7c6-4d62-bf70-7bacc175bea9",{"id":270,"name":271,"slug":271,"createdAt":272,"updatedAt":8},"be7b10bc-49eb-4a03-bea7-ceb915d500fe","规范","2022-07-16T14:41:06.000Z",{"id":274,"name":275,"slug":275,"createdAt":147,"updatedAt":8},"b42e2916-ad62-4b8a-a863-cd8c19a829de","面试",{"id":277,"name":278,"slug":278,"createdAt":147,"updatedAt":8},"7069add9-b636-44f1-9cd4-ea3a6d2b85d3","面试题",104,48,[282,299,311,324,337],{"id":283,"title":284,"slug":284,"content":285,"excerpt":284,"coverImage":286,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":290,"publishedAt":291,"createdAt":291,"updatedAt":292,"category":293,"author":294,"tags":297},"a1bd1f49-6f6d-4fea-9789-b5636e19a6b3","uni-app瀑布流","## 实现思路\n> 获取父组件的列表数组，watch监听数组长度变化，截取后面新的数据，创建两个左右数组，比较左右dom的长度，哪个短，就push一条数据进去，源数组删除一条数据。利用img的load（加载成功）和error方法（加载失败），触发数组的push，实现瀑布流\n\n## 代码实现\n\n```language\n\u003Ctemplate>\n    \u003Cview class=\\\"waterfall\\\">\n        \u003Cview class=\\\"waterfall_left\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in leftList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n        \u003Cview class=\\\"waterfall_right\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in rightList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n    \u003C\u002Fview>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport Search from '@\u002Fcomponents\u002Fsearch\u002Fsearch.vue';\nexport default {\n    components: { Search },\n    props: {\n        list: {\n            type: Array,\n            default: () => []\n        }\n    },\n    data() {\n        return {\n            \u002F\u002F 左侧列表\n            leftList: [],\n            \u002F\u002F 右侧列表\n            rightList: [],\n            \u002F\u002F 组件数据备份\n            newList: [],\n            \u002F\u002F默认请求数,主要为了正常排序\n            interceptNumber: 10\n        };\n    },\n    created() {\n        this.touchOff(); \u002F\u002F 触发排列\n    },\n    mounted() {},\n    watch: {\n        list(newValue, oldValue) {\n            this.interceptNumber = newValue.length - oldValue.length;\n            this.touchOff();\n        }\n    },\n    computed: {},\n    methods: {\n        \u002F\u002F 触发重新排列\n        touchOff() {\n            this.newList = [...this.list.slice(-this.interceptNumber)];\n            if (this.newList.length !== 0) {\n                this.leftList.push(this.newList.shift()); \u002F\u002F触发排列\n            }\n        },\n        \u002F\u002F 计算排列\n        considerPush() {\n            this.$nextTick(() => {\n                if (this.newList.length == 0) return; \u002F\u002F没有数据了\n                let leftH = 0;\n                let rightH = 0; \u002F\u002F左右高度\n                let query = uni.createSelectorQuery().in(this);\n                query.selectAll('.waterfall_left').boundingClientRect();\n                query.selectAll('.waterfall_right').boundingClientRect();\n                query.exec(res => {\n                    leftH = res[0].length != 0 ? res[0][0].height : 0; \u002F\u002F防止查询不到做个处理\n                    rightH = res[1].length != 0 ? res[1][0].height : 0;\n                    if (leftH == rightH || leftH \u003C rightH) {\n                        \u002F\u002F 相等 || 左边小\n                        this.leftList.push(this.newList.shift());\n                    } else {\n                        \u002F\u002F 右边小\n                        this.rightList.push(this.newList.shift());\n                    }\n\n                    \u002F\u002F console.log('左右高度：', leftH, rightH, leftH == rightH || leftH \u003C rightH);\n                });\n            });\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle scoped lang=\\\"scss\\\">\n.waterfall {\n    display: flex;\n    align-items: flex-start;\n    justify-content: flex-start;\n    .waterfall_left {\n        flex: 1;\n    }\n\n    .waterfall_right {\n        flex: 1;\n    }\n}\n\u003C\u002Fstyle>\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F7d4fa8d1d2775177b882a4656e3a5ed5.png","PUBLISHED",false,null,1819,"2022-11-22T07:54:41.000Z","2026-06-27T11:37:20.140Z",{"id":46,"name":47,"slug":48},{"id":295,"name":296,"avatar":289},"f9d0f2de-c700-4f90-b535-afd3dbe78128","Admin",[298],{"id":121,"name":122,"slug":123},{"id":300,"title":301,"slug":301,"content":302,"excerpt":301,"coverImage":303,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":304,"publishedAt":305,"createdAt":305,"updatedAt":306,"category":307,"author":308,"tags":309},"b2c46bf6-d971-4cce-b21b-052dbea8e8a2","v-html使用img点击实现放大效果","## 代码实现\n```js\n\u002F**\n * JS获取html代码中所有的图片地址\n * @param htmlstr\n * @returns arr 数组\n *\u002F\n\nexport function getimgsrc(htmlstr) {\n    let reg = \u002F\u003Cimg.+?src=('|\\\")?([^'\\\"]+)('|\\\")?(?:\\s+|>)\u002Fg;\n    let arr = [];\n    let tem = 0;\n    \u002F\u002Feslint-disable-next-line\n    while ((tem = reg.exec(htmlstr))) {\n        arr.push(tem[2]); \u002F\u002F eslint-disable-line\n    }\n\n    return arr;\n}\n\n```\n\n\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\\\"image-expansion\\\" :class=\\\"classArr\\\">\n        \u003Cdiv @click.stop=\\\"hanldeImage($event)\\\" v-html=\\\"formatHtmlData\\\">\u003C\u002Fdiv>\n\n        \u003Cel-image-viewer\n            v-if=\\\"imgPreviewUrl\\\"\n            :initial-index=\\\"subscript\\\"\n            :src=\\\"imgPreviewUrl\\\"\n            :on-close=\\\"closeViewer\\\"\n            :url-list=\\\"imgList\\\"\n        >\u003C\u002Fel-image-viewer>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { getimgsrc } from '..\u002F..\u002Futils\u002Fgetimgsrc';\nimport ElImageViewer from 'element-ui\u002Fpackages\u002Fimage\u002Fsrc\u002Fimage-viewer';\nexport default {\n    components: {\n        ElImageViewer\n    },\n    props: {\n        htmlData: {\n            type: String,\n            default: () => {\n                return '';\n            }\n        },\n        classArr: {\n            type: Array,\n            default: () => {\n                return ['min'];\n            }\n        },\n\n        isArticle: {\n            type: Boolean,\n            default: () => {\n                return false;\n            }\n        }\n    },\n    data() {\n        return {\n            imgList: [],\n            formatHtmlData: '',\n            imgPreviewUrl: '',\n            subscript: 0\n        };\n    },\n    computed: {},\n\n    watch: {\n        \u002F\u002F监听数据，防止数据不更新\n        htmlData: {\n            handler(newName, oldName) {\n                \u002F\u002F判断是否为文章\n                if (this.isArticle) {\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C([^>]+)>)\u002Fgi, '').replace(\u002F[\\\r\\\n]\u002Fg, '')) : '';\n                } else {\n                    \u002F\u002F剔除strong和p标签\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C\\\u002F?strong.*?>)|(\u003C\\\u002F?p.*?>)\u002Fg, '')) : '';\n\n                    \u002F\u002F获取html全部图片，push成图片数组\n                    this.imgList = Object.values(getimgsrc(this.formatHtmlData));\n                    \u002F\u002F获取图片下标\n                    let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                    this.subscript = subscript > -1 ? subscript : 0;\n                }\n            },\n\n            immediate: true\n        }\n    },\n\n    mounted() {},\n\n    methods: {\n        \u002F\u002F监听点击事件\n        hanldeImage(event) {\n            if (event.target.nodeName === 'IMG' || event.target.nodeName === 'img') {\n                \u002F\u002F获取点击的图片url,decodeURIComponent转码一下，防禁url转码\n                this.imgPreviewUrl = decodeURIComponent(event.target.currentSrc);\n\n                \u002F\u002F获取图片下标\n                let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                this.subscript = subscript > -1 ? subscript : 0;\n\n                \u002F\u002F禁止遮罩层后面的内容滚动\n                document.documentElement.style.overflowY = 'hidden';\n            } else {\n                this.$emit('goDetail');\n            }\n        },\n\n        \u002F\u002F关闭弹框\n        closeViewer() {\n            this.imgPreviewUrl = '';\n            \u002F\u002F恢复遮罩层后面的内容滚动\n            document.documentElement.style.overflowY = 'auto';\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle lang=\\\"scss\\\" scoped>\n.image-expansion {\n}\n\n.min {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n        height: 28px;\n        padding: 0 10px 3px;\n    }\n}\n\n.max {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n    }\n}\n\n.class1 {\n    \u002Fdeep\u002F div {\n        font-size: 15px;\n        font-family: Microsoft YaHei;\n        font-weight: 400;\n        color: #888888;\n        line-height: 30px;\n    }\n}\n\u003C\u002Fstyle>\n\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fc77c3fb113d1ab2f67e7afba1ca33b95.png",1572,"2023-01-10T07:22:29.000Z","2026-06-27T11:07:40.499Z",{"id":69,"name":70,"slug":71},{"id":295,"name":296,"avatar":289},[310],{"id":228,"name":229,"slug":229},{"id":312,"title":313,"slug":314,"content":315,"excerpt":313,"coverImage":316,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":317,"publishedAt":318,"createdAt":318,"updatedAt":319,"category":320,"author":321,"tags":322},"9f33b49b-0785-4404-9a4e-d85937e7fc92","在 vue3 中优雅的使用 jsx\u002Ftsx","在-vue3-中优雅的使用-jsx-tsx","## 安装插件（@vitejs\u002Fplugin-vue-jsx）\n```shell\npnpm add @vitejs\u002Fplugin-vue-jsx -D\n```\n\n## 配置vite.config.ts\n```ts\nimport vueJsx from \\\"@vitejs\u002Fplugin-vue-jsx\\\";\n\nexport default defineConfig({\n  plugins: [\n    vueJsx(),\n  ]\n})\n```\n\n## 插值\n```language\n\u002F\u002F vue3模板语法\n\u003Cspan>{{ a + b }}\u003C\u002Fspan>\n\n\u002F\u002F jsx\u002Ftsx\n\u003Cspan>{ a + b }\u003C\u002Fspan>\n```\n\n\n## class与style 绑定\n\n```ts\n\u002F\u002F 模板字符串\n\u003Cdiv className={`header ${ isBg ? 'headerBg' : '' }`}>header\u003C\u002Fdiv>\n\u002F\u002F数组\n\u003Cdiv class={ [ 'header', isBg && 'headerBg' ] } >header\u003C\u002Fdiv>\n```\n\n```ts\nconst color = 'red'\nconst element = \u003Csapn style={{ color, fontSize: '16px' }}>style\u003C\u002Fsapn>\n```\n\n## 条件渲染\n\n```ts\n   setup() {\n       const isShow = false\n       const element = () => {\n           if (isShow) {\n               return \u003Cspan>我是if\u003C\u002Fspan>\n           } else {\n               return \u003Cspan>我是else\u003C\u002Fspan>\n           }\n       }\n       return () => (\n           \u003Cdiv>\n               \u003Cspan v-show={isShow}>我是v-show\u003C\u002Fspan>\n               {\n                   element()\n               }\n               {\n                   isShow ? \u003Cp>我是三目1\u003C\u002Fp> : \u003Cp>我是三目2\u003C\u002Fp>\n               }\n           \u003Cdiv>\n       )\n   }\n```\n\n## 列表渲染\n```language\nsetup() {\n   const listData = [\n       {name: 'Tom', age: 18},\n       {name: 'Jim', age: 20},\n       {name: 'Lucy', age: 16}\n   ]\n   return () => (\n       \u003Cdiv>\n           \u003Cdiv class={'box'}>\n               \u003Cspan>姓名\u003C\u002Fspan>\n               \u003Cspan>年龄\u003C\u002Fspan>\n           \u003C\u002Fdiv>\n           {\n               prop.listData.map(item => {\n                   return \u003Cdiv class={'box'}>\n                       \u003Cspan>{item.name}\u003C\u002Fspan>\n                       \u003Cspan>{item.age}\u003C\u002Fspan>\n                   \u003C\u002Fdiv>\n               })\n           }\n       \u003C\u002Fdiv>\n   )\n}\n\n```\n\n## 事件处理\n\n```language\nsetup() {\n    const clickBox = val => {\n        console.log(val)\n    }\n    return () => (\n        \u003Cdiv class={'box1'} onClick={() => clickBox('box1')}>\n            \u003Cspan>我是box1\u003C\u002Fspan>\n            \u003Cdiv class={'box2'} onClick={() => clickBox('box2')}>\n                \u003Cspan>我是box2\u003C\u002Fspan>\n                \u003Cdiv class={'box3'} onClick={withModifiers(() => clickBox('box3'), ['stop'])}>我是box3\u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    )\n}\n```\n\n## v-model\n\n```ts\n\u002F\u002F 正常写法\n\u003Cinput v-model=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={value} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 指定绑定值写法\n\u003Cinput v-model:modelValue=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue']} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 修饰符写法\n\u003Cinput v-model:modelValue.trim=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue',['trim']]} \u002F> \u002F\u002F jsx\n```\n\n## slot插槽\n\n### 定义插槽\n```ts\nimport { renderSlot } from \\\"vue\\\"\nexport default defineComponent({\n    \u002F\u002F 从ctx中解构出来 slots\n    setup(props, { slots }) {\n        return () => (\n            \u003Cdiv>\n                { renderSlot(slots, 'default') }\n                { slots.title?.() }\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n### 使用插槽\n\n```ts\nimport Vslot from '.\u002FslotTem'\nexport default defineComponent({\n    setup() {\n        return () => (\n            \u003Cdiv class={'box'}>\n                \u003CVslot v-slots={{\n                    title: () => {\n                        return \u003Cp>我是title插槽\u003C\u002Fp>\n                    },\n                    default: () => {\n                        return \u003Cp>我是default插槽\u003C\u002Fp>\n                    }\n                }} \u002F>\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F3e4cb0d3df611ee3f57b8ed503e1015e.png",1392,"2023-04-19T09:23:27.000Z","2026-06-27T11:08:34.303Z",{"id":39,"name":40,"slug":41},{"id":295,"name":296,"avatar":289},[323],{"id":197,"name":40,"slug":41},{"id":325,"title":326,"slug":327,"content":328,"excerpt":326,"coverImage":329,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":330,"publishedAt":331,"createdAt":331,"updatedAt":332,"category":333,"author":334,"tags":335},"1dcdd755-0e46-45a3-8998-9a213fd3fcd5","Vue3 导入导出Excel","vue3-导入导出excel","## 安装\n```shell\npnpm add -S XLSX\n```\n\n\n## 方法封装\n```js\nimport * as XLSX from 'xlsx';\n\n\u002F\u002F参数说明\n\u002F\u002Fconfiguration: {\n\u002F\u002F  data: [], \u002F\u002F 导出的数据\n\u002F\u002F  head: {}, \u002F\u002F 导出的数据对应的表头\n\u002F\u002F  name: '', \u002F\u002F 导出的文件名\n\u002F\u002F  label: '', \u002F\u002F 导出的表单名\n\u002F\u002F  widthArr: [], \u002F\u002F 导出的表单列宽\n\u002F\u002F}\n\n\u002F\u002F 导出excel\nexport const ExportXlsx = (configuration) => {\n  const { data, head, name, label, widthArr } = configuration;\n\n  const list = data.map((item) => {\n    const obj = {};\n    for (const k in item) {\n      if (head[k]) {\n        obj[head[k]] = item[k];\n      }\n    }\n    return obj;\n  });\n\n  \u002F\u002F 创建工作表\n  const xLSXData = XLSX.utils.json_to_sheet(list);\n  \u002F\u002F 创建工作簿\n  const wb = XLSX.utils.book_new();\n  \u002F\u002F 将工作表放入工作簿中\n  XLSX.utils.book_append_sheet(wb, xLSXData, label);\n  xLSXData['!cols'] = [];\n  \u002F\u002F 设置列宽\n  widthArr.forEach((item) => {\n    xLSXData['!cols'].push({ wpx: item });\n  });\n\n  \u002F\u002F 生成文件并下载\n  XLSX.writeFile(wb, `${name}.xlsx`);\n};\n\n\u002F\u002F 导入excel\nexport const ImportXlsx = (e) => {\n  const file = e.target.files[0];\n  const reader = new FileReader();\n  reader.readAsArrayBuffer(file);\n  reader.onload = (e) => {\n    const data = e.target.result;\n    const workbook = XLSX.read(data, { type: 'binary', cellDates: true });\n    const wsname = workbook.SheetNames[0];\n    const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);\n    console.log(outdata);\n  };\n};\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F80231a326d3846b895a7d46c99e738ce.png",1336,"2023-03-14T06:18:59.000Z","2026-06-27T11:37:23.273Z",{"id":46,"name":47,"slug":48},{"id":295,"name":296,"avatar":289},[336],{"id":121,"name":122,"slug":123},{"id":338,"title":339,"slug":340,"content":341,"excerpt":339,"coverImage":342,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":343,"publishedAt":344,"createdAt":344,"updatedAt":345,"category":346,"author":350,"tags":351},"80920598-a452-4357-bf53-842b200560e8","React18入门到精通教程","react18入门到精通教程","\n## JSX实现列表渲染\n> 使用`map()`方法遍历数组，必须添加`key`属性提高性能\n```jsx\nconst songs = [\n  { id: 1, name: \\\"helo1\\\" },\n  { id: 2, name: \\\"helo2\\\" },\n  { id: 3, name: \\\"helo3\\\" },\n];\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cul>\n        {songs.map((song) => (\n          \u002F\u002F 关键：添加唯一key标识符（避免使用索引）\n          \u003Cli key={song.id}>  \n            {song.id}-{song.name}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**最佳实践：**\n1. 使用`\u003Cul>`包裹列表项\n2. `key`应使用稳定唯一标识（如ID），避免数组索引\n3. 空列表处理：`{songs.length > 0 && ...}` 或 `{songs.map(...) || \u003CEmptyView\u002F>}`\n\n## JSX实现条件渲染\n### 简单逻辑\n```jsx\n\u002F\u002F 三元表达式\n{isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>}\n\n\u002F\u002F 逻辑短路\n{hasNotification && \u003CNotificationBadge count={5} \u002F>}\n\n\u002F\u002F 空值处理\n{userProfile?.avatar || \u003CDefaultAvatar \u002F>}\n```\n\n### 复杂逻辑\n```jsx\nconst renderContent = (type) => {\n  switch(type) {\n    case 'success': \n      return \u003CSuccessAlert \u002F>;\n    case 'error':\n      return \u003CErrorAlert \u002F>;\n    default:\n      return \u003CInfoAlert \u002F>;\n  }\n}\n\nfunction App() {\n  return (\n    \u003Cdiv className=\\\"container\\\">\n      {renderContent(status)}\n      \n      {\u002F* 另一种模式：立即执行函数 *\u002F}\n      {(() => {\n        if (isLoading) return \u003CSpinner \u002F>;\n        if (isEmpty) return \u003CEmptyState \u002F>;\n        return \u003CDataTable \u002F>;\n      })()}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n## JSX样式处理\n### 行内样式\n```jsx\n\u002F\u002F 直接对象\n\u003Cdiv style={{ \n  color: 'white', \n  backgroundColor: 'teal',\n  padding: '1rem'\n}}>\n\n\u002F\u002F 样式对象复用\nconst alertStyle = {\n  padding: '15px',\n  borderRadius: '4px',\n  margin: '10px 0'\n};\n\nfunction Alert({ type }) {\n  return (\n    \u003Cdiv style={{\n      ...alertStyle,  \u002F\u002F 扩展运算符合并样式\n      background: type === 'error' ? '#f8d7da' : '#d4edda'\n    }}>\n      {message}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### 类名控制（推荐）\n```css\n\u002F* styles.module.css *\u002F\n.card {\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  padding: 20px;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.highlight {\n  background-color: #ffffe0;\n}\n```\n\n```jsx\nimport styles from '.\u002Fstyles.module.css';\n\nfunction ProductCard({ featured }) {\n  return (\n    \u003Cdiv className={`${styles.card} ${featured ? styles.highlight : ''}`}>\n      {\u002F* 内容 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## React 18新特性\n### 并发模式（Concurrent Mode）\n```jsx\nimport { startTransition } from 'react';\n\n\u002F\u002F 非紧急状态更新\nfunction handleSearch(query) {\n  startTransition(() => {\n    setSearchQuery(query); \u002F\u002F 可中断的渲染\n  });\n}\n```\n\n### 自动批处理（Automatic Batching）\n```jsx\n\u002F\u002F React 17及之前：两次渲染\n\u002F\u002F React 18：自动批处理，一次渲染\nfunction handleClick() {\n  setCount(c => c + 1);\n  setFlag(f => !f);\n}\n```\n\n## Redux状态管理（现代写法）\n### 安装依赖\n```bash\nnpm install @reduxjs\u002Ftoolkit react-redux\n```\n\n### 创建Store\n```js\n\u002F\u002F store.js\nimport { configureStore, createSlice } from '@reduxjs\u002Ftoolkit';\n\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1 },\n    decrement: state => { state.value -= 1 },\n    incrementByAmount: (state, action) => {\n      state.value += action.payload\n    }\n  }\n});\n\n\u002F\u002F 异步操作示例\nexport const fetchUserData = () => async (dispatch) => {\n  const response = await fetch('\u002Fapi\u002Fuser');\n  dispatch(setUser(await response.json()));\n};\n\nexport const store = configureStore({\n  reducer: {\n    counter: counterSlice.reducer,\n    \u002F\u002F 其他reducer...\n  }\n});\n\nexport const { increment, decrement } = counterSlice.actions;\n```\n\n### 组件集成\n```jsx\n\u002F\u002F index.js\nimport { Provider } from 'react-redux';\nimport { store } from '.\u002Fstore';\n\nroot.render(\n  \u003CProvider store={store}>\n    \u003CApp \u002F>\n  \u003C\u002FProvider>\n);\n\n\u002F\u002F Counter.js\nimport { useSelector, useDispatch } from 'react-redux';\nimport { increment, decrement } from '.\u002Fstore';\n\nfunction Counter() {\n  const count = useSelector(state => state.counter.value);\n  const dispatch = useDispatch();\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => dispatch(decrement())}>-\u003C\u002Fbutton>\n      \u003Cspan>{count}\u003C\u002Fspan>\n      \u003Cbutton onClick={() => dispatch(increment())}>+\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 最佳实践总结\n1. **组件设计**：遵循单一职责原则，拆分智能组件（容器组件）和展示组件\n2. **状态管理**：\n   - 局部状态用`useState`\u002F`useReducer`\n   - 全局共享状态用Redux\n   - 避免过度使用状态提升\n3. **性能优化**：\n   - 使用`React.memo`记忆组件\n   - 使用`useCallback`\u002F`useMemo`避免不必要的重渲染\n   - 虚拟化长列表（react-window）\n4. **Hooks规范**：\n   - 避免在循环\u002F条件中使用Hook\n   - 自定义Hook以`use`前缀命名\n5. **TypeScript集成**：\n   ```tsx\n   interface UserCardProps {\n     name: string;\n     age: number;\n     onSelect: (id: string) => void;\n   }\n   \n   const UserCard: React.FC\u003CUserCardProps> = ({ name, age }) => (\n     \u003Cdiv>{name} ({age})\u003C\u002Fdiv>\n   )\n   ```\n\n下面为您完善教程，增加React路由和生命周期相关内容：\n\n## React路由管理（React Router v6）\n\n### 安装与基础配置\n```bash\nnpm install react-router-dom@6\n```\n\n### 路由基础结构\n```jsx\n\u002F\u002F index.js\nimport { BrowserRouter } from 'react-router-dom';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  \u003CBrowserRouter>\n    \u003CApp \u002F>\n  \u003C\u002FBrowserRouter>\n);\n\n\u002F\u002F App.js\nimport { Routes, Route, Link } from 'react-router-dom';\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CLink to=\\\"\u002F\\\">首页\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fabout\\\">关于\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fusers\\\">用户列表\u003C\u002FLink>\n      \u003C\u002Fnav>\n      \n      \u003CRoutes>\n        \u003CRoute path=\\\"\u002F\\\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fabout\\\" element={\u003CAbout \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\\\" element={\u003CUserList \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\u002F:id\\\" element={\u003CUserDetail \u002F>} \u002F>\n        \u003CRoute path=\\\"*\\\" element={\u003CNotFound \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 嵌套路由\n```jsx\n\u002F\u002F Dashboard.js\nimport { Outlet } from 'react-router-dom';\n\nfunction Dashboard() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>仪表盘\u003C\u002Fh2>\n      \u003COutlet \u002F> {\u002F* 子路由渲染位置 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F 路由配置\n\u003CRoutes>\n  \u003CRoute path=\\\"\u002Fdashboard\\\" element={\u003CDashboard \u002F>}>\n    \u003CRoute index element={\u003CDashboardHome \u002F>} \u002F>\n    \u003CRoute path=\\\"settings\\\" element={\u003CDashboardSettings \u002F>} \u002F>\n    \u003CRoute path=\\\"analytics\\\" element={\u003CDashboardAnalytics \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n```\n\n### 编程式导航\n```jsx\nimport { useNavigate, useParams, useLocation } from 'react-router-dom';\n\nfunction UserCard({ user }) {\n  const navigate = useNavigate();\n  \n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fusers\u002F${user.id}`)}>\n      {user.name}\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction UserDetail() {\n  const { id } = useParams(); \u002F\u002F 获取URL参数\n  const location = useLocation(); \u002F\u002F 获取位置对象\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>用户ID: {id}\u003C\u002Fh2>\n      \u003Cp>当前路径: {location.pathname}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 路由守卫（认证保护）\n```jsx\nimport { Navigate } from 'react-router-dom';\n\nfunction ProtectedRoute({ children }) {\n  const { isAuthenticated } = useAuth();\n  \n  if (!isAuthenticated) {\n    return \u003CNavigate to=\\\"\u002Flogin\\\" replace \u002F>;\n  }\n  \n  return children;\n}\n\n\u002F\u002F 使用\n\u003CRoute \n  path=\\\"\u002Fdashboard\\\" \n  element={\n    \u003CProtectedRoute>\n      \u003CDashboard \u002F>\n    \u003C\u002FProtectedRoute>\n  } \n\u002F>\n```\n\n## React生命周期\n\n### 类组件生命周期方法\n\n```jsx\nclass LifecycleDemo extends React.Component {\n  \u002F\u002F 1. 初始化阶段\n  constructor(props) {\n    super(props);\n    this.state = { count: 0 };\n    console.log('Constructor');\n  }\n\n  \u002F\u002F 2. 挂载阶段\n  componentDidMount() {\n    console.log('Component did mount');\n    \u002F\u002F 适合进行API调用、事件订阅\n    this.timer = setInterval(() => {\n      this.setState(prev => ({ count: prev.count + 1 }));\n    }, 1000);\n  }\n\n  \u002F\u002F 3. 更新阶段\n  shouldComponentUpdate(nextProps, nextState) {\n    console.log('Should component update?');\n    return nextState.count !== this.state.count;\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    console.log('Component did update');\n  }\n\n  \u002F\u002F 4. 卸载阶段\n  componentWillUnmount() {\n    console.log('Component will unmount');\n    \u002F\u002F 清理操作\n    clearInterval(this.timer);\n  }\n\n  render() {\n    console.log('Render');\n    return \u003Cdiv>Count: {this.state.count}\u003C\u002Fdiv>;\n  }\n}\n```\n\n### 函数组件生命周期（Hooks实现）\n\n```jsx\nimport { useState, useEffect } from 'react';\n\nfunction FunctionLifecycle() {\n  const [count, setCount] = useState(0);\n  const [data, setData] = useState(null);\n\n  \u002F\u002F 相当于componentDidMount + componentDidUpdate\n  useEffect(() => {\n    console.log('每次渲染后执行');\n  });\n\n  \u002F\u002F 相当于componentDidMount\n  useEffect(() => {\n    console.log('组件挂载后执行');\n    \n    \u002F\u002F 数据获取\n    fetch('\u002Fapi\u002Fdata')\n      .then(res => res.json())\n      .then(setData);\n    \n    \u002F\u002F 相当于componentWillUnmount\n    return () => {\n      console.log('组件卸载前执行');\n    };\n  }, []); \u002F\u002F 空依赖数组\n\n  \u002F\u002F 依赖变化时执行\n  useEffect(() => {\n    console.log('count变化时执行:', count);\n    \n    document.title = `Count: ${count}`;\n    \n    return () => {\n      console.log('清理count效果');\n    };\n  }, [count]); \u002F\u002F count依赖\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Count: {count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>增加\u003C\u002Fbutton>\n      {data && \u003Cpre>{JSON.stringify(data, null, 2)}\u003C\u002Fpre>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 生命周期阶段对比\n\n| 阶段 | 类组件方法 | 函数组件Hook |\n|------|------------|--------------|\n| **挂载** | constructor | useState初始化 |\n|       | render      | 函数体执行    |\n|       | componentDidMount | useEffect(() => {}, []) |\n| **更新** | shouldComponentUpdate | React.memo, useMemo |\n|       | render      | 函数体执行    |\n|       | componentDidUpdate | useEffect(() => {}) |\n| **卸载** | componentWillUnmount | useEffect返回函数 |\n| **错误处理** | componentDidCatch | 暂无直接等效，需错误边界组件 |\n\n### 现代React开发建议\n\n1. **优先使用函数组件+Hooks**：\n   - 90%的场景可替代类组件\n   - 更简洁的代码结构\n   - 更好的逻辑复用\n\n2. **关键生命周期替代**：\n   - `componentDidMount` → `useEffect(() => {}, [])`\n   - `componentDidUpdate` → `useEffect(() => {})` 或带依赖的 `useEffect`\n   - `componentWillUnmount` → `useEffect(() => { return cleanup }, [])`\n   - `shouldComponentUpdate` → `React.memo` 或 `useMemo`\n\n3. **数据获取最佳实践**：\n```jsx\nuseEffect(() => {\n  let isMounted = true;\n  \n  const fetchData = async () => {\n    try {\n      const result = await api.getData();\n      if (isMounted) setData(result);\n    } catch (error) {\n      if (isMounted) setError(error);\n    }\n  };\n  \n  fetchData();\n  \n  return () => {\n    isMounted = false; \u002F\u002F 避免组件卸载后设置状态\n  };\n}, []);\n```\n\n## 路由与生命周期整合示例\n\n```jsx\nfunction UserProfile() {\n  const { id } = useParams();\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    let isActive = true;\n    \n    const fetchUser = async () => {\n      try {\n        setLoading(true);\n        const data = await fetchUserById(id);\n        if (isActive) {\n          setUser(data);\n          setLoading(false);\n        }\n      } catch (error) {\n        if (isActive) {\n          setError(error.message);\n          setLoading(false);\n        }\n      }\n    };\n    \n    fetchUser();\n    \n    return () => {\n      isActive = false; \u002F\u002F 清理效果\n    };\n  }, [id]); \u002F\u002F id变化时重新获取\n\n  if (loading) return \u003CSpinner \u002F>;\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>{user.name}\u003C\u002Fh2>\n      \u003Cp>Email: {user.email}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 完整项目结构建议\n\n```\nsrc\u002F\n├── components\u002F      # 通用UI组件\n├── pages\u002F           # 页面组件\n├── layouts\u002F         # 布局组件\n├── hooks\u002F           # 自定义Hooks\n├── store\u002F           # Redux状态\n│   ├── slices\u002F\n│   └── store.js\n├── services\u002F        # API服务\n├── routers\u002F         # 路由配置\n├── utils\u002F           # 工具函数\n├── assets\u002F          # 静态资源\n└── App.js           # 主应用组件\n```\n\n这些新增内容涵盖了React路由的现代用法（v6版本）以及React生命周期的详细解释，包括类组件和函数组件的实现方式对比。同时还提供了路由与生命周期整合的实际示例，帮助开发者理解如何在真实项目中应用这些概念。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F46e205aa1bd33d1bb7201019fc2fdf43.png",1148,"2023-02-21T02:01:52.000Z","2026-06-27T11:00:41.557Z",{"id":347,"name":348,"slug":349},"f1701085-b8c1-413a-8750-58e7a0a33832","React","react",{"id":295,"name":296,"avatar":289},[352],{"id":165,"name":166,"slug":167},{"list":354,"total":44,"page":19,"pageSize":72},[355,368,381,394,408,421,434,447],{"id":356,"title":357,"slug":358,"content":359,"excerpt":357,"coverImage":360,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":361,"publishedAt":362,"createdAt":362,"updatedAt":363,"category":364,"author":365,"tags":366},"ad58bc75-cc41-46ae-9b4e-b989508b9289","Reqable抓包教程","reqable抓包教程","# win安装adb,将文件下载下来，解压缩到自定义的安装目录\n### Windows版本\n\n```language\nhttps:\u002F\u002Fdl.google.com\u002Fandroid\u002Frepository\u002Fplatform-tools-latest-windows.zip\n```\n\n### Mac版本：\n```language\nhttps:\u002F\u002Fdl.google.com\u002Fandroid\u002Frepository\u002Fplatform-tools-latest-darwin.zip\n```\n\n### Linux版本：\n```language\nhttps:\u002F\u002Fdl.google.com\u002Fandroid\u002Frepository\u002Fplatform-tools-latest-linux.zip\n```\n\n\n# 配置环境变量\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F6d5c7654ae4b80cc052c3fe08180f5e1_1765157264912.png)\n\n\n# MuMu模拟器\n\n```language\nhttps:\u002F\u002Fmumu.163.com\u002F\n```\n\n\n\n\n# 电脑和模拟器安装reqable\n\n```language\nhttps:\u002F\u002Freqable.com\u002Fzh-CN\u002F\n```\n\n# 模拟器启动\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fc6e16cd9160316db4826dc1dd313d4ba_1765158631619.png)\n\n\n# adb连接\n\n```language\nadb connect 127.0.0.1:5557\n```\n\n\n# 安装证书\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F8a1bfdef0d3e4c0abfc67f4701995476_1765158526195.png)\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F35c98167dde928846546a19430e07fe1_1765158531940.png)\n\n\n\n\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F54904d03e74b54f5a05ea883bcfe449e_1765158486377.png",834,"2025-12-08T01:48:15.000Z","2026-06-27T11:01:53.876Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[367],{"id":235,"name":5,"slug":5},{"id":369,"title":370,"slug":371,"content":372,"excerpt":370,"coverImage":373,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":374,"publishedAt":375,"createdAt":375,"updatedAt":376,"category":377,"author":378,"tags":379},"c864ffe1-205a-457e-a23f-ef14f36026e9","OSS生成视频封面图","oss生成视频封面图","> 前言:在阿里云oss视频上传后,我们如何获取视频封面呢?而不是通过上传方式获取封面.其实OSS本身提供了视频截帧功能\n\n> OSS提供的视频截帧功能和OSS图片服务功能使用的方式是类似的，都是通过传入x-oss-process参数的方式来实现的。最简单的使用方式就是直接在public的object的url后面拼接处理字符串。比如如下的url：\n\n```http\nhttp:\u002F\u002Fa-image-demo.oss-cn-qingdao.aliyuncs.com\u002Fdemo.mp4?x-oss-process=video\u002Fsnapshot,t_10000,m_fast\n```\n\n\n## 注意\n>当前仅支持对视频编码格式为H264的视频文件进行视频截帧。\nOSS当前没有默认保存视频截帧的操作，视频截帧的图片需手动下载到本地。\n如果需要对private的object调用视频截图，需要使用SDK进行鉴权，接口和OSS图片服务的调用接口一致。\n图片处理除了能够处理已经存在的图片内容，还能够截取出视频中的指定位置产生图片，完成视频截帧。\n参数\n操作分类：video\n\n操作名称：snapshot\n| 参数 | 描述 | 取值范围 |\n| - | - | - |\n| t | 截图时间,单位ms | [0,视频时长] |\n| w | 截图宽度，如果指定为0则自动计算 | 像素值：[0,视频宽度] |\n| h | 截图高度，如果指定为0则自动计算，如果w和h都为0则输出为原视频宽高 | 像素值：[0,视频高度] |\n| m | 截图模式，不指定则为默认模式，根据时间精确截图，如果指定为fast则截取该时间点之前的最近的一个关键帧 | 枚举值：fast |\n| f | 输出图片格式\t| 枚举值：jpg、png |\n\n \n\n## 示例\n> 找到视频7s处的内容，输出为jpg。\n```http\nhttp:\u002F\u002Fa-image-demo.oss-cn-qingdao.aliyuncs.com\u002Fdemo.mp4?x-oss-process=video\u002Fsnapshot,t_7000,f_jpg,w_800,h_600,m_fast\n```\n> 找到视频50s处的内容，输出为jpg，精确为对应的时间。\n```http\nhttp:\u002F\u002Fa-image-demo.oss-cn-qingdao.aliyuncs.com\u002Fdemo.mp4?x-oss-process=video\u002Fsnapshot,t_50000,f_jpg,w_800,h_600\n```\n\n\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Faaa897105a7de57ed00315cf679a2d54.jpg",1046,"2022-10-24T09:47:25.000Z","2026-06-27T11:09:01.693Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[380],{"id":225,"name":226,"slug":226},{"id":382,"title":383,"slug":384,"content":385,"excerpt":383,"coverImage":386,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":387,"publishedAt":388,"createdAt":388,"updatedAt":389,"category":390,"author":391,"tags":392},"cfdabe24-215c-4f74-ba9f-ad1fc3dedd5e","Visual Studio Code 插件推荐","visual-studio-code-插件推荐","\n\n\n## 推荐插件\n\n\n|                插件                |                             功能                             |\n| :--------------------------------: | :----------------------------------------------------------: |\n| Chinese (Simplified) Language Pack |             适用于 VS Code 的中文（简体）语言包              |\n|              Beautify              |  美化javascript，JSON，CSS，Sass，和HTML在Visual Studio代码  |\n|            Live Server             | 可以运行本地开发服务器来测试代码。它具有针对静态和动态网页的实时重新加载功能 |                      \n| Vue L anguage Features (Volar)     |                       编写vue2\u002F3必备工具 \n|              Prettier              |                       提供格式化和检查                       |\n|               ESLint               | ESLint 是一个静态 JavaScript 代码分析器。它会扫描你的代码，并发现语法错误和低效的编程实践。 \n|             Stylelint              |            Stylelint的官方Visual Studio Code扩展  \n|       vscode-element-helper        |                        ElementUI提示                         |                  |\n|   JavaScript (ES6) code snippets   |                         es6代码提示                          |\n|  JavaScript 和 TypeScript Nightly  | 支持每晚构建 TypeScript ( `typescript@next`) 作为 VS Code 的内置 TypeScript 版本，用于支持 JavaScript 和 TypeScript IntelliSense。 |\n|       Bracket Pair Colorizor       |              为括号匹配颜色，以使代码更具可读性              |\n|          Project Manager           | 在项目的侧边菜单中打开一个额外的菜单。 你可以在项目之间快速切换，保存收藏夹或从文件系统自动检测Git项目 |\n|           VS Code Icons            |                         文件图标主题                         |\n|         Path Autocomplete          |                           路径补全                           |\n|     Markdown Preview Enhanced      | 自动滚动同步，数学排版，美人鱼，PlantUML，pandoc，PDF导出，代码块，演示文稿编写器等。MarkdownPreview Plus启发了它的许多思想和RStudio Markdown。 |\n|           CSS Navigation           | 允许从 HTML转到定义到 CSS\u002FSass\u002FLess，为类和 id 名称提供完成和工作区符号，并支持从 CSS 到 HTML 的查找引用。 |\n|          npm Intellisense          | Npm 智能感知自动完成导入语句中的 npm 模块的 Visual Studio Code 插件。            \n|            Tailwind CSS IntelliSense     |        Tailwind语法提示        \n|            GitLens — Git supercharged     |        通过 Git 责备注释和 CodeLens 使代码作者一目了\n|            git-commit-plugin    |        git代码提交规范   \n|            Time Master             |        从您的编程活动中自动生成指标、见解和时间跟踪。\n|              韭菜盒子              | 韭菜盒子——VSCode 里也可以看股票 & 基金实时数据，做最好用的投资插件。 |\n|              go-home              | 下班提醒小助手     \n|              SFTP              | SFTP上传工具\n|             GitHub Copilot              | AI智能代码提示，收费\n|             Codeium              | AI智能代码提示，免费\n|             微信小程序开发工具               | 识别小程序语法\n\n## 推荐编辑器\n> Cursor,基于vscode搭建的AI编辑器，https:\u002F\u002Fwww.cursor.com\u002Fcn\n\n> Trae,字节基于vscode搭建的AI编辑器，https:\u002F\u002Fwww.trae.ai\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fe9eec8f2db15a8f72cc0fb951b214181.png",824,"2022-09-26T03:15:52.000Z","2026-06-27T11:01:06.304Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[393],{"id":235,"name":5,"slug":5},{"id":395,"title":396,"slug":397,"content":398,"excerpt":399,"coverImage":400,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":401,"publishedAt":402,"createdAt":402,"updatedAt":403,"category":404,"author":405,"tags":406},"57ea9eee-45d8-4899-9bfb-519cd13690c6","Navicat Premium 16 下载与安装破解教程","navicat-premium-16-下载与安装破解教程","\n## 前言\n> 本篇文章是基于 Win11 操作系统下载与安装破解 Navicat；破解教程为使用注册机破解；Navicat 为付费软件，请支持正版\n\n## 1、下载 Navicat\n>Navicat 官网：www.navicat.com.cn\u002F \n在产品中可以看到很多的产品，点击免费试用 Navicat Premium 即可，是一套多连数据库开发工具，其他的只能连接单一类型数据库\n\n## 小白龙链接\n```http\nhttps:\u002F\u002Fcdn.xiaolong0418.com\u002FNavicat\u002Fnavicat161_premium_cs_x64.exe\n```\n\n\n\n\n\n\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fab91a0a7fcf63ded2747ffaf73405857.png)\n\n### 点击试用\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F7b4df7177cfc48097f26ec7528dfaed2.png)\n\n\n> 选择系统直接下载\n\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F2e4b4126b5ae3f52a40edac6cd3ff1f4.png)\n\n\n\n## 二、安装 Navicat\n> 安装很简单，只需要选择安装路径即可安装\n\n### 三、激活 Navicat\n#### 1. 获取注册机\n```http\nhttps:\u002F\u002Fcdn.xiaolong0418.com\u002FNavicat\u002FNavicatCracker.exe\n```\n\n#### 2. 使用注册机\n> 注意：关闭网络，关闭所有杀毒软件，WIN10\u002F11 系统需关闭 Windows Defender 的实时保护\n下载成功之后使用管理员身份打开注册机\n\n#### 2.1、设置安装路径，点击 Patch\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F3621660f5665faa8da83ac67bb4786ef.png)\n\n\n#### 出现提示，点击是\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F0c4ad49d092647e212037e5e2108d365.png)\n\n\n#### 2.2、生成许可证\n> 在 KeyGen 中选择软件，版本，语言后，点击 Generate，生成许可证\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F2d43bcb1d1522bc018996db97afc153e.png)\n\n\n#### 同时打开 Navicat Premium，点击注册\n\n> 将生成的许可证复制到下图位置\n\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F37e7149f66a24c92118a4b1855016fc0.png)\n\n\n\n#### 2.3、手动激活\n> 点击激活后，会出现服务器不可用提示，点击手动激活\n\n##### 1. 点击手动激活之后，只会出现激活码，将激活码复制到 Request Code 框中\n ![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F48a3669473f7e27f85a0580d9945d3f0.png)\n\n##### 2. 点击 Generate Activation Code，会在 Activation Code 框中生成激活码\n\n##### 3. 将 Activation Code 框中的激活码复制到左边的激活码框中，点击激活\n\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F4452faf49067c15b34f6699cff42d0dd.png)\n\n\n##### 点击激活之后成功会出现永久许可证标识\n\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fb62b92c42a771e6a73d9c0b649e5436d.png)\n\n\n#### 进入 Navicat 软件，点击帮助→关于，查看激活信息\n![](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Ff13685280e758bdacf9db65ede1fe8ec.png)\n\n\n","Navicat Premium 16 下载与安装破解教程（详细教程）","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F86e82255478d266517408057f9cdfc06.png",4389,"2022-09-11T11:45:15.000Z","2026-06-27T11:11:55.410Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[407],{"id":235,"name":5,"slug":5},{"id":409,"title":410,"slug":411,"content":412,"excerpt":410,"coverImage":413,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":414,"publishedAt":415,"createdAt":415,"updatedAt":416,"category":417,"author":418,"tags":419},"90ed1bf3-61f5-439b-8fdb-2f04bd311e38","RESTful API接口规范","restful-api接口规范","# RESTful规范\n\n> Restful API是目前比较成熟的一套互联网应用程序的API设计理念，Rest是一组架构约束条件和原则，如何Rest约束条件和原则的架构，我们就称为Restful架构，Restful架构具有结构清晰、符合标准、易于理解以及扩展方便等特点，受到越来越多网站的采用！\n\n##  Restful API接口规范包括以下部分：\n\n### 一、协议\n\n> API与用户的通信协议，总是使用HTTPs协议。\n\n### 二、域名\n\n> 应该尽量将API部署在专用域名之下，如https:\u002F\u002Fapi.专属域名.com；如果确定API很简单，不会有进一步扩展，可以考虑放在主域名下，如https:\u002F\u002F专属域名.com\u002Fapi\u002F。\n\n### 三、版本\n\n> 可以将版本号放在HTTP头信息中，也可以放入URL中，如https:\u002F\u002Fapi.专属域名.com\u002Fv1\u002F\n\n### 四、路径\n\n> 路径是一种地址，在互联网上表现为网址，在RESTful架构中，每个网址代表一种资源（resource），所以网址中不能有动词，只能有名词，而且所用的名词往往与数据库的表格名对应。一般来说，数据库中的表都是同种记录的\\\"集合\\\"（collection），所以API中的名词也应该使用复数，如https:\u002F\u002Fapi.专属域名.com\u002Fv1\u002Fstudents。\n\n### 五、HTTP动词\n\n> 对于资源的具体操作类型，由HTTP动词表示，HTTP动词主要有以下几种，括号中对应的是SQL命令。\n\n| 请求方式 | 说明 |\n| - | - |\n| GET（SELECT） | 从服务器取出资源（一项或多项） |\n| POST（CREATE） | 在服务器新建一个资源 |\n| PUT（UPDATE） | 在服务器更新资源（客户端提供改变后的完整资源） |\n| PATCH（UPDATE） | 在服务器更新资源（客户端提供改变的属性） |\n| DELETE（DELETE） | 从服务器删除资源 |\n| HEAD | 获取资源的元数据； |\n| OPTIONS | 获取信息，关于资源的哪些属性是客户端可以改变的。 |\n\n\n\n## 六、过滤信息\n\n> 如果记录数量很多，服务器不可能都将它们返回给用户，API会提供参数，过滤返回结果，常见的参数有：\n\n| 传参 | 说明 |\n| - | - |\n| ?limit=20 | 指定返回记录的数量为20； |\n| ?offset=8 | 指定返回记录的开始位置为8 |\n| ?page=1&per_page=50 | 指定第1页，以及每页的记录数为50 |\n| ?sortby=name&order=asc | 指定返回结果按照name属性进行升序排序 |\n| ?animal_type_id=2 | 指定筛选条件 |\n\n\n\n## 七、状态码\n\n> 服务器会向用户返回状态码和提示信息，以下是常用的一些状态码：\n\n| 状态码 | 说明 |\n| - | - |\n| 200 OK | [GET]：服务器成功返回用户请求的数据 |\n| 201 CREATED | [POST\u002FPUT\u002FPATCH]：用户新建或修改数据成功 |\n| 201 CREATED | [POST\u002FPUT\u002FPATCH]：用户新建或修改数据成功 |\n| 202 Accepted | [*]：表示一个请求已经进入后台排队（异步任务） |\n| 204 NO CONTENT | [DELETE]：用户删除数据成功 |\n| 400 INVALID REQUEST | [*]：[POST\u002FPUT\u002FPATCH]：用户发出的请求有错误，服务器没有进行新建或修改数据的操作； |\n| 401 Unauthorized | [*]：表示用户没有权限（令牌、用户名、密码错误） |\n| 403 Forbidden | [*] 表示用户得到授权（与401错误相对），但是访问是被禁止的 |\n| 404 NOT FOUND | [*]：用户发出的请求针对的是不存在的记录，服务器没有进行操作 |\n| 406 Not Acceptable  |  [GET]：用户请求的格式不可得 |\n| 410 Gone | [GET]：用户请求的资源被永久删除，且不会再得到的 |\n| 422 Unprocesable entit | [POST\u002FPUT\u002FPATCH] 当创建一个对象时，发生一个验证错误 |\n| 500 INTERNAL SERVER ERROR | [*]：服务器发生错误，用户将无法判断发出的请求是否成功。 |\n\n\n### 八、错误处理\n\n> 如果状态码是4xx，就会向用户返回出错信息，一般来说，返回的信息中将error作为键名，出错信息作为键值。\n\n### 九、返回结果\n\n> 针对不同操作，服务器向用户返回的结果应该符合以下规范：\n\n| 方法 | 路径 | 说明 |\n| - | - |  - |\n| GET | \u002Fcollection | 返回资源对象的列表（数组） |\n| GET | \u002Fcollection\u002Fresource | 返回单个资源对象 |\n| POST | \u002Fcollection | 返回新生成的资源对象 |\n| PUT | \u002Fcollection\u002Fresource | 返回完整的资源对象 |\n| PATCH | \u002Fcollection\u002Fresource | 返回完整的资源对象 |\n| DELETE | \u002Fcollection\u002Fresource | 返回一个空文档 |\n\n\n\n\n##  十、Hypermedia API\n\n> RESTful API最好做到Hypermedia，即返回结果中提供链接，连向其他API方法，使得用户不查文档，也知道下一步应该做什么。\n\n> 以上是Restful API设计应遵循的十大规范，除此之外，Restful API还需注意身份认证应该使用OAuth 2.0框架，服务器返回的数据格式，应该尽量使用JSON，避免使用XML。\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F55434c50d06c13c2b463a5f65df341d5.jpeg",561,"2022-07-29T06:31:23.000Z","2026-06-27T11:07:44.458Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[420],{"id":270,"name":271,"slug":271},{"id":422,"title":423,"slug":424,"content":425,"excerpt":423,"coverImage":426,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":427,"publishedAt":428,"createdAt":428,"updatedAt":429,"category":430,"author":431,"tags":432},"fc469738-508c-4c37-8d71-df3de68303c7","Nginx常用配置","nginx常用配置","## location的匹配规则\n\n- = 表示精确匹配。只有请求的url路径与后面的字符串完全相等时，才会命中。\n- ^~ 表示如果该符号后面的字符是最佳匹配，采用该规则，不再进行后续的查找。\n- ~ 表示该规则是使用正则定义的，区分大小写。\n- ~* 表示该规则是使用正则定义的，不区分大小写。\n\n> 注意的是，nginx的匹配优先顺序按照上面的顺序进行优先匹配，而且注意的是一旦某一个匹配命中直接退出，不再进行往下的匹配\n剩下的普通匹配会按照最长匹配长度优先级来匹配，就是谁匹配的越多就用谁。\n\n\n\n## Nginx跨域配置\n```language\n     server {\n         listen  80 default_server;\n         server_name _;  \n \n         add_header Access-Control-Allow-Credentials true;\n         add_header Access-Control-Allow-Origin $http_origin;\n         \n              \n         location \u002F {\n            if ($request_method = 'OPTIONS') {\n                add_header Access-Control-Allow-Origin $http_origin;\n                add_header Access-Control-Allow-Methods $http_access_control_request_method;\n                add_header Access-Control-Allow-Credentials true;\n                add_header Access-Control-Allow-Headers $http_access_control_request_headers;\n                add_header Access-Control-Max-Age 1728000;\n                return 204;\n             }         \n        }\n \n    }\n\n\n```\n\n## 重定向(h5跳pc)\n```shell\n        if ($http_user_agent !~* (mobile|nokia|iphone|ipad|android|samsung|htc|blackberry)) {\n            rewrite  ^(.*)   https:\u002F\u002Fblog.xiaolong0418.com$1 permanent;\n        }\n```\n\n## 重定向（效率高）\n\n```language\n    location \u002F { try_files $uri $uri\u002F \u002Findex.html; }\n\n```\n\n## html设置history模式\n```language\nlocation \u002F {\n    index index.html index.htm;\n    proxy_set_header Host $host;\n    # history模式最重要就是这里\n    try_files $uri $uri\u002F \u002Findex.html;\n    # index.html文件不可以设置强缓存 设置协商缓存即可\n    add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0';\n}\n```\n\n## 接口反向代理\n```language\nlocation ^~ \u002Fapi\u002F {\n    # 跨域处理 设置头部域名\n    add_header Access-Control-Allow-Origin *;\n    # 跨域处理 设置头部方法\n    add_header Access-Control-Allow-Methods 'GET,POST,DELETE,OPTIONS,HEAD';\n    # 改写路径\n    rewrite ^\u002Fapi\u002F(.*)$ \u002F$1 break;\n    # 反向代理\n    proxy_pass http:\u002F\u002Fstatic_env;\n    proxy_set_header Host $http_host;\n}\n```\n\n## 静态资源设置七天强缓存\n```language\nlocation ~* \\.(?:css(\\.map)?|js(\\.map)?|gif|svg|jfif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {\n    # 静态资源设置七天强缓存\n    expires 7d;\n    access_log off;\n}\n```\n\n## 以目录去区分多个history单文件\n```language\nserver {\n    listen 80;\n    server_name taobao.com;\n    index index.html index.htm;\n    # 通过正则来匹配捕获 [tmall|alipay]中间的这个路径\n    location ~ ^\u002F([^\\\u002F]+)\u002F(.*)$ {\n        try_files $uri $uri\u002F \u002F$1\u002Fdist\u002Findex.html =404;\n    }\n}\n```\n\n\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1658709313228.png",882,"2022-07-25T00:37:59.000Z","2026-05-23T04:32:16.000Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[433],{"id":144,"name":145,"slug":146},{"id":435,"title":436,"slug":437,"content":438,"excerpt":436,"coverImage":439,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":440,"publishedAt":441,"createdAt":441,"updatedAt":442,"category":443,"author":444,"tags":445},"43a2f38f-bd3e-46fd-89cb-3882e2393d50","Markdown基本语法","markdown基本语法","\n\n# Markdown 基本语法\n\n## 简介\n\n> Markdown 是一种纯文本格式的标记语言。通过简单的标记语法，它可以使普通文本内容具有一定的格式。\n\n### 优点：\n\n- 因为是纯文本\n- 操作简单\n\n### 缺点：\n\n- 需要记一些语法（当然，是很简单。五分钟学会）。\n- 有些平台不支持 Markdown 编辑模式。\n\n## Typora 软件编写\n\n```\nhttps:\u002F\u002Fwww.typora.io\u002F\n```\n\n## 在 vs code 编写\n\n> 安装插件 Markdown-preview-enhanced\n\n### 美化 Markdown 文档\n\nhttps:\u002F\u002Fgithub.com\u002FMaserhe\u002FVScode-Markdown-theme-Maserhe\n\n### 一、标题\n\n在想要设置为标题的文字前面加#来表示\n\n- 一个# 是一级标题\n- 二个## 是二级标题\n- 以此类推。支持六级标题\n\n#### 示例：\n\n```\n# 这是一级标题\n## 这是二级标题\n### 这是三级标题\n#### 这是四级标题\n##### 这是五级标题\n###### 这是六级标题\n```\n\n#### 效果如下：\n\n# 这是一级标题\n\n## 这是二级标题\n\n### 这是三级标题\n\n#### 这是四级标题\n\n##### 这是五级标题\n\n###### 这是六级标题\n\n### 二、字体\n\n### 示例：\n\n```\n**这是加粗的文字**\n\n*这是倾斜的文字*`\n\n***这是斜体加粗的文字***\n\n~~这是加删除线的文字~~\n```\n\n#### 效果如下：\n\n**这是加粗的文字**\n\n_这是倾斜的文字_`\n\n**_这是斜体加粗的文字_**\n\n~~这是加删除线的文字~~\n\n### 三、引用\n\n在引用的文字前加>即可。引用也可以嵌套，如加两个>>三个>>>n 个...\n\n#### 示例：\n\n```\n>这是引用的内容\n>>这是引用的内容\n>>>>>>>>>>这是引用的内容\n```\n\n#### 效果如下：\n\n> 这是引用的内容\n>\n> > 这是引用的内容\n> >\n> > > > > > > > > > 这是引用的内容\n\n### 四、分割线\n\n三个或者三个以上的 - 或者 \\* 都可以。\n\n#### 示例：\n\n```\n---\n\n----\n\n***\n\n*****\n```\n\n#### 效果如下：\n\n---\n\n---\n\n---\n\n---\n\n### 五、图片\n\n#### 语法：\n\n```\n![图片alt](图片地址，图片title)\n```\n\n- 图片 alt 就是显示在图片下面的文字，相当于对图片内容的解释。\n\n- 图片 title 是图片的标题，当鼠标移到图片上时显示的内容。title 可加可不加\n\n#### 示例：\n\n```\n![测试](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002FBreakpoint_debugging\u002Fimages\u002F01.png)\n```\n\n### 效果如下\n\n![测试](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002FBreakpoint_debugging\u002Fimages\u002F01.png)\n\n### 六、超链接\n\n#### 语法：\n\n```\n[超链接名](超链接地址 '超链接title')\ntitle 可加可不加\n```\n\n#### 示例：\n\n```\n[简书](http:\u002F\u002Fjianshu.com)\n[百度](http:\u002F\u002Fbaidu.com)\n```\n\n效果如下：\n\n[简书](http:\u002F\u002Fjianshu.com)\n[百度](http:\u002F\u002Fbaidu.com)\n\n### 七、列表\n\n#### 无序列表\n\n- 语法：\n\n```\n无序列表用 - + \\* 任何一种都可以\n\n- 列表内容\n\n* 列表内容\n\n- 列表内容\n\n注意：- + \\* 跟内容之间都要有一个空格\n```\n\n#### 效果如下：\n\n- 列表内容\n\n* 列表内容\n\n- 列表内容\n\n### 八、表格\n\n#### 语法：\n\n```\n- 有一个就行，为了对齐，多加了几个\n  文字默认居左 -两边加：表示文字居中 -右边加：表示文字居右\n  注：原生的语法两边都要用 | 包起来。此处省略\n  示例：\n\n| 姓名 | 技能 | 排行 |\n| ---- | :--: | ---: |\n| 刘备 |  哭  | 大哥 |\n| 关羽 |  打  | 二哥 |\n| 张飞 |  骂  | 三弟 |\n```\n\n效果如下：\n| 姓名 | 技能 | 排行 |\n| ---- | :--: | ---: |\n| 刘备 | 哭 | 大哥 |\n| 关羽 | 打 | 二哥 |\n| 张飞 | 骂 | 三弟 |\n\n### 九、代码\n\n#### 语法：\n\n##### 单行代码：代码之间分别用一个反引号包起来\n\n```\n    `单行代码`\n```\n\n`代码内容`\n\n###### 代码块：代码之间分别用三个反引号包起来，且两边的反引号单独占一行\n\n````\n    ```代码块```\n````\n\n`代码块`\n\n### 十、流程图\n\n```\nflow\nst=>start: 开始\nop=>operation: My Operation\ncond=>condition: Yes or No?\ne=>end\nst->op->cond\ncond(yes)->e\ncond(no)->op\n&\n\n```\n\n效果如下：\n\n``````flow\nst=>start: 开始\nop=>operation: My Operation\ncond=>condition: Yes or No?\ne=>end\nst->op->cond\ncond(yes)->e\ncond(no)->op\n&`````\n``````\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1657982270955.png",773,"2022-07-16T14:38:14.000Z","2026-05-25T07:21:34.000Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[446],{"id":134,"name":135,"slug":136},{"id":448,"title":449,"slug":450,"content":451,"excerpt":449,"coverImage":452,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":453,"publishedAt":454,"createdAt":454,"updatedAt":455,"category":456,"author":457,"tags":458},"d0bac553-e294-4728-b2ec-859d16c78cc1","Axure RP 9 激活及汉化","axure-rp-9-激活及汉化","\n\n> Axure RP 9 是一款快速原型设计软件，主要用于 WEB 界面、APP 界面、软件界面产品的交互原型设计。是互联网产品经理必备的软件之一，主流科技公司都在用。\n\n## 官方安装包下载地址\n\n- ### Axure RP 9 MAC 正式版：\n\n```http\nhttps:\u002F\u002Fwww.axure.com\u002Frelease-history\u002Frp9\n```\n\n- ### Axure RP 9 WIN 正式版：\n\n```http\nhttps:\u002F\u002Fwww.axure.com\u002Frelease-history\u002Frp9\n```\n\n## 授权码\n\n#### Axure RP 9.0.0.3687 正式版（目前最新版）\n\n```json\nLicensee :Freecrackdownload.com\nKEY:5vYpJgQZ431X\u002FG5kp6jpOO8Vi3TySCBnAslTcNcKkszfPH7jaM4eKM8CrALBcEC1\n```\n\n#### Axure RP 9.0.0.3686 正式版\n\n```json\nLicensee : yygg329405\nKEY: QmNyZyXeMrJU+1yw6XiXZGenOs9R3wV7S+KS7K90yTk=\n```\n\n#### Axure RP 9.0.0.3680 可用秘钥\n\n```json\nLicensee : yygg329405\nKEY: QmNyZyXeMrJU+1yw6XiXZGenOs9R3wV7S+KS7K90yTk=\n```\n\n## Axure9 软件汉化方法\n\n### 中文汉化包下载地址\n\n```http\nhttps:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002FAxure\u002Fsoftware\u002Flang.zip\n```\n\n- #### 首先退出正在运行中的 Axure (如果您正在使用).\n\n- #### 将 汉化包.rar 文件解压, 得到 lang 文件夹, 然后将其复制到 Axure 安装目录.\n\n- #### 默认安装 Axure 后是没有 lang 文件夹的，所以要拷贝进去！\n\n## 如果您使用的为 Windows 版：\n\n① 将 lang 文件夹复制到 axure 安装目录下，**汉化后的**目录结构**类似这样：**\n\n```\nc:\u002F\u002FProgram Files\u002FAxure\u002FAxure RP 9.0\u002Flang\u002Fdefault（32位系统）\n```\n\n```\nc:\u002F\u002FProgram Files (x86)\u002FAxure\u002FAxure RP 9.0\u002Flang\u002Fdefault（64位系统）\n```\n\n## 如果您使用的为 MAC 版：\n\n① 在 应用程序 文件夹里找到 Axure RP 9.app 程序，然后右键选择“显示包内容”，然后依次打开 Contents\u002FResources 文件夹;\n\n② 将 lang 文件夹复制到这个目录下即可;\n\n## 常见问题\n\n- 启动 Axure 即可看到简体中文界面, 说明已成功汉化,如果仍为英文则一定是汉化文件位置不正确.\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F6450ba64e9dfd2539cdfd9829c0f8e60.webp",1053,"2022-06-21T03:34:53.000Z","2026-05-25T08:46:32.000Z",{"id":4,"name":5,"slug":5},{"id":295,"name":296,"avatar":289},[459],{"id":103,"name":104,"slug":105}]