如何快速为团队打造自己的组件库(上)—— Element 源码架构
文章已收录到 github,欢迎 Watch 和 Star。
简介
详细讲解了 ElementUI 的源码架构,为下一步基于 ElementUI 打造团队自己的组件库打好坚实的基础。
如何快速为团队打造自己的组件库?
组件库是现代前端领域中不可缺少的一项基建。它可以提高代码的复用性、可维护性,提高团队的生产效率,更好的服务于未来。
那么如何为团队打造自己的组件库呢? 最理想的方案是借用社区的能力,去裁剪一个优秀的开源库,只保留你需要的东西,比如它的架构、工程化和文档能力,以及部分基础组件,在裁剪的过程中你可能会发现它的一些问题,然后在你的组件库中去优化并解决。
Element 源码架构
因为团队的技术栈是 Vue,所以选择基于 element 进行二次开发,在开始前先对 element 框架源码进行详细的刨析,为打造组件库做知识储备。element 框架源码由工程化、官网、组件库、测试和类型声明这 5 部分组成。
工程化
element 的架构是真的优秀,通过大量的脚本实现优秀的工程化,致力于让组件库的开发者专注于事情本身。比如添加新组件时,一键生成组件所有文件并完成这些文件基本结构的编写和相关的引入配置,总共涉及 13 个文件的添加和改动,而你只需完成组件定义这件事即可。element 的工程化由 5 部分组成:build 目录下的工程化配置和脚本、eslint、travis ci、Makefile、package.json 的 scripts。
build
build 目录存放工程化相关配置和脚本。比如 /build/bin
目录下的 JS 脚本让组件库开发者专注于组件的开发,除此之外不需要管其他任何事情;build/md-loader
是官网组件页面根据 markdown 实现组件 demo + 文档 的关键;还有比如持续集成、webpack 配置等,接下来就详细介绍这些配置和脚本。
/build/bin/build-entry.js
组件配置文件(components.json)结合字符串模版库,自动生成 /src/index.js 文件,避免每次新增组件时手动在 /src/index.js 中引入和导出组件。
/*** 生成 /src/index.js* 1、自动导入组件库所有组件* 2、定义全量注册组件库组件的 install 方法* 3、导出版本、install、各个组件*/// key 为包名、路径为值
var Components = require('../../components.json');
var fs = require('fs');
// 模版库
var render = require('json-templater/string');
// 负责将 comp-name 形式的字符串转换为 CompName
var uppercamelcase = require('uppercamelcase');
var path = require('path');
var endOfLine = require('os').EOL;// 输出路径 /src/index.js
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');
// 导入模版,import CompName from '../packages/comp-name/index.js'
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
// ' CompName'
var INSTALL_COMPONENT_TEMPLATE = ' {{name}}';
// /src/index.js 的模版
var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */{{include}}
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';const components = [
{{install}},CollapseTransition
];const install = function(Vue, opts = {}) {locale.use(opts.locale);locale.i18n(opts.i18n);components.forEach(component => {Vue.component(component.name, component);});Vue.use(InfiniteScroll);Vue.use(Loading.directive);Vue.prototype.$ELEMENT = {size: opts.size || '',zIndex: opts.zIndex || 2000};Vue.prototype.$loading = Loading.service;Vue.prototype.$msgbox = MessageBox;Vue.prototype.$alert = MessageBox.alert;Vue.prototype.$confirm = MessageBox.confirm;Vue.prototype.$prompt = MessageBox.prompt;Vue.prototype.$notify = Notification;Vue.prototype.$message = Message;};/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {install(window.Vue);
}export default {version: '{{version}}',locale: locale.use,i18n: locale.i18n,install,CollapseTransition,Loading,
{{list}}
};
`;delete Components.font;// 得到所有的包名,[comp-name1, comp-name2]
var ComponentNames = Object.keys(Components);// 存放所有的 import 语句
var includeComponentTemplate = [];
// 组件名数组
var installTemplate = [];
// 组件名数组
var listTemplate = [];// 遍历所有的包名
ComponentNames.forEach(name => {// 将连字符格式的包名转换成大驼峰形式,就是组件名,比如 form-item =》 FormItemvar componentName = uppercamelcase(name);// 替换导入语句中的模版变量,生成导入语句,import FromItem from '../packages/form-item/index.js'includeComponentTemplate.push(render(IMPORT_TEMPLATE, {name: componentName,package: name}));// 这些组件从 components 数组中剔除,不需要全局注册,采用挂载到原型链的方式,在模版字符串的 install 方法中有写if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) {installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {name: componentName,component: name}));}// 将所有的组件放到 listTemplates,最后导出if (componentName !== 'Loading') listTemplate.push(` ${componentName}`);
});// 替换模版中的四个变量
var template = render(MAIN_TEMPLATE, {include: includeComponentTemplate.join(endOfLine),install: installTemplate.join(',' + endOfLine),version: process.env.VERSION || require('../../package.json').version,list: listTemplate.join(',' + endOfLine)
});// 将就绪的模版写入 /src/index.js
fs.writeFileSync(OUTPUT_PATH, template);
console.log('[build entry] DONE:', OUTPUT_PATH);
/build/bin/build-locale.js
通过 babel 将 ES Module 风格的所有翻译文件(/src/locale/lang)转译成 UMD 风格。
/*** 通过 babel 将 ES Module 风格的翻译文件转译成 UMD 风格*/
var fs = require('fs');
var save = require('file-save');
var resolve = require('path').resolve;
var basename = require('path').basename;// 翻译文件目录,这些文件用于官网
var localePath = resolve(__dirname, '../../src/locale/lang');
// 得到目录下的所有翻译文件
var fileList = fs.readdirSync(localePath);// 转换函数
var transform = function(filename, name, cb) {require('babel-core').transformFile(resolve(localePath, filename), {plugins: ['add-module-exports',['transform-es2015-modules-umd', {loose: true}]],moduleId: name}, cb);
};// 遍历所有文件
fileList// 只处理 js 文件,其实目录下不存在非 js 文件.filter(function(file) {return /\.js$/.test(file);}).forEach(function(file) {var name = basename(file, '.js');// 调用转换函数,将转换后的代码写入到 lib/umd/locale 目录下transform(file, name, function(err, result) {if (err) {console.error(err);} else {var code = result.code;code = code.replace('define(\'', 'define(\'element/locale/').replace('global.', 'global.ELEMENT.lang = global.ELEMENT.lang || {}; \n global.ELEMENT.lang.');save(resolve(__dirname, '../../lib/umd/locale', file)).write(code);console.log(file);}});});
/build/bin/gen-cssfile.js
自动在 /packages/theme-chalk/src/index.scss|css
中引入各个组件包的样式,在全量注册组件库时需要用到这个样式文件,即 import 'packages/theme-chalk/src/index.scss
。
/*** 自动在 /packages/theme-chalk/src/index.scss|css 中引入各个组件包的样式* 在全量注册组件库时需要用到该样式文件,即 import 'packages/theme-chalk/src/index.scss*/
var fs = require('fs');
var path = require('path');
var Components = require('../../components.json');
var themes = ['theme-chalk'
];
// 得到所有的包名
Components = Object.keys(Components);
// 所有组件包的基础路径,/packages
var basepath = path.resolve(__dirname, '../../packages/');// 判断指定文件是否存在
function fileExists(filePath) {try {return fs.statSync(filePath).isFile();} catch (err) {return false;}
}// 遍历所有组件包,生成引入所有组件包样式的 import 语句,然后自动生成 packages/theme-chalk/src/index.scss|css 文件
themes.forEach((theme) => {// 是否是 scss,element-ui 默认使用 scss 编写样式var isSCSS = theme !== 'theme-default';// 导入基础样式文件 @import "./base.scss|css";\nvar indexContent = isSCSS ? '@import "./base.scss";\n' : '@import "./base.css";\n';// 遍历所有组件包,并生成 @import "./comp-package.scss|css";\nComponents.forEach(function(key) {// 跳过这三个组件包if (['icon', 'option', 'option-group'].indexOf(key) > -1) return;// comp-package.scss|cssvar fileName = key + (isSCSS ? '.scss' : '.css');// 导入语句,@import "./comp-package.scss|css";\nindexContent += '@import "./' + fileName + '";\n';// 如果该组件包的样式文件不存在,比如 /packages/form-item/theme-chalk/src/form-item.scss 不存在,则认为其被遗漏了,创建该文件var filePath = path.resolve(basepath, theme, 'src', fileName);if (!fileExists(filePath)) {fs.writeFileSync(filePath, '', 'utf8');console.log(theme, ' 创建遗漏的 ', fileName, ' 文件');}});// 生成 /packages/theme-chalk/src/index.scss|css,负责引入所有组件包的样式fs.writeFileSync(path.resolve(basepath, theme, 'src', isSCSS ? 'index.scss' : 'index.css'), indexContent);
});
/build/bin/i18n.js
根据模版(/examples/pages/template
)生成四种语言的官网页面的 .vue 文件。
'use strict';var fs = require('fs');
var path = require('path');
// 官网页面翻译配置,内置了四种语言
var langConfig = require('../../examples/i18n/page.json');// 遍历所有语言
langConfig.forEach(lang => {// 创建 /examples/pages/{lang},比如: /examples/pages/zh-CNtry {fs.statSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));} catch (e) {fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));}// 遍历所有的页面,根据 page.tpl 自动生成对应语言的 .vue 文件Object.keys(lang.pages).forEach(page => {// 比如 /examples/pages/template/index.tplvar templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);// /examples/pages/zh-CN/index.vuevar outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);// 读取模版文件var content = fs.readFileSync(templatePath, 'utf8');// 读取 index 页面的所有键值对的配置var pairs = lang.pages[page];// 遍历这些键值对,通过正则匹配的方式替换掉模版中对应的 keyObject.keys(pairs).forEach(key => {content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);});// 将替换后的内容写入 vue 文件fs.writeFileSync(outputPath, content);});
});
/build/bin/iconInit.js
根据 icon.scss 样式文件中的选择器,通过正则匹配的方式,匹配出所有的 icon 名称,然后将这些 icon 名组成数组,将数组写入到 /examples/icon.json
文件中,该文件在官网的 icon 图标页用来自动生成所有的 icon 图标。
'use strict';/*** 根据 icon.scss 样式文件中的选择器,通过正则匹配的方式,匹配出所有的 icon 名称,* 然后将所有 icon 名组成的数组写入到 /examples/icon.json 文件中* 该文件在官网的 icon 图标页用来自动生成所有的 icon 图标*/
var postcss = require('postcss');
var fs = require('fs');
var path = require('path');
// icon.scss 文件内容
var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8');
// 得到样式节点
var nodes = postcss.parse(fontFile).nodes;
var classList = [];// 遍历所有的样式节点
nodes.forEach((node) => {// 从选择器中匹配出 icon 名称,比如 el-icon-add,匹配得到 addvar selector = node.selector || '';var reg = new RegExp(/\.el-icon-([^:]+):before/);var arr = selector.match(reg);// 将 icon 名称写入数组,if (arr && arr[1]) {classList.push(arr[1]);}
});classList.reverse(); // 希望按 css 文件顺序倒序排列// 将 icon 名组成的数组写入 /examples/icon.json 文件
fs.writeFile(path.resolve(__dirname, '../../examples/icon.json'), JSON.stringify(classList), () => {});
/build/bin/new-lang.js
为组件库添加新语言,比如 fr(法语),分别为涉及到的文件(components.json、page.json、route.json、nav.config.json、docs)设置该语言的相关配置,具体的配置项默认为英语,你只需要在相应的文件中将这些英文配置项翻译为对应的语言即可。
'use strict';/*** 为组件库添加新语言,比如 fr(法语)* 分别为涉及到的文件(components.json、page.json、route.json、nav.config.json、docs)设置该语言的相关配置* 具体的配置项默认为英语,你只需要在相应的文件中将这些英文配置项翻译为对应的语言即可*/console.log();
process.on('exit', () => {console.log();
});if (!process.argv[2]) {console.error('[language] is required!');process.exit(1);
}var fs = require('fs');
const path = require('path');
const fileSave = require('file-save');
const lang = process.argv[2];
// const configPath = path.resolve(__dirname, '../../examples/i18n', lang);// 添加到 components.json
const componentFile = require('../../examples/i18n/component.json');
if (componentFile.some(item => item.lang === lang)) {console.error(`${lang} already exists.`);process.exit(1);
}
let componentNew = Object.assign({}, componentFile.filter(item => item.lang === 'en-US')[0], { lang });
componentFile.push(componentNew);
fileSave(path.join(__dirname, '../../examples/i18n/component.json')).write(JSON.stringify(componentFile, null, ' '), 'utf8').end('\n');// 添加到 page.json
const pageFile = require('../../examples/i18n/page.json');
// 新语言的默认配置为英语,你只需要去 page.json 中将该语言配置中的应为翻译为该语言即可
let pageNew = Object.assign({}, pageFile.filter(item => item.lang === 'en-US')[0], { lang });
pageFile.push(pageNew);
fileSave(path.join(__dirname, '../../examples/i18n/page.json')).write(JSON.stringify(pageFile, null, ' '), 'utf8').end('\n');// 添加到 route.json
const routeFile = require('../../examples/i18n/route.json');
routeFile.push({ lang });
fileSave(path.join(__dirname, '../../examples/i18n/route.json')).write(JSON.stringify(routeFile, null, ' '), 'utf8').end('\n');// 添加到 nav.config.json
const navFile = require('../../examples/nav.config.json');
navFile[lang] = navFile['en-US'];
fileSave(path.join(__dirname, '../../examples/nav.config.json')).write(JSON.stringify(navFile, null, ' '), 'utf8').end('\n');// docs 下新建对应文件夹
try {fs.statSync(path.resolve(__dirname, `../../examples/docs/${ lang }`));
} catch (e) {fs.mkdirSync(path.resolve(__dirname, `../../examples/docs/${ lang }`));
}console.log('DONE!');
/build/bin/new.js
为组件库添加新组件时会使用该脚本,一键生成组件所有文件并完成这些文件基本结构的编写和相关的引入配置,总共涉及 13 个文件的添加和改动,比如:make new city 城市列表
。该脚本的存在,让你为组件库开发新组件时,只需专注于组件代码的编写即可,其它的一概不用管。
'use strict';/*** 添加新组件* 比如:make new city 城市列表* 1、在 /packages 目录下新建组件目录,并完成目录结构的创建* 2、创建组件文档,/examples/docs/{lang}/city.md* 3、创建组件单元测试文件,/test/unit/specs/city.spec.js* 4、创建组件样式文件,/packages/theme-chalk/src/city.scss* 5、创建组件类型声明文件,/types/city.d.ts* 6、配置* 在 /components.json 文件中配置组件信息* 在 /examples/nav.config.json 中添加该组件的路由配置* 在 /packages/theme-chalk/src/index.scss 文件中自动引入该组件的样式文件* 将类型声明文件在 /types/element-ui.d.ts 中自动引入* 总之,该脚本的存在,让你只需专注于编写你的组件代码,其它的一概不用管*/console.log();
process.on('exit', () => {console.log();
});if (!process.argv[2]) {console.error('[组件名]必填 - Please enter new component name');process.exit(1);
}const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');
// 组件名称,比如 city
const componentname = process.argv[2];
// 组件的中文名称
const chineseName = process.argv[3] || componentname;
// 将组件名称转换为大驼峰形式,city => City
const ComponentName = uppercamelcase(componentname);
// 组件包目录,/packages/city
const PackagePath = path.resolve(__dirname, '../../packages', componentname);
// 需要添加的文件列表和文件内容的基本结构
const Files = [// /packages/city/index.js{filename: 'index.js',// 文件内容,引入组件,定义组件静态方法 install 用来注册组件,然后导出组件content: `import ${ComponentName} from './src/main';/* istanbul ignore next */
${ComponentName}.install = function(Vue) {Vue.component(${ComponentName}.name, ${ComponentName});
};export default ${ComponentName};`},// 定义组件的基本结构,/packages/city/src/main.vue{filename: 'src/main.vue',// 文件内容,sfccontent: `<template><div class="el-${componentname}"></div>
</template><script>
export default {name: 'El${ComponentName}'
};
</script>`},// 四种语言的文档,/examples/docs/{lang}/city.md,并设置文件标题{filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),content: `## ${ComponentName} ${chineseName}`},{filename: path.join('../../examples/docs/en-US', `${componentname}.md`),content: `## ${ComponentName}`},{filename: path.join('../../examples/docs/es', `${componentname}.md`),content: `## ${ComponentName}`},{filename: path.join('../../examples/docs/fr-FR', `${componentname}.md`),content: `## ${ComponentName}`},// 组件测试文件,/test/unit/specs/city.spec.js{filename: path.join('../../test/unit/specs', `${componentname}.spec.js`),// 文件内容,给出测试文件的基本结构content: `import { createTest, destroyVM } from '../util';
import ${ComponentName} from 'packages/${componentname}';describe('${ComponentName}', () => {let vm;afterEach(() => {destroyVM(vm);});it('create', () => {vm = createTest(${ComponentName}, true);expect(vm.$el).to.exist;});
});
`},// 组件样式文件,/packages/theme-chalk/src/city.scss{filename: path.join('../../packages/theme-chalk/src', `${componentname}.scss`),// 文件基本结构content: `@import "mixins/mixins";
@import "common/var";@include b(${componentname}) {
}`},// 组件类型声明文件{filename: path.join('../../types', `${componentname}.d.ts`),// 类型声明文件基本结构content: `import { ElementUIComponent } from './component'/** ${ComponentName} Component */
export declare class El${ComponentName} extends ElementUIComponent {
}`}
];// 将组件添加到 components.json,{ City: './packages/city/index.js' }
const componentsFile = require('../../components.json');
if (componentsFile[componentname]) {console.error(`${componentname} 已存在.`);process.exit(1);
}
componentsFile[componentname] = `./packages/${componentname}/index.js`;
fileSave(path.join(__dirname, '../../components.json')).write(JSON.stringify(componentsFile, null, ' '), 'utf8').end('\n');// 将组件样式文件在 index.scss 中引入
const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
fileSave(sassPath).write(sassImportText, 'utf8').end('\n');// 将组件的类型声明文件在 element-ui.d.ts 中引入
const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');let elementTsText = `${fs.readFileSync(elementTsPath)}
/** ${ComponentName} Component */
export class ${ComponentName} extends El${ComponentName} {}`;const index = elementTsText.indexOf('export') - 1;
const importString = `import { El${ComponentName} } from './${componentname}'`;elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);fileSave(elementTsPath).write(elementTsText, 'utf8').end('\n');// 遍历 Files 数组,创建列出的所有文件并写入文件内容
Files.forEach(file => {fileSave(path.join(PackagePath, file.filename)).write(file.content, 'utf8').end('\n');
});// 在 nav.config.json 中添加新组件对应的路由配置
const navConfigFile = require('../../examples/nav.config.json');// 遍历配置中的各个语言,在所有语言配置中都增加该组件的路由配置
Object.keys(navConfigFile).forEach(lang => {let groups = navConfigFile[lang][4].groups;groups[groups.length - 1].list.push({path: `/${componentname}`,title: lang === 'zh-CN' && componentname !== chineseName? `${ComponentName} ${chineseName}`: ComponentName});
});fileSave(path.join(__dirname, '../../examples/nav.config.json')).write(JSON.stringify(navConfigFile, null, ' '), 'utf8').end('\n');console.log('DONE!');
这里有个缺点就是,新建组件时不会自动重新生成 /src/index.js,也就是说不会将新生成的组件自动在组件库入口中引入。这也简单,只需要配置下 Makefile 即可,将 new 命令改成 node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS)) && npm run build:file
即可。
/build/bin/template.js
监听 /examples/pages/template
目录下的所有模版文件,当模版文件发生改变时自动执行 npm run i18n
,即执行 i18n.js 脚本,重新生成四种语言的 .vue 文件。
/*** 监听 /examples/pages/template 目录下的所有模版文件,当模版文件发生改变时自动执行 npm run i18n,* 即执行 i18n.js 脚本,重新生成四种语言的 .vue 文件*/const path = require('path');
// 监听目录
const templates = path.resolve(process.cwd(), './examples/pages/template');// 负责监听的库
const chokidar = require('chokidar');
// 监听模板目录
let watcher = chokidar.watch([templates]);// 当目录下的文件发生改变时,自动执行 npm run i18n
watcher.on('ready', function() {watcher.on('change', function() {exec('npm run i18n');});
});// 负责执行命令
function exec(cmd) {return require('child_process').execSync(cmd).toString().trim();
}
/build/bin/version.js
根据 /package.json
文件,自动生成 /examples/version.json
,用于记录组件库的版本信息,这些版本洗洗在官网组件页面的头部导航栏会用到。
/*** 根据 package.json 自动生成 /examples/version.json,用于记录组件库的版本信息* 这些版本信息在官网组件页面的头部导航栏会用到*/
var fs = require('fs');
var path = require('path');
var version = process.env.VERSION || require('../../package.json').version;
var content = { '1.4.13': '1.4', '2.0.11': '2.0', '2.1.0': '2.1', '2.2.2': '2.2', '2.3.9': '2.3', '2.4.11': '2.4', '2.5.4': '2.5', '2.6.3': '2.6', '2.7.2': '2.7', '2.8.2': '2.8', '2.9.2': '2.9', '2.10.1': '2.10', '2.11.1': '2.11', '2.12.0': '2.12', '2.13.2': '2.13', '2.14.1': '2.14' };
if (!content[version]) content[version] = '2.15';
fs.writeFileSync(path.resolve(__dirname, '../../examples/versions.json'), JSON.stringify(content));
/build/md-loader
它是一个 loader,官网组件页面的 组件 demo + 文档的模式一大半的功劳都是源自于它。
可以在 /examples/route.config.js
中看到 registerRoute
方法生成组件页面的路由配置时,使用 loadDocs
方法加载/examples/docs/{lang}/comp.md
。注意,这里加载的 markdown
文档,而不是平时常见的 vue 文件,但是却能想 vue 文件一样在页面上渲染成一个 Vue 组件,这是怎么做到的呢?
我们知道,webpack 的理念是一切资源都可以 require,只需配置相应的 loader 即可。在 /build/webpack.demo.js
文件中的 module.rules
下可以看到对 markdow(.md) 规则的处理,先通过 md-loader
处理 markdown
文件,从中解析出 vue 代码,然后交给 vue-loader,最终生成 sfc(vue 单文件组件)渲染到页面。这就能看到组件页面的文档 + 组件 demo 展示效果。
{test: /\.md$/,use: [{loader: 'vue-loader',options: {compilerOptions: {preserveWhitespace: false}}},{loader: path.resolve(__dirname, './md-loader/index.js')}]
}
如果对 loader 的具体实现感兴趣可以自行深入阅读。
/build/config.js
webpack 的公共配置,比如 externals、alias 等。通过 externals 的配置解决了组件库部分代码的冗余问题,比如组件和组件库公共模块的代码,但是组件样式冗余问题没有得到解决;alias 别名配置为开发组件库提供了方便。
/*** webpack 公共配置,比如 externals、alias*/
var path = require('path');
var fs = require('fs');
var nodeExternals = require('webpack-node-externals');
var Components = require('../components.json');var utilsList = fs.readdirSync(path.resolve(__dirname, '../src/utils'));
var mixinsList = fs.readdirSync(path.resolve(__dirname, '../src/mixins'));
var transitionList = fs.readdirSync(path.resolve(__dirname, '../src/transitions'));
/*** externals 解决组件依赖其它组件并按需引入时代码冗余的问题* 比如 Table 组件依赖 Checkbox 组件,在项目中如果我同时引入 Table 和 Checkbox 时,会不会产生冗余代码* 如果没有以下内容的的话,会,这时候你会看到有两份 Checkbox 组件代码。* 包括 locale、utils、mixins、transitions 这些公共内容,也会出现冗余代码* 但有了 externals 的设置,就会将告诉 webpack 不需要将这些 import 的包打包到 bundle 中,运行时再从外部去* 获取这些扩展依赖。这样就可以在打包后 /lib/tables.js 中看到编译后的 table.js 对 Checkbox 组件的依赖引入:* module.exports = require("element-ui/lib/checkbox")* 这么处理之后就不会出现冗余的 JS 代码,但是对于 CSS 部分,element-ui 并未处理冗余情况。* 可以看到 /lib/theme-chalk/table.css 和 /lib/theme-chalk/checkbox.css 中都有 Checkbox 组件的样式*/
var externals = {};Object.keys(Components).forEach(function(key) {externals[`element-ui/packages/${key}`] = `element-ui/lib/${key}`;
});externals['element-ui/src/locale'] = 'element-ui/lib/locale';
utilsList.forEach(function(file) {file = path.basename(file, '.js');externals[`element-ui/src/utils/${file}`] = `element-ui/lib/utils/${file}`;
});
mixinsList.forEach(function(file) {file = path.basename(file, '.js');externals[`element-ui/src/mixins/${file}`] = `element-ui/lib/mixins/${file}`;
});
transitionList.forEach(function(file) {file = path.basename(file, '.js');externals[`element-ui/src/transitions/${file}`] = `element-ui/lib/transitions/${file}`;
});externals = [Object.assign({vue: 'vue'
}, externals), nodeExternals()];exports.externals = externals;// 设置别名,方便使用
exports.alias = {main: path.resolve(__dirname, '../src'),packages: path.resolve(__dirname, '../packages'),examples: path.resolve(__dirname, '../examples'),'element-ui': path.resolve(__dirname, '../')
};exports.vue = {root: 'Vue',commonjs: 'vue',commonjs2: 'vue',amd: 'vue'
};exports.jsexclude = /node_modules|utils\/popper\.js|utils\/date\.js/;
/build/deploy-ci.sh
和 travis ci 结合使用的持续集成脚本,这个脚本在 .travis.yml 文件中被执行,代码被提交到 github 仓库以后会自动被 Tavis CI 执行,ci 会自动找项目中的 .travis.yml 文件,并执行里面的命令。但这个我们可能用不到,一般团队内部都会有自己的持续集成方案。
/build/git-release.sh
这里主要是和远程的 dev 分支做 diff 然后合并。
#!/usr/bin/env sh# 这里主要是和远程的 dev 分支做 diff 然后合并git checkout devif test -n "$(git status --porcelain)"; thenecho 'Unclean working tree. Commit or stash changes first.' >&2;exit 128;
fiif ! git fetch --quiet 2>/dev/null; thenecho 'There was a problem fetching your branch. Run `git fetch` to see more...' >&2;exit 128;
fiif test "0" != "$(git rev-list --count --left-only @'{u}'...HEAD)"; thenecho 'Remote history differ. Please pull changes.' >&2;exit 128;
fiecho 'No conflicts.' >&2;
/build/release.sh
脚本完成了以下工作:
-
合并 dev 分支到 master、
-
修改样式包和组件库的版本号
-
发布样式包和组件库
-
提交 master 和 dev 分支到远程仓库
该脚本在发布组件库时可以使用,特别是其中自动更改版本号的功能(每次 publish 时都忘改版本号)。这里提交代码到远程仓库的日志很简单,更详细的提交日志时通过更新日志文件 CHANGELOG.{lang}.md
提供的。
#!/usr/bin/env sh
set -e# 合并 dev 分支到 master
# 编译打包
# 修改样式包和组件库的版本号
# 发布样式包和组件库
# 提交 master 和 dev 分支到远程仓库# 合并 dev 分支到 master
git checkout master
git merge dev# 版本选择 cli
VERSION=`npx select-version-cli`# 是否确认当前版本信息
read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
thenecho "Releasing $VERSION ..."# build,编译打包VERSION=$VERSION npm run dist# ssr testnode test/ssr/require.test.js # publish themeecho "Releasing theme-chalk $VERSION ..."cd packages/theme-chalk# 更改主题包的版本信息npm version $VERSION --message "[release] $VERSION"# 发布主题if [[ $VERSION =~ "beta" ]]thennpm publish --tag betaelsenpm publishficd ../..# commitgit add -Agit commit -m "[build] $VERSION"# 更改组件库的版本信息npm version $VERSION --message "[release] $VERSION"# publish,将 master 推到远程仓库git push eleme mastergit push eleme refs/tags/v$VERSIONgit checkout devgit rebase mastergit push eleme dev# 发布组件库if [[ $VERSION =~ "beta" ]]thennpm publish --tag betaelsenpm publishfi
fi
/build/webpack.xx.js
-
webpack.common.js,构建 commonjs2 规范的包,会打一个全量的包
-
webpack.component.js,构建 commonjs2 规范的包,支持按需加载
支持按需加载的重点在于 entry 和 ouput 的配置,将每个组件打成单独的包
-
webpack.conf.js,构建 UMD 规范的包,会打一个全量的包
-
webpack.demo.js,官网项目的 webpack 配置
-
webpack.extension.js,主题编辑器的 chorme 插件项目的 webpack 配置,项目在 extension 目录下
-
webpack.test.js,这个文件没什么用,不过看命名,应该是想用于测试项目的 webpack 配置,不过现在测试用的是 karma 框架
eslint
element 通过 eslint 来保证代码风格的一致性,还专门编写了 elemefe 作为 eslint 的扩展规则配置。为了保证官网项目的质量,在 /build/webpack.demo.js
中配置了 eslint-loader
规则,在项目启动时强制检查代码质量。但是 element 在代码质量控制这块儿做的还是不够,比如:代码自动格式化能力太弱、只保证了 /src、/test、/packages、/build 目录下的代码质量,对于官网项目做的不够,特别是 文档格式的限制。这里建议大家再集成一个 prettier
专门去做格式限制,让 eslint
专注于代码语法的限制,可以参考 搭建自己的 typescript 项目 + 开发自己的脚手架工具 ts-cli 中的 代码质量
部分去配置。
travis ci
travis ci 结合脚本的方式来完成持续集成的工作,不过这个可能对于内部项目用不上,因为 travis ci 只能用于 github,内部一般使用 gitlab,也有配套的持续集成
Makefile
make 命令的配置文件,写过 C、C++ 的同学应该比较熟悉。
执行 make 命令可以看到详细的帮助信息。比如:执行 make install 装包、make dev 启动本地开发环境、make new comp-name 中文名 新建组件等。使用 make 命令相较于 npm run xx
更方便、清晰、简单,不过其内部也是依赖于 npm run xx
来完成真正的工作,相当于为了更好的开发体验,将众多 npm run cmd
提供了一层封装。
package.json -> scripts
elemnt 编写了很多 npm scripts,这些 script 结合 /build 中的众多脚本实现通过脚本来自动完成大量重复的体力劳动,比人工靠谱且效率更高,这个设计我觉得是 element 中最值得大家学习的地方,可以将这样的设计应用到自己的项目中,助力业务提效。
{// 装包"bootstrap": "yarn || npm i",// 通过JS脚本,自动生成以下文件:生成 examples/icon.json 文件 && 生成 src/index.js 文件 && 生成四种语言的官网的 .vue 文件 && 生成 examples/version.json 文件,包含了组件库的版本信息"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",// 构建主题样式:在 index.scss 中自动引入各个组件的样式文件 && 通过 gulp 将 scss 文件编译成 css 并输出到 lib 目录 && 拷贝基础样式 theme-chalk 到 lib/theme-chalk"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",// 通过 babel 编译 src 目录,然后将编译后的文件输出到 lib 目录,忽略 /src/index.js"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",// 将 ES Module 风格的翻译文件编译成 UMD 风格"build:umd": "node build/bin/build-locale.js",// 清除构建产物"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",// 构建官网项目"deploy:build": "npm run build:file && cross-env NODE_ENV=production webpack --config build/webpack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME",// 构建主题插件"deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js",// 启动主题插件的开发环境"dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js",// 启动组件库的本地开发环境。执行 build:file,自动化生成一些文件 && 启动 example 项目,即官网 && 监听 examples/pages/template 目录下所有模版文件的变化,如果改变了则重新生成 .vue","dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",// 组件测试项目,在 examples/play/index.vue 中可以引入组件库任意组件,也可以直接使用 dev 启动的项目,在文档中使用组件"dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js",// 构建组件库"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",// 生成四种语言的官网的 .vue 文件"i18n": "node build/bin/i18n.js",// lint,保证项目代码质量"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",// 装包 && 合并远程仓库的 dev 分支 && 合并 dev 分支到 master、打包编译、修改样式包和组件库的版本号、发布样式包和组件库、提交代码到远程仓库。使用时注掉最后一个脚本,那个脚本有问题"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js",// 生成测试报告,不论是 test 还是 test:watch,生成一次测试报告耗时太长了"test": "npm run lint && npm run build:theme && cross-env CI_ENV=/dev/ BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",// 启动测试项目,可以检测测试文件的更新"test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js"
}
官网
element 的官网是和组件库在一个仓库内,官网的所有东西都放在 /examples 目录下,就是一个 vue 项目。
entry.js
官网项目的入口,在这里全量引入组件库,及其样式。
// 官网项目的入口,就是一个普通的 vue 项目
import Vue from 'vue';
import entry from './app';
import VueRouter from 'vue-router';
// 引入组件库,main 是别名,在 /build/config.js 中有配置
import Element from 'main/index.js';
import hljs from 'highlight.js';
// 路由配置
import routes from './route.config';
// 官网项目的一些组件
import demoBlock from './components/demo-block';
import MainFooter from './components/footer';
import MainHeader from './components/header';
import SideNav from './components/side-nav';
import FooterNav from './components/footer-nav';
import title from './i18n/title';// 组件库样式
import 'packages/theme-chalk/src/index.scss';
import './demo-styles/index.scss';
import './assets/styles/common.css';
import './assets/styles/fonts/style.css';
// 将 icon 信息挂载到 Vue 原型链上,在 markdown 文档中被使用,在官网的 icon 图标 页面展示出所有的 icon 图标
import icon from './icon.json';Vue.use(Element);
Vue.use(VueRouter);
Vue.component('demo-block', demoBlock);
Vue.component('main-footer', MainFooter);
Vue.component('main-header', MainHeader);
Vue.component('side-nav', SideNav);
Vue.component('footer-nav', FooterNav);const globalEle = new Vue({data: { $isEle: false } // 是否 ele 用户
});Vue.mixin({computed: {$isEle: {get: () => (globalEle.$data.$isEle),set: (data) => {globalEle.$data.$isEle = data;}}}
});Vue.prototype.$icon = icon; // Icon 列表页用const router = new VueRouter({mode: 'hash',base: __dirname,routes
});router.afterEach(route => {// https://github.com/highlightjs/highlight.js/issues/909#issuecomment-131686186Vue.nextTick(() => {const blocks = document.querySelectorAll('pre code:not(.hljs)');Array.prototype.forEach.call(blocks, hljs.highlightBlock);});const data = title[route.meta.lang];for (let val in data) {if (new RegExp('^' + val, 'g').test(route.name)) {document.title = data[val];return;}}document.title = 'Element';ga('send', 'event', 'PageView', route.name);
});new Vue({ // eslint-disable-line...entry,router
}).$mount('#app');
nav.config.json
官网组件页面的侧边导航栏配置,一定要了解该 json 文件的结构,才能看懂 route.config.js
文件中生成组件页面所有路由的代码。
route.config.js
根据路由配置自动生成官网项目的路由配置。
// 根据路由配置自动生成官网项目的路由
import navConfig from './nav.config';
// 支持的所有语言
import langs from './i18n/route';// 加载官网各个页面的 .vue 文件
const LOAD_MAP = {'zh-CN': name => {return r => require.ensure([], () =>r(require(`./pages/zh-CN/${name}.vue`)),'zh-CN');},'en-US': name => {return r => require.ensure([], () =>r(require(`./pages/en-US/${name}.vue`)),'en-US');},'es': name => {return r => require.ensure([], () =>r(require(`./pages/es/${name}.vue`)),'es');},'fr-FR': name => {return r => require.ensure([], () =>r(require(`./pages/fr-FR/${name}.vue`)),'fr-FR');}
};const load = function(lang, path) {return LOAD_MAP[lang](path);
};// 加载官网组件页面各个组件的 markdown 文件
const LOAD_DOCS_MAP = {'zh-CN': path => {return r => require.ensure([], () =>r(require(`./docs/zh-CN${path}.md`)),'zh-CN');},'en-US': path => {return r => require.ensure([], () =>r(require(`./docs/en-US${path}.md`)),'en-US');},'es': path => {return r => require.ensure([], () =>r(require(`./docs/es${path}.md`)),'es');},'fr-FR': path => {return r => require.ensure([], () =>r(require(`./docs/fr-FR${path}.md`)),'fr-FR');}
};const loadDocs = function(lang, path) {return LOAD_DOCS_MAP[lang](path);
};// 添加组件页的各个路由配置,以下这段代码要看懂必须明白 nav.config.json 文件的结构
const registerRoute = (navConfig) => {let route = [];// 遍历配置,生成四种语言的组件路由配置Object.keys(navConfig).forEach((lang, index) => {// 指定语言的配置,比如 lang = zh-CN,navs 就是所有配置项都是中文写的let navs = navConfig[lang];// 组件页面 lang 语言的路由配置route.push({// 比如: /zh-CN/componentpath: `/${ lang }/component`,redirect: `/${ lang }/component/installation`,// 加载组件页的 component.vuecomponent: load(lang, 'component'),// 组件页的所有子路由,即各个组件,放这里,最后的路由就是 /zh-CN/component/comp-pathchildren: []});// 遍历指定语言的所有配置项navs.forEach(nav => {if (nav.href) return;if (nav.groups) {// 该项为组件nav.groups.forEach(group => {group.list.forEach(nav => {addRoute(nav, lang, index);});});} else if (nav.children) {// 该项为开发指南nav.children.forEach(nav => {addRoute(nav, lang, index);});} else {// 其它,比如更新日志、Element React、Element AngularaddRoute(nav, lang, index);}});});// 生成子路由配置,并填充到 children 中function addRoute(page, lang, index) {// 根据 path 决定是加载 vue 文件还是加载 markdown 文件const component = page.path === '/changelog'? load(lang, 'changelog'): loadDocs(lang, page.path);let child = {path: page.path.slice(1),meta: {title: page.title || page.name,description: page.description,lang},name: 'component-' + lang + (page.title || page.name),component: component.default || component};// 将子路由添加在上面的 children 中route[index].children.push(child);}return route;
};// 得到组件页面所有侧边栏的路由配置
let route = registerRoute(navConfig);const generateMiscRoutes = function(lang) {let guideRoute = {path: `/${ lang }/guide`, // 指南redirect: `/${ lang }/guide/design`,component: load(lang, 'guide'),children: [{path: 'design', // 设计原则name: 'guide-design' + lang,meta: { lang },component: load(lang, 'design')}, {path: 'nav', // 导航name: 'guide-nav' + lang,meta: { lang },component: load(lang, 'nav')}]};let themeRoute = {path: `/${ lang }/theme`,component: load(lang, 'theme-nav'),children: [{path: '/', // 主题管理name: 'theme' + lang,meta: { lang },component: load(lang, 'theme')},{path: 'preview', // 主题预览编辑name: 'theme-preview-' + lang,meta: { lang },component: load(lang, 'theme-preview')}]};let resourceRoute = {path: `/${ lang }/resource`, // 资源meta: { lang },name: 'resource' + lang,component: load(lang, 'resource')};let indexRoute = {path: `/${ lang }`, // 首页meta: { lang },name: 'home' + lang,component: load(lang, 'index')};return [guideRoute, resourceRoute, themeRoute, indexRoute];
};langs.forEach(lang => {route = route.concat(generateMiscRoutes(lang.lang));
});route.push({path: '/play',name: 'play',component: require('./play/index.vue')
});let userLanguage = localStorage.getItem('ELEMENT_LANGUAGE') || window.navigator.language || 'en-US';
let defaultPath = '/en-US';
if (userLanguage.indexOf('zh-') !== -1) {defaultPath = '/zh-CN';
} else if (userLanguage.indexOf('es') !== -1) {defaultPath = '/es';
} else if (userLanguage.indexOf('fr') !== -1) {defaultPath = '/fr-FR';
}route = route.concat([{path: '/',redirect: defaultPath
}, {path: '*',redirect: defaultPath
}]);export default route;
play
包括 play.js
和 play/index.vue
,示例项目,比如你想看一个 element 中某个组件的效果,特别是组件按需加载时的显示效果,可以在 play/index.vue
中引入使用,使用 npm run dev:play
命令启动项目,也是在 /build/webpack.demo.js
中通过环境变量来配置的。
// play.js
import Vue from 'vue';
// 全量引入组件库和其样式
import Element from 'main/index.js';
import 'packages/theme-chalk/src/index.scss';
import App from './play/index.vue';Vue.use(Element);new Vue({ // eslint-disable-linerender: h => h(App)
}).$mount('#app');
<!-- play/index.vue -->
<template><div style="margin: 20px;"><el-input v-model="input" placeholder="请输入内容"></el-input></div>
</template><script>export default {data() {return {input: 'Hello Element UI!'};}};
</script>
pages
官网的各个页面都在这里,通过 i18n.js 脚本 结合 pages/template 目录下的各个模版文件自动在 pages 目录下生成四种语言的 .vue 文件,这些 vue 文件会在 route.config.js 中被加载。
i18n
官网页面的翻译配置文件都在这里。
- component.json,组件页面的翻译配置
- page.json,其它页面的一些翻译配置,比如首页、设计页等
- route.json,语言配置,表示组件库目前都支持那些语言
- theme-editor.json,主题编辑器页面的翻译配置
- title.json,官网各个页面在 tab 标签中显示的 title 信息
extension
主题编辑器的 chrome 插件项目。
dom
定义了 dom 样式操作方法,包括判断是否存在指定的样式、添加样式、移除样式、切换样式。
// dom/class.js
export const hasClass = function(obj, cls) {return obj.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
};export const addClass = function(obj, cls) {if (!hasClass(obj, cls)) obj.className += ' ' + cls;
};export const removeClass = function(obj, cls) {if (hasClass(obj, cls)) {const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');obj.className = obj.className.replace(reg, ' ');}
};export const toggleClass = function(obj, cls) {if (hasClass(obj, cls)) {removeClass(obj, cls);} else {addClass(obj, cls);}
};
docs
组件文档目录,默认提供了四种语言的文档,目录结构为:docs/{lang}/comp-name.md
。这些文档在组件页面加载(在 route.config.js 中有配置),先交给 md-loader 处理,提取其中的 vue 代码,然后交给 vue-loader 去处理,最后渲染到页面形成组件 demo + 文档。
demo-style
组件页面中显示的 组件 demo 的排版样式,和组件自身的样式无关,就像你业务代码中给组件定义排版样式一样。因为组件在有些场景下直接显示效果不好,所以就需要经过一定的排版,比如 button 页面、icon 页面等。
components
官网项目存放一些全局组件的目录。
assets
官网项目的静态资源目录
组件库
element 组件库由两部分组成:/src
和 /packages
。
src
利用模块化的开发思想,把组件依赖的一些公共模块放在 /src
目录下,并依据功能拆分出以下模块:
- utils,定义了一些工具方法
- transitions,动画
- mixins,全局混入的一些方法
- locale,国际化功能以及各种语言的
部分组件
的翻译文件 - directives,指令
/src/index.js
是通过脚本 /build/bin/build-entry.js
脚本自动生成,是组件库的入口。负责自动导入组件库的所有组件、定义全量注册组件库组件的 install 方法,然后导出版本信息、install 和 各个组件。
/* 通过 './build/bin/build-entry.js' 文件自动生成 */// 引入所有组件
import Pagination from '../packages/pagination/index.js';
import Dialog from '../packages/dialog/index.js';
// ...// 组件数组,有些组件没在里面,这些组件不需要通过 Vue.use 或者 Vue.component 的方式注册,直接挂载到 Vue 原型链上
const components = [Pagination,Dialog,// ...
]// 定义 install 方法,负责全量引入组件库
const install = function(Vue, opts = {}) {locale.use(opts.locale);locale.i18n(opts.i18n);// 全局注册组件components.forEach(component => {Vue.component(component.name, component);});Vue.use(InfiniteScroll);Vue.use(Loading.directive);// 在 Vue 原型链上挂点东西Vue.prototype.$ELEMENT = {size: opts.size || '',zIndex: opts.zIndex || 2000};// 这些组件不需要Vue.prototype.$loading = Loading.service;Vue.prototype.$msgbox = MessageBox;Vue.prototype.$alert = MessageBox.alert;Vue.prototype.$confirm = MessageBox.confirm;Vue.prototype.$prompt = MessageBox.prompt;Vue.prototype.$notify = Notification;Vue.prototype.$message = Message;};// 通过 CDN 引入组件库时,走下面这段代码,全量注册组件库
if (typeof window !== 'undefined' && window.Vue) {install(window.Vue);
}// 导出版本信息、install 方法、各个组件
export default {version: '2.15.0',locale: locale.use,i18n: locale.i18n,install,CollapseTransition,Loading,// ...
}
为了减少篇幅,只贴出文件的一部分,但足以说明一切。
/packages
element 将组件全部都放在了 /packages
目录下,每个组件以目录为单位,目录结构以及其中的基本代码是通过脚本 /build/bin/new.js
自动生成的。目录结构为:
- package-name,连字符形式的包名
- index.js,组件的 install 方法,表示组件是以 Vue 插件的形式存在
- src,组件的源码目录
- main.vue 组件的基本结构已经就绪
比如新建的 city 组件的目录及文件是这样的:
-
city
-
index.js
import City from './src/main';/* istanbul ignore next */ City.install = function(Vue) {Vue.component(City.name, City); };export default City;
-
src
-
main.vue
<template><div class="el-city"></div> </template><script> export default {name: 'ElCity' }; </script>
-
-
其实 /packages
目录下除了组件之外,还有一个特殊的目录 theme-chalk
,它是组件库的样式目录,所有组件的样式代码都在这里,element 的组件文件中没有定义样式。theme-chalk
目录也是一个项目,通过 gulp
打包,并支持独立发布,其目录结构是这样的:
-
theme-chalk
-
src,组件样式的源码目录
- index.scss,引入目录下所有的样式文件
- comp.scss,组件样式文件,比如:button.scss
- other,比如:字体、公共样式、变量、方法等
-
.gitignore
-
gulpfile.js
'use strict';// gulp 配置文件const { series, src, dest } = require('gulp'); const sass = require('gulp-sass'); const autoprefixer = require('gulp-autoprefixer'); const cssmin = require('gulp-cssmin');// 将 scss 编译成 css 并压缩,最后输出到 ./lib 目录下 function compile() {return src('./src/*.scss').pipe(sass.sync()).pipe(autoprefixer({browsers: ['ie > 9', 'last 2 versions'],cascade: false})).pipe(cssmin()).pipe(dest('./lib')); }// 拷贝 ./src/fonts 到 ./lib/fonts function copyfont() {return src('./src/fonts/**').pipe(cssmin()).pipe(dest('./lib/fonts')); }exports.build = series(compile, copyfont);
-
package.json
-
README.md
-
测试
组件库的测试项目,使用 karma
框架
类型声明
每个组件的类型声明文件,TS 项目使用组件库时有更好的代码提示。
结束
到这里 element 的源码架构分析就结束了,建议读者参照文章,亲自去阅读框架源码并添加注释,这样理解会更深,也更利于后续工作的开展。下一篇将详细讲解 基于 Element 为团队打造组件库
的过程。
链接
- Element 源码架构 思维导图版
- Element 源码架构 视频版,关注微信公众号,回复: “Element 源码架构视频版” 获取
- 组件库专栏
- 如何快速为团队打造自己的组件库(下)—— 基于 element-ui 为团队打造自己的组件库
- github
文章已收录到 github,欢迎 Watch 和 Star。
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 火山引擎 MARS-APMPlus X 美篇 | 形成应用性能全面监控,大幅提升APP稳定性
本文主要介绍了美篇 App 通过使用火山引擎 APMPlus ,使应用性能的稳定性大幅提升的技术实践。 2月24日晚 MARS TALK 直播间,我们邀请了火山引擎 APMPlus 和美篇的研发工程师,为大家分享相关技术方案及实现细节。现在报名加入活动群 还有机会获…...
2024/4/15 9:58:44 - 最大值(二)
题目描述 输入三个正整数 a,b,ca,b,c , 输出三个数中最大的那个。 输入格式 一行三个正整数,a,b,ca,b,c 输出格式 a,b,ca,b,c 中的最大值 样例数据 样例输入#1 1 2 3样例输出#1 3样例输入#2 6 4 7样例输出#2 7样例输入#3 6 3 5样例输出#3 6数据范围 对…...
2024/4/20 8:25:31 - http消息
菜鸟教程 – 学的不仅是技术,更是梦想! html教程 首页 HTML CSS JAVASCRIPT VUE BOOTSTRAP NODEJS JQUERY PYTHON JAVA C C C# GO SQL LINUX 本地书签 HTTP 教程 HTTP 教程 HTTP 简介 HTTP 消息结构 HTTP 请求方法 HTTP 响应头信息 HTTP 状态码 HTTP con…...
2024/4/19 9:07:55 - Conda和PiP使用清华源安装Python的channel设置方法
设置源: conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.c…...
2024/5/5 19:59:45 - 力扣-222. 完全二叉树的节点个数
222. 完全二叉树的节点个数 AC Code class Solution { public:int countNodes(TreeNode* root) {if(!root) return 0;return countNodes(root->left) countNodes(root->right) 1;} };...
2024/4/13 7:26:14 - django利用Paginator 配合layui实现分页效果
django利用Paginator 配合layui实现分页效果最近 自己写的一个运维管理平台需要在前端页面展示一个用户表的数据,顺便实现分页功能,作此记录,方便日后翻阅 前端用的Layui ,后端用的django框架。 #相关代码如下 <table class&qu…...
2024/5/5 21:18:19 - MATLAB函数的求导(一阶、高阶、参数方程求导实例)
当求导的函数比较复杂,可以利用MATLAB中diff语句进行求解导数。 注:matlab中可以使用指令syms x定义一个符号变量x,符号变量可以进行算术运算、积分、求导等操作。 1、(一阶导数) syms x >> diff(x^sin(x))ans …...
2024/5/5 19:29:08 - 幸运数字
题目描述 如果一个数能被3整除,或者能被5整除,那么这个数是小T的幸运数字。 编写程序,判断输入的整数是否是幸运数字。如果是,输出Yes;不是幸运数字就输出No。 输入格式 一行一个整数 aa 输出格式 符合题目要求的…...
2024/4/18 10:45:44 - 力扣 1984. 学生分数的最小差值
题目: 分析: 我们可以先让这个数组有序,要使得最小,则在有序状态下这几个人必须是相邻的,根据k的大小取相邻数量的差值来进行比较 代码: class Solution {public int minimumDifference(int[] nums, int…...
2024/4/17 0:17:24 - C#/.NET第六期000开学典礼:课程简介、C/S、B/S的概念
系列文章目录 文章目录系列文章目录一、老师介绍开发工具二、开发分类三、课程介绍四、项目案例五、就业薪资六、学员须知群下载软件开发工具代码在哪里PPT——在群里面,会实时更新学员须知七、老师致辞一、老师介绍 开发工具 二、开发分类 我们主要是学习C/S和B/S…...
2024/4/19 8:07:50 - JUC——读写锁
目录读写锁介绍ReentrantReadWriteLockCOW和读写锁对比读写锁介绍 读锁又叫共享锁,可以允许多个线程读数据;写锁又叫做独占锁,只能允许一个线程进行写操作。 读锁和写锁都可能引起死锁现象: 所以为避免死锁现象,对…...
2024/4/27 13:12:34 - 悬浮窗 (拖动)+录屏
用到: 1.android的onClick()和onTouch()方法详解 button.setOnClickListener(new OnClickListener() {Overridepublic void onClick(View v) {Log.d("TAG", "onClick execute");} }); button.setOnTouchListener(new OnTouchListener() {Overr…...
2024/4/18 3:40:08 - Spring 工作原理
内部最核心的就是 IOC 了,动态注入,让一个对象的创建不用 new 了,可以自动的 生产,这其实就是利用 java 里的反射,反射其实就是在运行时动态的去创建、调用 对象,Spring 就是在运行时,跟 xml Spring 的配置…...
2024/5/5 19:21:15 - 秒懂 Web Component
前言 哈喽,大家好,我是海怪。 最近不是写了一篇关于京东微前端框架的文章嘛 《初探 MicroApp,一个极致简洁的微前端框架》,里面提到了一个叫 Web Components 的东西。虽然对它早有耳闻,但是一直也没怎么仔细看过&…...
2024/4/25 18:00:58 - 基于关系图卷积网络的源代码漏洞检测
文章结构 1. 引言 1.1 漏洞现状 1.2 漏洞检测方法 1.2.1 传统方法:动态静态 1.2.2 机器学习方法 1.2.2.1 基于软件度量 1.2.2.2 基于模式学习 1.2.2 深度学习方法 2. 背景介绍 2.1 各种图 2.2 图深度学习模型 3. 方法介绍 3.1 基本过程 3.2 生成图 3…...
2024/5/5 4:59:51 - LeetCode个人题解
1...
2024/5/5 9:14:12 - 221900437方彬彬
221900437方彬彬 寒假作业 这个作业属于哪个课程2022年福大软件工程实践W班这个作业要求在哪里软件工程实践寒假作业这个作业的目标回首过去 立足当下展望未来学习路线其他参考文献无目录 gitcode上传截图和代码地址一、回首过去 当初你为什么选择软件工程这个专业?当初对软…...
2024/4/13 7:26:59 - 写一个单例模式?描述工厂模式和单例优缺点 举例在什么情况下用
class Single{} public class SingleFactory { private SingleFactory(){}; private static Single single; public static Single getSingle(){ if(singlenull){ singlenew Single(); } return single; }} 简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个…...
2024/5/5 10:26:03 - 设计模式-重点掌握
一、https://kdocs.cn/l/cn8o3MBg9yBN 二、每种设计模式全面介绍 三、典型具体模式: 1、创建型模式 (1)工厂模式 I)概念及举例: 我们将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。 Facto…...
2024/5/4 21:31:22 - 禅道开源版修改mysql密码
按照官方指导在服务器上部署了禅道 选择适合您的安装方法 - 禅道开源版使用帮助 - 禅道开源项目管理软件https://www.zentao.net/book/zentaopmshelp/40.html要注意的是,如果使用一键安装方式,由于目前云服务器预装了 apache 或者 lamp, 使…...
2024/4/25 8:16:49
最新文章
- Spring 原理
🎥 个人主页:Dikz12🔥个人专栏:Spring学习之路📕格言:吾愚多不敏,而愿加学欢迎大家👍点赞✍评论⭐收藏 目录 Bean的作用域 代码实现 观察Bean的作用域 Bean的生命周期 Spring …...
2024/5/5 22:11:25 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 基于物联网的智能家居远程视频监控系统设计与实现
基于物联网的智能家居远程视频监控系统设计与实现 摘要:随着物联网技术的快速发展,智能家居系统已成为提升家居安全性和便利性的重要手段。本文设计并实现了一套基于物联网的智能家居远程视频监控系统,该系统结合了嵌入式技术、网络通信技术…...
2024/5/4 14:29:47 - 金融数据_PySpark-3.0.3决策树(DecisionTreeClassifier)实例
金融数据_PySpark-3.0.3决策树(DecisionTreeClassifier)实例 逻辑回归: 逻辑回归常被用于二分类问题, 比如涨跌预测。你可以将涨跌标记为类别, 然后使用逻辑回归进行训练。 决策树和随机森林: 决策树和随机森林是用于分类问题的强大模型。它们能够处理非线性关系, 并且对于特…...
2024/5/4 17:20:04 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/4 23:54:56 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/4 23:54:56 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/5/4 23:54:56 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/4 23:55:17 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/5/4 23:54:56 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/5/4 23:55:05 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/5/4 23:54:56 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/5/4 23:55:16 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/5/4 23:54:56 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/4 18:20:48 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/5/4 23:54:56 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/5/4 23:55:17 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/5/4 23:55:06 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/5/4 23:54:56 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/4 23:55:06 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/5 8:13:33 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/5/4 23:55:16 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/5/4 23:54:58 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/4 23:55:01 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/5/4 23:54:56 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57