diff --git a/packages/fes-cli/build/preComplie/route.js b/packages/fes-cli/build/preComplie/route.js
index 15fed007..a4e2460b 100644
--- a/packages/fes-cli/build/preComplie/route.js
+++ b/packages/fes-cli/build/preComplie/route.js
@@ -1,121 +1,164 @@
// pages
-// ├── index.fes # 根路由页面 路径 index.html#/
-// ├── a.fes # 路径 /a
+// ├── index.vue # 根路由页面 路径 /
+// ├── *.vue # 模糊匹配 路径 *
+// ├── a.vue # 路径 /a
// ├── b
-// │ ├── index.fes # 路径 /b
-// │ ├── @id.fes # 动态路由 /b/:id
-// │ └── c.fes # 路径 /b/c
-// └── layout.fes # 根路由下所有page共用的外层
+// │ ├── index.vue # 路径 /b
+// │ ├── @id.vue # 动态路由 /b/:id
+// │ └── c.vue # 路径 /b/c
+// └── layout.vue # 根路由下所有page共用的外层
const fs = require('fs');
const Path = require('path');
-let pagesDir;
-let outputPageDir;
-let components = [];
-function checkHasLayout(path) {
+const isProcessFile = function (path) {
+ const ext = Path.extname(path);
+ return fs.statSync(path).isFile() && ['.fes', '.vue'].includes(ext);
+};
+
+const isProcessDirectory = function (path, item) {
+ const component = Path.posix.join(path, item);
+ return fs.statSync(component).isDirectory() && !['components'].includes(item);
+};
+
+const checkHasLayout = function (path) {
const dirList = fs.readdirSync(path);
- let hasLayout = false;
- dirList.forEach((item) => {
- if (fs.statSync(`${path}/${item}`).isFile()
- && item[0] !== '.' && ['.fes', '.vue'].indexOf(Path.extname(item)) !== -1
- && Path.basename(item, Path.extname(item)) === 'layout') {
- hasLayout = true;
+ return dirList.some((item) => {
+ if (!isProcessFile(Path.posix.join(path, item))) {
+ return false;
}
+ const ext = Path.extname(item);
+ const fileName = Path.basename(item, ext);
+ return fileName === 'layout';
});
- return hasLayout;
-}
+};
-function routeUrlFormmter(str) {
- return str.replace(/@/g, ':');
-}
+const getRouteName = function (parentRoutePath, fileName) {
+ const routeName = Path.posix.join(parentRoutePath, fileName);
+ return routeName.slice(1).replace(/\//g, '_').replace(/@/g, '_').replace(/\*/g, 'FUZZYMATCH');
+};
-function genRoute(path, prePathUrl, preRoutes) {
- const hasLayout = checkHasLayout(path);
+const getRoutePath = function (parentRoutePath, fileName) {
+ // /index.vue -> /
+ if (fileName === 'index') {
+ fileName = '';
+ }
+ let routePath = Path.posix.join(parentRoutePath, fileName);
+ // /@id.vue -> /:id
+ routePath = routePath.replace(/@/g, ':');
+ // /*.vue -> *
+ if (routePath === '/*') {
+ routePath = '*';
+ }
+ return routePath;
+};
+
+const build = function (components, parentRoutes, path, parentRoutePath) {
const dirList = fs.readdirSync(path);
- let childRoutes = {};
- const parentRoutes = {};
- const preRouteUrl = routeUrlFormmter(prePathUrl);
+ const hasLayout = checkHasLayout(path);
+ const layoutRoute = {
+ children: []
+ };
if (hasLayout) {
- parentRoutes[preRouteUrl] = {
- subRoutes: childRoutes
- };
- } else {
- childRoutes = parentRoutes;
+ layoutRoute.path = parentRoutePath;
+ parentRoutes.push(layoutRoute);
}
dirList.forEach((item) => {
- if (fs.statSync(`${path}/${item}`).isFile()
- && item[0] !== '.' && ['.fes', '.vue'].indexOf(Path.extname(item)) !== -1) {
- const fileName = Path.basename(item, Path.extname(item));
- const preRouteName = path.slice(pagesDir.length + 1);
- let routePath = Path.posix.join(preRouteUrl, (fileName === 'index' ? '' : fileName.replace(/@/g, ':')));
- const filePath = Path.resolve(path.replace(pagesDir, outputPageDir), item);
- let routeName = preRouteName ? `${preRouteName}_${fileName}` : fileName;
- routeName = routeName.replace(/\//g, '_').replace(/@/g, '');
- routePath = routePath.replace(/@/g, ':');
-
- if (hasLayout && fileName === 'index') {
- routePath = '/';
- } else if (hasLayout && fileName !== 'index') {
- routePath = routePath.split(preRouteUrl)[1];
- }
+ // 文件或者目录的绝对路径
+ const component = Path.posix.join(path, item);
+ if (isProcessFile(component)) {
+ const ext = Path.extname(item);
+ const fileName = Path.basename(item, ext);
+ // 路由的path
+ const routePath = getRoutePath(parentRoutePath, fileName);
+ // 路由名称
+ const routeName = getRouteName(parentRoutePath, fileName);
components.push({
name: routeName,
- path: filePath.replace(/\\/g, '\\\\')
+ path: component
});
- if (fileName === 'layout') {
- parentRoutes[preRouteUrl].component = routeName;
- return;
+ if (hasLayout) {
+ if (fileName === 'layout') {
+ layoutRoute.component = routeName;
+ } else {
+ layoutRoute.children.push({
+ path: routePath,
+ component: routeName,
+ name: routeName
+ });
+ }
+ } else {
+ parentRoutes.push({
+ path: routePath,
+ component: routeName,
+ name: routeName
+ });
}
- childRoutes[routePath] = {
- name: routeName || 'index',
- component: routeName
- };
}
});
- preRoutes = Object.assign(preRoutes, parentRoutes);
- const toNextRoutes = hasLayout ? childRoutes : preRoutes;
dirList.forEach((item) => {
- if (fs.statSync(`${path}/${item}`).isDirectory()) {
- let toNextPreRouteUrl = Path.posix.join(prePathUrl, item);
+ if (isProcessDirectory(path, item)) {
+ // 文件或者目录的绝对路径
+ const component = Path.posix.join(path, item);
+ const nextParentRouteUrl = Path.posix.join(parentRoutePath, item);
if (hasLayout) {
- toNextPreRouteUrl = Path.posix.join('/', item);
+ build(components, layoutRoute.children, component, nextParentRouteUrl);
+ } else {
+ build(components, parentRoutes, component, nextParentRouteUrl);
}
- genRoute(`${path}/${item}`, toNextPreRouteUrl, toNextRoutes, outputPageDir, components);
}
});
-}
+};
-function fixRouter2(routes, newRoutes, f) {
- Object.keys(routes).forEach((p) => {
- const item = {
- path: f ? p.slice(1) : p,
- component: routes[p].component
- };
- if (routes[p].name) {
- item.name = routes[p].name;
+/**
+ * 智能路由
+ * 1、路由的路径是多个“/”组成的字符串,使用“/”分割后得到不同的子项
+ * 2、计算子项个数,用个数乘以4,计入得分
+ * 3、判断子项是否是静态的,即不包含“:”、“*”等特殊字符串,若是计入3分。
+ * 4、判断子项是否是动态的,即包含“:”特殊字符,若是计入2分。
+ * 5、判断子项是否是模糊匹配,即包含“*”特殊字符,若是扣除1分。
+ * 6、判断子项是否是根端,即只是“/”,若是计入1分。
+
+ * @param {*} routes
+ */
+const fix = function (routes) {
+ routes.forEach((item) => {
+ const path = item.path;
+ let arr = path.split('/');
+ // console.log(arr);
+ if (arr[0] === '') {
+ arr = arr.slice(1);
}
- if (routes[p].subRoutes) {
- item.children = [];
- fixRouter2(routes[p].subRoutes, item.children, true);
+ let count = 0;
+ arr.forEach((sonPath) => {
+ count += 4;
+ if (sonPath.indexOf(':') !== -1) {
+ count += 2;
+ } else if (sonPath.indexOf('*') !== -1) {
+ count -= 1;
+ } else if (sonPath === '') {
+ count += 1;
+ } else {
+ count += 3;
+ }
+ });
+ item.count = count;
+ if (item.children && item.children.length) {
+ fix(item.children);
}
- newRoutes.push(item);
});
-}
+ routes = routes.sort((a, b) => b.count - a.count);
+};
-module.exports = function (_pagesDir, _outputPageDir) {
- const routes = {};
- const newRoutes = [];
- components = [];
- pagesDir = _pagesDir;
- outputPageDir = _outputPageDir;
- genRoute(pagesDir, '/', routes);
- fixRouter2(routes, newRoutes);
+module.exports = function (pageDir) {
+ const components = [];
+ const routes = [];
+ build(components, routes, pageDir, '/');
+ fix(routes);
return {
- routes,
- newRoutes,
- components
+ components,
+ routes
};
};
diff --git a/packages/fes-cli/build/tasks/route.js b/packages/fes-cli/build/tasks/route.js
index 9543b7d9..121c20f6 100644
--- a/packages/fes-cli/build/tasks/route.js
+++ b/packages/fes-cli/build/tasks/route.js
@@ -14,22 +14,22 @@ function generateRoute(config) {
export default {{routes}};
`;
- const routes = getRoute(config.folders.PROJECT_PAGE_DIR, config.folders.PROJECT_PAGE_DIR);
+ const { components, routes } = getRoute(config.folders.PROJECT_PAGE_DIR);
const componentsTemplate = [];
let template = '';
if (config.lazyRouter) {
const componentsObj = {};
- routes.components.forEach((item) => {
+ components.forEach((item) => {
componentsObj[item.name] = item.path;
});
// component: () => import( /* webpackChunkName: "home" */ '../views/Home.vue')
template = render(MAIN_TEMPLATE, {
include: '',
- routes: JSON.stringify(routes.newRoutes).replace(/"component":"(.+?)"/g, ($0, $1) => `"component": () => import( /* webpackChunkName: "${$1}" */ '${componentsObj[$1]}')`)
+ routes: JSON.stringify(routes).replace(/"component":"(.+?)"/g, ($0, $1) => `"component": () => import( /* webpackChunkName: "${$1}" */ '${componentsObj[$1]}')`)
});
} else {
- routes.components.forEach((item) => {
+ components.forEach((item) => {
componentsTemplate.push(render(IMPORT_TEMPLATE, {
name: item.name,
path: item.path
@@ -38,7 +38,7 @@ export default {{routes}};
template = render(MAIN_TEMPLATE, {
include: componentsTemplate.join(endOfLine),
- routes: JSON.stringify(routes.newRoutes).replace(/"component":"(.+?)"/g, '"component": $1')
+ routes: JSON.stringify(routes).replace(/"component":"(.+?)"/g, '"component": $1')
});
}
diff --git a/packages/fes-doc/docs/guide/directory-structure.md b/packages/fes-doc/docs/guide/directory-structure.md
index 4921da4f..14e04d5e 100644
--- a/packages/fes-doc/docs/guide/directory-structure.md
+++ b/packages/fes-doc/docs/guide/directory-structure.md
@@ -29,6 +29,8 @@ fes-project
│ ├── home
│ │ └── index.vue
│ └── list
+ │ ├── components
+ │ │ └── common.vue
│ ├── edit
│ │ └── index.vue
│ └── index.vue
diff --git a/packages/fes-doc/docs/guide/route.md b/packages/fes-doc/docs/guide/route.md
index 2b8de416..1165dcc0 100644
--- a/packages/fes-doc/docs/guide/route.md
+++ b/packages/fes-doc/docs/guide/route.md
@@ -6,6 +6,7 @@
```
pages
├── index.vue # 根路由页面 路径 index.html#/
+ ├── *.vue # 模糊匹配 路径 *
├── a.vue # 路径 /a
├── b # 文件夹b
│ ├── index.vue # 路径 /b
@@ -14,41 +15,19 @@
└── layout.vue # 根路由下所有page共用的外层
```
1. 如果目录下有`layout.vue`,则子目录对应的路径是当前目录对应路径的子路由。如果没有则子目录对应的路径和当前目录对应路径是平级的。
-2. 带参数的路径使用`@[filename].vue`的方式
+2. 带参数的路径使用`@[filename].vue`的方式,例如@id.vue
+3. 支持模糊匹配,例如`pages/*.vue`对应的路径是`*`,可以通过此路由实现404效果
+4. pages下的components文件夹下的.vue不被解析成路由,可以存放跟业务相关的公共组件。
-
-Fes编译代码之前会根据 pages 目录结构生成下面的配置代码:
+## 智能路由匹配
+根据精准匹配优先算法原则设计出路由排名算法,对匹配到的路由打分,选择分数最高的路由:
+- 路由的路径每个子项得到4分
+- 子项为静态细分(/list)再加3分
+- 子项为动态细分(/:orderId)再加2分
+- 根段加1分
+- 通配符匹配到的减去1分
-```javascript
-import layout from 'D:\\git\\fes-template\\src\\pages\\layout.vue';
-import index from 'D:\\git\\fes-template\\src\\pages\\index.vue';
-import a from 'D:\\git\\fes-template\\src\\pages\\a.vue';
-import b_index from 'D:\\git\\fes-template\\src\\pages\\b\\index.vue';
-import b__id from 'D:\\git\\fes-template\\src\\pages\\b\\@id.vue';
-import b_c from 'D:\\git\\fes-template\\src\\pages\\b\\b_c';
-export default {
- '/': {
- component: layout,
- subRoutes: {
- '/' : {
- name: 'index', component: index
- },
- '/a' : {
- name: 'a', component: a
- },
- '/b' : {
- name: 'b_index', component: b_index
- },
- '/b/:id' : {
- name: 'b__id', component: b__id
- },
- '/c' : {
- name: 'b_c', component: b_c
- }
- }
- }
-};
-```
+通过智能路由匹配可以解决类似`/list/create`和`/list/:id`到底匹配什么路由的问题。
## 跳转路由
API参考[Vue-router](https://router.vuejs.org/zh-cn/)。[路由实例](https://router.vuejs.org/zh-cn/api/router-instance.html)路由实例会挂载在FesApp对象上。
diff --git a/packages/fes-template/package-lock.json b/packages/fes-template/package-lock.json
index 39b138d9..b809ad16 100644
--- a/packages/fes-template/package-lock.json
+++ b/packages/fes-template/package-lock.json
@@ -380,6 +380,28 @@
"vue-eslint-parser": "^6.0.4"
}
},
+ "@webank/fes-core": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@webank/fes-core/-/fes-core-0.2.4.tgz",
+ "integrity": "sha512-sBpNzpp5EJrsEh2fx8wQGtYiZdRG0eSWe9cr+LTCtou119zFZ3Ed9Bsth0mgLAx//Z57c0/FyxMsSsK3q2Lrmg==",
+ "requires": {
+ "axios": "^0.16.2",
+ "lodash": "^4.17.15",
+ "vue": "^2.6.10",
+ "vue-i18n": "^8.4.0",
+ "vue-router": "^2.6.0",
+ "vue-template-compiler": "^2.6.10"
+ }
+ },
+ "@webank/fes-ui": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@webank/fes-ui/-/fes-ui-0.2.4.tgz",
+ "integrity": "sha512-ovemdhoY1w65Op7RYnMnGv2IZkj+LD1cMxzyjyb48LhZaQj2ZkO3y8ViLUHVxAUpapEQ2qlCrd5y1IFs9bwefg==",
+ "requires": {
+ "async-validator": "^1.8.2",
+ "xss": "^1.0.7"
+ }
+ },
"abs-svg-path": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
@@ -508,6 +530,20 @@
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
+ "async-validator": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.12.2.tgz",
+ "integrity": "sha512-57EETfCPFiB7M4QscvQzWSGNsmtkjjzZv318SK1CBlstk+hycV72ocjriMOOM48HjvmoAoJGpJNjC7Z76RlnZA=="
+ },
+ "axios": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz",
+ "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=",
+ "requires": {
+ "follow-redirects": "^1.2.3",
+ "is-buffer": "^1.1.5"
+ }
+ },
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -687,6 +723,11 @@
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
"dev": true
},
+ "cssfilter": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
+ "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4="
+ },
"d3-array": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
@@ -817,6 +858,11 @@
"lodash": "^4.17.15"
}
},
+ "de-indent": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+ "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
+ },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -1316,6 +1362,11 @@
"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
"dev": true
},
+ "follow-redirects": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
+ "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1394,6 +1445,11 @@
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
"dev": true
},
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+ },
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
@@ -1504,6 +1560,11 @@
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
"is-callable": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz",
@@ -2482,6 +2543,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "vue": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
+ "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
+ },
"vue-eslint-parser": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-6.0.5.tgz",
@@ -2496,6 +2562,25 @@
"lodash": "^4.17.11"
}
},
+ "vue-i18n": {
+ "version": "8.22.1",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.22.1.tgz",
+ "integrity": "sha512-JNgiEJ5a8YPfk5y2lKyfOAGLmkpAVfhaUi+T4wGpSppRYZ3XSyawSDDketY5KV2CsAiBLAGEIO6jO+0l2hQubg=="
+ },
+ "vue-router": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-2.8.1.tgz",
+ "integrity": "sha512-MC4jacHBhTPKtmcfzvaj2N7g6jgJ/Z/eIjZdt+yUaUOM1iKC0OUIlO/xCtz6OZFFTNUJs/1YNro2GN/lE+nOXA=="
+ },
+ "vue-template-compiler": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz",
+ "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==",
+ "requires": {
+ "de-indent": "^1.0.2",
+ "he": "^1.1.0"
+ }
+ },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -2530,6 +2615,15 @@
"requires": {
"mkdirp": "^0.5.1"
}
+ },
+ "xss": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.8.tgz",
+ "integrity": "sha512-3MgPdaXV8rfQ/pNn16Eio6VXYPTkqwa0vc7GkiymmY/DqR1SE/7VPAAVZz1GJsJFrllMYO3RHfEaiUGjab6TNw==",
+ "requires": {
+ "commander": "^2.20.3",
+ "cssfilter": "0.0.10"
+ }
}
}
}
diff --git a/packages/fes-template/src/assets/images/404.png b/packages/fes-template/src/assets/images/404.png
new file mode 100644
index 00000000..736d8dbe
Binary files /dev/null and b/packages/fes-template/src/assets/images/404.png differ
diff --git a/packages/fes-template/src/pages/*.vue b/packages/fes-template/src/pages/*.vue
new file mode 100644
index 00000000..77d79e6a
--- /dev/null
+++ b/packages/fes-template/src/pages/*.vue
@@ -0,0 +1,42 @@
+
+
+

+
404
+
对不起, 你访问的页面不存在!
+
返回首页
+
+
+
+
diff --git a/packages/fes-template/src/pages/form/components/card.vue b/packages/fes-template/src/pages/form/components/card.vue
new file mode 100644
index 00000000..12e5036b
--- /dev/null
+++ b/packages/fes-template/src/pages/form/components/card.vue
@@ -0,0 +1,8 @@
+
+ card
+
+