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 @@ + + + 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 @@ + +