This commit is contained in:
harrywan 2021-11-03 14:50:19 +08:00
commit bd4eba275f
88 changed files with 9982 additions and 723 deletions

View File

@ -2,9 +2,9 @@ name: Deploy Docs
on:
push:
branches:
- master
- vue3
paths:
- 'packages/fes-doc/**/**'
- 'docs/**/**'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
@ -14,7 +14,7 @@ jobs:
- name: Build and Deploy
uses: JamesIves/github-pages-deploy-action@master
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
BRANCH: gh-pages
FOLDER: packages/fes-doc/docs/.vuepress/dist
BUILD_SCRIPT: cd packages/fes-doc && npm install && npm run build && cd ../../
ACCESS_TOKEN: ${{ secrets.QLIN_GITEE_TOKEN }}
BRANCH: master
FOLDER: docs/.vuepress/dist
BUILD_SCRIPT: npm install && npm run docs:build

View File

@ -104,3 +104,16 @@ For bugs and feature requests, [please create an issue](https://github.com/WeBan
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request :D
## 社区活动
### Fesjs 社区有奖征文活动
为了 Fes.js 开源项目更好的运转,同时回馈开源社区,社区推出有奖征文活动!欢迎大家投递实践经验,给社区用户,更广泛的开发者提供借鉴。
经验输出也可以帮助到你系统沉淀自有项目,梳理工作思路,也能够帮助你的技术博客做宣传。优秀的实践案例将有机会邀请参与项目社区技术会议分享,赶快来参与吧。
请戳https://mp.weixin.qq.com/s/nV4NG_OUUrdgtft8g_IW4g

View File

@ -172,7 +172,8 @@ export default function ({ cgiMock, mockjs, utils }) {
- utils.file(path)从项目根目录根据path寻找文件返回文件流。
## 配置 Mock
详见配置 #mock
详见配置 [mock](../reference/config/#mock)。
## 关闭 Mock
可以通过配置关闭。

View File

@ -31,6 +31,7 @@ export default {
### top
<!-- ![top](/top.png) -->
<img :src="$withBase('top.png')" alt="top">
### mixin
<!-- ![mixin](/mixin.png) -->
<img :src="$withBase('mixin.png')" alt="mixin">
@ -69,6 +70,8 @@ export default {
title: "Fes.js",
// 底部文字
footer: 'Created by MumbelFe',
// 主题light
theme: 'dark'
// 是否开启 tabs
multiTabs: false,
// 布局类型
@ -98,6 +101,13 @@ export default {
- **详情**:页面底部的文字。
### theme
- **类型**`String`
- **默认值**`dark`
- **详情**:主题,可选有 `dark``light`
### navigation
- **类型**`String`

View File

@ -23,5 +23,19 @@ Fes.js 中约定 `src/global.css` 为全局样式,如果存在此文件,会
</style>
```
## CSS Modules
支持 `Vue` 的 [CSS Modules](https://vue-loader.vuejs.org/zh/guide/css-modules.html#%E7%94%A8%E6%B3%95) 用法,可以直接使用:
```vue
<style module>
.layout-content {
max-width: 1000px;
}
```
如果想直接引入CSS文件的话则CSS文件名需要包含`.module`,比如:
```js
import style from '@/styles/index.module.css'
console.log(style)
```
## CSS 预处理器
Fes.js 内置支持 `less`,不支持 `sass``stylus`,但如果有需求,可以通过 `chainWebpack` 配置或者 `fes-plugin` 插件的形式支持。

View File

@ -172,7 +172,7 @@ export default function ({ cgiMock, mockjs, utils }) {
- utils.file(path)从项目根目录根据path寻找文件返回文件流。
## 配置 Mock
详见配置 #mock
详见配置 [mock](../reference/config/#mock)
## 关闭 Mock
可以通过配置关闭。

View File

@ -144,6 +144,24 @@ export default {
}
```
## extraBabelPlugins
- 类型: `array`
- 默认值: `[]`
- 详情:
配置额外的 babel 插件。
- 示例:
```js
export default {
extraBabelPlugins: [
['import', { libraryName: 'ant-design-vue', libraryDirectory: 'es', style: 'css' }],
],
}
```
## extraPostCSSPlugins
- 类型: `array`
@ -278,6 +296,12 @@ export default {
配置 webpack 的 publicPath。当打包的时候webpack 会在静态文件路径前面添加 `publicPath` 的值,当你需要修改静态文件地址时,比如使用 CDN 部署,把 `publicPath` 的值设为 CDN 的值就可以。
## router
- 类型: `object`
- 默认值: `{ mode: 'hash' }`
- 详情: 配置路由,具体请查看指南中关于路由的介绍
## singular
- 类型: `boolean`
- 默认值: `false`

View File

@ -14,27 +14,6 @@
- 可配置页面是否需要 layout。
## 布局类型
配置参数是 `navigation`, 内容默认是 `side`
```js
export default {
layout: {
navigation: 'side
}
}
```
### side
<!-- ![side](/side.png) -->
<img :src="$withBase('side.png')" alt="side">
### top
<!-- ![top](/top.png) -->
<img :src="$withBase('top.png')" alt="top">
### mixin
<!-- ![mixin](/mixin.png) -->
<img :src="$withBase('mixin.png')" alt="mixin">
## 启用方式
`package.json` 中引入依赖:
```json
@ -46,17 +25,53 @@ export default {
}
```
## 布局类型
配置参数是 `navigation`, 布局有三种类型 `side``mixin``top` 默认是 `side`
```js
export default {
layout: {
navigation: 'side'
}
}
```
### side
<!-- ![side](/side.png) -->
<img :src="$withBase('side.png')" alt="side">
### top
<!-- ![top](/top.png) -->
<img :src="$withBase('top.png')" alt="top">
### mixin
<!-- ![mixin](/mixin.png) -->
<img :src="$withBase('mixin.png')" alt="mixin">
### 页面禁用布局
Fes.js 渲染路由时,如果路由元信息存在配置 `layout``false`,则表示禁用此配置,用户只需要如下配置:
布局是默认开启的,但是可能某些页面不需要展示布局样式,比如登录页面。我们只需要在页面的`.vue`中添加如下配置:
```vue
<config>
<config lang="json">
{
"layout": false
}
</config>
<script>
</script>
```
如果只是不想展示`side`,则:
<config lang="json">
{
"layout": {
"side": false
}
}
</config>
```
`layout`的可选配置有:
- **side** 左侧区域
- **top** 头部区域
- **logo**logo和标题区域。
## 配置
@ -69,6 +84,8 @@ export default {
title: "Fes.js",
// 底部文字
footer: 'Created by MumbelFe',
// 主题light
theme: 'dark'
// 是否开启 tabs
multiTabs: false,
// 布局类型
@ -98,6 +115,13 @@ export default {
- **详情**:页面底部的文字。
### theme
- **类型**`String`
- **默认值**`dark`
- **详情**:主题,可选有 `dark``light`
### navigation
- **类型**`String`
@ -164,13 +188,13 @@ export default {
- 图标使用[antv icon](https://www.antdv.com/components/icon-cn/)在这里使用组件type。
```js
{
name: "user"
icon: "user"
}
```
- 图使用本地或者远程svg图片。
- 图使用本地或者远程svg图片。
```js
{
name: "/wine-outline.svg"
icon: "/wine-outline.svg"
}
```
@ -191,7 +215,7 @@ export const layout = {
- **默认值**`null`
- **详情**布局的 Header 部位提供组件自定义功能。
- **详情**top的区域部分位置提供组件自定义功能。
#### unAccessHandler
- **类型**`Function`

View File

@ -45,7 +45,7 @@ export default {
};
```
```js
// src/locales/zh-CN.js
// src/locales/en-US.js
export default {
menu: {
interface: 'interface'

View File

@ -19,8 +19,7 @@
```js
export default {
request: {
dataField: 'result',
base: '',
dataField: 'result'
},
}
```
@ -34,13 +33,15 @@ export default {
`dataField` 对应接口统一格式中的数据字段,比如接口如果统一的规范是 `{ success: boolean, result: any}` ,那么就不需要配置,这样你通过 `useRequest` 消费的时候会生成一个默认的 `formatResult`,直接返回 `result` 中的数据,方便使用。如果你的后端接口不符合这个规范,可以自行配置 `dataField`。配置为 `''`(空字符串)的时候不做处理。
#### base
#### base(即将废弃)
- 类型: `string`
- 默认值: `''`
- 详情:
`base` 接口前缀。
⚠️警告,这个字段将在下个版本废弃,推荐使用 [axios baseURL](https://github.com/axios/axios)。
### 运行时配置
`app.js` 中进行运行时配置。
@ -51,6 +52,8 @@ export const request = {
responseDataAdaptor: (data) => {
},
// 关闭 response data 校验(只判断 xhr status
closeResDataCheck: false,
// 请求拦截器
requestInterceptors: [],
// 相应拦截器
@ -63,7 +66,6 @@ export const request = {
// 特殊 code 处理逻辑
},
404(error) {
},
default(error) {
// 异常统一处理
@ -73,6 +75,32 @@ export const request = {
...otherConfigs
}
```
#### skipErrorHandler
- 类型: `boolean | string | number | array<string | number>`
- 默认值: ``
- 详情:
指定当前请求的某些错误状态不走 `errorHandler`,单独进行处理。如果设置为 `true`,当前请求的错误处理都不走 `errorHandler`
- 示列:
```js
import {request} from '@fesjs/fes';
request('/api/login', null, {
skipErrorHandler: '110'
}).then((res) => {
// do something
}).catch((err) => {
// 这里处理 code 为 110 的异常
// 此时 errorHandler[110] 函数不会生效,也不会执行 errorHandler.default
})
```
## 使用
### 发起一个普通 post 请求
@ -90,6 +118,27 @@ request('/api/login', {
})
```
### merge 重复请求
连续发送多个请求,会被合并成一个请求,不会报 `REPEAT` 接口错误。
当发生 `REPEAT` 请求异常,并且确保自身代码合理的情况下,可以使用该配置。
```js
import {request} from '@fesjs/fes';
request('/api/login', {
username: 'robby',
password: '123456'
}, {
mergeRequest: true, // 在一个请求没有回来前,重复发送的请求会合并成一个请求
}).then((res) => {
// do something
}).catch((err) => {
// 处理异常
})
```
### 请求节流
```js
@ -118,7 +167,7 @@ request('/api/login', {
}, {
cache: {
cacheType: 'ram', // ram: 内存session: sessionStoragelocallocalStorage
cacheTime: 1000 * 60 * 3 // 缓存时间默认3min
cacheTime: 1000 * 60 * 3 // 缓存时间默认3min
},
}).then((res) => {
// do something
@ -129,6 +178,7 @@ request('/api/login', {
`cache``true`,则默认使用 `ram` 缓存类型,缓存时间 3min。
### 结合 use 使用
```js

View File

@ -1,4 +1,4 @@
# @fesjs/plugin-access
# @fesjs/plugin-sass

View File

@ -1,5 +1,5 @@
{
"version": "2.0.0-rc.18",
"version": "independent",
"changelog": {
"repo": "WeBankFinTech/fes.js",
"cacheDir": ".changelog",

View File

@ -11,7 +11,7 @@
"clean": "lerna clean",
"bootstrap": "lerna bootstrap",
"build": "father-build --watch",
"ver": "lerna version prerelease --preid rc --no-changelog --no-commit-hooks --no-private",
"ver": "lerna version patch --no-changelog --no-commit-hooks --no-private",
"release": "father-build && lerna publish from-git",
"docs:dev": "vuepress dev docs --clean-cache",
"docs:build": "vuepress build docs --clean-cache"

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/create-fes-app",
"version": "2.0.0-rc.18",
"version": "2.0.1",
"description": "create a app base on fes.js",
"main": "lib/index.js",
"files": [

View File

@ -1,5 +1,5 @@
// fes.config.js 只负责管理 cli 相关的配置
import pxtoviewport from 'postcss-px-to-viewport';
import pxtoviewport from '@ttou/postcss-px-to-viewport';
export default {

View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "none"
}

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/template-h5",
"version": "2.0.0-rc.0",
"version": "2.0.0",
"description": "fes 移动端项目模版",
"scripts": {
"build": "fes build",
@ -40,13 +40,13 @@
"access": "public"
},
"devDependencies": {
"@webank/eslint-config-webank": "0.2.10",
"postcss-px-to-viewport": "1.1.1"
"@webank/eslint-config-webank": "0.3.0",
"@ttou/postcss-px-to-viewport": "1.1.1"
},
"dependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/plugin-icon": "^2.0.0-rc.0",
"@fesjs/plugin-request": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"@fesjs/plugin-icon": "^2.0.0",
"@fesjs/plugin-request": "^2.0.0",
"vue": "^3.0.5"
},
"private": true

View File

@ -2,9 +2,6 @@
export default {
define: {
__DEV__: false
},
publicPath: './',
access: {
roles: {
@ -17,13 +14,8 @@ export default {
multiTabs: false,
menus: [{
name: 'index'
}, {
name: 'onepiece'
}]
},
locale: {
legacy: true
},
devServer: {
port: 8000
},

View File

@ -1,4 +1,5 @@
.DS_Store
.cache
# dependencies
/node_modules

View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "none"
}

View File

@ -1,5 +0,0 @@
import sum from '@/utils/sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/template",
"version": "2.0.0-rc.0",
"version": "2.0.0",
"description": "fes项目模版",
"scripts": {
"build": "fes build",
@ -43,21 +43,16 @@
"access": "public"
},
"devDependencies": {
"@webank/eslint-config-webank": "0.2.10"
"@webank/eslint-config-webank": "0.3.0"
},
"dependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/plugin-access": "^2.0.0-rc.0",
"@fesjs/plugin-layout": "^2.0.0-rc.0",
"@fesjs/plugin-locale": "^2.0.0-rc.0",
"@fesjs/plugin-model": "^2.0.0-rc.0",
"@fesjs/plugin-enums": "^2.0.0-rc.0",
"@fesjs/plugin-jest": "^2.0.0-rc.0",
"@fesjs/plugin-vuex": "^2.0.0-rc.0",
"ant-design-vue": "^2.0.0",
"vue": "^3.0.5",
"vuex": "^4.0.0"
"@fesjs/fes": "^2.0.0",
"@fesjs/plugin-access": "^2.0.0",
"@fesjs/plugin-layout": "^2.0.0",
"@fesjs/plugin-model": "^2.0.0",
"@fesjs/plugin-enums": "^2.0.0",
"ant-design-vue": "^2.2.0",
"vue": "^3.1.0"
},
"private": true
}

View File

@ -11,6 +11,7 @@ export const beforeRender = {
return new Promise((resolve) => {
setTimeout(() => {
setRole('admin');
// 初始化应用的全局状态,可以通过 useModel('@@initialState') 获取,具体用法看@/components/UserCenter 文件
resolve({
userName: 'harrywan'
});

View File

@ -0,0 +1 @@
// 放工具函数

View File

@ -1,5 +1,5 @@
<template>
<div>{{initialState.userName}}</div>
<div class="right">{{initialState.userName}}</div>
</template>
<script>
import { useModel } from '@fesjs/fes';
@ -13,3 +13,9 @@ export default {
}
};
</script>
<style scope>
.right {
text-align: right;
padding: 0 20px;
}
</style>

View File

@ -1,11 +0,0 @@
export default {
test: 'test',
'navBar.lang': 'Languages',
'layout.user.link.help': 'Help',
'layout.user.link.privacy': 'Privacy',
'layout.user.link.terms': 'Terms',
'app.preview.down.block': 'Download this page to your local project',
'app.welcome.link.fetch-blocks': 'Get all block',
'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development'
};

View File

@ -1,11 +0,0 @@
export default {
'navbar.lang': 'Bahasa',
'layout.user.link.help': 'Bantuan',
'layout.user.link.privacy': 'Privasi',
'layout.user.link.terms': 'Ketentuan',
'app.preview.down.block': 'Unduh halaman ini dalam projek lokal anda',
'app.welcome.link.fetch-blocks': 'Dapatkan semua blok',
'app.welcome.link.block-list':
'Buat standar dengan cepat, halaman-halaman berdasarkan pengembangan `block`'
};

View File

@ -1,8 +0,0 @@
export default {
'navBar.lang': 'Idiomas',
'layout.user.link.help': 'ajuda',
'layout.user.link.privacy': 'política de privacidade',
'layout.user.link.terms': 'termos de serviços',
'app.preview.down.block': 'Download this page to your local project'
};

View File

@ -1,11 +0,0 @@
export default {
test: '测试',
'navBar.lang': '语言',
'layout.user.link.help': '帮助',
'layout.user.link.privacy': '隐私',
'layout.user.link.terms': '条款',
'app.preview.down.block': '下载此页面到本地项目',
'app.welcome.link.fetch-blocks': '获取全部区块',
'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面'
};

View File

@ -1,8 +0,0 @@
export default {
'navBar.lang': '語言',
'layout.user.link.help': '幫助',
'layout.user.link.privacy': '隱私',
'layout.user.link.terms': '條款',
'app.preview.down.block': '下載此頁面到本地項目'
};

View File

@ -1,100 +1,43 @@
<template>
<div class="haizekuo">
<div>国际化 {{t("test")}}</div>
fes & 拉夫德鲁 <br />
<access :id="accessId"> accessOnepicess1 <input /> </access>
<div v-access="accessId"> accessOnepicess2 <input /> </div>
<input />
<div style="padding: 32px;">
<h3>fes & 拉夫德鲁 </h3>
<h4>数据字典</h4>
<div v-for="item in enumsGet('status')" :key="item.key">{{item.value}}{{item.key}}</div>
<div v-for="item in roles" :key="item.key">{{item.name}}{{item.disabled}}</div>
<div>{{enumsGet('roles', '2', { dir: 'eName' })}}</div>
<h4>Vuex <button @click="increment">click me{{count}}</button></h4>
<section>
计数器
<button @click="increment">click me{{count}}</button>
</section>
</div>
</template>
<script>
import { ref } from 'vue';
import {
enums
} from '@fesjs/fes';
export default {
setup() {
const fes = ref('fes upgrade to vue3');
const count = ref(0);
const increment = () => {
count.value++;
};
return {
fes,
increment,
count,
enumsGet: enums.get
};
}
};
</script>
<config>
{
"name": "index",
"title": "首页"
}
</config>
<script>
import { ref, onMounted, computed } from 'vue';
import { useStore } from 'vuex';
import {
useAccess, useRouter, useI18n, locale, enums
} from '@fesjs/fes';
export default {
setup() {
const fes = ref('fes upgrade to vue3');
const accessOnepicess = useAccess('/onepiece1');
const localI18n = useI18n();
const router = useRouter();
const accessId = ref('/onepiece1');
enums.push('roles', [
{
id: '1',
cName: '系统管理员',
eName: 'System',
perm: ['1', '2', '3']
},
{
id: '2',
cName: '业务管理员',
eName: 'Business',
perm: ['1', '2']
},
{
id: '3',
cName: '普通用户',
eName: 'User',
perm: ['1']
}
], { keyName: 'id' });
const roles = enums.get('roles', {
extend: [
{
key: 'name',
dir: 'cName'
},
{
key: 'disabled',
transfer: item => item.value.perm.some(i => i >= 2)
}
]
});
console.log(roles);
const store = useStore();
console.log('store==>', store);
onMounted(() => {
console.log(router);
setTimeout(() => {
locale.setLocale({ lang: 'en-US' });
locale.addLocale({ lang: 'ja-JP', messages: { test: 'テスト' } });
console.log(locale.getAllLocales());
}, 2000);
setTimeout(() => {
accessId.value = '11';
}, 4000);
// router.push('/onepiece');
});
return {
accessId,
fes,
accessOnepicess,
t: localI18n.t,
enumsGet: enums.get,
roles,
count: computed(() => store.state.counter.count),
increment: () => store.commit('counter/increment')
};
}
};
</script>
<style scoped>
.haizekuo {
/* background: url('../images/icon.png'); */
}
</style>

View File

@ -1,21 +0,0 @@
<template>
<div>{{fes}}</div>
</template>
<config>
{
"name": "onepiece",
"title": "onepiece"
}
</config>
<script>
import { ref } from 'vue';
export default {
setup() {
const fes = ref('fes upgrade to vue3');
return {
fes
};
}
};
</script>

View File

@ -1,10 +0,0 @@
<template>
<div>test</div>
</template>
<script>
import { } from '@fesjs/fes';
export default {
};
</script>

View File

@ -1,23 +0,0 @@
export default {
namespaced: true,
state: () => ({
count: 0
}),
mutations: {
increment(state) {
state.count++;
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => {
commit('increment');
}, 2000);
}
}
};

View File

@ -1,3 +0,0 @@
import { createLogger } from 'vuex';
export default createLogger();

View File

@ -1,25 +0,0 @@
export default {
namespaced: true,
state: () => ({
name: 'aring',
age: 20,
count: 0
}),
mutations: {
increment(state) {
state.count++;
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => {
commit('increment');
}, 2000);
}
}
};

View File

@ -1,3 +0,0 @@
export default function sum(a, b) {
return a + b;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/compiler",
"version": "2.0.0-rc.5",
"version": "2.0.0",
"description": "@fesjs/compiler",
"main": "lib/index.js",
"files": [

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-access",
"version": "2.0.0-rc.1",
"version": "2.0.0",
"description": "@fesjs/plugin-access",
"main": "lib/index.js",
"files": [
@ -26,8 +26,11 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"lodash": "^4.17.15"
},
"peerDependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"vue": "^3.0.5"
}
}

View File

@ -1,6 +1,7 @@
import { reactive, unref, computed, inject } from "vue";
import createDirective from "./createDirective";
import createComponent from "./createComponent";
import isPlainObject from "lodash/isPlainObject";
const accessKey = Symbol("plugin-access");
@ -50,16 +51,21 @@ const setAccess = (accessIds) => {
if (isPromise(accessIds)) {
return _syncSetAccessIds(accessIds);
}
if(isPlainObject(accessIds)){
if(accessIds.accessIds){
setAccess(accessIds.accessIds);
}
if(accessIds.roleId){
setRole(accessIds.roleId);
}
return
}
if (!Array.isArray(accessIds)) {
throw new Error("[plugin-access]: argument to the setAccess() must be array or promise");
throw new Error("[plugin-access]: argument to the setAccess() must be array or promise or object");
}
state.currentAccessIds = accessIds;
};
const getAccess = () => {
return state.currentAccessIds.slice(0)
}
const _syncSetRoleId = (promise) => {
rolePromiseList.push(promise);
promise
@ -143,7 +149,7 @@ export const access = {
isDataReady,
setRole,
setAccess,
getAccess,
getAccess: getAllowAccessIds,
};
export const useAccess = (path) => {

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-enums",
"version": "2.0.0-rc.1",
"version": "2.0.0",
"description": "@fesjs/plugin-enums",
"main": "lib/index.js",
"files": [
@ -27,7 +27,7 @@
"access": "public"
},
"peerDependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"vue": "^3.0.5"
}
}

View File

@ -22,11 +22,11 @@ Object.keys(_ENUMS).forEach(key => {
function get(name, key, opt = { dir: 'value', extend: []}) {
if (Object.prototype.toString.call(key) === '[object Object]') {
opt = key
key = ''
key = null
}
let list = ENUMS[name] || []
let value
if (key) {
if (key !== undefined && key !== null) {
let res = list.filter(item => item.key === key)[0]
if (!res) return key
value = parseValueDir(res.value, opt.dir) || key
@ -81,7 +81,7 @@ function concat(name, _enum, opt = { keyName: '', valueName: '', before: false,
} else {
list = list.concat(partList)
}
return readonly(format(list, extend))
return readonly(format(list, opt.extend))
}
/**
@ -111,7 +111,7 @@ function format(_enum = [], extend = []) {
* @param dir
*/
function parseValueDir(value, dir='value') {
if (!['object', 'function'].includes(typeof value) || !value || !dir) return value
if (!['object', 'function'].includes(typeof value) || !value || !dir || dir === 'value') return value
if (dir.startsWith('[')) {
let key = dir.slice(1, dir.indexOf(']'))
return parseValueDir(value[key], dir.slice(dir.indexOf(']') + 1))

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-icon",
"version": "2.0.0-rc.1",
"version": "2.0.0",
"description": "@fesjs/plugin-icon",
"main": "lib/index.js",
"files": [

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-jest",
"version": "2.0.0-rc.5",
"version": "2.0.0",
"description": "@fesjs/plugin-jest",
"main": "lib/index.js",
"files": [
@ -31,7 +31,7 @@
},
"dependencies": {
"@babel/core": "7.11.6",
"@fesjs/compiler": "^2.0.0-rc.5",
"@fesjs/compiler": "^2.0.0",
"@umijs/babel-preset-umi": "3.2.24",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^26.6.3",

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-layout",
"version": "2.0.0-rc.18",
"version": "2.0.0",
"description": "@fesjs/plugin-layout",
"main": "lib/index.js",
"files": [
@ -31,7 +31,7 @@
},
"peerDependencies": {
"@ant-design/icons-vue": "^5.1.6",
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"ant-design-vue": "2.0.0",
"vue": "^3.0.5"
}

View File

@ -6,7 +6,7 @@ const matchName = (config, name) => {
for (let i = 0; i < config.length; i++) {
const item = config[i];
if (item.meta && item.meta.name === name) {
res = item.meta || {};
res = item.meta;
res.path = item.path;
break;
}
@ -68,10 +68,10 @@ export function getIconsFromMenu(data) {
return [];
}
let icons = [];
(data || []).forEach((item = { path: '/' }) => {
data.forEach((item = { path: '/' }) => {
if (item.icon) {
const { icon } = item;
if (icon && icon.type === 'icon') {
if (icon.type === 'icon') {
icons.push(icon.name);
}
}

View File

@ -1,13 +1,14 @@
<template>
<a-layout
v-if="routeHasLayout"
v-if="routeLayout"
:class="[
collapsed ? 'main-layout-collapsed' : '',
`main-layout-navigation-${navigation}`
`main-layout-navigation-${navigation}`,
`main-layout-theme-${siderTheme}`
]"
class="main-layout"
>
<template v-if="navigation !== 'top'">
<template v-if="navigation !== 'top' && routeLayout.side">
<div v-if="fixedSideBar" :style="siderFixedStuffStyle" class="layout-sider-fixed-stuff"></div>
<a-layout-sider
v-model:collapsed="collapsed"
@ -19,7 +20,7 @@
:theme="siderTheme"
collapsible
>
<div v-if="navigation !== 'mixin'" class="layout-logo">
<div v-if="navigation !== 'mixin' && routeLayout.logo" class="layout-logo">
<img :src="logo" class="logo-img" />
<h1 class="logo-name">{{title}}</h1>
</div>
@ -27,19 +28,20 @@
</a-layout-sider>
</template>
<a-layout class="child-layout">
<a-layout-header v-if="currentFixedHeader" class="layout-header">
<a-layout-header v-if="currentFixedHeader && routeLayout.top" class="layout-header">
</a-layout-header>
<a-layout-header
v-if="routeLayout.top"
:style="headerFixedStyle"
:class="[currentFixedHeader ? 'layout-header-fixed' : '']"
class="layout-header"
>
<div v-if="navigation === 'mixin'" class="layout-logo">
<div v-if="navigation === 'mixin' && routeLayout.logo" class="layout-logo">
<img :src="logo" class="logo-img" />
<h1 class="logo-name">{{title}}</h1>
</div>
<template v-if="navigation === 'top'">
<div class="layout-logo">
<div v-if="routeLayout.logo" class="layout-logo">
<img :src="logo" class="logo-img" />
<h1 class="logo-name">{{title}}</h1>
</div>
@ -134,9 +136,32 @@ export default {
setup(props) {
const collapsed = ref(false);
const route = useRoute();
const routeHasLayout = computed(() => {
const _routeLayout = route.meta.layout;
return _routeLayout === undefined ? true : _routeLayout;
const routeLayoutDefault = {
side: true,
top: true,
logo: true
};
const routeLayout = computed(() => {
let config;
// meta layout true
const metaLayoutConfig = route.meta.layout === undefined ? true : route.meta.layout;
if (typeof metaLayoutConfig === 'boolean') {
config = metaLayoutConfig ? routeLayoutDefault : false;
} else if (typeof metaLayoutConfig === 'object') {
config = { ...routeLayoutDefault, ...metaLayoutConfig };
} else {
console.error('[plugin-layout]: meta layout must be object or boolean');
}
// query layout false
const routeQueryLayoutConfig = route.query.layout && JSON.parse(route.query.layout);
if (typeof routeQueryLayoutConfig === 'boolean') {
config = routeQueryLayoutConfig ? routeLayoutDefault : false;
} else if (typeof routeQueryLayoutConfig === 'object') {
config = { ...config, ...routeQueryLayoutConfig };
} else if (routeQueryLayoutConfig !== undefined) {
console.error('[plugin-layout]: query layout must be object or boolean');
}
return config;
});
const siderTheme = computed(() => {
if (props.navigation === 'mixin') {
@ -174,7 +199,7 @@ export default {
siderTheme,
currentFixedHeader,
route,
routeHasLayout,
routeLayout,
collapsed,
siderFixedStuffStyle,
headerFixedStyle
@ -334,5 +359,21 @@ export default {
.layout-footer {
text-align: center;
}
&.main-layout-theme-light{
.logo-name{
color: rgba(0, 0, 0, 0.65) !important;
}
&.main-layout-navigation-mixin{
.logo-name{
color: #fff !important;
}
}
&.main-layout-navigation-top{
.layout-header {
background: #fff;
color: rgba(0, 0, 0, 0.85);
}
}
}
}
</style>

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-locale",
"version": "2.0.0-rc.8",
"version": "2.0.0",
"description": "@fesjs/plugin-locale",
"main": "lib/index.js",
"files": [
@ -32,7 +32,7 @@
},
"peerDependencies": {
"@ant-design/icons-vue": "^5.1.6",
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"ant-design-vue": "2.0.0",
"vue": "^3.0.5"
}

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-model",
"version": "2.0.0-rc.8",
"version": "2.0.0",
"description": "@fesjs/plugin-model",
"main": "lib/index.js",
"files": [
@ -30,7 +30,7 @@
"@umijs/utils": "3.3.3"
},
"peerDependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"vue": "^3.0.5"
}
}

View File

@ -43,10 +43,10 @@
"access": "public"
},
"devDependencies": {
"@webank/eslint-config-webank": "0.2.10"
"@webank/eslint-config-webank": "0.3.0"
},
"dependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"vue": "^3.0.5",
"ant-design-vue": "2.0.0"
},

View File

@ -10,3 +10,22 @@ export const beforeRender = {
});
}
};
export const qiankun = {
// 应用加载之前
async bootstrap(props) {
console.log('app1 bootstrap', props);
},
// 应用 render 之前触发
async mount(props) {
console.log('app1 mount', props);
},
// 当 props 更新时触发
async update(props) {
console.log('app1 update', props);
},
// 应用卸载之后触发
async unmount(props) {
console.log('app1 unmount', props);
}
};

View File

@ -5,8 +5,8 @@
</template>
<config>
{
"name": "index",
"title": "home"
"name": "test",
"title": "test"
}
</config>
<script>

View File

@ -43,10 +43,10 @@
"access": "public"
},
"devDependencies": {
"@webank/eslint-config-webank": "0.2.10"
"@webank/eslint-config-webank": "0.3.0"
},
"dependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"vue": "^3.0.5",
"ant-design-vue": "2.0.0"
},

View File

@ -1,5 +1,7 @@
import { access as accessApi } from '@fesjs/fes';
import PageLoading from '@/components/PageLoading';
import Antdv from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
export const beforeRender = {
loading: <PageLoading />,
@ -13,3 +15,7 @@ export const beforeRender = {
});
}
};
export const onAppCreated = ({ app }) => {
app.use(Antdv);
};

View File

@ -1,8 +1,12 @@
<template>
<div>
main
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="Tab 1"><MicroAppWithMemoHistory key="1" name="app1" url="/app1" /></a-tab-pane>
<a-tab-pane key="2" tab="Tab 2"><MicroAppWithMemoHistory key="2" name="app1" url="/app1/test" /></a-tab-pane>
<a-tab-pane key="3" tab="Tab 3">Content of Tab Pane 3</a-tab-pane>
</a-tabs>
</div>
<MicroAppWithMemoHistory name="app1" :url="url" />
</template>
<config>
{
@ -24,6 +28,7 @@ export default {
}, 3000);
});
return {
activeKey: ref('1'),
url
};
}

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-qiankun",
"version": "2.0.0-rc.17",
"version": "2.0.0",
"description": "@fesjs/plugin-qiankun",
"main": "lib/index.js",
"files": [
@ -38,7 +38,7 @@
"npm-run-all": "^4.1.5"
},
"peerDependencies": {
"@webank/fes": "^2.0.0-rc.0",
"@webank/fes": "^2.0.0",
"vue": "^3.0.5"
}
}

View File

@ -45,7 +45,7 @@ function modifyRoutesWithAttachMode({
export default function modifyRoutes({ api, namespace }) {
api.modifyRoutes((routes) => {
const { router, base } = api.config;
const masterHistoryType = (router && router?.mode) || defaultHistoryType;
const masterHistoryType = (router && router.mode) || defaultHistoryType;
modifyRoutesWithAttachMode({
routes,

View File

@ -165,7 +165,7 @@ export default function (api) {
api.addEntryCode(
() => `
export const bootstrap = qiankun_genBootstrap(completeClientRender, app);
export const bootstrap = qiankun_genBootstrap(clientRender, app);
export const mount = qiankun_genMount('#${api.config.mountElementId}');
export const unmount = qiankun_genUnmount();
export const update = qiankun_genUpdate();

View File

@ -38,10 +38,12 @@ function getSlaveRuntime() {
// 子应用生命周期钩子Bootstrap
export function genBootstrap(oldRender, appPromise) {
return async (props) => {
if (typeof props !== 'undefined') {
const slaveRuntime = getSlaveRuntime();
if (slaveRuntime.bootstrap) {
await slaveRuntime.bootstrap(props);
}
}
render = oldRender;
if (isPromise(appPromise)) {
cacheAppPromise = appPromise;
@ -61,8 +63,6 @@ export function genMount(mountElementId) {
if (slaveRuntime.mount) {
await slaveRuntime.mount(props);
}
}
// 更新 clientRender 配置
const clientRenderOpts = {
// 支持通过 props 注入 container 来限定子应用 mountElementId 的查找范围
@ -79,6 +79,7 @@ export function genMount(mountElementId) {
if(props.onRouterInit){
history.onRouterInit = props.onRouterInit;
}
}
// 第一次 mount 会自动触发 render非第一次 mount 则需手动触发
if (hasMountedAtLeastOnce) {

View File

@ -1,4 +1,4 @@
import { createMemoryHistory } from '@@/core/coreExports';
import { createMemoryHistory, getHistory } from '@@/core/coreExports';
import qiankunRender, { clientRenderOptsStack, history } from './lifecycles';
@ -14,16 +14,18 @@ export function modifyClientRenderOpts(memo) {
};
}
export function modifyHistroy(memo) {
export function modifyCreateHistroy(memo) {
if (history.url) {
const memoHistroy = createMemoryHistory();
memoHistroy.push(history.url)
return memoHistroy
return createMemoryHistory
}
return memo;
}
export function onRouterCreated({ router }) {
if(history.url) {
const memoryHistory = getHistory();
memoryHistory.push(history.url)
}
if(history.onRouterInit){
history.onRouterInit(router)
}

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-request",
"version": "2.0.0-rc.16",
"version": "2.0.0",
"description": "@fesjs/plugin-request",
"main": "lib/index.js",
"files": [
@ -27,10 +27,11 @@
"access": "public"
},
"peerDependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"vue": "^3.0.5"
},
"dependencies": {
"@fesjs/compiler": "^2.0.0",
"axios": "0.21.1"
}
}

View File

@ -1,6 +1,9 @@
import { Logger } from '@fesjs/compiler';
import { readFileSync } from 'fs';
import { join } from 'path';
const logger = new Logger('fes:plugin-request');
export default (api) => {
api.addRuntimePluginKey(() => 'request');
// 配置
@ -31,6 +34,11 @@ export default (api) => {
api.onGenerateFiles(() => {
// 文件写出
const { dataField = '', base = '' } = api.config.request;
if (base) {
// DEPRECATED
logger.warn('[DEPRECATED]: reqeust base 即将废弃,建议使用 axios baseURL代替https://github.com/axios/axios');
}
api.writeTmpFile({
path: absoluteFilePath,
content: requestTemplate

View File

@ -46,7 +46,7 @@ const CACHE_TYPE = {
const CACHE_DATA_MAP = new Map();
function genInnerKey(key, cacheType) {
function genInnerKey(key, cacheType = 'ram') {
if (cacheType !== CACHE_TYPE.ram) {
return `${CACHE_KEY_PREFIX}${key}`;
}
@ -121,6 +121,57 @@ function getCacheData({ key, cacheType = 'ram' }) {
}
}
// 存储缓存队列
const cacheStartFlag = new Map();
const cachingQueue = new Map();
/**
* 等上一次请求结果
* 1. 如果上一次请求成功直接使用上一次的请求结果
* 2. 如果上一次请求失败重启本次请求
*/
function handleCachingStart(ctx, config) {
const _key = genInnerKey(ctx.key, config.cache.cacheType);
const caching = cacheStartFlag.get(_key);
if (caching) {
return new Promise((resolve) => {
const queue = cachingQueue.get(_key) || [];
cachingQueue.set(_key, queue.concat(resolve));
});
}
cacheStartFlag.set(_key, true);
}
// 有请求成功的
function handleCachingQueueSuccess(ctx, config) {
// 移除首次缓存 flag
const _key = genInnerKey(ctx.key, config.cache.cacheType);
const queue = cachingQueue.get(_key);
if (queue && queue.length > 0) {
queue.forEach((resolve) => {
resolve({
response: ctx.response
});
});
}
cachingQueue.delete(_key);
cacheStartFlag.delete(_key);
}
// 处理请求失败
function handleCachingQueueError(ctx, config) {
const _key = genInnerKey(ctx.key, config.cache.cacheType);
const queue = cachingQueue.get(_key);
if (queue && queue.length > 0) {
const firstResolve = queue.shift();
firstResolve();
cachingQueue.set(_key, queue);
} else {
cachingQueue.delete(_key);
cacheStartFlag.delete(_key);
}
}
export default async (ctx, next) => {
const { config } = ctx;
if (config.cache) {
@ -131,17 +182,28 @@ export default async (ctx, next) => {
};
return;
}
const result = await handleCachingStart(ctx, config);
if (result) {
Object.keys(result).forEach((key) => {
ctx[key] = result[key];
});
return;
}
}
await next();
if (config.cache) {
const requestdata = checkHttpRequestHasBody(config.method) ? config.data : config.params;
if (ctx.response && canCache(requestdata) && canCache(ctx.response.data)) {
if (!ctx.error && ctx.response && canCache(requestdata) && canCache(ctx.response.data)) {
handleCachingQueueSuccess(ctx, config);
setCacheData({
key: ctx.key,
data: ctx.response.data,
...config.cache
});
} else {
handleCachingQueueError(ctx, config);
}
}
};

View File

@ -1,17 +1,63 @@
const requestMap = new Map();
const mergeRequestMap = new Map();
const requestQueue = new Map();
function handleCachingStart(ctx) {
const isRequesting = mergeRequestMap.get(ctx.key);
if (isRequesting) {
return new Promise((resolve) => {
const queue = requestQueue.get(ctx.key) || [];
requestQueue.set(ctx.key, queue.concat(resolve));
});
}
mergeRequestMap.set(ctx.key, true);
}
function handleRepeatRequest(ctx) {
const queue = requestQueue.get(ctx.key);
if (queue && queue.length > 0) {
queue.forEach((resolve) => {
if (ctx.error) {
resolve({
error: ctx.error
});
} else {
resolve({
response: ctx.response
});
}
});
}
requestQueue.delete(ctx.key);
mergeRequestMap.delete(ctx.key);
}
export default async (ctx, next) => {
const key = ctx.key;
if (requestMap.get(key)) {
if (ctx.config.mergeRequest) {
const result = await handleCachingStart(ctx);
if (result) {
Object.keys(result).forEach((key) => {
ctx[key] = result[key];
});
return;
}
} else {
if (requestMap.get(ctx.key) && !ctx.config.mergeRequest) {
ctx.error = {
type: 'REPEAT',
msg: '重复请求'
};
return;
}
requestMap.set(key, true);
requestMap.set(ctx.key, true);
}
await next();
requestMap.delete(key);
if (ctx.config.mergeRequest) {
handleRepeatRequest(ctx);
} else {
requestMap.delete(ctx.key);
}
};

View File

@ -65,11 +65,12 @@ function getRequestInstance() {
addRequestInterceptors(instance, requestInterceptors);
addResponseInterceptors(instance, responseInterceptors);
// 洋葱模型内部应该这是对数据的处理,避免有副作用调用
scheduler.use(paramsProcess)
.use(genRequestKey)
.use(cacheControl)
.use(preventRepeatReq)
.use(throttle)
.use(cacheControl)
.use(axiosMiddleware)
.use(resDataAdaptor)
.use(resErrorProcess)
@ -87,7 +88,7 @@ function getRequestInstance() {
};
}
// FEATURE 后续优化,使用 axios baseURL
// DEPRECATED 废弃,使用 axios baseURL
function handleApiPathBase(url, options = {}) {
if (url.startsWith('http')) return url;
@ -120,6 +121,52 @@ function createContext(userConfig) {
};
}
function getResponseCode(response) {
if (response) {
if (response._rawData) return response._rawData.code;
if (response.data) return response.data.code;
}
return null;
}
function skipErrorHandlerToObj(skipErrorHandler = []) {
if (!Array.isArray(skipErrorHandler)) {
skipErrorHandler = [skipErrorHandler];
}
return skipErrorHandler.reduce((acc, cur) => {
acc[cur] = true;
return acc;
}, {});
}
function handleRequestError({
errorHandler = {},
error,
response,
config
}) {
// 跳过所有错误类型处理
if (config.skipErrorHandler === true) return;
const skipObj = skipErrorHandlerToObj(config.skipErrorHandler);
const resCode = getResponseCode(response);
let errorKey = 'default';
if (resCode && errorHandler[resCode]) {
errorKey = resCode;
} else if (error.type && errorHandler[error.type]) {
errorKey = error.type;
} else if (error.response && errorHandler[error.response.status]) {
errorKey = error.response.status;
}
if (!skipObj[errorKey] && errorHandler[errorKey]) {
return errorHandler[errorKey](error);
}
}
export const request = (url, data, options = {}) => {
if (typeof options === 'string') {
options = {
@ -132,10 +179,11 @@ export const request = (url, data, options = {}) => {
const userConfig = userConfigHandler(url, data, options);
const context = createContext(userConfig);
return currentRequestInstance.request(context).then(() => {
return currentRequestInstance.request(context).then(async () => {
if (!context.error) {
return context.config.useResonse ? context.response : context.response.data;
}
await handleRequestError(context);
return Promise.reject(context.error);
});
};

View File

@ -1,40 +1,16 @@
import { isObject } from './helpers';
function handleAbnormalCode(errorHandler = {}, code, response) {
if (errorHandler[code]) {
errorHandler[code](response.data);
} else if (errorHandler.default) {
// 处理其他异常
errorHandler.default({
response
});
}
}
function handleRequestError(errorHandler = {}, error) {
if (error.type) {
errorHandler[error.type] && errorHandler[error.type](error);
} else if (error.response) {
errorHandler[error.response.status] && errorHandler[error.response.status](error);
} else if (errorHandler.default) {
errorHandler.default(error);
}
}
// 错误处理等副作用网上提
export default async (ctx, next) => {
const {
error,
errorHandler = {},
response
response,
config
} = ctx;
if (response && isObject(response.data)) {
if (!config.closeResDataCheck && response && isObject(response.data)) {
const code = response.data.code;
if (code !== '0') {
handleAbnormalCode(errorHandler, code, response);
ctx.error = response; // code 不为零进入 reject
}
} else if (error) {
handleRequestError(errorHandler, error);
}
await next();

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-sass",
"version": "2.0.0-rc.18",
"version": "2.0.0",
"description": "@fesjs/plugin-sass",
"main": "lib/index.js",
"files": [
@ -31,6 +31,6 @@
"sass-loader": "^11.0.1"
},
"peerDependencies": {
"@fesjs/fes": "^2.0.0-rc.0"
"@fesjs/fes": "^2.0.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/plugin-vuex",
"version": "2.0.0-rc.16",
"version": "2.0.0",
"description": "@fesjs/plugin-vuex",
"main": "lib/index.js",
"files": [
@ -30,7 +30,7 @@
"@umijs/utils": "3.3.3"
},
"peerDependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"vue": "^3.0.5",
"vuex": "^4.0.0"
}

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/preset-built-in",
"version": "2.0.0-rc.18",
"version": "2.0.1",
"description": "@fesjs/preset-built-in",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@ -32,7 +32,7 @@
"@babel/plugin-proposal-pipeline-operator": "^7.12.13",
"@babel/plugin-transform-runtime": "^7.12.13",
"@babel/preset-env": "^7.12.13",
"@fesjs/compiler": "^2.0.0-rc.5",
"@fesjs/compiler": "^2.0.0",
"@soda/friendly-errors-webpack-plugin": "^1.8.0",
"@umijs/utils": "3.3.3",
"@vue/babel-plugin-jsx": "^1.0.2",
@ -48,7 +48,7 @@
"copy-webpack-plugin": "^7.0.0",
"core-js": "^3.8.3",
"css-loader": "^5.0.1",
"css-minimizer-webpack-plugin": "^1.2.0",
"css-minimizer-webpack-plugin": "^3.0.0",
"deepmerge": "^4.2.2",
"envinfo": "^7.7.3",
"file-loader": "^6.2.0",
@ -58,7 +58,7 @@
"less-loader": "^8.0.0",
"mini-css-extract-plugin": "^1.3.5",
"mockjs": "^1.1.0",
"postcss": "^8.2.4",
"postcss": "8.3.0",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-loader": "^4.2.0",
"postcss-safe-parser": "^5.0.2",

View File

@ -39,7 +39,7 @@ export default function (api) {
try {
// clear output path before exec build
if (process.env.CLEAR_OUTPUT !== 'none') {
if (paths.absOutputPath && existsSync(paths.absOutputPath || '')) {
if (paths.absOutputPath && existsSync(paths.absOutputPath)) {
logger.debug(`Clear OutputPath: ${paths.absNodeModulesPath}`);
rimraf.sync(paths.absOutputPath);
}

View File

@ -21,8 +21,7 @@ function createRules({
options,
browserslist
}) {
const rule = webpackConfig.module.rule(lang).test(test);
function applyLoaders(rule, isCSSModules) {
if (isDev) {
rule.use('extra-css-loader')
.loader(require.resolve('style-loader'))
@ -37,9 +36,22 @@ function createRules({
rule.use('css-loader')
.loader(require.resolve('css-loader'))
.options({
...config.cssLoader
});
.options(
deepmerge(
{
importLoaders: 1,
// https://webpack.js.org/loaders/css-loader/#onlylocals
...(isCSSModules
? {
modules: {
localIdentName: '[local]___[hash:base64:5]'
}
}
: {})
},
config.cssLoader || {}
)
);
rule.use('postcss-loader')
.loader(require.resolve('postcss-loader'))
@ -62,6 +74,11 @@ function createRules({
}
}
const rule = webpackConfig.module.rule(lang).test(test);
applyLoaders(rule.oneOf('css-modules').resourceQuery(/module/), true);
applyLoaders(rule.oneOf('css'), false);
}
export default function createCssWebpackConfig({
isDev,
config,
@ -102,9 +119,7 @@ export default function createCssWebpackConfig({
if (!isDev) {
webpackConfig.optimization
.minimizer('css')
.use(require.resolve('css-minimizer-webpack-plugin'), [{
sourceMap: config.devtool !== false
}]);
.use(require.resolve('css-minimizer-webpack-plugin'), [{}]);
}
return (options) => {

View File

@ -28,7 +28,7 @@ export default function (api) {
// 修改路由
'patchRoutes',
// 修改histror
'modifyHistroy',
'modifyCreateHistroy',
// 生成router时触发
'onRouterCreated'
]

View File

@ -31,7 +31,7 @@ const renderClient = (opts = {}) => {
plugin.applyPlugins({
key: 'onAppCreated',
type: ApplyPluginsType.event,
args: { app },
args: { app, routes },
});
if (rootElement) {
@ -40,29 +40,7 @@ const renderClient = (opts = {}) => {
return app;
}
const getClientRender = (args = {}) => plugin.applyPlugins({
key: 'render',
type: ApplyPluginsType.compose,
initialValue: () => {
const opts = plugin.applyPlugins({
key: 'modifyClientRenderOpts',
type: ApplyPluginsType.modify,
initialValue: {
initialState: args.initialState,
routes: args.routes || getRoutes(),
plugin,
rootElement: '{{{ rootElement }}}',
{{#enableTitle}}
defaultTitle: `{{{ defaultTitle }}}`,
{{/enableTitle}}
},
});
return renderClient(opts);
},
args,
});
const beforeRender = async () => {
const beforeRender = async ({rootElement}) => {
const beforeRenderConfig = plugin.applyPlugins({
key: "beforeRender",
type: ApplyPluginsType.modify,
@ -74,7 +52,7 @@ const beforeRender = async () => {
let initialState = {};
if (typeof beforeRenderConfig.action === "function") {
const app = createApp(beforeRenderConfig.loading);
app.mount('{{{ rootElement }}}');
app.mount(rootElement);
try {
initialState = await beforeRenderConfig.action();
} catch(e){
@ -86,14 +64,31 @@ const beforeRender = async () => {
return initialState;
};
const completeClientRender = async () => {
const initialState = await beforeRender();
const clientRender = getClientRender({initialState});
const app = clientRender();
return app;
};
const getClientRender = (args = {}) => plugin.applyPlugins({
key: 'render',
type: ApplyPluginsType.compose,
initialValue: async () => {
const opts = plugin.applyPlugins({
key: 'modifyClientRenderOpts',
type: ApplyPluginsType.modify,
initialValue: {
routes: args.routes || getRoutes(),
plugin,
rootElement: '{{{ rootElement }}}',
{{#enableTitle}}
defaultTitle: `{{{ defaultTitle }}}`,
{{/enableTitle}}
},
});
const initialState = await beforeRender(opts);
return renderClient({...opts, initialState});
},
args,
});
const app = completeClientRender();
const clientRender = getClientRender();
const app = clientRender();
{{{ entryCode }}}

View File

@ -94,24 +94,21 @@ const genRoutes = function (parentRoutes, path, parentRoutePath, config) {
// 路由名称
const routeName = getRouteName(parentRoutePath, fileName);
const componentPath = getComponentPath(parentRoutePath, fileName, config);
const routeMeta = routeMetaBlock?.content ? JSON.parse(routeMetaBlock.content) : {};
const routeConfig = {
path: routePath,
component: componentPath,
name: routeMeta.name || routeName,
meta: routeMeta
};
if (hasLayout) {
if (fileName === 'layout') {
layoutRoute.component = componentPath;
} else {
layoutRoute.children.push({
path: routePath,
component: componentPath,
name: routeName,
meta: routeMetaBlock?.content ? JSON.parse(routeMetaBlock.content) : {}
});
layoutRoute.children.push(routeConfig);
}
} else {
parentRoutes.push({
path: routePath,
component: componentPath,
name: routeName,
meta: routeMetaBlock?.content ? JSON.parse(routeMetaBlock.content) : {}
});
parentRoutes.push(routeConfig);
}
}
});
@ -166,7 +163,7 @@ const rank = function (routes) {
rank(item.children);
}
});
routes = routes.sort((a, b) => b.count - a.count);
routes.sort((a, b) => b.count - a.count);
};
const getRoutes = function ({ config, absPagesPath }) {

View File

@ -3,31 +3,34 @@ import { plugin } from '@@/core/coreExports';
export function getRoutes() {
const routes = {{{ routes }}};
plugin.applyPlugins({
key: 'patchRoutes',
type: ApplyPluginsType.event,
args: { routes },
});
return routes;
}
const ROUTER_BASE = '{{{ routerBase }}}';
let router = null;
let history = null;
export const createRouter = () => {
export const createRouter = (routes) => {
if (router) {
return router;
}
history = plugin.applyPlugins({
key: 'modifyHistroy',
const createHistory = plugin.applyPlugins({
key: 'modifyCreateHistroy',
type: ApplyPluginsType.modify,
initialValue: {{{ CREATE_HISTORY }}}(ROUTER_BASE),
args: {
base: ROUTER_BASE
},
initialValue: {{{ CREATE_HISTORY }}},
});
history = createHistory(ROUTER_BASE);
// 修改routes
plugin.applyPlugins({
key: 'patchRoutes',
type: ApplyPluginsType.event,
args: { routes },
});
router = createVueRouter({
history,
routes: getRoutes()
routes
});
plugin.applyPlugins({

View File

@ -1,6 +1,6 @@
import { createRouter } from "./routes";
export function onAppCreated({ app }) {
const router = createRouter();
export function onAppCreated({ app, routes }) {
const router = createRouter(routes);
app.use(router);
}

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/runtime",
"version": "2.0.0-rc.5",
"version": "2.0.0",
"description": "@fesjs/runtime",
"main": "dist/index.js",
"files": [

View File

@ -1,5 +1,5 @@
// fes.config.js 只负责管理 cli 相关的配置
import pxtoviewport from 'postcss-px-to-viewport';
import pxtoviewport from '@ttou/postcss-px-to-viewport';
export default {
@ -10,7 +10,7 @@ export default {
publicPath: '/',
request: {
base: '/ras-mas',
dataField: ''
dataField: 'result'
},
html: {
title: '拉夫德鲁'

View File

@ -41,13 +41,14 @@
"access": "public"
},
"devDependencies": {
"@webank/eslint-config-webank": "0.2.10",
"postcss-px-to-viewport": "1.1.1"
"@webank/eslint-config-webank": "0.3.0",
"@ttou/postcss-px-to-viewport": "1.1.4"
},
"dependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/plugin-icon": "^2.0.0-rc.0",
"@fesjs/plugin-request": "^2.0.0-rc.0",
"debounce": "1.2.1",
"@fesjs/fes": "^2.0.0",
"@fesjs/plugin-icon": "^2.0.0",
"@fesjs/plugin-request": "^2.0.0",
"vue": "^3.0.5"
},
"private": true

View File

@ -1,13 +1,18 @@
export const request = {
errorHandler: {
111(responseData) {
console.log(responseData);
111() {
console.log('root:111');
},
404() {
console.log('to 404 page');
500() {
console.log('500 error');
},
default(error) {
console.log(error.response.data);
console.log('default error');
console.log(error);
}
}
};
export function patchRoutes() {
console.log('patchRoutes');
}

View File

@ -0,0 +1,4 @@
import { request } from '@fesjs/fes';
import { debounce } from 'debounce';
export const debounceRequest = debounce(request, 300);

View File

@ -2,38 +2,96 @@
<div class="onepiece">
fes & 拉夫德鲁<br />
<fes-icon :spin="true" class="one-icon" type="smile" @click="clickIcon" />
<div v-if="loading" class="loading">loading</div>
<div v-else class="data">{{data}}</div>
</div>
</template>
<config>
{
"title": "首页",
"name": "testIndex",
"layout": "false"
}
</config>
<script>
import { ref, onMounted } from 'vue';
import { useRouter, useRequest } from '@fesjs/fes';
import { ref } from 'vue';
import { request } from '@fesjs/fes';
export default {
setup() {
const fes = ref('fes upgrade to vue3');
const rotate = ref(90);
const router = useRouter();
onMounted(() => {
console.log(router);
console.log('mounted1!!');
});
const clickIcon = () => {
console.log('click Icon');
};
const { loading, data } = useRequest('/api', null, {
dataField: false
// request('/api', null, {
// mergeRequest: true
// }).then((res) => {
// console.log(res);
// });
// request('/api', null, {
// mergeRequest: true
// }).then((res) => {
// console.log(res);
// });
// request('/api', null, {
// mergeRequest: true
// }).then((res) => {
// console.log(res);
// });
// request('/api', null, {
// throttle: 3000,
// cache: true
// }).then((res) => {
// console.log(res);
// });
// setTimeout(() => {
// request('/api', null, {
// throttle: 3000,
// cache: true
// }).then((res) => {
// console.log(res);
// });
// }, 1000);
// setTimeout(() => {
// request('/api', null, {
// throttle: 3000,
// cache: true
// }).then((res) => {
// console.log(res);
// });
// request('/api', null, {
// throttle: 3000,
// cache: true
// }).then((res) => {
// console.log(res);
// });
// }, 3200);
request('/api', null, {
cache: true
}).then((res) => {
console.log(res);
});
request('/api', null, {
cache: true
}).then((res) => {
console.log(res);
});
request('/api', null, {
cache: true
}).then((res) => {
console.log(res);
});
// request('/api', null, {
// // skipErrorHandler: [500]
// }).then((res) => {
// console.log(res);
// }).catch((err) => {
// console.log('inner error', err);
// });
return {
loading,
data,
fes,
rotate,
clickIcon

View File

@ -35,6 +35,7 @@ export default {
footer: "Created by MumbleFe",
multiTabs: false,
navigation: "mixin",
theme: 'light',
menus: [
{
name: "index",
@ -81,4 +82,7 @@ export default {
strict: true,
},
dynamicImport: true,
extraBabelPlugins: [
['import', { libraryName: 'ant-design-vue', libraryDirectory: 'es', style: 'css' }, 'ant-design-vue'],
]
};

View File

@ -43,20 +43,20 @@
"access": "public"
},
"devDependencies": {
"@webank/eslint-config-webank": "0.2.10"
"@webank/eslint-config-webank": "0.3.0"
},
"dependencies": {
"@fesjs/fes": "^2.0.0-rc.0",
"@fesjs/plugin-access": "^2.0.0-rc.0",
"@fesjs/plugin-layout": "^2.0.0-rc.0",
"@fesjs/plugin-locale": "^2.0.0-rc.0",
"@fesjs/plugin-model": "^2.0.0-rc.0",
"@fesjs/plugin-enums": "^2.0.0-rc.0",
"@fesjs/plugin-jest": "^2.0.0-rc.0",
"@fesjs/plugin-vuex": "^2.0.0-rc.0",
"@fesjs/plugin-request": "^2.0.0-rc.0",
"@fesjs/plugin-qiankun": "^2.0.0-rc.0",
"@fesjs/plugin-sass": "^2.0.0-rc.0",
"@fesjs/fes": "^2.0.0",
"@fesjs/plugin-access": "^2.0.0",
"@fesjs/plugin-layout": "^2.0.0",
"@fesjs/plugin-locale": "^2.0.0",
"@fesjs/plugin-model": "^2.0.0",
"@fesjs/plugin-enums": "^2.0.0",
"@fesjs/plugin-jest": "^2.0.0",
"@fesjs/plugin-vuex": "^2.0.0",
"@fesjs/plugin-request": "^2.0.0",
"@fesjs/plugin-qiankun": "^2.0.0",
"@fesjs/plugin-sass": "^2.0.0",
"ant-design-vue": "2.0.0",
"vue": "^3.0.5",
"vuex": "^4.0.0"

View File

@ -1,5 +1,7 @@
<template>
<div class="haizekuo">
<div :class="$style.red">
<a-input placeholder="请输入。。。" />
<a-button type="primary">Primary</a-button>
<div>国际化 {{t("test")}}</div>
fes & 拉夫德鲁 <br />
<access :id="accessId"> accessOnepicess1 <input /> </access>
@ -22,8 +24,16 @@ import { ref, onMounted } from 'vue';
import {
useAccess, useRouter, useI18n, locale, enums, request
} from '@fesjs/fes';
import { Button, Input } from 'ant-design-vue';
export default {
components: {
[Button.name]: Button,
[Input.name]: Input,
},
mounted(){
console.log("$style:", this.$style)
},
setup() {
const fes = ref('fes upgrade to vue3');
const accessOnepicess = useAccess('/onepiece1');
@ -62,8 +72,11 @@ export default {
}
]
});
console.log(roles);
console.log(enums.get('status', {
console.log('enums roles=>', roles);
console.log('enums roles[1]=>', enums.get('roles', '1'));
console.log('enums status[0]=> ', enums.get('status', 0));
console.log('enums status concat', enums.concat('status', [['3', '普通的']], { extend: [{ key: 'name', dir: 'value' }] }));
console.log('enums status get extend=>', enums.get('status', {
extend: [
{
key: 'name',
@ -120,8 +133,11 @@ export default {
};
</script>
<style scoped>
.haizekuo {
/* background: url('../images/icon.png'); */
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>

View File

@ -1,6 +1,6 @@
{
"name": "@fesjs/fes",
"version": "2.0.0-rc.18",
"version": "2.0.1",
"description": "一个好用的前端管理台快速开发框架",
"preferGlobal": true,
"scripts": {
@ -39,9 +39,9 @@
"strong"
],
"dependencies": {
"@fesjs/compiler": "^2.0.0-rc.5",
"@fesjs/preset-built-in": "^2.0.0-rc.18",
"@fesjs/runtime": "^2.0.0-rc.5",
"@fesjs/compiler": "^2.0.0",
"@fesjs/preset-built-in": "^2.0.1",
"@fesjs/runtime": "^2.0.0",
"@umijs/utils": "3.3.3",
"resolve-cwd": "^3.0.0"
},

782
yarn.lock

File diff suppressed because it is too large Load Diff