vue脚手架多页自动化生成实践

前言
在前端开发过程中,常常面对多种业务场景。到目前为止,前端对于不同场景的处理通常会采用不同的渲染方案来组合处理,常见的渲染方案包括:CSR(Client Side Rendering)、SSR(Server Side Rendering)、SSG(Static Site Generation)、ISR(Incremental Site Rendering)、DPR(Distributed Persistent Rendering)、NSR(Native Side Rendering)以及ESR(Edge Side Rendering)等。在目前项目开发过程中,遇到了需要构建门户类应用的需求,而团队主要技术栈以Vue为主,整个技术方案以Vue全家桶进行构建。因此,本文旨在针对门户类应用的场景下的Vue脚手架构建方案的一些总结和分析,通过自动化的配置脚本来生成模板化的多页应用实践,以期能够给读者提供一个基于Vue全家桶的门户类工程构建方案。
架构

对于门户类型的应用,由于其大部分内容变动内容较少,而对于部分关键页面却会有动态更新的要求,因而在通常会采用多页形式的处理配合部分单页应用中的优势进行处理。因而,在技术选型方面,团队采用了预渲染配合多页的方式实现门户类SEO及首屏加载快的需求。同时,结合单页应用的优势,在多页中的部分关键页面中采用单页中的优点,如:路由切换快、用户体验好等。综上,架构风格采用ISR的增量渲染方案,由于项目背景的特殊性,无法配合常规CDN等部署方案特点,但可以使用云原生相关的中间件实现类似效果,整体部署仍以“云+端”的形式为主。
目录
selfService├─portal
├─ build // vue cli打包所需的options中内容一些抽离,对其中做了环境区分
| ├─ demo
| | ├─config.json
| | ├─configureWebpack.js
| ├─ dev
| | ├─ config.json
| | ├─ configureWebpack.js
| ├─ production
| | ├─ config.json
| | ├─ configureWebpack.js
| ├─ chainWebpack.js
| ├─ configureWebpack.js
| ├─ devServer.js
| ├─ pages.js
| ├─ routes.js
| ├─ index.js
| ├─ utils.js
├─ deploy // 不同环境的部署
| ├─ demo
| | ├─ default.conf
| | ├─ Dockerfile
| | ├─ env.sh
| ├─ dev
| | ├─ default.conf
| | ├─ Dockerfile
| | ├─ env.sh
| ├─ production
| | ├─ default.conf
| | ├─ Dockerfile
| | ├─ env.sh
| ├─ build.sh
├─ public
| ├─ pageA // pageA的html,这里可以存放一些静态资源,非构建状态下的js、css等
| | ├─ index.html
| ├─ pageB // pageB的html,这里可以存放一些静态资源,非构建状态下的js、css等
| | ├─ index.html
| ├─ favicon.ico
├─ src
| ├─ assets // 存放小资源,通常为必须,如:logo等,其他静态资源请放入cdn或者public下
| | ├─ logo.png
| ├─ components // 公共组件,可抽离多个静态页面的公共组件
| | ├─ Header.vue
| ├─ router
| | ├─ pageA // pageA的router,使用了history模式
| | ├─ index.js
| | ├─ pageB // pageB的router,使用了history模式
| | ├─ index.js
| ├─ store
| | ├─ pageA // pageA的Vuex
| | ├─ index.js
| | ├─ pageB // pageB的Vuex
| | ├─ index.js
| ├─ views
| | ├─ pageA // pageA的页面,写法和之前一个的单页应用一致
| | ├─ main.js // 注入了mode,挂载到了vue的原型上,使用this可以获取环境变量
| | ├─ pageA.vue
| | ├─ pageB // pageB的页面,写法和之前一个的单页应用一致
| | ├─ main.js // 注入了mode,挂载到了vue的原型上,使用this可以获取环境变量
| | ├─ pageB.vue
├─ scripts
├─ babel.config.js // 配置es转化语法
├─ vue.config.js // vue cli打包相关配置
├─ app.json // 存放各个多页应用的public、router、vuex、views入口地址
实践
配置

Vue脚手架中配置多页主要是使用Webpack中的pages入口配置,这里主要是修改vue.config.js中的pages的设置,代码如下:
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
module.exports = {// ...pages: {page3:{entry: "src/views/page3/main.js",template: "public/page3/index.html",filename: "page3.html",title: "page3"}},configureWebpack: config => {config.plugins.push(new PrerenderSPAPlugin({staticDir: path.resolve(__dirname,'../../dist'),routes: ['/page3'],renderer: new Renderer({less: false,//renderAfterDocumentEvent: 'render-event',//renderAfterTime: 5000,//renderAfterElementExists: 'my-app-element'}),}))}
}
其中,如果配置了pages,@vue/cli-service会先清除原有的entry,如果没有index,则devServer默认入口的根路径’/‘仍为index.html;如果有index的key值,则会进行相应的覆盖。在这里,对pages下的key值为对应多页的路径,如:上述代码下的page3,则对应的路径为’/page3.html’;pages下的value可以为字符串,也可以为对象,其中:entry为多页的入口(必选项)、template为模板来源、filename为打包后的输出名称以及title会通过html-webpack-plugin的插件对template中的<title><%= htmlWebpackPlugin.options.title %></title>进行替换。
而对于预渲染的应用,这里使用了prerender-spa-plugin和vue-meta-info来进行SEO及首屏加载优化,代码如下:
// ...
import MetaInfo from 'vue-meta-info'Vue.use(MetaInfo)new Vue({router,store,render: h => h(index),mounted () {document.dispatchEvent(new Event('custom-render-trigger'))}
}).$mount('#page3')
脚本

通过上述的配置,基本就可以实现一个 预渲染+多页 的vue脚手架搭建。但是,除了开发环境的配置,对于生产环境、部署等也需要进行一定的设置,这样频繁的操作就会带来一定的功效降低。因而,在前端工程化领域中,通常会进行一定的脚本化或者说脚手架方案的构建。这里,在目前项目中,团队对多页应用的配置进行了自动化的脚本实现。
生成多页的脚本主要通过page.js进行实现,代码如下:
const inquirer = require('inquirer');
const chalk = require('chalk');
const fs = require('fs');
const path = require('path');
const ora = require('ora');
const { transform, compose } = require('./utils');const spinner = ora();const PAGE_REG = /[a-zA-Z_0-9]/ig;
const rootDir = path.resolve(process.cwd(), '.');// 判断dir目录下是否存在name的文件夹
const isExt = (dir, name) => fs.existsSync(path.join(dir, name));const APP_JSON_EJS = `{"pages": <%= page_name %>
}`;const INDEX_HTML_EJS = `<!DOCTYPE html>
<html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1.0"><link rel="icon" href="../favicon.ico"><title><%= htmlWebpackPlugin.options.title %></title></head><body><noscript><strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><%= page_name %></body>
</html>
`const INDEX_VUE_EJS = `<%= page_name %><script>
export default {
components: {
},
data() {return {};
},
};
</script><style lang="less">
</style>`const MAIN_JS_EJS = `<%= page_name %>`const INDEX_ROUTER_EJS = `import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [<%= page_name %>
]const router = new VueRouter({mode: 'history',routes
})export default router`const INDEX_STORE_EJS = `import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {},mutations: {},actions: {},modules: {}
})
`// inquirer list
const promptList = [{type: 'input',name: 'page_name',message: '请输入你想要创建的多页应用名称',filter: function (v) {return v.match(PAGE_REG).join('')}}
];// nginx的default.conf所需添加内容
const addDefaultConf = page_name => {return `location /${page_name} {root /usr/share/nginx/html;index ${page_name}.html;try_files $uri $uri/ /${page_name}.html;gzip_static on;
}`
};// page_name下的index.html
const addIndexHtml = page_name => {return `<div id="${page_name}" data-server-rendered="true"></div>`
};// page_name下的router
const addRouterIndex = page_name => {return `{path: '/',component: () => import('../../views/${page_name}/index.vue')
},`
};// page_name下的views index.vue
const addViewsIndex = page_name => {return `<template><div>${page_name}</div>
</template>`
};// page_name下的views main.js
const addViewsMain = page_name => {return `import Vue from 'vue'
import index from './index.vue'
import router from '../../router/${page_name}/index.js'
import store from '../../store/${page_name}/index.js'
import MetaInfo from 'vue-meta-info'Vue.use(MetaInfo)import axios from 'axios'Vue.prototype.$mode = process.env.VUE_APP_MODE;Vue.prototype.axios = axios;Vue.config.productionTip = falsenew Vue({router,store,render: h => h(index),mounted () {document.dispatchEvent(new Event('custom-render-trigger'))}
}).$mount('#${page_name}')`
};// page_name下的pages.js
const addPages = page_name => {return JSON.stringify({entry: `src/views/${page_name}/main.js`,template: `public/${page_name}/index.html`,filename: `${page_name}.html`,title: `${page_name}`,})
}const updateApp = page_name => {// 获取pages的数组const pages = require('../app.json')['pages'];if(pages.includes(page_name)) return true;pages.push(page_name);spinner.start()fs.writeFile(`${rootDir}/app.json`, transform(/<%= page_name %>/g, JSON.stringify(pages), APP_JSON_EJS), err => {spinner.color = 'red';spinner.text = 'Loading Update app.json'if(err) {spinner.fail(chalk.red(`更新app.json失败`))return false;} else {spinner.succeed(chalk.green(`更新app.json成功`))return true;}});
}// 处理 public 文件夹下的核心逻辑
const processPublic = args => {const { page_name } = args;if(isExt(`${rootDir}/public`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/public/${page_name}`)}fs.writeFileSync(`${rootDir}/public/${page_name}/index.html`, transform(/<%= page_name %>/g, addIndexHtml(page_name), INDEX_HTML_EJS));// 处理默认页面的跳转const content = require('../app.json')['pages'].map(page => {return `<li><a href="/${page}.html">${page}</a>
</li>`}).join(`
`);const ejs_arr = fs.readFileSync(`${rootDir}/public/index.html`, 'utf-8').split(`<body>`);fs.writeFileSync(`${rootDir}/public/index.html`, ejs_arr[0] + `<body>
`+`<h1>自服务门户</h1>
<ul>${content}
</ul>` + `
</body>
</html>`);return args;
};// 处理 src/views 文件夹下的核心逻辑
const processViews = args => {const { page_name } = args;if(isExt(`${rootDir}/src/views`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/src/views/${page_name}`)}fs.writeFileSync(`${rootDir}/src/views/${page_name}/index.vue`, transform(/<%= page_name %>/g, addViewsIndex(page_name), INDEX_VUE_EJS));fs.writeFileSync(`${rootDir}/src/views/${page_name}/main.js`, transform(/<%= page_name %>/g, addViewsMain(page_name), MAIN_JS_EJS));return args;
};// 处理 src/router 文件夹下的核心逻辑
const processRouter = args => {const { page_name } = args;if(isExt(`${rootDir}/src/router`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/src/router/${page_name}`)}fs.writeFileSync(`${rootDir}/src/router/${page_name}/index.js`, transform(/<%= page_name %>/g, addRouterIndex(page_name), INDEX_ROUTER_EJS));return args;
};// 处理 src/store 文件夹下的核心逻辑
const processStore = args => {const { page_name } = args;if(isExt(`${rootDir}/src/store`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/src/store/${page_name}`)}fs.writeFileSync(`${rootDir}/src/store/${page_name}/index.js`, INDEX_STORE_EJS);return args;
};// 处理 build 文件夹下的核心逻辑
const processBuild = args => {const { page_name } = args;// 处理 build/page.jsconst pages = require('../build/pages.js');if(Object.keys(pages).includes(page_name)) return args;pages[`${page_name}`] = JSON.parse(addPages(page_name));const PAGES_JS_EJS =`const pages = ${JSON.stringify(pages)}
module.exports = pages;`;fs.writeFileSync(`${rootDir}/build/pages.js`, PAGES_JS_EJS);// 处理 build/routes.jsconst routes = require('../build/routes.js');if(routes.includes(`/${page_name}`)) return args;routes.push(`/${page_name}`);const ROUTES_JS_EJS =`const pages = ${JSON.stringify(routes)}
module.exports = pages;`;fs.writeFileSync(`${rootDir}/build/routes.js`, ROUTES_JS_EJS);return args;
}// 处理 deploy 文件夹下的核心逻辑
const processDeploy = args => {const { page_name } = args;const reg = new RegExp(`location /${page_name}`);['demo', 'dev', 'production'].forEach(item => {const content = fs.readFileSync(`${rootDir}/deploy/${item}/default.conf`, 'utf-8');if(reg.test(content)) return args;const ejs_arr = content.split(`location /api/`)fs.writeFileSync(`${rootDir}/deploy/${item}/default.conf`, transform(/<%= page_name %>/g, addDefaultConf(page_name), ejs_arr[0] + `<%= page_name %>
location /api/`+ ejs_arr[1]));});return args;
};inquirer.prompt(promptList).then(answers => {const page_name = answers.page_name;return updateApp(page_name)}).then(() => {const pages = require('../app.json')['pages'];pages.forEach(page => {console.log('page', page)compose(processDeploy,processBuild, processStore, processRouter, processViews, processPublic)({page_name: page});})}).catch(err => {if(err) {console.log(chalk.red(err))}})
为了更好的实现代码的优雅性,对代码工具进行了抽离,放入到utils.js中,代码如下:
// 将内容替换进ejs占位符
const transform = ($, content, ejs) => ejs.replace($,content);// 将流程串联
const compose = (...args) => args.reduce((prev,current) => (...values) => prev(current(...values)));module.exports = {transform,compose
}
总结
仅管到目前为止,单页应用仍是前端开发中的主流方案。但是,随着各大应用的复杂度提升,多种方案的建设也都有了来自业界不同的声音,诸如:多种渲染方案、Island架构等都是为了能更好的提升Web领域的体验与开发建设。技术方案的选择不只局限于生态的整合,更重要的是对合适场景的合理应用。
“形而上者谓之道,形而下者谓之器”,各位前端开发者不仅应该只着眼于眼前的业务实现,同时也需要展望未来,站在更高的视野上来俯视技术的走向与演进,共勉!!!
参考
- vue-cli搭建自动化多页面项目(vue高阶)
- Vue-cli配置多页面
- vue预渲染之prerender-spa-plugin插件应用
- 预渲染插件prerender-spa-plugin生成多页面
- CSR、SSR、NSR、ESR傻傻分不清楚,一文帮你理清前端渲染方案!
- vue项目改造SSR(服务端渲染)
- 什么是SSR/SSG/ISR?如何在AWS上托管它们?
- 卷起来,前端建站SSG,SSR,ISR,Hydration, Island…一网打尽
- 你知道吗?SSR、SSG、ISR、DPR 有什么区别?
- SSR、ISR、CSR、SSG有什么区别
- 一文看懂Next.js渲染方法:CSR、SSR、SSG和ISR
相关文章:
vue脚手架多页自动化生成实践
前言 在前端开发过程中,常常面对多种业务场景。到目前为止,前端对于不同场景的处理通常会采用不同的渲染方案来组合处理,常见的渲染方案包括:CSR(Client Side Rendering)、SSR(Server Side Rendering)、SSG(Static Site Generati…...
【SQL语句优化】
SQL语句优化是提高数据库查询性能的重要手段之一,下面是几种常见的SQL语句优化方法和案例: 减少查询的数据量 减少查询的数据量:使用 WHERE 子句和索引来限制检索行数,只检索需要的行,避免检索全部行数据。 例子&am…...
阿里P8:做测试10年我的一些经验分享,希望你们少走弯路
我是在2015年毕业的,当时是读的普通本科,不上不下的专业水平,毕业的时候,恰好遇到了金融危机。校园招聘里阴差阳错的巧合,让我走上了软件测试工程师的道路。 入职第一天,来了个高大上的讲师,记…...
栈在括号匹配中的应用(栈/链栈 纯C实现)
目录 1 问题背景 2 具体思路 3 代码实现 3.1 顺序栈实现 3.2 链栈实现 1 问题背景 栈的括号匹配问题是指在给定一个字符串(包含多种括号),判断其中的括号是否能够正确匹配,即每个左括号是否有一个对应的右括号与之匹配&#x…...
C语言Switch语句用法
C switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。 语法 C 语言中 switch 语句的语法: switch(expression){case constant-expression :statement(s);break;…...
Curl编码请求参数,API接口请求示例参数
请求参数请求参数:num_iid610947572360 参数说明:num_iid:1688商品ID sales_data:&sales_data1 获取近30天成交数据 agent:&agent1 获取1688分销代发价格数据请求示例 测试入口 Curl PHP PHPsdk JAVA C# Python-- 请求示例 url 默认请求参数已经…...
【C/C++】类型限定符extern、const、Volatile、register
1、extern: 声明一个变量,extern声明的变量没有建立存储空间。 extern int a ; //变量在定义的时候创建存储空间。 ①当我们在编译器中试图运行以下代码,系统会报错。 错误原因是“无法解析外部符号_a”.系统认为变量a是没有开辟内存空间的…...
day54【代码随想录】二刷数组
文章目录前言一、二分查找(力扣724)二、移除元素(力扣27)【双指针】三、有序数组的平方(力扣977)【双指针】四、合并两个有序数组(力扣88)五、长度最小的子数组(力扣209&…...
哪个品牌蓝牙耳机性价比高?性价比高的平价蓝牙耳机推荐
现如今,随着蓝牙技术的进步,蓝牙耳机在人们日常生活中的便捷性更胜从前。越来越多的蓝牙耳机品牌被大众看见、认可。那么,哪个品牌的蓝牙耳机性价比高?接下来,我给大家推荐几款性价比高的平价蓝牙耳机,一起…...
揭秘关于TFRcord的五脏六腑
揭秘关于TFRcord的五脏六腑 前言:本篇文章将演示如何创建、解析和使用tf.Example消息,以及如何在.tfrecord文件之间对tf.Example消息进行序列化、写入和读取。 教程讲解使用的都是结构化数据,文章最后还会演示如果将图片写成.tfrecord文件&am…...
【Shell学习笔记】3.Shell 传递参数及数组
前言 本章介绍Shell的传递参数和数组。 Shell 传递参数 我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,…...
【终结Bug】ModuleNotFoundError: No module named ‘cv2’
解决方案: 打开 cmd键入 pip install opencv_python -i https://pypi.tuna.tsinghua.edu.cn/simple...
SQL Server2008详细安装步骤(保姆式教程)
安装包下载 链接:https://pan.baidu.com/s/1Rjx4DHJBeCW2asC_4Kzo6Q?pwdchui 提取码:chui 安装过程 1.解压后使用管理员身份打开安装程序 2.选择全新安装或向现有安装添加新功能 3.确认 4.输入产品密钥(上方网盘安装包里有࿰…...
Linux常用操作
Linux常用操作 前言常用命令:一些操作命令:前言 本文是笔者在使用cadence的过程中,操作linux的笔记,仅记录个人常用,持续更新 常用命令: (1)高频:会了这几个就能在文件…...
Golang 处理parquet文件实战教程
Parquet是Apache基金会支持的项目,是面向列存储二进制文件格式。支持不同类型的压缩方式,广泛用于数据科学和大数据环境,如Hadoop生态。 本文主要介绍Go如何生成和处理parquet文件。 创建结构体 首先创建struct,用于表示要处理…...
腾讯TIM实现即时通信 v3+ts实践
目录 初始化sdk 功能描述 初始化 准备 SDKAppID 调用初始化接口 监听事件 发送消息 创建消息 创建文本消息 登录登出 功能描述 登录 登出 销毁 登录设置 获取会话列表 功能描述 获取会话列表 获取全量的会话列表 历史消息 功能描述 拉取消息列表 分页拉取…...
华为OD机试 - 回文字符串(Java JS Python)
题目描述 如果一个字符串正读和反渎都一样(大小写敏感),则称它为一个「回文串」,例如: leVel是一个「回文串」,因为它的正读和反读都是leVel;同理a也是「回文串」art不是一个「回文串」,因为它的反读tra与正读不同Level不是一个「回文串」,因为它的反读leveL与正读不…...
APP测试的7大注意点。
1. 运行 1) App安装完成后的试运行,可正常打开软件。 2) App打开测试,是否有加载状态进度提示。 3) App⻚面间的切换是否流畅,逻辑是否正确。 4) 注册 同表单编辑⻚面 用户名密码⻓度 …...
设计模式-第4章(装饰模式)
装饰模式装饰模型装饰模式示例商场收银程序(简单工厂策略装饰模式实现)装饰模式总结装饰模型 装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为…...
【算法设计-分治】快速幂与龟速乘
文章目录1. 快速幂2. 龟速乘3. 快速幂取模4. 龟速乘取模5. 快速幂取模优化1. 快速幂 算法原理: 计算 311: 311 (35)2 x 335 (32)2 x 332 3 x 3仅需计算 3 次,而非 11 次 计算 310: 310 (35)235 (32)2 x 332 3 x 3仅需计算…...
多模态推理模型Phi-4的技术突破与应用实践
1. 多模态推理模型的技术演进与核心挑战在人工智能领域,多模态模型正逐步从单纯的感知能力向复杂推理能力跨越。传统视觉语言模型(如BLIP-2、LLaVA)主要解决"看到了什么"的问题,而新一代模型如Phi-4-reasoning-vision-1…...
大语言模型推理能力与自指认知的架构解析
1. 大语言模型推理能力的底层架构解析大语言模型的逻辑推理能力建立在Transformer架构的多层自注意力机制之上。这种架构设计使得模型能够通过注意力权重动态构建不同概念之间的关联网络。在推理任务中,特定模式的注意力分布会形成类似人类"思维链"的信息…...
CANN MLA Prolog算子文档
MlaProlog 【免费下载链接】cann-recipes-infer 本项目针对LLM与多模态模型推理业务中的典型模型、加速算法,提供基于CANN平台的优化样例 项目地址: https://gitcode.com/cann/cann-recipes-infer 产品支持情况 产品是否支持Atlas A2 推理系列产品√Atlas A…...
99.手把手教你落地YOLOv5车辆检测,含COCO格式适配+全流程代码实操
摘要 YOLO(You Only Look Once)是一种基于回归的单阶段目标检测算法,以其极快的推理速度和良好的检测精度在工业界广泛应用。本文从零开始,系统讲解YOLOv5的核心原理、训练流程与部署方法。通过一个完整的车辆检测案例,覆盖数据准备、模型训练、推理优化、模型导出等全链…...
抖音批量下载工具完整指南:免费快速获取无水印视频
抖音批量下载工具完整指南:免费快速获取无水印视频 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support.…...
VLA-0视觉语言动作模型:零修改部署与多模态AI实践
1. 项目概述VLA-0是一种突破性的视觉语言动作模型,它最大的特点在于实现了"开箱即用"的零修改部署能力。作为一名长期关注多模态AI发展的从业者,我首次接触到这个模型时就被它的设计理念所震撼——传统视觉语言模型往往需要针对特定任务进行繁…...
差分编码在40Gbps光通信中的实现与优化
1. 差分编码的核心原理与工程价值差分编码作为数字通信系统的基石技术,其数学本质是模2加法运算的链式反应。给定输入比特序列d_k,输出编码序列c_k满足递归关系:c_k c_{k-1} ⊕ d_k。这个看似简单的公式却解决了通信工程中的关键难题——相位…...
Windows 11安装完全指南:使用MediaCreationTool.bat轻松绕过硬件限制
Windows 11安装完全指南:使用MediaCreationTool.bat轻松绕过硬件限制 【免费下载链接】MediaCreationTool.bat Universal MCT wrapper script for all Windows 10/11 versions from 1507 to 21H2! 项目地址: https://gitcode.com/gh_mirrors/me/MediaCreationTool…...
Chrome升级后网页错乱?别慌!手把手教你回退到稳定版本(Windows/Mac/Linux全平台指南)
Chrome升级后网页错乱?全平台降级指南与深度解决方案 早上打开电脑,发现Chrome自动更新后最常访问的网站排版全乱了,插件图标变成灰色,工作效率瞬间归零——这种场景对现代办公族来说简直是噩梦。浏览器作为数字生活的枢纽&#…...
如何用技能树结构化你的技术成长路径
1. 项目概述与核心价值如果你在GitHub上搜索过“技能树”或者“学习路径”相关的项目,大概率会看到过kyledh/skills这个仓库。乍一看,它可能只是一个简单的Markdown文件集合,但当你真正深入进去,会发现它远不止于此。这是一个由资…...
