init: init

This commit is contained in:
陈文彬 2020-07-11 10:34:12 +08:00
commit ed51b20ed5
436 changed files with 34708 additions and 0 deletions

4
.browserslistrc Normal file
View File

@ -0,0 +1,4 @@
# Build target (browser compatible) https://github.com/browserslist/browserslist
> 1%
last 3 versions
ie > 8

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
root = true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=false
indent_style=space
indent_size=2
[*.yml]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

12
.env Normal file
View File

@ -0,0 +1,12 @@
# post
VUE_APP_PORT=9988
# static resource dir
VUE_APP_ASSETS_DIR=static
# spa-title
GLOB_APP_TITLE=vben admin
# spa shortname
GLOB_APP_SHORT_NAME=vben_admin

24
.env.development Normal file
View File

@ -0,0 +1,24 @@
# Modify the configuration file must be re-run to work
# import() => require() Increase hot update speed
VUE_CLI_BABEL_TRANSPILE_MODULES=true
# vue public path
VUE_APP_PUBLIC_PATH=/
# Cross-domain proxy, you can configure multiple
VUE_APP_PROXY=[["/app","http://localhost:3000"]]
# Whether to use cache for development TRUE or FALSE
VUE_APP_USE_CACHE=FALSE
# Development environment supports ie environment TRUE or FALSE
VUE_APP_SUPPORT_IE=TRUE
# Whether to open the mock TRUE or FALSE
VUE_APP_USE_MOCK=TRUE
# Basic interface address SPA
GLOB_API_URL=/app
# Interface prefix
GLOB_API_URL_PREFIX=/v1.0

25
.env.production Normal file
View File

@ -0,0 +1,25 @@
# The production environment configuration at the beginning of GLOB will be extracted into a js file and added to the window variable
# delete or not console.log TRUE or FALSE
BUILD_ON_CLEAN_CONSOLE=TRUE
# vue public path
VUE_APP_PUBLIC_PATH=./
# Whether to use cache to compile TRUE or FALSE
VUE_APP_USE_CACHE=TRUE
# Whether to enable gzip compression TRUE or FALSE
BUILD_ON_GZIP=FALSE
# Production environment support ie environment TRUE or FALSE
VUE_APP_SUPPORT_IE=TRUE
# Whether to open the mock TRUE or FALSE
VUE_APP_USE_MOCK=TRUE
# interface address
GLOB_API_URL=/app
# Interface prefix
GLOB_API_URL_PREFIX=/v1.0

25
.eslintignore Normal file
View File

@ -0,0 +1,25 @@
*.sh
node_modules
lib
*.md
*.scss
*.woff
*.ttf
.vscode
.idea
/dist/
/mock/
/public
/docs
.vscode
.local
/bin
/build
/config
Dockerfile
vue.config.js
commit-lint.js
/src/assets/iconfont/
/types/shims
/src/types/shims

126
.eslintrc.js Normal file
View File

@ -0,0 +1,126 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'eslint:recommended',
'@vue/standard',
'@vue/typescript/recommended',
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
'no-console': 'off',
'no-debugger': 'off',
'no-new': 'off',
'vue/no-side-effects-in-computed-properties': 'off',
'no-useless-constructor': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'prefer-const': [
'error',
{
destructuring: 'all',
ignoreReadBeforeAssign: false,
},
],
'vue/attribute-hyphenation': 'error',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'vue/attributes-order': [
'error',
{
order: [
'DEFINITION',
'LIST_RENDERING',
'CONDITIONALS',
'RENDER_MODIFIERS',
'GLOBAL',
'UNIQUE',
'TWO_WAY_BINDING',
'OTHER_DIRECTIVES',
'OTHER_ATTR',
'EVENTS',
'CONTENT',
],
alphabetical: false,
},
],
'comma-dangle': ['error', 'only-multiline'],
indent: 'off',
'no-mixed-spaces-and-tabs': 'error',
semi: ['error', 'always'],
'arrow-spacing': [
'error',
{
before: true,
after: true,
},
],
'consistent-this': ['error', 'that'],
'eol-last': ['error', 'always'],
'wrap-iife': 'error',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'no-use-before-define': [
'error',
{
functions: false,
classes: true,
},
],
'@typescript-eslint/no-use-before-define': [
'error',
{
functions: false,
classes: true,
},
],
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^h$',
varsIgnorePattern: '^h$',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^h$',
varsIgnorePattern: '^h$',
},
],
'space-before-function-paren': 'off',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
env: {
jest: true,
},
},
],
};

49
.gitattributes vendored Normal file
View File

@ -0,0 +1,49 @@
* text=auto eol=lf
# Images
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.tiff binary
# Fonts
*.oft binary
*.ttf binary
*.eot binary
*.woff binary
*.woff2 binary
# Videos
*.mov binary
*.mp4 binary
*.webm binary
*.ogg binary
*.mpg binary
*.3gp binary
*.avi binary
*.wmv binary
*.flv binary
*.asf binary
# Audio
*.mp3 binary
*.wav binary
*.flac binary
# Compressed
*.gz binary
*.zip binary
*.7z binary
*.html text eol=lf
*.css text eol=lf
*.js text eol=lf
*.scss text eol=lf
*.vue text eol=lf
*.hbs text eol=lf
*.sh text eol=lf
*.md text eol=lf
*.json text eol=lf
*.yml text eol=lf

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
.DS_Store
node_modules
/dist
/build/.cache
.local
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

1
.npmrc Normal file
View File

@ -0,0 +1 @@
registry = https://registry.npm.taobao.org

8
.prettierignore Normal file
View File

@ -0,0 +1,8 @@
/dist/*
.local
/public/**
.output.js
/node_modules/**
**/*.svg
**/*.sh

8
.stylelintignore Normal file
View File

@ -0,0 +1,8 @@
*.js
*.png
*.eot
*.ttf
*.woff
/dist/
/src/assets/iconfont/

48
.yarnclean Normal file
View File

@ -0,0 +1,48 @@
# test directories
__tests__
test
tests
powered-test
# asset directories
docs
doc
website
images
assets
# examples
example
examples
# code coverage directories
coverage
.nyc_output
# build scripts
Makefile
Gulpfile.js
Gruntfile.js
# configs
appveyor.yml
circle.yml
codeship-services.yml
codeship-steps.yml
wercker.yml
.tern-project
.gitattributes
.editorconfig
.*ignore
.eslintrc
.jshintrc
.flowconfig
.documentup.json
.yarn-metadata.json
.travis.yml
# misc
*.md
!istanbul-reports/lib/html/assets
!istanbul-api/node_modules/istanbul-reports/lib/html/assets

56
README.md Normal file
View File

@ -0,0 +1,56 @@
# VBEN ADMIN
一个基于`ant-design-vue`,`typescript`,`vue-composition-api`实现的 vue3 风格的后台管理系统,用 vue3 的写法写 vue2
兼容 vue2 的浏览器,一切免费,不收取任何费用,请放心使用。后续定期提供更新及维护,可供参考与学习
## 为什么写这个
目前在网上暂时没有找到相关的使用 composition-api 写的后台系统,所以就把自己写的分享出来,后续会陆续加上新的功能,并且等`vue3`完全稳定的时候
会提供`vue3`版本,该项目后续切换 vue3 的成本相对较低,后续会做成一键切换 2 和 3 的版本
## 为什么要使用 vue-composition-api
最大的原因是我们还要兼容`ie10` 和`ie11``ie9` 需要自己修改 css 兼容性,就算 `vue3`稳定下来最多也就支持`ie11`,所以使用了`vue-composition-api`进行开发
## 安装
```js
// 拉取项目代码
git clone https://github.com/anncwb/vben-admin.git
cd vben-admin
// 最好使用yarn否则热更新可能出现问题
yarn install
```
## 开发计划
由于开发时间较短,所以功能暂时较少
后续会逐步完善,有需要什么组件可以提出来
- [x] 项目搭建(基于 vue-cli4已经优化
- [x] 登陆页(已 mock
- [x] 菜单(可以搜索及拖拽以及菜单布局)
- [x] 多标签页(面包屑没做,后续可以加)
- [x] 基于角色的权限管理
- [] 基于后台的权限管理
- [] 组件封装(目前想到的只有 modal table form upload 等) 其余的可以在想我提出
- [] 常用页面实现及例子
- [] 首页设计
- [] 项目可配置
- [] 工具封装
- [] vue3 一键切换
- [] 主题配置
- 更多功能
## 讨论群
QQ: 569291866
由于项目刚开始几天,群还没什么人,有兴趣的可以加群一起讨论

64
babel.config.js Normal file
View File

@ -0,0 +1,64 @@
const { isProductionFn } = require('./build/utils');
const plugins = [];
if (isProductionFn() && process.env.BUILD_ON_CLEAN_CONSOLE === 'TRUE') {
plugins.push('transform-remove-console');
} else {
plugins.push([
'captains-log',
{
injectVariableName: true,
injectFileName: false,
ignorePatterns: [
'node_modules',
'build',
'config',
'mock',
'dist',
'docs',
'public',
'.spec.js',
],
},
]);
}
module.exports = (api) => {
return {
plugins: [
'@vue/babel-plugin-transform-vue-jsx',
[
'babel-plugin-component',
{
libraryName: '@ylz/ele-ui',
styleLibraryName: 'theme-chalk',
},
],
[
'babel-plugin-import',
{ libraryName: 'ant-design-vue', libraryDirectory: 'es', style: true },
'ant-design-vue',
],
...plugins,
],
presets: [
require('./build/lib/jsx/index.js'),
// 'babel-preset-vca-jsx',
[
'@vue/cli-plugin-babel/preset',
{
// 对ES6的模块文件不做转化以便使用tree shaking、sideEffects等
modules: false,
// browserslist环境不支持的所有垫片都导入
useBuiltIns: 'entry',
corejs: { version: 3, proposals: true },
forceAllTransforms: api.env('production'),
targets: {
chrome: '58',
ie: '9',
},
},
],
],
};
};

5
build/getCwdPath.js Normal file
View File

@ -0,0 +1,5 @@
const { join } = require('path');
module.exports = function (dir) {
return join(process.cwd(), dir);
};

44
build/getEnvConfig.js Normal file
View File

@ -0,0 +1,44 @@
const resolve = require('./getCwdPath');
const dotenv = require('dotenv');
const fs = require('fs-extra');
const { readFileSync } = fs;
/**
* @description: 获取环境变量
* @Date: 2020-06-16 17:09:07
*/
function getEnvConfig(match = 'GLOB_', confFiles = ['.env', '.env.production']) {
let envConfig = {};
confFiles.forEach((item) => {
try {
const env = dotenv.parse(readFileSync(resolve(item)));
envConfig = { ...envConfig, ...env };
} catch (error) {}
});
Object.keys(envConfig).forEach((key) => {
const reg = new RegExp(`^(${match})`);
if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key);
}
});
return envConfig;
}
/**
* @description: 获取多页标题
* @Date: 2020-06-16 17:09:00
*/
function getMpaTitles() {
const { GLOB_APP_TITLE_MPA } = getEnvConfig('GLOB_', ['.env']);
try {
const titleObj = JSON.parse(GLOB_APP_TITLE_MPA);
return titleObj;
} catch (error) {
return {};
}
}
module.exports = {
getEnvConfig,
getMpaTitles,
};

View File

@ -0,0 +1,80 @@
const syntaxJsx = require('@babel/plugin-syntax-jsx').default;
const importSource = '@/setup/vue';
const hasJSX = (t, path) => {
const JSXChecker = {
hasJSX: false,
};
path.traverse(
{
JSXElement() {
this.hasJSX = true;
},
},
JSXChecker
);
return JSXChecker.hasJSX;
};
// remove `var h = this.$createElement;` in `setup()`
const remove$createElement = (t, path) => {
path.traverse({
ObjectMethod(p) {
const isSetup = p.node.key.name === 'setup';
if (!isSetup) return;
p.traverse({
VariableDeclaration(varPath) {
varPath.traverse({
MemberExpression(p) {
if (
t.isThisExpression(p.node.object) &&
t.isIdentifier(p.node.property) &&
p.node.property.name === '$createElement'
) {
varPath.remove();
}
},
});
},
});
},
});
};
// auto import `createElement as h` from `@vue/composition-api`
const autoImportH = (t, path) => {
if (hasJSX(t, path)) {
const importNodes = path
.get('body')
.filter((p) => p.isImportDeclaration())
.map((p) => p.node);
const vcaImportNodes = importNodes.filter((p) => p.source.value === importSource);
const hasH = vcaImportNodes.some((p) =>
p.specifiers.some((s) => t.isImportSpecifier(s) && s.local.name === 'h')
);
if (!hasH) {
const vcaImportSpecifier = t.importSpecifier(t.identifier('h'), t.identifier('h'));
if (vcaImportNodes.length > 0) {
vcaImportNodes[0].specifiers.push(vcaImportSpecifier);
} else {
path.unshiftContainer(
'body',
t.importDeclaration([vcaImportSpecifier], t.stringLiteral(importSource))
);
}
}
}
};
module.exports = ({ types: t }) => {
return {
inherits: syntaxJsx,
visitor: {
Program(path) {
remove$createElement(t, path);
autoImportH(t, path);
},
},
};
};

View File

@ -0,0 +1,88 @@
/**
* Convert object to functional component
* @param t
* @param path
*/
const convertSetupFunctionalComponent = (t, path) => {
const properties = path.node.properties;
const nameProp = properties.find(node => node.key.name === 'name');
const renderProp = properties.find(node => node.key.name === 'render');
const renderBody = renderProp.value.body;
const renderParams = renderProp.value.params;
const name = nameProp && nameProp.value.value;
const params = [...renderParams.slice(renderParams.length && renderParams[0].name === 'h' ? 1 : 0)];
const props = [
t.objectProperty(t.identifier('setup'), t.arrowFunctionExpression(params, renderBody))
];
if (name) {
props.unshift(t.objectProperty(t.identifier('name'), t.stringLiteral(name)));
}
path.replaceWith(t.objectExpression(props));
}
/**
* Check if it's a setup functional componet declarator
* @param t
* @param path
* @returns boolean
*/
const isSetupFunctionalComponentDeclarator = (t, path) => {
const properties = path.node.properties;
const isFunctional = properties.filter(node => {
return (
t.isObjectProperty(node) &&
((node.key.name === 'functional' && node.value.value === true) || node.key.name === 'render')
);
}).length === 2;
if (!isFunctional) return false;
const renderBody = properties.find(node => node.key.name === 'render').value.body;
const returnStatement = t.isBlockStatement(renderBody) && renderBody.body.find(node => t.isReturnStatement(node));
return (
t.isArrowFunctionExpression(renderBody) ||
t.isFunctionExpression(renderBody) ||
(returnStatement && t.isArrowFunctionExpression(returnStatement.argument)) ||
(returnStatement && t.isFunctionExpression(returnStatement.argument))
);
}
module.exports = ({ types: t }) => {
return {
visitor: {
Program(path) {
path.traverse({
ExportDefaultDeclaration(path) {
const declaration = path.get('declaration');
if (
!t.isObjectExpression(declaration) ||
!isSetupFunctionalComponentDeclarator(t, declaration)
) {
return;
}
convertSetupFunctionalComponent(t, declaration);
},
VariableDeclaration(path) {
if (
path.node.declarations.length !== 1 ||
!t.isVariableDeclarator(path.node.declarations[0]) ||
!t.isObjectExpression(path.node.declarations[0].init)
) {
return;
}
const declarator = path.get('declarations')[0];
const init = declarator.get('init');
if (!isSetupFunctionalComponentDeclarator(t, init)) {
return;
}
convertSetupFunctionalComponent(t, init);
}
});
}
}
};
}

View File

@ -0,0 +1,225 @@
/**
* Definition like `const root = ref(null);`.
* @param {*} t
* @param {*} path
* @returns {boolean}
*/
const isRefCallee = (t, path) => {
if (!t.isVariableDeclaration(path)) return;
const declarations = path.get('declarations');
if (declarations.length !== 1) return;
const init = declarations[0].get('init');
if (!t.isCallExpression(init)) return;
return init.node.callee.name === 'ref' && t.isNullLiteral(init.get('arguments')[0]);
};
/**
* Match definedRefs in JSX
* @param {*} t
* @param {*} path ObjectMethod
* @returns {*} `refs` to be processed
*/
const matchRefsInJSX = (t, path, definedRefs = []) => {
const refs = [];
path.traverse({
// Definition like `h('h1', { ref: ... })`.
CallExpression(path) {
if (path.node.callee.name !== 'h') return;
const [, obj] = path.get('arguments');
if (!obj || !t.isObjectExpression(obj)) return;
const props = obj.get('properties');
const refProp = props.find((path) => path.node.key.name === 'ref');
if (!refProp) return;
const value = refProp.get('value');
if (t.isIdentifier(value)) {
const ref = value.node.name;
if (definedRefs.indexOf(ref) > -1) {
refs.push(ref);
value.replaceWith(t.stringLiteral(ref));
}
} else if (t.isStringLiteral(value)) {
const ref = value.node.value;
if (definedRefs.indexOf(ref) > -1) {
refs.push(ref);
}
}
},
// Definition like `<h1 ref={...}></h1>`.
JSXAttribute(path) {
if (path.node.name.name !== 'ref') return;
const value = path.get('value');
if (t.isJSXExpressionContainer(value)) {
const exp = value.get('expression');
if (!exp || !t.isIdentifier(exp)) return;
const ref = exp.node.name;
if (definedRefs.indexOf(ref) > -1) {
refs.push(ref);
value.replaceWith(t.stringLiteral(ref));
}
} else if (t.isStringLiteral(value)) {
const ref = value.node.value;
if (definedRefs.indexOf(ref) > -1) {
refs.push(ref);
}
}
},
});
return refs;
};
const autoImportVue = (t, path) => {
const importSource = 'vue';
const imports = path.node.body.filter(t.isImportDeclaration);
const imported = imports.find((node) => node.source.value === importSource);
const autoImport = () =>
path.unshiftContainer(
'body',
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier('Vue'))],
t.stringLiteral(importSource)
)
);
if (!imported) {
autoImport();
return;
}
const defaultSpecifier = imported.specifiers.find(t.isImportDefaultSpecifier);
if (!defaultSpecifier) {
imported.specifiers.push(t.importDefaultSpecifier(t.identifier('Vue')));
} else if (defaultSpecifier.local.name !== 'Vue') {
autoImport();
}
};
/**
* auto import lifeCycle
* @param {*} t
* @param {*} path Program
* @param {*} lifeCycles
*/
const autoImportLifeCycle = (t, path, lifeCycles = []) => {
const importSource = '@/setup/vue';
const imports = path.node.body.filter(t.isImportDeclaration);
const imported = imports.find((node) => node.source.value === importSource);
const genImportSpecifiers = (list) =>
list.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
if (!imported) {
path.unshiftContainer(
'body',
t.importDeclaration(genImportSpecifiers(lifeCycles), t.stringLiteral(importSource))
);
return lifeCycles;
}
const specifiers = imported.specifiers.filter(t.isImportSpecifier);
const names = lifeCycles.map((name) => {
const node = specifiers.find((node) => name === node.imported.name);
if (node) {
return node.local.name;
} else {
imported.specifiers.push(...genImportSpecifiers([name]));
return name;
}
});
return names;
};
/**
* inject onMounted(() => {
* Vue.$nextTick(() => {
* ...
* });
* });
* @param {*} t
* @param {*} path ObjectMethod
* @param {*} lifeCycles
* @param {*} refs
*/
const injectLifeCycle = (t, path, lifeCycles = [], refs = []) => {
const [prop, ctx] = path.get('params');
const props = ['refs'];
const getProperties = () =>
props.map((val) => t.objectProperty(t.identifier(val), t.identifier(val), false, true));
if (!prop) {
path.node.params.push(t.identifier('_'));
}
if (!ctx) {
path.node.params.push(t.objectPattern(getProperties()));
} else {
if (t.isObjectPattern(ctx)) {
const refsProp = ctx.node.properties.find((node) => node.key.name === props[0]);
if (!refsProp) {
ctx.node.properties.push(...getProperties());
} else {
props[0] = refsProp.value.name;
}
} else {
props.unshift(ctx.node.name);
}
}
const identifiers = props.map((val) => t.identifier(val));
const statements = refs.map((val) => {
return t.expressionStatement(
t.assignmentExpression(
'=',
t.memberExpression(t.identifier(val), t.identifier('value')),
t.logicalExpression(
'||',
t.memberExpression(
identifiers.length > 1 ? t.memberExpression(...identifiers) : identifiers[0],
t.identifier(val)
),
t.nullLiteral()
)
)
);
});
const nextTick = t.expressionStatement(
t.callExpression(t.memberExpression(t.identifier('Vue'), t.identifier('nextTick')), [
t.arrowFunctionExpression([], t.blockStatement(statements)),
])
);
const content = lifeCycles.map((val) =>
t.expressionStatement(
t.callExpression(t.identifier(val), [
t.arrowFunctionExpression([], t.blockStatement([...statements, nextTick])),
])
)
);
path.get('body').unshiftContainer('body', content);
};
module.exports = ({ types: t }) => {
return {
visitor: {
Program(p) {
p.traverse({
'ObjectMethod|ObjectProperty'(path) {
if (path.node.key.name !== 'setup') return;
let container = path;
if (t.isObjectProperty(path)) {
container = path.get('value');
}
if (!t.isBlockStatement(container.get('body'))) return;
const body = container.get('body').get('body');
const definedRefs = body
.filter((path) => isRefCallee(t, path))
.map((path) => path.get('declarations')[0].node.id.name);
if (!definedRefs.length) return;
const refs = matchRefsInJSX(t, container, definedRefs);
if (!refs.length) return;
autoImportVue(t, p);
const lifeCycles = autoImportLifeCycle(t, p, ['onMounted', 'onUpdated']);
injectLifeCycle(t, container, lifeCycles, refs);
},
});
},
},
};
};

View File

@ -0,0 +1,56 @@
const autoImportVue = (t, path) => {
const importSource = "vue";
const imports = path.node.body.filter(t.isImportDeclaration);
const imported = imports.find(node => node.source.value === importSource);
const autoImport = () =>
path.unshiftContainer(
"body",
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier("Vue"))],
t.stringLiteral(importSource)
)
);
if (!imported) {
autoImport();
return;
}
const defaultSpecifier = imported.specifiers.find(t.isImportDefaultSpecifier);
if (!defaultSpecifier) {
imported.specifiers.push(t.importDefaultSpecifier(t.identifier("Vue")));
} else if (defaultSpecifier.local.name !== "Vue") {
autoImport();
}
};
module.exports = ({ types: t }) => {
return {
visitor: {
Program(p) {
p.traverse({
"ObjectMethod|ObjectProperty"(path) {
if (path.node.key.name !== "setup") return;
path.traverse({
JSXAttribute(path) {
const n = path.get("name");
const isInputOrModel = n.node.name === "on-input" || n.node.name === "on-change" || n.node.name === "model";
if (!isInputOrModel) return;
path.traverse({
MemberExpression(path) {
const obj = path.get("object");
const prop = path.get("property");
if (t.isThisExpression(obj) && t.isIdentifier(prop) && prop.node.name === "$set") {
autoImportVue(t, p);
obj.replaceWith(t.identifier("Vue"));
prop.replaceWith(t.identifier("set"));
}
}
});
}
});
}
});
}
}
};
};

10
build/lib/jsx/index.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = function () {
return {
plugins: [
require('./babel-sugar-inject-h'),
require('./babel-sugar-setup-functional'),
require('./babel-sugar-setup-ref'),
require('./babel-sugar-v-model-v1.1.2-patch'),
],
};
};

View File

@ -0,0 +1,263 @@
/**
* mock service middleware, modified from umijs
*/
const { existsSync } = require('fs');
const { join } = require('path');
const bodyParser = require('body-parser');
const glob = require('glob');
const assert = require('assert');
const chokidar = require('chokidar');
const pathToRegexp = require('path-to-regexp');
const chalk = require('chalk');
const VALID_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
const BODY_PARSED_METHODS = ['post', 'put', 'patch'];
/**
* mockPath: mock目录地址
* configPath: mock导入文件配置,如果没有则会遍历mockPath地址进行获取
* ignoreFiles: 遍历地址时候忽略的文件
* ignoreStartsWith: 遍历地址的时候忽略以什么开头的文件
* handleWrap 包装返回结果函数
*/
module.exports = function createMockMiddleware({
mockPath = 'mock',
configPath = 'mock.config.js',
ignoreFiles = [],
ignoreStartsWith = '_',
handleWrap = null,
} = {}) {
const cwd = process.cwd();
const absMockPath = join(cwd, mockPath);
const absConfigPath = join(cwd, configPath);
require('@babel/register');
let mockData = getConfig();
watch();
function watch() {
if (process.env.WATCH_FILES === 'none') return;
const watcher = chokidar.watch([absConfigPath, absMockPath], {
ignoreInitial: true,
});
watcher.on('all', (event, file) => {
console.log(chalk.green(`mock file ${event}.`));
// console.log(chalk.green(`mock file ${event}, reload mock data${file}`));
// console.log(`file: ${file}`);
mockData = getConfig();
});
}
function getConfig() {
cleanRequireCache();
let ret = null;
if (existsSync(absConfigPath)) {
// console.log(chalk.blue(`load mock data from ${absConfigPath}`));
ret = require(absConfigPath);
} else {
// 如果没有mock文件夹,则读取配置文件内所有文件
const mockFiles = glob
.sync('**/*.js', {
cwd: absMockPath,
ignore: ignoreFiles,
})
.filter((mf) => !mf.startsWith(ignoreStartsWith));
// including files ${JSON.stringify(
// mockFiles
// )}
// console.log(chalk.blue(`load mock data from ${absMockPath}`));
try {
ret = mockFiles.reduce((memo, mockFile) => {
const mod = require(join(absMockPath, mockFile));
memo = {
...memo,
...mod.default,
};
return memo;
}, {});
} catch (error) {
ret = {};
console.log(`${chalk.red('mock reload error!')}`);
}
}
return normalizeConfig(ret);
}
function randomFrom(lowerValue, upperValue) {
return Math.floor(Math.random() * (upperValue - lowerValue + 1) + lowerValue);
}
function parseKey(key) {
const len = key.length - key.replace(/\s/g, '').length;
let method = 'get';
let path = key;
let timeout = 0;
if (len > 0) {
const splitEd = key.split(' ');
method = splitEd[0].toLowerCase();
path = splitEd[1];
if (len === 2) {
timeout = splitEd[2];
}
}
assert(
VALID_METHODS.includes(method),
`Invalid method ${method} for path ${path}, please check your mock files.`
);
return {
method,
path,
timeout,
};
}
function createHandler(method, path, handler, timeout) {
return function (req, res, next) {
if (BODY_PARSED_METHODS.includes(method)) {
bodyParser.json({ limit: '5mb', strict: false })(req, res, () => {
bodyParser.urlencoded({ limit: '5mb', extended: true })(req, res, () => {
sendData(req);
});
});
} else {
sendData();
}
async function sendData(iReq) {
try {
if (timeout) {
await sleep(timeout);
}
if (typeof handler === 'function') {
const { body = {}, query = {} } = iReq || req || {};
res.json(
handler({
body,
method,
query,
})
);
} else {
res.json(handler);
}
} catch (error) {
console.log(
chalk.red('mock handle error: 处理您的函数时候出现了错误,请检查你的mock函数!')
);
res.json({});
}
}
};
}
function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
}
function normalizeConfig(config) {
return Object.keys(config).reduce((memo, key) => {
const handler = config[key];
const type = typeof handler;
assert(
type === 'function' || type === 'object',
`mock value of ${key} should be function or object, but got ${type}`
);
let { method, path, timeout } = parseKey(key);
if (timeout && timeout.indexOf('-')) {
let [a, b] = timeout.split('-');
if (a > b) {
[b, a] = [a, b];
}
timeout = randomFrom(~~a, ~~b);
}
const keys = [];
const re = pathToRegexp(path, keys);
memo.push({
method,
path,
re,
keys,
handler: createHandler(method, path, handler, timeout),
});
return memo;
}, []);
}
function cleanRequireCache() {
Object.keys(require.cache).forEach((file) => {
if (file === absConfigPath || file.indexOf(absMockPath) > -1) {
delete require.cache[file];
}
});
}
/**
* 请求是否匹配
* @param {*} req
*/
function matchMock(req) {
const { path: exceptPath } = req;
const exceptMethod = req.method.toLowerCase();
for (const mock of mockData) {
const { method, re, keys } = mock;
if (method === exceptMethod) {
const match = re.exec(req.path);
if (match) {
const params = {};
for (let i = 1; i < match.length; i = i + 1) {
const key = keys[i - 1];
const prop = key.name;
const val = decodeParam(match[i]);
if (val !== undefined || !hasOwnProperty.call(params, prop)) {
params[prop] = val;
}
}
req.params = params;
return mock;
}
}
}
function decodeParam(val) {
if (typeof val !== 'string' || val.length === 0) {
return val;
}
try {
return decodeURIComponent(val);
} catch (err) {
if (err instanceof URIError) {
err.message = `Failed to decode param ' ${val} '`;
err.status = err.statusCode = 400;
}
throw err;
}
}
return mockData.filter(({ method, re }) => {
return method === exceptMethod && re.test(exceptPath);
})[0];
}
return (req, res, next) => {
const match = matchMock(req);
if (match) {
console.log(
`${chalk.yellow('mock matched:')} ${chalk.green.bold(
match.method.toUpperCase()
)} ${chalk.blue.bold(match.path)}`
);
const wrapIsFunc = typeof handleWrap === 'function';
const result = match.handler(req, res, next);
return wrapIsFunc ? wrapIsFunc(result) : result;
} else {
return next();
}
};
};

View File

@ -0,0 +1,59 @@
/**
* 使用cnpm 来安装image-webpack-loader
*/
// const shelljs = require('shelljs');
// const { exec } = shelljs;
// /**
// * @description: 判断是否装了cnpm
// * @Date: 2020-06-10 13:46:40
// */
// function isInstallCnpm() {
// return new Promise((resolve) => {
// const runSuccess = exec('cnpm -v').code === 0;
// resolve(runSuccess);
// });
// }
// /**
// * @description: 尝试安装cnpm
// * @Date: 2020-06-10 13:47:05
// */
// function tryInstallCnpm() {
// return new Promise((resolve) => {
// if (exec('npm install -g cnpm --registry=https://registry.npm.taobao.org').code === 0) {
// resolve();
// } else {
// shell.echo('请安装 cnpm');
// shell.exit(1);
// }
// });
// }
// /**
// * @description: 安装插件
// * @Date: 2020-06-10 13:46:47
// */
// async function installImageWebpack() {
// const installCnpm = await isInstallCnpm();
// if (!installCnpm) {
// await tryInstallCnpm();
// installImageWebpack();
// return;
// }
// if (exec('cnpm install image-webpack-loader -D').code !== 0) {
// //执行npm run build 命令
// shell.echo('请安装 cnpm');
// shell.exit(1);
// }
// }
// /**
// * @description: 使git对文件名大小写敏感
// * @Date: 2020-06-11 11:07:04
// */
// function ignoreCaseGit() {
// exec('git config core.ignorecase false ');
// }
// installImageWebpack();
// ignoreCaseGit();

View File

@ -0,0 +1,15 @@
// 安装之前先行删除image-webpack-loader,防止yarn 安装失败
// const { join } = require('path');
// const pkg = require('../../package.json');
// const { writeFileSync } = require('fs');
// function deleteDep() {
// delete pkg.devDependencies['image-webpack-loader'];
// writeFileSync(join(process.cwd(), 'package.json'), JSON.stringify(pkg, null, 2), {
// encoding: 'utf8',
// });
// }
// deleteDep();

114
build/task/build-conf.js Normal file
View File

@ -0,0 +1,114 @@
#!/usr/bin/env node
const resolve = require('../getCwdPath');
const { successTip, errorTip } = require('../utils');
const fs = require('fs-extra');
const mkdirp = require('mkdirp');
const { getEnvConfig, getMpaTitles } = require('../getEnvConfig');
const { writeFileSync } = fs;
const getShortName = (env) => {
return `__PRODUCTION__${env.GLOB_APP_SHORT_NAME || '__APP'}__CONF__`.toUpperCase();
};
const pages = getMpaTitles();
const pageKeys = Object.keys(pages);
const MPA_END_REG = /(?<=_MPA)$/;
/**
* @description: 获取多页文件配置
* @Date: 2020-06-16 16:49:34
*/
function getMpaConfig(config) {
const formatConfig = {};
const result = {};
try {
Object.keys(config).forEach((key) => {
if (MPA_END_REG.test(key)) {
formatConfig[key] = JSON.parse(config[key]);
} else {
formatConfig[key] = config[key];
}
});
pageKeys.forEach((page) => {
const pageConfigItem = {};
Object.keys(formatConfig).forEach((key) => {
if (!MPA_END_REG.test(key)) {
const mpaKey = `${key}_MPA`;
pageConfigItem[key] = formatConfig[key];
if (formatConfig[mpaKey]) {
pageConfigItem[key] = formatConfig[mpaKey][page] || formatConfig[key];
}
}
});
result[page] = pageConfigItem;
});
return result;
} catch (error) {
console.error(error);
return [];
}
}
/**
* @description: 创建配置文件
* @Date: 2020-06-16 17:29:58
*/
function createConfig({ configName, config, configFileName = 'window-glob.js' } = {}) {
try {
const configStr = `window.${configName}=${JSON.stringify(config)};`;
mkdirp.sync(resolve('dist'), {});
writeFileSync(resolve(`dist/${configFileName}`), configStr);
successTip('The configuration file is packaged successfully!');
} catch (error) {
errorTip('Configuration file configuration file failed to package', error);
}
}
/**
* @description: 打包配置文件
* @Date: 2020-03-18 09:27:17
*/
async function buildConfig(moduleStr = '') {
const config = getEnvConfig();
// 是否为单页应用
// const appConfig = getEnvConfig('VUE_APP');
// const isSpa = appConfig.VUE_APP_MODE === 'SPA';
let CONFIG_NAME;
let CONFIG_OBJ = [];
// // 单页应用配置文件抽取
// if (isSpa) {
Object.keys(config).forEach((key) => {
if (MPA_END_REG.test(key)) {
Reflect.deleteProperty(config, key);
}
});
CONFIG_NAME = getShortName(config);
// }
// else {
// // 多页应用配置文件抽取
// CONFIG_OBJ = getMpaConfig(config);
// }
// 打包成dist
// if (isSpa) {
createConfig({ config, configName: CONFIG_NAME });
// }
// else {
// // 多页
// pageKeys.forEach((key) => {
// if (moduleStr && !moduleStr.split(',').includes(key)) {
// return;
// }
// const _configName = getShortName({
// GLOB_APP_SHORT_NAME: `${config.GLOB_APP_SHORT_NAME}`,
// });
// const _config = CONFIG_OBJ[key];
// createConfig({
// config: _config,
// configName: _configName,
// configFileName: `window-glob-${key}.js`,
// });
// });
// }
}
module.exports = { buildConfig, getEnvConfig };

36
build/task/build-zip.js Normal file
View File

@ -0,0 +1,36 @@
const compressing = require('compressing');
const fs = require('fs-extra');
const moment = require('moment');
const { successTip, errorTip } = require('../utils');
const resolve = require('../getCwdPath');
const { readFile } = fs;
/**
* @description:
* @Date: 2020-03-17 15:14:55
*/
function buildZip(dir, zipName) {
readFile(resolve('package.json'), (err, res) => {
if (err) {
errorTip('压缩报错' + err);
return;
}
const pkg = JSON.parse(res.toString());
compressing.zip
.compressDir(
resolve(dir),
resolve(`${zipName}_${moment().format('MMDD')}_v${pkg.version}.zip`)
)
.then(() => {
successTip(
`The file was successfully compressed and has been compressed to【${resolve(zipName)}`
);
})
.catch((err) => {
errorTip(' Compression error\n' + err);
console.error(err);
});
});
}
module.exports = { buildZip };

59
build/task/build.js Normal file
View File

@ -0,0 +1,59 @@
const runjs = require('runjs');
const yargs = require('yargs');
const mkdirp = require('mkdirp');
const { buildConfig } = require('./build-conf');
const { updateVersion } = require('./update-version');
const { buildZip } = require('./build-zip');
const { successTip, errorTip, isReportModeFn } = require('../utils');
const resolve = require('../getCwdPath');
const { selectBuildModule } = require('./selectBuildModule');
const { getEnvConfig } = require('../getEnvConfig');
const { run } = runjs;
async function build(v) {
const argvList = yargs.argv._;
try {
if (!argvList.includes('no-uv')) {
await updateVersion();
}
let command = `cross-env --max_old_space_size=4096 vue-cli-service build --no-clean --mode=production ${argvList.join(
' '
)} `;
// 多页模式
// const env = getEnvConfig('VUE_APP');
// const isSpa = env.VUE_APP_MODE === 'SPA';
// let str = '';
// if (!isSpa) {
// const _module = yargs.argv.module;
// if (_module) {
// command += ` --module=${_module}`;
// str = _module;
// } else {
// const moduleStr = await selectBuildModule('请选择需要打包的模块');
// str = moduleStr;
// command += ` --module=${moduleStr}`;
// }
// }
// 更新配置文件
if (!argvList.includes('no-conf')) {
await buildConfig();
}
await run(command, {
async: true,
stdio: 'inherit',
});
if (!isReportModeFn()) {
const zipPath = '.local/output/dist/';
mkdirp.sync(resolve(zipPath));
buildZip('dist/', zipPath);
}
successTip('Packaged successfully!');
} catch (error) {
errorTip('Packaging failed', error);
}
}
build();

30
build/task/log.js Normal file
View File

@ -0,0 +1,30 @@
// #!/usr/bin/env node
const { runjs } = require('@ylz/script-node-utils');
const { successTip, errorTip } = require('../utils');
const { run } = runjs;
const createChangeLog = async () => {
try {
await run(
`conventional-changelog -p --config ./node_modules/@ylz/script-code-lint/lib/log/index.js -i CHANGELOG.md -s && git add ./CHANGELOG.md`,
{
async: true,
stdio: 'inherit',
}
);
await run('prettier --write **/CHANGELOG.md ', {
async: true,
stdio: 'inherit',
});
successTip('CHANGE_LOG generated successfully');
} catch (error) {
errorTip('CHANGE_LOG generated error\n' + error);
process.exit(1);
}
};
createChangeLog();
module.exports = {
createChangeLog,
};

66
build/task/preview.js Normal file
View File

@ -0,0 +1,66 @@
const resolve = require('../getCwdPath');
const { getIPAddress, inquirerPrompt } = require('../utils');
const Koa = require('koa');
const chalk = require('chalk');
const runjs = require('runjs');
const portfinder = require('portfinder');
const staticServer = require('koa-static');
const { run } = runjs;
const BUILD = 1;
const NO_BUILD = 2;
// const util = require('../util');
// 启动服务器
const startApp = () => {
const port = 9680;
portfinder.basePort = port;
const app = new Koa();
// const connect = require('connect');
// const serveStatic = require('serve-static');
// const app = connect();
app.use(staticServer(resolve(`./dist`)));
portfinder.getPort(async (err, port) => {
if (err) {
throw err;
} else {
const publicPath = process.env.BASE_URL;
app.listen(port, function () {
const empty = ' ';
const common = `The preview program is already running:
- LOCAL: http://localhost:${port}/
- NETWORK: http://${getIPAddress()}:${port}/
`;
console.log(chalk.cyan('\n' + empty + common));
});
}
});
};
const preview = async () => {
const prompt = inquirerPrompt({
type: 'list',
message: 'Please select a preview method',
name: 'type',
choices: [
{
name: 'Preview after packaging',
value: BUILD,
},
{
name: `No packaging, preview directly (need to have dist file after packaging)`,
value: NO_BUILD,
},
],
});
const { type } = await prompt;
if (type === BUILD) {
await run('npm run build no-uv', { async: true });
await build({ noUv: true });
}
startApp();
};
preview();

View File

@ -0,0 +1,29 @@
const { inquirerPrompt } = require('../utils');
const { getMpaTitles } = require('../getEnvConfig');
async function selectBuildModule(msg = '') {
const pages = getMpaTitles();
const keys = Object.keys(pages);
const prompt = inquirerPrompt({
type: 'list',
message: msg,
name: 'type',
choices: [
{
name: 'All modules',
value: keys.join(','),
},
...keys.map((key) => {
return {
name: pages[key] || '',
value: key,
};
}),
],
});
const { type } = await prompt;
return type;
}
module.exports = {
selectBuildModule,
};

29
build/task/serve.js Normal file
View File

@ -0,0 +1,29 @@
const { selectBuildModule } = require('./selectBuildModule');
const { getEnvConfig } = require('../getEnvConfig');
const runjs = require('runjs');
const yargs = require('yargs');
const { run } = runjs;
async function devServer() {
const argvList = yargs.argv._;
let command = `cross-env --max_old_space_size=4096 vue-cli-service serve --mode development ${argvList.join(
' '
)} `;
// const env = getEnvConfig('VUE_APP');
// const isSpa = env.VUE_APP_MODE === 'SPA';
// if (!isSpa) {
// const _module = yargs.argv.module;
// if (_module) {
// command += ` --module=${_module}`;
// } else {
// const moduleStr = await selectBuildModule('请选择要运行的模块');
// command += ` --module=${moduleStr}`;
// }
// }
await run(command, {
async: true,
stdio: 'inherit',
});
}
devServer();

View File

@ -0,0 +1,79 @@
#!/usr/bin/env node
const pkg = require('../../package.json');
const runjs = require('runjs');
const fs = require('fs-extra');
const resolve = require('../getCwdPath');
const { inquirerPrompt, successTip, errorTip } = require('../utils');
const { run } = runjs;
const { writeFileSync } = fs;
const MAJOR = 1;
const FEAT = 2;
const FIX = 3;
const SKIP = 4;
const updateVersion = async (msg = 'Please select this update to submit changes') => {
try {
const prompt = inquirerPrompt({
type: 'list',
message: msg,
name: 'type',
choices: [
{
name: 'Some other features (such as bug fixes, style adjustments)',
value: FIX,
},
{
name: 'New features added',
value: FEAT,
},
{
name: 'Major version changes',
value: MAJOR,
},
{
name: 'jump over',
value: SKIP,
},
],
});
const { type } = await prompt;
const versionString = pkg.version.split('.');
const versionNumber = versionString.map((vs) => Number(vs));
if (type !== SKIP) {
if (type === MAJOR) {
versionNumber[0]++;
versionNumber[1] = 0;
versionNumber[2] = 0;
} else if (type === FEAT) {
versionNumber[1]++;
versionNumber[2] = 0;
} else {
versionNumber[2]++;
}
pkg.version = versionNumber.join('.');
writeFileSync(resolve('package.json'), JSON.stringify(pkg, null, 2), {
encoding: 'utf8',
});
}
await run(` git add ./package.json `, {
async: true,
stdio: 'inherit',
});
successTip(
`The version number has been updated successfully! The current version is ${pkg.version}!`
);
} catch (err) {
errorTip('Version number update failed\n' + err);
}
};
module.exports = {
updateVersion,
};

6
build/tsconfig.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "../tsconfig",
"compilerOptions": {
"module": "commonjs"
}
}

116
build/utils.js Normal file
View File

@ -0,0 +1,116 @@
const { networkInterfaces, NetworkInterfaceInfo } = require('os');
const ora = require('ora');
const chalk = require('chalk');
const inquirer = require('inquirer');
const spinner = ora();
/**
* @description: success tip
* @Date: 2020-03-16 13:06:12
*/
function successTip(msg) {
spinner.succeed(
console.log(
chalk.blue.bold('**************** ') +
chalk.green.bold(msg) +
chalk.blue.bold(' ****************') +
'. 🎉 🎉 🎉 🎉\n'
)
);
}
/**
* @description: error tip
* @Date: 2020-03-16 13:06:12
*/
function errorTip(msg, err) {
spinner.fail(
console.error('\n') +
console.error(msg + '\n') +
console.error(chalk.magenta('The specific error message is as follows \n')) +
chalk.red(err) +
'. 😭\n'
);
process.exit(1);
}
function inquirerPrompt(question) {
return inquirer.prompt(question);
}
/**
* @description: get env
* @Date: 2020-06-07 22:32:25
*/
function getEnvFn() {
return process.env;
}
/**
* @description: get mode
* @Date: 2020-06-07 22:32:56
*/
function getModeFn() {
return getEnvFn().NODE_ENV;
}
/**
* @description: Whether it is a production mode
* @Date: 2020-06-07 22:33:35
*/
function isProductionFn() {
return getModeFn() === 'production';
}
/**
* @description: Whether to develop mode
* @Date: 2020-06-07 22:34:11
*/
function isDevFn() {
return getModeFn() === 'development';
}
/**
* @description: Whether to support ie
*
* @Date: 2020-06-15 11:06:12
*/
function supportIeFn() {
return process.env.VUE_APP_SUPPORT_IE === 'TRUE';
}
/**
* @description: Whether it is report mode
* @Date: 2020-06-15 11:06:18
*/
function isReportModeFn() {
return !!process.env.REPORT;
}
/**
* @description: Get local ip
* @Date: 2020-06-15 11:06:35
*/
function getIPAddress() {
let interfaces = networkInterfaces();
for (let devName in interfaces) {
let iFace = interfaces[devName];
for (let i = 0; i < iFace.length; i++) {
let alias = iFace[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
return alias.address;
}
}
}
return '';
}
module.exports = {
getEnvFn,
getModeFn,
isProductionFn,
isDevFn,
errorTip,
successTip,
inquirerPrompt,
supportIeFn,
isReportModeFn,
getIPAddress,
};

18
commitlint.config.js Normal file
View File

@ -0,0 +1,18 @@
/**
buildThe main purpose is to modify the submission of the project build system (such as the configuration of glup, webpack, rollup, etc.)
ciThe main purpose is to modify the project to continue the integration process (such as Travis, Jenkins, GitLab CI, Circle, etc.) submission
docsupdate doc
mergeMerge branch ? of ?
update: Update a function
featnew features
fixbug fix
perfPerformance optimization
refactorRefactored code (no new features or bug fixes)
styleCode modification that does not affect the program logic (modify blank characters, complete missing semicolons, etc.)
testAdd test cases or update existing tests
revertRoll back an earlier commit
choreOther types that are not of the above type
wipRemove files or code
*/
module.exports = require('./config/lint/commit-lint');

16
config/getGlobConfig.ts Normal file
View File

@ -0,0 +1,16 @@
import { GlobConfig, GlobEnvConfig } from '../src/types/config';
import { isDevMode } from '../src/utils/envUtil';
import { getShortName } from './getShortName';
export default (): GlobConfig => {
const ENV_NAME = getShortName(process.env);
const ENV = ((isDevMode() ? process.env : window[ENV_NAME]) as unknown) as GlobEnvConfig;
const { GLOB_APP_TITLE, GLOB_API_URL, GLOB_APP_SHORT_NAME, GLOB_API_URL_PREFIX } = ENV;
return {
title: GLOB_APP_TITLE,
apiUrl: GLOB_API_URL,
shortName: GLOB_APP_SHORT_NAME,
urlPrefix: GLOB_API_URL_PREFIX,
};
};

3
config/getShortName.ts Normal file
View File

@ -0,0 +1,3 @@
export const getShortName = (env) => {
return `__PRODUCTION__${env.GLOB_APP_SHORT_NAME || '__APP'}__CONF__`.toUpperCase();
};

View File

@ -0,0 +1,35 @@
/**
* less global variable
*/
const primaryColor = '#0593FF';
const modifyVars = {
// -------- Colors -----------
'primary-color': primaryColor, // 全局主色 Global dominant color
'info-color': primaryColor, // 默认颜色 Default color
'success-color': '#55D187', // 成功色 Success color
'error-color': '#ED6F6F', // 错误色 False color
'warning-color': '#FFD164', // 警告色 Warning color
'link-color': primaryColor, // 链接色 Link color
'disabled-color': '#C2C2CC', // 失效色 Failure color
'heading-color': '#2C3A61', // 标题色 Title color
'text-color': '#2C3A61', // 主文本色 Main text color
'text-color-secondary ': '#606266', // Subtext color
'background-color-base': '#F0F2F5', // background color
'font-size-base': '14px', // 主字号 Main font size
'box-shadow-base': '0 2px 8px rgba(0, 0, 0, 0.15)', // 浮层阴影 Floating shadow
'border-color-base': '#CECECE', // 边框色 Border color,
'border-color-split': '#CECECE', // 边框色 Border color,
'border-radius-base': '2px', // 组件/浮层圆角 Component/float fillet
};
module.exports = {
modifyVars,
primaryColor,
};

View File

@ -0,0 +1,57 @@
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
parserPreset: {
parserOpts: {
headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\](.*)[\)\])?[\:\] (.*)/,
headerCorrespondence: ['type', 'scope', 'subject'],
referenceActions: [
'close',
'closes',
'closed',
'fix',
'fixes',
'fixed',
'resolve',
'resolves',
'resolved',
],
issuePrefixes: ['#'],
noteKeywords: ['BREAKING CHANGE', '不兼容变更'],
fieldPattern: /^-(.*?)-$/,
revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./,
revertCorrespondence: ['header', 'hash'],
warn() {},
mergePattern: null,
mergeCorrespondence: null,
},
},
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'init',
'update',
'feat',
'fix',
'perf',
'merge',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
],
],
},
};

116
config/lint/cz/engine.js Normal file
View File

@ -0,0 +1,116 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const wrap = require('word-wrap');
const longest = require('longest');
const rightPad = require('right-pad');
const chalk = require('chalk');
const filter = (array) => {
return array.filter((x) => {
return x;
});
};
// 获取选择列表
const getList = (obj) => {
const objLen = longest(Object.keys(obj)).length * 2 + 1;
return Object.keys(obj).map((key) => ({
name: `${rightPad(`${key}:`, objLen / 2, ' ')} ${obj[key].description}`,
value: key,
}));
};
exports.default = (options) => {
const typeList = getList(options.types);
const scopeList = getList(options.scopes);
return {
prompter(cz, commit) {
console.log(chalk.yellow('\n标题会在100个字符后进行裁剪。 主体内容每行会在100个字符后自动换行手动换行请直接输入"\\n"。\n'));
cz.prompt([
{
type: 'list',
name: 'type',
message: '(必选)选择你提交的信息类型:',
choices: typeList,
},
{
type: 'input',
name: 'scope',
message: '(必填)本次提交的改变所影响的范围(整个项目,UI,数据,构建,具体那个模块)',
validate(str) {
return str ? true : console.log(chalk.yellow(`请输入影响范围`));
},
},
{
type: 'input',
name: 'subject',
validate(str) {
const charLen = 3;
if (str.length > charLen) {
return str.length > charLen;
}
else {
console.log(chalk.yellow(`字符长度大于${charLen}`));
}
return false;
},
message: '(必填)写一个的变化描述:',
},
{
type: 'input',
name: 'body',
message: '(非必填)提供更详细的变更描述(没有则直接回车):',
},
{
type: 'confirm',
name: 'isBreaking',
message: '是否存在不兼容变更(没有则直接回车)?',
default: false,
},
{
type: 'input',
name: 'breaking',
message: '列出所有的不兼容变更:\n',
default: false,
when(answers) {
return answers.isBreaking;
},
},
{
type: 'confirm',
name: 'isIssueAffected',
message: '此次变更是否影响某些打开的 issue(没有则直接回车) ?',
default: false,
},
{
type: 'input',
name: 'issues',
message: '列出此次改动引用的所有 issues (如:"fix #123", "Closes #123, #124":\n',
when(answers) {
return answers.isIssueAffected;
},
},
]).then((answers) => {
const maxLineWidth = 100;
const wrapOptions = {
trim: true,
newline: '\n',
indent: '',
width: maxLineWidth,
};
// 判断影响范围是否输入
const scope = answers.scope ? `(${answers.scope.trim()})` : '';
// 限制短描述为 100 个字符
const head = `${answers.type + scope}: ${answers.subject.trim()}`.slice(0, maxLineWidth);
// 限制详细描述最长宽度为 100 个字符串
const body = wrap(answers.body, wrapOptions);
// Apply breaking change prefix, removing it if already present
let breaking = answers.breaking ? answers.breaking.trim() : '';
// 如果手动输入了 不兼容变更,则过滤掉,最后进行长度限制
breaking = breaking ? `不兼容变更: ${breaking.replace(/^不兼容变更: /, '')}` : '';
breaking = wrap(breaking, wrapOptions);
const issues = answers.issues ? wrap(answers.issues, wrapOptions) : '';
const footer = filter([breaking, issues]).join('\n\n');
commit(`${head}\n\n${body}\n\n${footer}`);
});
},
};
};
module.exports = exports.default;

86
config/lint/cz/index.js Normal file
View File

@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const engine = require('./engine');
exports.default = engine({
types: {
feat: {
description: '🌟 一个新功能',
title: '🌟 新功能',
},
update: {
description: '✅ 更新某个模块,跟fix和feat不一样',
title: '✅ 更新',
},
fix: {
description: '🐛 一个 bug 修复',
title: '🐛 Bug 修复',
},
style: {
description: '🎨 对代码含义无影响的改动(空格,格式化,等,非 UI 变动)',
title: '🎨 代码样式',
},
chore: {
description: '🏠 影响构建系统或外部依赖的更改例如gulpnpmwebpack',
title: '🏠 构建',
},
docs: {
description: '📝 只有文档发生改变',
title: '📝 文档',
},
build: {
description: '🎉 修改项目构建系统(如 glupwebpackrollup 的配置等)的提交',
title: '🎉 项目构建',
},
refactor: {
description: '♻️ 既不是修复 bug 也不是添加新功能的代码更改',
title: '♻️ 代码重构',
},
perf: {
description: '🚀 提升性能的代码更改',
title: '🚀 性能优化',
},
test: {
description: '🔧 添加一些缺失的测试或者修正已存在的测试',
title: '🔧 测试',
},
ci: {
description: '📦 持续集成的配置文件和脚本的改变(例如: Travis, Circle',
title: '📦 持续集成',
},
revert: {
description: '⏪ 撤销上一次的提交',
title: '⏪ 撤销',
},
wip: {
description: '🔒 删除某些文件',
title: '🔒 删除',
},
},
scopes: {
global: {
description: '影响整个项目',
title: 'global',
},
ui: {
description: 'UI 界面',
title: 'ui',
},
data: {
description: '数据变化',
title: 'data',
},
component: {
description: '影响公共组件使用',
title: 'component',
},
build: {
description: '影响构建',
title: 'build',
},
unknown: {
description: '不知道影响范围',
title: 'unknown',
},
},
});
module.exports = exports.default;

View File

@ -0,0 +1,8 @@
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'],
'package.json': ['prettier --write'],
'*.vue': ['eslint --fix', 'stylelint --fix', 'prettier --write', 'git add .'],
'*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write', 'git add .'],
'*.md': ['prettier --write'],
};

28
config/lint/prettier.js Normal file
View File

@ -0,0 +1,28 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'es5',
jsxBracketSameLine: false,
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'lf',
rangeStart: 0,
overrides: [
{
files: '*.md',
options: {
tabWidth: 2,
},
},
],
};

181
config/lint/stylelint.js Normal file
View File

@ -0,0 +1,181 @@
module.exports = {
root: true,
plugins: ['stylelint-order'],
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
rules: {
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
'at-rule-no-unknown': [
true,
{
ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin'],
},
],
'no-empty-source': null,
'unicode-bom': 'never',
'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
'declaration-block-trailing-semicolon': 'always',
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested'],
},
],
// 指定声明块内属性的字母顺序
'order/properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'float',
'width',
'height',
'max-width',
'max-height',
'min-width',
'min-height',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'margin-collapse',
'margin-top-collapse',
'margin-right-collapse',
'margin-bottom-collapse',
'margin-left-collapse',
'overflow',
'overflow-x',
'overflow-y',
'clip',
'clear',
'font',
'font-family',
'font-size',
'font-smoothing',
'osx-font-smoothing',
'font-style',
'font-weight',
'hyphens',
'src',
'line-height',
'letter-spacing',
'word-spacing',
'color',
'text-align',
'text-decoration',
'text-indent',
'text-overflow',
'text-rendering',
'text-size-adjust',
'text-shadow',
'text-transform',
'word-break',
'word-wrap',
'white-space',
'vertical-align',
'list-style',
'list-style-type',
'list-style-position',
'list-style-image',
'pointer-events',
'cursor',
'background',
'background-attachment',
'background-color',
'background-image',
'background-position',
'background-repeat',
'background-size',
'border',
'border-collapse',
'border-top',
'border-right',
'border-bottom',
'border-left',
'border-color',
'border-image',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
'border-spacing',
'border-style',
'border-top-style',
'border-right-style',
'border-bottom-style',
'border-left-style',
'border-width',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'border-radius',
'border-top-right-radius',
'border-bottom-right-radius',
'border-bottom-left-radius',
'border-top-left-radius',
'border-radius-topright',
'border-radius-bottomright',
'border-radius-bottomleft',
'border-radius-topleft',
'content',
'quotes',
'outline',
'outline-offset',
'opacity',
'filter',
'visibility',
'size',
'zoom',
'transform',
'box-align',
'box-flex',
'box-orient',
'box-pack',
'box-shadow',
'box-sizing',
'table-layout',
'animation',
'animation-delay',
'animation-duration',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
'animation-fill-mode',
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
'background-clip',
'backface-visibility',
'resize',
'appearance',
'user-select',
'interpolation-mode',
'direction',
'marks',
'page',
'set-link-source',
'unicode-bidi',
'speak',
],
},
};

20
config/vue/alias.js Normal file
View File

@ -0,0 +1,20 @@
const resolve = require('../../build/getCwdPath');
const baseAlias = {
examples: 'examples',
config: 'config',
'#': 'src/types',
'@': 'src',
'@ant-design/icons/lib/dist$': 'src/assets/icons/setupIcon.ts',
'@design': 'src/design/index.less',
};
function configAlias(config) {
// 单页应用
Object.keys(baseAlias).forEach((key) => {
config.resolve.alias.set(key, resolve(baseAlias[key]));
});
}
module.exports = {
configAlias,
};

24
config/vue/banner.js Normal file
View File

@ -0,0 +1,24 @@
const webpack = require('webpack');
const pkg = require('../../package.json');
const { isProductionFn } = require('../../build/utils');
function createBannerPlugin(config) {
// bannerPlugin
config.when(isProductionFn(), (config) => {
config
.plugin('banner')
.use(webpack.BannerPlugin, [
{
banner:
`Build by ${pkg.author || 'vben'} ,Version:${process.env.VUE_APP_VERSION} ` +
new Date().toLocaleDateString(),
},
])
.end();
});
}
module.exports = {
createBannerPlugin,
};

30
config/vue/css.js Normal file
View File

@ -0,0 +1,30 @@
const { modifyVars } = require('../glob/lessModifyVars');
const { isProductionFn } = require('../../build/utils');
function createCss(isProd) {
return {
requireModuleExtension: true,
extract: isProd ? { ignoreOrder: true } : false,
sourceMap: false,
loaderOptions: {
css: {
modules: {
// https://github.com/webpack/loader-utils#interpolatename
localIdentName: isProductionFn()
? '[contenthash:5]'
: '[folder]__[name]_[local]-[emoji]$',
},
},
less: {
lessOptions: {
modifyVars: modifyVars,
javascriptEnabled: true,
},
},
},
};
}
module.exports = {
createCss,
};

65
config/vue/devServer.js Normal file
View File

@ -0,0 +1,65 @@
const path = require('path');
const { getEnvFn } = require('../../build/utils');
const mockMiddleware = require('../../build/middleware/mockMiddleware.js');
const ENV = getEnvFn();
let proxyList = ENV.VUE_APP_PROXY || [];
function createProxy() {
const proxyResult = {};
try {
proxyList = JSON.parse(proxyList);
if (!Array.isArray(proxyList) || proxyList.length <= 0) {
return {};
}
for (const [key, target] of proxyList) {
proxyResult[key] = {
target: target,
changeOrigin: true,
ws: true,
pathRewrite: {
['^' + key]: '',
},
};
}
return {
proxy: proxyResult,
};
} catch (error) {}
return {};
}
function createDevServer() {
const { VUE_APP_USE_MOCK } = ENV;
return {
disableHostCheck: true,
open: false,
host: '0.0.0.0',
port: ENV.VUE_APP_PORT,
https: false,
hotOnly: true,
clientLogLevel: 'warn',
overlay: {
warnings: true,
errors: true,
},
before: (app) => {
VUE_APP_USE_MOCK === 'TRUE' && app.use(mockMiddleware());
},
historyApiFallback: {
rewrites: [
{
from: /.*/,
to: path.posix.join('/', 'index.html'),
},
],
},
...createProxy(),
};
}
module.exports = {
createDevServer,
};

18
config/vue/entry.js Normal file
View File

@ -0,0 +1,18 @@
const { configPolyfill } = require('./polyfill');
const { getEnvFn } = require('../../build/utils');
function configEntry(config) {
const entry = ['./src/main.ts'];
config.entry = configPolyfill(entry);
const { VUE_APP_USE_MOCK } = getEnvFn();
// use mock
if (VUE_APP_USE_MOCK === 'TRUE') {
config.entry.unshift('./mock/_util/mock.config.js');
}
}
module.exports = {
configEntry,
};

7
config/vue/env.js Normal file
View File

@ -0,0 +1,7 @@
function configEnv() {
// version
process.env.VUE_APP_VERSION = require('../../package.json').version;
// build time
process.env.VUE_APP_BUILD_TIME = require('moment')().format('YYYY-M-D HH:mm:ss');
}
module.exports = configEnv;

15
config/vue/getPath.js Normal file
View File

@ -0,0 +1,15 @@
const resolve = require('../../build/getCwdPath');
const { getEnvFn } = require('../../build/utils');
const ENV = getEnvFn();
function getPublicPath() {
return ENV.VUE_APP_PUBLIC_PATH;
}
function getAssetsPath(dir) {
return resolve(ENV.VUE_APP_ASSETS_DIR + dir);
}
module.exports = {
getPublicPath,
getAssetsPath,
};

70
config/vue/html.js Normal file
View File

@ -0,0 +1,70 @@
const { isProductionFn, isDevFn, getEnvFn } = require('../../build/utils');
const { getPublicPath } = require('./getPath');
const HtmlWebpackHardDiskPlugin = require('html-webpack-harddisk-plugin');
// Write html to disk, which is good for hmr
function createHardDiskPlugin(config) {
config.plugin('hard-disk').after('html').use(HtmlWebpackHardDiskPlugin).end();
}
/**
* @description: 通用配置
*/
function getMergeCommonOptions(filename = '') {
const isDev = isDevFn();
const ENV = getEnvFn();
// Sort the chunks in the order in which they were introduced. Without this, the JS introduced into the html may be sorted out of order
const chunksSortMode = 'none';
// const chunksSortMode = 'manual';
const title = ENV.GLOB_APP_TITLE || 'APP';
const minify = {
html5: true,
ignoreCustomComments: [/^!/], // 保留的注释
sortClassName: true, // class排序
sortAttributes: true, // 属性排序
processConditionalComments: true, // 处理条件注释(IE)
removeStyleLinkTypeAttributes: true, // 去 link type="text/css"
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
removeComments: true, // 移除注释
collapseWhitespace: true, // 去多余空格
removeAttributeQuotes: true, // 去属性括号
collapseBooleanAttributes: true, // Boolean属性简写
removeScriptTypeAttributes: true, // 去script type属性
minifyJS: { output: { comments: /^!/ } }, // 压缩script标签里的js
minifyCSS: true,
minifyURLs: true,
};
const addToHead = isDev
? ''
: `<script src='${getPublicPath()}window-glob.js?v=${
ENV.VUE_APP_VERSION
}-${new Date().getTime()}'></script>`;
return { addToHead, chunksSortMode, title, minify, inject: true };
}
function configHtml(config) {
config.when(isDevFn(), (config) => {
config.plugin('html').tap((args) => {
args[0].alwaysWriteToDisk = true;
return args;
});
createHardDiskPlugin(config);
});
config.when(isProductionFn(), (config) => {
config.plugin('html').tap((args) => {
const { addToHead, chunksSortMode, title, minify, inject } = getMergeCommonOptions();
args[0].addToHead = addToHead;
// args[0].chunksSortMode = chunksSortMode;
args[0].title = title;
args[0].minify = minify;
return args;
});
});
}
module.exports = {
configHtml,
getMergeCommonOptions,
};

96
config/vue/index.js Normal file
View File

@ -0,0 +1,96 @@
require('./env')();
const { isProductionFn, getEnvFn } = require('../../build/utils');
const { createDevServer } = require('./devServer');
const { configAlias } = require('./alias');
const { createCss } = require('./css');
const { configSourceMap } = require('./sourceMap');
const { createBannerPlugin } = require('./banner');
const { configPerformance } = require('./performance');
const { configOptimization } = require('./optimization');
const { configHtml } = require('./html');
const { getPublicPath } = require('./getPath');
const { configEntry } = require('./entry');
const { configVueLoader } = require('./loader/vue-loader');
const { createSvgLoader } = require('./loader/svg-loader');
const { createImageLoader } = require('./loader/image-webpack');
const { createWorkerLoader } = require('./loader/worker-loader');
const { createGzip } = require('./plugins/gzip');
const { createHardSourcePlugin } = require('./plugins/hardSource');
const { configIgnorePlugin } = require('./plugins/ignore');
const { createReport } = require('./plugins/report');
const { configClean } = require('./plugins/clean');
const { createWebpackBar } = require('./plugins/processBar');
const { createStyleLintPlugin } = require('./plugins/stylelint');
const { configDefinePlugin } = require('./plugins/define');
const { createSkeletonPlugin } = require('./plugins/skeleton');
const { createThemeColorReplacerPlugin } = require('./plugins/theme');
const isProd = isProductionFn();
const ENV = getEnvFn();
function createVueConfig() {
return {
publicPath: getPublicPath(),
lintOnSave: !isProd,
assetsDir: ENV.VUE_APP_ASSETS_DIR,
productionSourceMap: false,
devServer: createDevServer(),
pluginOptions: {
lintStyleOnBuild: true,
stylelint: {},
},
css: createCss(isProd),
configureWebpack: (config) => {
config.resolve.extensions = ['.js', '.jsx', '.tsx', '.ts'];
configEntry(config);
},
chainWebpack: (config) => {
config.name = ENV.GLOB_APP_SHORT_NAME;
configIgnorePlugin(config);
// alias
configAlias(config);
// sourceMap设置
configSourceMap(config);
// Performance
configPerformance(config);
// Optimization
configOptimization(config);
// env
configDefinePlugin(config);
// html config
configHtml(config);
// vue-loader config
configVueLoader(config);
// icon
createSvgLoader(config);
// web-worker
createWorkerLoader(config);
// banner
createBannerPlugin(config);
// build report
createReport(config);
// cache
createHardSourcePlugin(config);
// clean build dist
configClean(config);
// build process bar
createWebpackBar(config);
// Picture compression
// createImageLoader(config);
// stlyelint
createStyleLintPlugin(config);
// gzip
createGzip(config);
// Skeleton screen
createSkeletonPlugin(config);
createThemeColorReplacerPlugin(config);
},
};
}
module.exports = {
createVueConfig,
};

View File

@ -0,0 +1,18 @@
const { isProductionFn } = require('../../../build/utils');
// Turn on image compression
function createImageLoader(config) {
config.when(isProductionFn(), (config) => {
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
bypassOnDebug: true,
})
.end();
});
}
module.exports = {
createImageLoader,
};

View File

@ -0,0 +1,20 @@
const resolve = require('../../../build/getCwdPath');
const createSvgLoader = (config) => {
config.module.rule('svg').exclude.add(resolve('src/assets/icons/svg/')).end();
config.module
.rule('svg-sprite-loader')
.test(/\.svg(\?[a-z0-9=\.]+)?$/)
.include.add(resolve('src/assets/icons/svg/'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
})
.end();
};
module.exports = {
createSvgLoader,
};

View File

@ -0,0 +1,19 @@
const { merge } = require('webpack-merge');
const configVueLoader = (config) => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap((options) =>
merge(options, {
compilerOptions: {
preserveWhitespace: true,
},
})
)
.end();
};
module.exports = {
configVueLoader,
};

View File

@ -0,0 +1,24 @@
const { getPublicPath } = require('../getPath');
const { getEnvFn } = require('../../../build/utils');
const resolve = require('../../../build/getCwdPath');
function createWorkerLoader(config) {
config.module
.rule('web-workers')
.merge(config.module.rule('ts').toConfig())
.test(/(?:\.worker|[\\/]workers[\\/]\w+)\.[tj]s$/)
.include.add(resolve('src'))
.end()
.use('worker-loader')
.loader('worker-loader')
.options({
name: getEnvFn().VUE_APP_ASSETS_DIR + '/js/[name].[hash:3].worker.js',
fallback: true,
publicPath: getPublicPath(),
})
.after('0');
}
module.exports = {
createWorkerLoader,
};

117
config/vue/optimization.js Normal file
View File

@ -0,0 +1,117 @@
// const { isProductionFn, isDevFn } = require('../../build/utils');
// const resolve = require('../../build/getCwdPath');
// const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
// function createScriptExtPlugin(config) {
// config
// .plugin('ext-script')
// .after('html')
// .use(ScriptExtHtmlWebpackPlugin, [
// {
// inline: /\_r.*\.js$/,
// // defer: /\.js$/,
// },
// ])
// .end();
// }
function configOptimization(config) {
// config.when(isProductionFn(), (config) => {
// createScriptExtPlugin(config);
config.optimization.runtimeChunk({
name: (entry) => `_r-${entry.name}`,
});
// config.optimization.runtimeChunk('single');
config.optimization.splitChunks({
chunks: 'all',
maxAsyncRequests: 6, //分割后按需加载的代码块最多允许的并行请求数在webpack5里默认值变为6
maxInitialRequests: 4, //分割后入口代码块最多允许的并行请求数在webpack5里默认值变为4
// maxInitialRequests: Infinity,
automaticNameMaxLength: 15, // 分割chunk自动命名最大长度
automaticNameDelimiter: '.', // 分割chunk自动命名分隔符
minSize: 30000, // 大小超过30kb的模块才会被提取
maxSize: 0, // 只是提示可以被违反会尽量将chunk分的比maxSize小当设为0代表能分则分分不了不会强制
minChunks: 1, //某个模块至少被多少代码块引用才会被提取成新的chunk
name: true, //每个缓存组打包得到的代码块的名称
cacheGroups: {
default: false,
// light: {
// name: 'theme-light',
// test: (m, c) => {
// return (
// m.constructor.name === 'CssModule' &&
// new RegExp('-light.less|theme=light').test(m._identifier)
// );
// },
// chunks: 'all',
// enforce: true,
// priority: 40,
// },
// dark: {
// name: 'theme-dark',
// test: (m, c) => {
// return (
// m.constructor.name === 'CssModule' &&
// new RegExp('-dark.less|theme=dark').test(m._identifier)
// );
// },
// chunks: 'all',
// enforce: true,
// priority: 40,
// },
styles: {
name: 'styles',
test: /\.(css|scss|sass|less|styl)$/,
chunks: 'async',
// enforce: true,
priority: 30,
},
vendor: {
priority: 10,
reuseExistingChunk: true,
test: /[\\/]node_modules[\\/]/,
name(module) {
const match = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)(.*?)([\\/]|$)/);
let packageName = match[1];
// if (match[1] === '@xxx') {
// packageName += `-${match[3]}`;
// }
if (
packageName.indexOf('axios') !== -1 ||
packageName.indexOf('babel') !== -1 ||
packageName.indexOf('crypto-js') !== -1 ||
packageName.indexOf('moment') !== -1 ||
packageName.indexOf('lodash') !== -1 ||
packageName.indexOf('mutationobserver') !== -1 ||
packageName.indexOf('resize-observer-polyfill') !== -1 ||
packageName.indexOf('vue') !== -1 ||
packageName.indexOf('dom-align') !== -1 ||
packageName.indexOf('async-validator') !== -1 ||
packageName.indexOf('tinycolor2') !== -1 ||
packageName.indexOf('asn1') !== -1 ||
packageName.indexOf('readable-stream') !== -1 ||
packageName.indexOf('browserify-sign') !== -1 ||
packageName.indexOf('bn') !== -1 ||
packageName.indexOf('buffer') !== -1 ||
packageName.indexOf('elliptic') !== -1 ||
packageName.indexOf('regenerator-runtime') !== -1 ||
packageName.indexOf('core-js') !== -1
) {
packageName = 'entry-lib';
} else if (packageName.indexOf('ant-design') !== -1) {
packageName = 'design';
} else {
packageName = 'vendor';
}
// const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `${packageName.replace('@', '')}.chunk`;
},
},
},
});
// });
}
module.exports = {
configOptimization,
};

20
config/vue/performance.js Normal file
View File

@ -0,0 +1,20 @@
// const webpack = require('webpack');
const { isProductionFn, isDevFn } = require('../../build/utils');
module.exports = {
configPerformance(config) {
config.optimization.minimize(isProductionFn());
config.cache(true);
config.resolve.symlinks(true);
config.performance.hints(false);
config.module.noParse(/^(vue|vue-router|vuex|lodash|moment|vuex-router-sync|normalize.css)$/);
config.when(isProductionFn(), (config) => {
config.merge({ recordsPath: require('path').resolve('build/.cache/~records') });
config.plugins.delete('prefetch').delete('preload');
});
config.when(isDevFn(), (config) => {
config.watchOptions({ ignored: /node_modules/ });
});
},
};

View File

@ -0,0 +1,16 @@
const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin;
const { isProductionFn } = require('../../../build/utils');
function configClean(config) {
config.when(isProductionFn(), (config) => {
let noCleanDir = ['**/*', '!window-glob.js'];
config.plugin('clean').use(CleanWebpackPlugin, [
{
cleanOnceBeforeBuildPatterns: noCleanDir, // Do not delete files in the dll directory
},
]);
});
}
module.exports = {
configClean,
};

View File

@ -0,0 +1,19 @@
const { merge } = require('webpack-merge');
const configDefinePlugin = (config) => {
config.plugin('define').tap((args) => {
// Incorporate dynamic configuration into process.env
const name = 'process.env';
const ENV = process.env;
const conf = {};
Object.keys(ENV).forEach((key) => {
if (/^(GLOB_)/.test(key)) {
conf[key] = JSON.stringify(ENV[key]);
}
});
args[0][name] = merge(args[0][name], conf);
return args;
});
};
module.exports = {
configDefinePlugin,
};

View File

@ -0,0 +1,29 @@
const { isProductionFn } = require('../../../build/utils');
const { gzip: zopGzip } = require('@gfx/zopfli');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const productionGzipExtensions = /\.(js|css|svg|woff|ttf|json|html)(\?.*)?$/i;
function createGzip(config) {
config.when(isProductionFn() && process.env.BUILD_ON_GZIP === 'TRUE', (config) => {
config.plugin('compression').use(CompressionWebpackPlugin, [
{
cache: true,
exclude: /.+\.html$/,
algorithm(input, compressionOptions, callback) {
return zopGzip(input, compressionOptions, callback);
},
compressionOptions: {
numiterations: 15,
},
threshold: 5120,
minRatio: 0.7,
test: productionGzipExtensions,
},
]);
});
}
module.exports = {
createGzip,
};

View File

@ -0,0 +1,23 @@
const { isDevFn, getEnvFn } = require('../../../build/utils');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const notCache = getEnvFn().VUE_APP_USE_CACHE !== 'TRUE';
function createHardSourcePlugin(config) {
if (notCache || isDevFn()) {
return;
}
config.plugin('hard-source').use(HardSourceWebpackPlugin);
const tests = isDevFn()
? [{ test: /[\\/]src[\\/]/ }]
: [
{ test: /[\\/]mini-css-extract-plugin[\\/]dist[\\/]loader/ },
{ test: /[\\/]file-loader[\\/]/ },
{ test: /[\\/]url-loader[\\/]/ },
];
config.plugin('hard-source-exclude').use(HardSourceWebpackPlugin.ExcludeModulePlugin, tests);
}
module.exports = {
createHardSourcePlugin,
};

View File

@ -0,0 +1,10 @@
const webpack = require('webpack');
const { isProductionFn } = require('../../../build/utils');
module.exports = {
configIgnorePlugin(config) {
config.when(isProductionFn(), (config) => {
config.plugin('ignore').use(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
});
},
};

View File

@ -0,0 +1,16 @@
const Webpackbar = require('webpackbar');
const { isProductionFn } = require('../../../build/utils');
function createWebpackBar(config) {
config.when(isProductionFn(), (config) => {
config.plugin('bar').use(Webpackbar, [
{
name: 'TOOLKIT',
color: 'blue',
},
]);
});
}
module.exports = {
createWebpackBar,
};

View File

@ -0,0 +1,14 @@
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer');
function createReport(config) {
config.when(process.env.REPORT, (config) => {
config.plugin('webpack-bundle-analyzer').use(BundleAnalyzerPlugin.BundleAnalyzerPlugin, [
{
analyzerPort: 8888,
generateStatsFile: false,
},
]);
});
}
module.exports = {
createReport,
};

View File

@ -0,0 +1,22 @@
const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');
const { isProductionFn } = require('../../../build/utils');
const resolve = require('../../../build/getCwdPath');
function createSkeletonPlugin(config) {
config.when(isProductionFn(), (config) => {
const main = resolve('src/setup/skeleton/index.js');
config.plugin('skeleton').use(SkeletonWebpackPlugin, [
{
webpackConfig: {
entry: {
main: main,
},
},
minimize: true,
},
]);
});
}
module.exports = {
createSkeletonPlugin,
};

View File

@ -0,0 +1,21 @@
const StyleLintPlugin = require('stylelint-webpack-plugin');
const { isDevFn } = require('../../../build/utils');
function createStyleLintPlugin(config) {
config.when(isDevFn(), (config) => {
config.plugin('stylelint').use(StyleLintPlugin, [
{
fix: true,
cache: true,
lintDirtyModulesOnly: true,
files: ['**/*.vue', '**/*.less', , '**/*.scss', '**/*.css'],
cacheLocation: 'node_modules/.cache/stylelint/',
},
]);
});
}
module.exports = {
createStyleLintPlugin,
};

View File

@ -0,0 +1,47 @@
const ThemeColorReplacer = require('webpack-theme-color-replacer');
const generate = require('@ant-design/colors/lib/generate').default;
const { primaryColor } = require('../../glob/lessModifyVars.js');
const getAntdSerials = (color) => {
const lightens = new Array(9).fill().map((t, i) => {
return ThemeColorReplacer.varyColor.lighten(color, i / 10);
});
const colorPalettes = generate(color);
return lightens.concat(colorPalettes);
};
const themePluginOption = {
fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials(primaryColor),
changeSelector(selector, util) {
switch (selector) {
case '.ant-calendar-today .ant-calendar-date':
return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector;
case '.ant-btn:focus,.ant-btn:hover':
return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)';
case '.ant-btn.active,.ant-btn:active':
return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)';
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return ':not(.ant-steps-item-process)' + selector;
case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover':
case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover':
return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover';
case '.ant-menu-horizontal > .ant-menu-item-selected > a':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a';
case '.ant-menu-horizontal > .ant-menu-item > a:hover':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover';
case 'a:hover':
return 'body .ant-menu-item > a:hover';
default:
return util.changeEach(selector, '', 'body ');
}
},
};
const createThemeColorReplacerPlugin = (config) => {
config.plugin('theme-color-replace').use(ThemeColorReplacer, [themePluginOption]);
};
module.exports = {
createThemeColorReplacerPlugin,
};

13
config/vue/polyfill.js Normal file
View File

@ -0,0 +1,13 @@
/**
* 兼容ie 10 11
*/
const { supportIeFn } = require('../../build/utils');
function configPolyfill(entry) {
if (supportIeFn()) {
entry.unshift(...['core-js/stable', 'regenerator-runtime/runtime']);
}
return entry;
}
module.exports = { configPolyfill };

6
config/vue/sourceMap.js Normal file
View File

@ -0,0 +1,6 @@
const { isProductionFn } = require('../../build/utils');
module.exports = {
configSourceMap(config) {
config.devtool(isProductionFn() ? false : 'cheap-eval-source-map');
},
};

6
jest.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
moduleNameMapper: {
'^config/(.*)$': '<rootDir>/src/config/$1',
},
};

1
lint-staged.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./config/lint/lint-staged');

19
mock/_util/index.js Normal file
View File

@ -0,0 +1,19 @@
/**
* @param {string} url
* @returns {Object}
*/
export function param2Obj(url) {
const search = url.split('?')[1];
if (!search) {
return {};
}
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"')
.replace(/\+/g, ' ') +
'"}'
);
}

87
mock/_util/mock.config.js Normal file
View File

@ -0,0 +1,87 @@
import Mock from 'mockjs';
import { param2Obj } from './index';
const mockApiMethods = require.context('../', true, /^[^\_]*\.js$/);
let modules = {};
mockApiMethods.keys().forEach((key) => {
if (key) {
const mods = mockApiMethods(key);
key = key.replace(/^\.\//, '').replace(/\.js$/, '');
Object.keys(mods).forEach((mod) => {
modules = { ...modules, ...mods[mod] };
});
}
});
function getModuleInfo(mockModules) {
const moduleInfo = [];
Object.keys(mockModules).forEach((key) => {
const handle = mockModules[key];
const { path, method, timeout } = parseKey(key);
moduleInfo.push({ handle, path, method, timeout });
});
return moduleInfo;
}
function parseKey(key) {
const len = key.length - key.replace(/\s/g, '').length;
let method = 'get';
let path = key;
let timeout = 0;
if (len > 0) {
const splitEd = key.split(' ');
method = splitEd[0].toLowerCase();
path = splitEd[1];
if (len === 2) {
timeout = splitEd[2];
}
}
return {
method,
path,
timeout,
};
}
function XHR2ExpressReqWrap(handle, timeout = 0) {
return function (options) {
let result = null;
if (handle instanceof Function) {
const { body, type, url } = options;
result = handle({
method: type,
body: JSON.parse(body),
query: param2Obj(url),
});
} else {
result = handle;
}
return Mock.mock(result);
};
}
function mockXHR() {
const mockMethods = getModuleInfo(modules);
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send;
Mock.XHR.prototype.send = function () {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false;
if (this.responseType) {
this.custom.xhr.responseType = this.responseType;
}
}
this.proxy_send(...arguments);
};
for (const { path, method, handle, timeout } of mockMethods) {
setupMock(timeout);
Mock.mock(new RegExp(path), method || 'get', XHR2ExpressReqWrap(handle, timeout));
}
}
function setupMock(timeout = 0) {
if (timeout) {
Mock.setup({
timeout,
});
}
}
mockXHR();

38
mock/_util/resultUtil.js Normal file
View File

@ -0,0 +1,38 @@
export default class ResultUtil {
static success(result, { message = 'ok' } = {}) {
return {
code: 0,
result,
message,
type: 'success',
};
}
static pageSuccess(items, total, { message = 'ok' } = {}) {
return {
code: 0,
result: {
items,
total,
},
message,
type: 'success',
};
}
static error(message = 'Request failed', { code = -1, result = null } = {}) {
return {
code,
result,
message,
type: 'error',
};
}
/**
* @description: 分页
*/
static pagination(pageNo, pageSize, array) {
let offset = (pageNo - 1) * pageSize;
return offset + pageSize >= array.length
? array.slice(offset, array.length)
: array.slice(offset, offset + pageSize);
}
}

56
mock/sys/login.js Normal file
View File

@ -0,0 +1,56 @@
import ResultUtil from '../_util/resultUtil';
const fakeUserList = [
{
userId: '2',
username: 'admin',
realName: '管理员',
desc: 'vben admin 管理员',
password: '123456',
token: 'fakeToken1',
},
{
userId: '1',
username: 'vben',
password: '123456',
realName: '测试用户',
desc: 'vben admin 测试员',
token: 'fakeToken2',
},
];
const fakeRoles = [
{
// 只是模拟
userId: '1',
roleName: '普通用户',
value: 'normal',
},
{
userId: '2',
roleName: '管理员',
value: 'admin',
},
];
export default {
'POST /login 300': ({ body }) => {
const { username, password } = body;
const checkUser = fakeUserList.find(
(item) => item.username === username && password === item.password
);
if (!checkUser) {
return ResultUtil.error('Incorrect account or password(vben/123456,test1/123456)');
}
const { userId, username: _username, token, realName, desc } = checkUser;
const roles = fakeRoles.filter((item) => item.userId === userId);
return ResultUtil.success({
roles,
userId,
username: _username,
token,
realName,
desc,
});
},
};

136
package.json Normal file
View File

@ -0,0 +1,136 @@
{
"name": "vben-admin",
"version": "1.0.4",
"private": true,
"scripts": {
"bootstrap": "yarn && cd ./mock && yarn --registry https://registry.npm.taobao.org && cd ../",
"serve": "node ./build/task/serve",
"build": "node ./build/task/build",
"build:no-cache": "npm run clean:cache && npm run build",
"report": "cross-env REPORT=true npm run build ",
"log": "node ./build/task/log",
"preview": "node ./build/task/preview",
"reinstall": "npx rimraf node_modules && yarn run bootstrap",
"clean:cache": "npx rimraf node_modules/.cache/",
"clean:lib": "npx rimraf node_modules",
"test:unit": "vue-cli-service test:unit",
"lint": "cross-env --max_old_space_size=4096 vue-cli-service lint --mode=production --fix",
"lint:eslint": "cross-env --max_old_space_size=4096 eslint --fix --ext \"src/**/*.{vue,less,css,scss}\"",
"lint:stylelint": "cross-env --max_old_space_size=4096 stylelint --fix \"**/*.{vue,less,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:prettier": "cross-env --max_old_space_size=4096 prettier --write --loglevel warn \"src/**/*.{js,json,css,less,scss,vue,html,md}\"",
"preinstall": "node ./build/npm-hook/preinstall.js",
"postinstall": "node ./build/npm-hook/postinstall.js && npm run clean:cache"
},
"dependencies": {
"@vue/composition-api": "^1.0.0-beta.2",
"ant-design-vue": "^1.6.3",
"axios": "^0.19.2",
"core-js": "^3.6.5",
"crypto-js": "^3.3.0",
"mockjs": "^1.1.0",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"vue": "^2.6.11",
"vue-router": "^3.3.4",
"vuex": "^3.4.0",
"vuex-module-decorators": "^0.17.0"
},
"devDependencies": {
"@babel/register": "^7.10.4",
"@commitlint/cli": "^9.0.1",
"@commitlint/config-conventional": "^9.0.1",
"@gfx/zopfli": "^1.0.14",
"@typescript-eslint/eslint-plugin": "^3.6.0",
"@typescript-eslint/parser": "^3.6.0",
"@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
"@vue/cli-plugin-babel": "~4.4.6",
"@vue/cli-plugin-eslint": "~4.4.6",
"@vue/cli-plugin-router": "~4.4.6",
"@vue/cli-plugin-typescript": "~4.4.6",
"@vue/cli-plugin-unit-jest": "~4.4.6",
"@vue/cli-plugin-vuex": "~4.4.6",
"@vue/cli-service": "~4.4.6",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/eslint-config-typescript": "^5.0.2",
"@vue/test-utils": "^1.0.3",
"babel-plugin-captains-log": "^1.2.0",
"babel-plugin-component": "^1.1.1",
"babel-plugin-import": "^1.13.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"babel-preset-vca-jsx": "^0.3.5",
"cache-loader": "^4.1.0",
"chalk": "^4.1.0",
"chokidar": "^3.4.0",
"clean-webpack-plugin": "^3.0.0",
"commitizen": "^4.1.2",
"compressing": "^1.5.1",
"compression-webpack-plugin": "^4.0.0",
"conventional-changelog-cli": "^2.0.34",
"copy-webpack-plugin": "^6.0.2",
"cross-env": "^7.0.2",
"css-loader": "^3.6.0",
"eslint": "^7.4.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.0.0",
"fs-extra": "^9.0.1",
"glob": "^7.1.6",
"hard-source-webpack-plugin": "^0.13.1",
"html-webpack-harddisk-plugin": "^1.0.1",
"html-webpack-plugin": "4.3.0",
"husky": "^4.2.5",
"inquirer": "^7.3.0",
"koa": "^2.12.1",
"koa-static": "^5.0.0",
"less": "^3.11.3",
"less-loader": "^6.2.0",
"less-plugin-functions": "^1.0.0",
"lint-staged": "^10.2.10",
"markdownlint-cli": "^0.23.1",
"postcss-flexbugs-fixes": "^4.2.1",
"prettier": "^2.0.5",
"rimraf": "^3.0.2",
"runjs": "^4.4.2",
"script-ext-html-webpack-plugin": "^2.1.4",
"shelljs": "^0.8.4",
"style-loader": "^1.2.1",
"stylelint": "^13.6.0",
"stylelint-config-prettier": "^8.0.1",
"stylelint-config-standard": "^20.0.0",
"stylelint-order": "^4.1.0",
"stylelint-webpack-plugin": "^2.0.0",
"svg-sprite-loader": "^5.0.0",
"terser-webpack-plugin": "^3.0.4",
"thread-loader": "^2.1.3",
"typescript": "^3.9.5",
"vue-skeleton-webpack-plugin": "^1.2.2",
"vue-template-compiler": "^2.6.11",
"vue-tsx-support": "^2.3.3",
"webpack": "^4.43.0",
"webpack-bundle-analyzer": "^3.8.0",
"webpack-cli": "^3.3.11",
"webpack-merge": "^5.0.7",
"webpack-theme-color-replacer": "^1.3.13",
"webpackbar": "^4.0.0",
"worker-loader": "^2.0.0",
"yargs": "^15.4.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"config": {
"commitizen": {
"path": "./config/lint/cz/index.js"
}
},
"engines": {
"node": ">=10.0.0"
}
}

10
postcss.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
plugins: [
require('postcss-flexbugs-fixes')({
remove: false,
}),
require('autoprefixer')({
remove: false,
}),
],
};

1
prettier.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./config/lint/prettier');

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

16
public/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="icon" href="./favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
<%= htmlWebpackPlugin.options.addToHead %>
</head>
<body>
<div id="app"></div>
</body>
</html>

0
src/a.vue Normal file
View File

23
src/api/sys/login.ts Normal file
View File

@ -0,0 +1,23 @@
import http from '@/utils/http/axios';
import { LoginParams, LoginResultModel } from './model/LoginModel';
enum Api {
Login = '/login',
}
/**
* @description:
*/
export function loginApi(params: LoginParams) {
return http.request<LoginResultModel>(
{
url: Api.Login,
method: 'POST',
params,
},
{
// 登陆接口不加 /v1.0
joinPrefix: false,
}
);
}

View File

@ -0,0 +1,25 @@
/**
* @description:
*/
export interface LoginParams {
username: string;
password: string;
}
/**
* @description:
*/
export interface LoginResultModel {
// 角色值数组
roles: { roleName: string; value: string }[];
// 用户id
userId: string | number;
// 用户名
username: string;
// token
token: string;
// 真实名字
realName: string;
// 介绍
desc?: string;
}

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 200 200" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css">
.left-linear {
fill: url(#left-linear);
}
.right-linear {
fill: url(#right-linear);
}
.top {
fill: #1c6cc7;
}
.bottom {
fill: #9dbfe4;
}
svg {
display: block;
}
.tip {
display: block;
min-width: 100px;
margin-top: 4px;
font-size: 13px;
color: #333;
text-align: left;
}
</style>
<circle cx="97" cy="97" r="81" stroke-width="16" stroke="#4d83c6" fill="none"></circle>
<g class="load">
<!--右半圆环-->
<linearGradient id="left-linear" gradientUnits="userSpaceOnUse" x1="50" y1="0" x2="100" y2="180">
<stop offset="0" style="stop-color: #1c6cc7;" />
<stop offset="1" style="stop-color: #9DBFE4;" />
</linearGradient>
<path class="left-linear" d="M20,100c0-44.1,35.9-80,80-80V0C44.8,0,0,44.8,0,100s44.8,100,100,100v-20C55.9,180,20,144.1,20,100z" />
<!--左半圆环-->
<circle class="bottom" cx="100" cy="190" r="10" />
<linearGradient id="right-linear" gradientUnits="userSpaceOnUse" x1="100" y1="120" x2="100" y2="180">
<stop offset="0" style="stop-color: transparent;" />
<stop offset="1" style="stop-color: transparent;" />
</linearGradient>
<path class="right-linear" d="M100,0v20c44.1,0,80,35.9,80,80c0,44.1-35.9,80-80,80v20c55.2,0,100-44.8,100-100S155.2,0,100,0z" />
<!--左半圆环-->
<circle class="top" cx="100" cy="10" r="10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,67 @@
// export what you need
export { default as SmileOutline } from '@ant-design/icons/lib/outline/SmileOutline';
export { default as MehOutline } from '@ant-design/icons/lib/outline/MehOutline';
// upload
export { default as ZoomInOutline } from '@ant-design/icons/lib/outline/ZoomInOutline';
export { default as SettingOutline } from '@ant-design/icons/lib/outline/SettingOutline';
// 复制
export { default as CopyOutline } from '@ant-design/icons/lib/outline/CopyOutline';
// 锁定
export { default as LockOutline } from '@ant-design/icons/lib/outline/LockOutline';
// 解锁
export { default as UnlockOutline } from '@ant-design/icons/lib/outline/UnlockOutline';
// 全屏
export { default as FullscreenOutline } from '@ant-design/icons/lib/outline/FullscreenOutline';
// 退出全屏
export { default as FullscreenExitOutline } from '@ant-design/icons/lib/outline/FullscreenExitOutline';
// 刷新
export { default as ReloadOutline } from '@ant-design/icons/lib/outline/ReloadOutline';
// 关闭其他
export { default as PicCenterOutline } from '@ant-design/icons/lib/outline/PicCenterOutline';
// 关闭左侧
export { default as PicLeftOutline } from '@ant-design/icons/lib/outline/PicLeftOutline';
// 关闭右侧
export { default as PicRightOutline } from '@ant-design/icons/lib/outline/PicRightOutline';
// 关闭全部
export { default as LineOutline } from '@ant-design/icons/lib/outline/LineOutline';
// 以下为ant-design需要的图标请勿删除额外引入的图标添加在上方并备注用途
// export what antd other components need
export { default as CloseOutline } from '@ant-design/icons/lib/outline/CloseOutline';
export { default as CheckOutline } from '@ant-design/icons/lib/outline/CheckOutline';
export { default as LoadingOutline } from '@ant-design/icons/lib/outline/LoadingOutline';
export { default as CheckCircleOutline } from '@ant-design/icons/lib/outline/CheckCircleOutline';
export { default as InfoCircleOutline } from '@ant-design/icons/lib/outline/InfoCircleOutline';
export { default as QuestionCircleOutline } from '@ant-design/icons/lib/outline/QuestionCircleOutline';
export { default as CloseCircleOutline } from '@ant-design/icons/lib/outline/CloseCircleOutline';
export { default as ExclamationCircleOutline } from '@ant-design/icons/lib/outline/ExclamationCircleOutline';
export { default as CheckCircleFill } from '@ant-design/icons/lib/fill/CheckCircleFill';
export { default as InfoCircleFill } from '@ant-design/icons/lib/fill/InfoCircleFill';
export { default as CloseCircleFill } from '@ant-design/icons/lib/fill/CloseCircleFill';
export { default as ExclamationCircleFill } from '@ant-design/icons/lib/fill/ExclamationCircleFill';
export { default as UpOutline } from '@ant-design/icons/lib/outline/UpOutline';
export { default as DownOutline } from '@ant-design/icons/lib/outline/DownOutline';
export { default as LeftOutline } from '@ant-design/icons/lib/outline/LeftOutline';
export { default as DoubleLeftOutline } from '@ant-design/icons/lib/outline/DoubleLeftOutline';
export { default as RightOutline } from '@ant-design/icons/lib/outline/RightOutline';
export { default as DoubleRightOutline } from '@ant-design/icons/lib/outline/DoubleRightOutline';
export { default as RedoOutline } from '@ant-design/icons/lib/outline/RedoOutline';
export { default as CalendarOutline } from '@ant-design/icons/lib/outline/CalendarOutline';
export { default as SearchOutline } from '@ant-design/icons/lib/outline/SearchOutline';
export { default as BarsOutline } from '@ant-design/icons/lib/outline/BarsOutline';
export { default as StarFill } from '@ant-design/icons/lib/fill/StarFill';
export { default as FilterOutline } from '@ant-design/icons/lib/outline/FilterOutline';
export { default as CaretUpOutline } from '@ant-design/icons/lib/outline/CaretUpOutline';
export { default as CaretDownOutline } from '@ant-design/icons/lib/outline/CaretDownOutline';
export { default as PlusOutline } from '@ant-design/icons/lib/outline/PlusOutline';
export { default as FileOutline } from '@ant-design/icons/lib/outline/FileOutline';
export { default as FolderOpenOutline } from '@ant-design/icons/lib/outline/FolderOpenOutline';
export { default as FolderOutline } from '@ant-design/icons/lib/outline/FolderOutline';
export { default as PaperClipOutline } from '@ant-design/icons/lib/outline/PaperClipOutline';
export { default as PictureOutline } from '@ant-design/icons/lib/outline/PictureOutline';
export { default as EyeOutline } from '@ant-design/icons/lib/outline/EyeOutline';
export { default as DeleteOutline } from '@ant-design/icons/lib/outline/DeleteOutline';

View File

@ -0,0 +1,11 @@
/**
* svgicon图片
*/
export default (): void => {
const requireAll = (requireContext) => {
requireContext.keys().map(requireContext);
};
const req = require.context('./svg', true, /\.svg$/);
requireAll(req);
};

View File

@ -0,0 +1 @@
# 关于图标存放请按模块命名文件夹,公共图标放 common 文件夹,大于 20k 以上的 svg 请压缩或者单独引入,不要放于该文件夹

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1589522763496" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2137" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M937.173333 984.32a6.997333 6.997333 0 0 1-4.224-1.365333l-128.853333-94.933334-129.706667 94.762667a7.125333 7.125333 0 0 1-7.381333 0.597333 7.082667 7.082667 0 0 1-3.84-6.314666v-191.488a7.082667 7.082667 0 0 1 9.258667-6.698667l45.824 15.146667c2.986667 0.853333 5.205333 3.584 5.205333 6.826666v71.338667l76.629333-55.978667a7.082667 7.082667 0 0 1 8.362667 0l75.434667 55.637334v-71.04c0-2.986667 1.92-5.674667 4.778666-6.656l46.165334-15.957334a7.04 7.04 0 0 1 9.386666 6.656v192.426667a7.082667 7.082667 0 0 1-7.04 7.04z m-802.432-33.024A109.226667 109.226667 0 0 1 25.6 842.154667V147.968A109.269333 109.269333 0 0 1 134.741333 38.741333h605.568a109.269333 109.269333 0 0 1 109.184 109.141334v216.576a7.082667 7.082667 0 0 1-7.082666 7.082666H797.44a7.082667 7.082667 0 0 1-7.082667-7.082666V147.968c0-27.605333-22.442667-50.048-50.005333-50.048H134.741333c-27.562667 0-50.005333 22.442667-50.005333 50.048v694.186667c0 27.562667 22.442667 50.005333 50.005333 50.005333H605.013333c3.882667 0 7.04 3.157333 7.04 7.082667v44.970666a7.082667 7.082667 0 0 1-7.04 7.082667H134.741333z m668.928-150.613333a203.605333 203.605333 0 0 1-203.392-203.392 203.605333 203.605333 0 0 1 203.392-203.349334 203.605333 203.605333 0 0 1 203.392 203.349334 203.605333 203.605333 0 0 1-203.392 203.392z m0-346.453334A143.232 143.232 0 0 0 660.608 597.333333a143.232 143.232 0 0 0 143.061333 143.061334A143.232 143.232 0 0 0 946.730667 597.333333a143.232 143.232 0 0 0-143.061334-143.018666z m-610.133333 306.645334a7.082667 7.082667 0 0 1-7.082667-7.082667v-49.194667c0-3.925333 3.2-7.082667 7.082667-7.082666h356.096c3.925333 0 7.082667 3.157333 7.082667 7.082666v49.194667a7.082667 7.082667 0 0 1-7.082667 7.082667H193.536z m610.133333-75.648a88.021333 88.021333 0 0 1-87.893333-87.893334c0-48.512 39.424-87.936 87.893333-87.936 48.469333 0 87.893333 39.424 87.893334 87.893334 0 48.469333-39.424 87.893333-87.893334 87.893333z m0-138.581334c-27.904 0-50.645333 22.698667-50.645333 50.645334a50.773333 50.773333 0 0 0 50.688 50.688 50.730667 50.730667 0 0 0 0-101.333334z m-610.133333 57.728a7.082667 7.082667 0 0 1-7.082667-7.082666v-49.237334c0-3.882667 3.2-7.04 7.082667-7.04h356.096c3.925333 0 7.082667 3.157333 7.082667 7.04V597.333333a7.082667 7.082667 0 0 1-7.082667 7.082667H193.536z m0-155.306666a7.082667 7.082667 0 0 1-7.082667-7.04V392.832c0-3.925333 3.2-7.082667 7.082667-7.082667h356.096c3.925333 0 7.082667 3.157333 7.082667 7.082667v49.194667a7.082667 7.082667 0 0 1-7.082667 7.082666H193.536z m0-156.501334a7.082667 7.082667 0 0 1-7.082667-7.04V236.288c0-3.882667 3.2-7.04 7.082667-7.04h488.021333c3.925333 0 7.082667 3.157333 7.082667 7.04v49.237333a7.082667 7.082667 0 0 1-7.082667 7.04H193.536z" p-id="2138" fill="#45657F"></path></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1555985440062" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1939" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M940.032 102.4H83.968C37.888 102.4 0 140.288 0 186.88v650.752c0 46.592 37.888 83.968 83.968 83.968h855.552c46.592 0 83.968-37.888 83.968-83.968V186.88c0.512-46.592-37.376-84.48-83.456-84.48z m-322.56 145.408c46.592 0 84.48 37.888 84.48 84.48s-37.888 84.48-84.48 84.48-84.48-37.888-84.48-84.48 37.888-84.48 84.48-84.48zM83.968 809.472s69.12-402.432 229.376-402.432c157.184 0 197.12 281.088 307.712 281.088 69.632 0 62.464-126.976 173.056-126.976 79.36 0 130.048 247.808 130.048 247.808l-840.192 0.512z" p-id="1940"></path></svg>

After

Width:  |  Height:  |  Size: 913 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1589955512947" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2141" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M513.442909 1002.914909A488.634182 488.634182 0 0 0 1002.914909 513.396364c0-180.596364-96.162909-337.92-238.917818-422.446546-46.592-26.205091-72.843636 29.137455-32.023273 55.389091a423.563636 423.563636 0 0 1 209.733818 367.057455c0 235.985455-192.232727 428.264727-428.218181 428.264727-235.985455 0-428.311273-192.232727-428.311273-428.218182a423.563636 423.563636 0 0 1 209.780363-367.104c37.841455-26.251636 14.568727-81.594182-32.069818-55.389091C117.201455 175.522909 21.085091 332.846545 21.085091 513.442909c0 270.941091 221.416727 489.472 492.357818 489.472z m0-981.829818c20.386909 0 40.820364 17.501091 40.820364 37.888v550.632727c0 20.386909-20.433455 37.888-40.820364 37.888-23.272727 0-40.773818-17.501091-40.773818-37.888V58.973091c0-20.386909 17.501091-37.888 40.773818-37.888z" p-id="2142" ></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1589271287315" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1983" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M203.1 599.3c-48.9 0-88.6-39.6-88.6-88.5s39.6-88.5 88.6-88.5c48.9 0 88.6 39.6 88.6 88.5-0.1 48.9-39.7 88.5-88.6 88.5z m309.9 0c-48.9 0-88.6-39.6-88.6-88.5s39.6-88.5 88.6-88.5c48.9 0 88.6 39.6 88.6 88.5s-39.7 88.5-88.6 88.5z m309.9 0c-48.9 0-88.6-39.6-88.6-88.5s39.6-88.5 88.6-88.5c48.9 0 88.6 39.6 88.6 88.5s-39.6 88.5-88.6 88.5z" p-id="1984" fill="#91939a"></path></svg>

After

Width:  |  Height:  |  Size: 746 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1589279146592" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1961" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M711.871126 512.00224h182.527201v365.6944h94.463587V1024H39.810066v-146.30336h672.06106V512.00224z m-109.439522 0v365.6944h-182.399202V512.00224H602.431604zM310.400882 512.00224v365.6944H127.873681V512.00224H310.400882zM512 0.00448c3.839983 0 7.743966 0.767997 11.32795 2.36799l4.991979 2.751988 483.453884 346.238485a26.559884 26.559884 0 0 1 9.663958 30.719865 27.903878 27.903878 0 0 1-20.223911 17.983922l-5.759975 0.639997H28.546115a25.919887 25.919887 0 0 1-26.047886-18.687918 26.367885 26.367885 0 0 1 5.311977-26.623884L12.162187 351.362943 495.616072 5.124458A28.031877 28.031877 0 0 1 512 0.00448z" p-id="1962" fill="#0064BF"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1589279096702" class="icon" viewBox="0 0 1102 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="1803"
xmlns:xlink="http://www.w3.org/1999/xlink" width="68.875" height="64">
<defs>
<style type="text/css"></style>
</defs>
<path d="M663.236923 652.996923a301.371077 301.371077 0 1 0-221.105231 0c-199.286154 34.816-362.023385 161.949538-431.576615 330.200615H1094.892308c-69.553231-168.251077-232.290462-295.384615-431.576616-330.200615z" p-id="1804" fill="#67C23A"></path>
</svg>

After

Width:  |  Height:  |  Size: 649 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1589699877718" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2139" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M512 0a512 512 0 0 0-512 512 512 512 0 0 0 512 512 512 512 0 0 0 512-512 512 512 0 0 0-512-512z m0 192a31.963429 31.963429 0 1 1 0 63.926857 31.963429 31.963429 0 0 1 0-63.926857z m95.524571 640H416.475429a31.963429 31.963429 0 1 1 0-64h62.025142V445.44h-62.976a31.963429 31.963429 0 1 1 0-63.926857h94.939429c17.700571 0 32.036571 14.262857 32.036571 31.963428V768h65.024c17.700571 0 32.914286 14.336 32.914286 32.036571 0 17.627429-15.213714 31.963429-32.914286 31.963429z" p-id="2140" fill="#F56C6C"></path></svg>

After

Width:  |  Height:  |  Size: 891 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1587884014415" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2035" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M500.487915 683.510886a340.246081 340.246081 0 0 1-241.344476-99.924901 338.199488 338.199488 0 0 1-73.114534-108.469428 338.455312 338.455312 0 0 1-26.810368-132.875048 340.246081 340.246081 0 0 1 99.924902-241.344476A338.199488 338.199488 0 0 1 367.612866 27.7825 338.455312 338.455312 0 0 1 500.539079 0.972132a340.246081 340.246081 0 0 1 241.344476 99.924901 338.199488 338.199488 0 0 1 73.165699 108.469428c17.805359 42.10865 26.759203 86.775542 26.759203 132.875048a340.246081 340.246081 0 0 1-99.924902 241.344476 340.246081 340.246081 0 0 1-241.344476 99.924901z m0-620.271163a279.308775 279.308775 0 0 0-278.848292 279.001786 279.206446 279.206446 0 0 0 278.848292 278.848292 279.206446 279.206446 0 0 0 278.848291-278.848292 279.308775 279.308775 0 0 0-278.848291-279.001786z" fill="#5B8FC9" p-id="2036"></path><path d="M33.711224 1023.296484a31.210543 31.210543 0 0 1-29.675598-41.136519 522.392855 522.392855 0 0 1 74.495984-143.261508 523.109162 523.109162 0 0 1 113.27892-113.023096 519.322965 519.322965 0 0 1 308.677385-100.692374c111.89747 0 218.627294 34.79208 308.728549 100.692374a525.30925 525.30925 0 0 1 187.72374 256.284604 31.159378 31.159378 0 1 1-59.146537 19.800787 460.585747 460.585747 0 0 0-437.305752-314.35668 460.636912 460.636912 0 0 0-437.254588 314.45901 31.210543 31.210543 0 0 1-29.522103 21.233402z" fill="#5B8FC9" p-id="2037"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588923316927" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2381" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M192.350316 202.698105V89.249684c0.053895-17.084632 13.904842-30.935579 31.043368-30.989473H800.121263c17.084632 0 30.935579 13.904842 30.989474 30.989473v113.448421H889.047579v-140.665263C889.047579 27.917474 861.130105 0 827.014737 0H196.500211C162.438737 0 134.521263 27.917474 134.521263 61.978947v140.719158h57.829053z m638.814316 604.752842v123.580632a31.097263 31.097263 0 0 1-30.989474 30.989474H223.393684a31.097263 31.097263 0 0 1-31.043368-30.989474v-123.580632H134.521263v150.797474c0 34.115368 27.917474 62.032842 61.978948 62.032842H827.068632c34.115368 0 61.978947-27.917474 61.978947-61.978947v-150.851369h-57.829053zM269.312 661.611789H210.512842V439.888842A203.506526 203.506526 0 0 1 134.521263 484.513684V431.157895c15.36-5.066105 32.013474-14.551579 50.014316-28.618106 18.000842-14.012632 30.342737-30.396632 37.025684-49.04421h47.750737v308.11621z m328.272842-54.649263v54.649263H391.437474c2.209684-20.641684 8.946526-40.205474 20.102737-58.745263 11.156211-18.485895 33.199158-43.008 66.128842-73.566315 26.516211-24.683789 42.738526-41.498947 48.774736-50.229895 8.084211-12.126316 12.126316-24.144842 12.126316-36.001684 0-13.096421-3.503158-23.174737-10.563368-30.234948-7.060211-7.006316-16.707368-10.563368-29.210948-10.563368-12.234105 0-22.042947 3.664842-29.264842 11.048421-7.275789 7.383579-11.479579 19.671579-12.557473 36.864l-58.637474-5.874526c3.503158-32.336842 14.443789-55.565474 32.875789-69.685895 18.378105-14.066526 41.445053-21.126737 69.039158-21.126737 30.288842 0 54.056421 8.138105 71.410527 24.46821 17.300211 16.330105 25.923368 36.648421 25.923368 60.901053 0.053895 13.473684-2.479158 26.947368-7.437474 39.450947a160.768 160.768 0 0 1-23.552 39.289264c-7.114105 9.054316-19.941053 22.096842-38.480842 39.181473-18.539789 16.976842-30.288842 28.348632-35.247158 33.845895a101.322105 101.322105 0 0 0-12.072421 16.384h116.789895z m87.578947-26.785684l56.966737-6.898526c1.778526 14.551579 6.682947 25.6 14.605474 33.253052a40.205474 40.205474 0 0 0 28.887579 11.533474 39.720421 39.720421 0 0 0 30.666105-13.797053c8.299789-9.216 12.449684-21.665684 12.449684-37.295157 0-14.767158-3.988211-26.516211-11.910736-35.139369a37.995789 37.995789 0 0 0-29.103158-13.042526c-7.545263 0-16.545684 1.509053-27.001263 4.419368l6.467368-47.966316c15.952842 0.431158 28.025263-3.018105 36.432842-10.293894a36.917895 36.917895 0 0 0 12.557474-29.210948 33.522526 33.522526 0 0 0-9.162106-24.737684 33.253053 33.253053 0 0 0-24.522105-9.162105 35.408842 35.408842 0 0 0-25.761684 10.455579c-7.114105 6.952421-11.425684 17.138526-12.934737 30.558316l-54.218105-9.216c3.772632-18.539789 9.431579-33.414737 17.030737-44.517053 7.653053-11.048421 18.216421-19.779368 31.797894-26.138947 13.635368-6.359579 28.887579-9.539368 45.810527-9.539369 28.833684 0 51.954526 9.216 69.470316 27.648 14.336 15.090526 21.557895 32.121263 21.557894 51.092211 0 26.947368-14.767158 48.397474-44.193684 64.458105 17.569684 3.772632 31.636211 12.234105 42.199579 25.330526 10.509474 13.096421 15.791158 28.941474 15.791158 47.481263 0 26.947368-9.862737 49.906526-29.534316 68.877474-19.671579 18.970947-44.139789 28.456421-73.458526 28.456421-27.755789 0-50.768842-7.976421-69.039158-23.929263-18.324211-15.952842-28.887579-36.864-31.797895-62.679579z" p-id="2382" fill="#1B65B9"></path></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1587884026333" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2185" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M49.777778 494.250667v479.971555c0 19.626667 15.928889 35.555556 35.555555 35.555556h853.333334c19.626667 0 35.555556-15.928889 35.555555-35.555556V494.193778a35.555556 35.555556 0 0 0-35.555555-35.498667h-853.333334a35.555556 35.555556 0 0 0-35.555555 35.498667z m853.333333 444.416H120.888889V529.749333h782.222222V938.666667z m-36.181333-575.544889A355.555556 355.555556 0 0 0 155.875556 369.208889v124.984889H227.555556V369.777778a284.444444 284.444444 0 0 1 568.888888 0 35.555556 35.555556 0 1 0 70.485334-6.656z" fill="#5B8FC9" p-id="2186"></path></svg>

After

Width:  |  Height:  |  Size: 935 B

Some files were not shown because too many files have changed in this diff Show More