init: drip-table 1.0.0
This commit is contained in:
parent
bf71c821a3
commit
8b267309c0
|
@ -0,0 +1,9 @@
|
|||
export default {
|
||||
esm: 'rollup',
|
||||
disableTypeCheck: false,
|
||||
cjs: { type: 'babel', lazy: true },
|
||||
autoPkgs: false,
|
||||
pkgs: [
|
||||
'drip-table',
|
||||
],
|
||||
};
|
|
@ -17,3 +17,7 @@
|
|||
/packages/*/dist
|
||||
/packages/*/dist-visualizer
|
||||
/packages/*/dist-visualizer-css
|
||||
|
||||
# temporary files
|
||||
/.tsconfig-lint.json
|
||||
/packages/*/.tsconfig-lint.json
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
"extends": "stylelint-config-standard",
|
||||
"plugins": [],
|
||||
"rules": {
|
||||
"at-rule-empty-line-before": ["always", {
|
||||
except: ["inside-block", "blockless-after-same-name-blockless", "first-nested"],
|
||||
ignore: ["blockless-after-blockless"],
|
||||
ignoreAtRules: ["array", "of", "at-rules", "at-root"],
|
||||
}],
|
||||
"at-rule-no-unknown": null,
|
||||
"color-hex-length": "long",
|
||||
"comment-empty-line-before": ["always", {
|
||||
ignore: ["after-comment", "stylelint-commands"],
|
||||
}],
|
||||
"max-nesting-depth": null,
|
||||
"no-empty-source": null,
|
||||
"no-descending-specificity": null,
|
||||
"number-leading-zero": "never",
|
||||
"selector-id-pattern": /^\$?[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u,
|
||||
"selector-max-compound-selectors": null,
|
||||
"selector-no-qualifying-type": null,
|
||||
"selector-pseudo-class-no-unknown": [true, {
|
||||
ignorePseudoClasses: ["global"],
|
||||
}],
|
||||
},
|
||||
"ignoreDisables": true,
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
|
||||
changed=$(git diff --cached --name-only)
|
||||
|
||||
if [ -z "$changed" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo $changed | xargs egrep '^[><=]{7}( |$)' -H -I --line-number
|
||||
|
||||
# If the egrep command has any hits - echo a warning and exit with non-zero status.
|
||||
if [ $? = 0 ]; then
|
||||
echo "WARNING: You have merge markers in the above files. Fix them before committing."
|
||||
echo " If these markers are intentional, you can force the commit with the --no-verify argument."
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,84 @@
|
|||
#!/bin/bash
|
||||
|
||||
# ----------------------------------
|
||||
# Colors
|
||||
# ----------------------------------
|
||||
if test -t 1; then
|
||||
if test -n "$(tput colors)" && test $(tput colors) -ge 8; then
|
||||
NOCOLOR="\033[0m"
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
ORANGE="\033[0;33m"
|
||||
BLUE="\033[0;34m"
|
||||
PURPLE="\033[0;35m"
|
||||
CYAN="\033[0;36m"
|
||||
LIGHTGRAY="\033[0;37m"
|
||||
DARKGRAY="\033[1;30m"
|
||||
LIGHTRED="\033[1;31m"
|
||||
LIGHTGREEN="\033[1;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
LIGHTBLUE="\033[1;34m"
|
||||
LIGHTPURPLE="\033[1;35m"
|
||||
LIGHTCYAN="\033[1;36m"
|
||||
WHITE="\033[1;37m"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ----------------------------------
|
||||
# npm or yarn
|
||||
# ----------------------------------
|
||||
if [ "${NPM}" = "" ]; then
|
||||
NPM='yarn'
|
||||
echo "> using ${LIGHTPURPLE}${NPM}${NOCOLOR} as building command tool."
|
||||
echo ''
|
||||
fi
|
||||
|
||||
# ----------------------------------
|
||||
# url encode / decode
|
||||
# ----------------------------------
|
||||
urlencode() {
|
||||
# urlencode <string>
|
||||
|
||||
old_lc_collate=$LC_COLLATE
|
||||
LC_COLLATE=C
|
||||
|
||||
local length="${#1}"
|
||||
i=0
|
||||
while [ "$i" -lt "${length}" ]; do
|
||||
local c="${1:$i:1}"
|
||||
case $c in
|
||||
[a-zA-Z0-9.~_-]) printf '%s' "$c" ;;
|
||||
*) printf '%%%02X' "'$c" ;;
|
||||
esac
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
|
||||
LC_COLLATE=$old_lc_collate
|
||||
}
|
||||
|
||||
urldecode() {
|
||||
# urldecode <string>
|
||||
|
||||
local url_encoded="${1//+/ }"
|
||||
printf '%b' "${url_encoded//%/\\x}"
|
||||
}
|
||||
|
||||
# ----------------------------------
|
||||
# echo
|
||||
# ----------------------------------
|
||||
echoeol() {
|
||||
echo " "
|
||||
}
|
||||
|
||||
echoline() {
|
||||
# echostep <string>
|
||||
s="${*}"
|
||||
echo "> ${s}"
|
||||
}
|
||||
|
||||
echocmd() {
|
||||
# echocmd <string>
|
||||
cmd="${*}"
|
||||
echo "${LIGHTRED}\$${NOCOLOR} ${cmd}"
|
||||
${cmd} || exit 1
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
#!/bin/bash -e
|
||||
__DIR__=$(dirname "$0")
|
||||
PACKAGE_NAME=${1}
|
||||
|
||||
# includes
|
||||
. ${__DIR__}/includes/env.sh
|
||||
|
||||
# create tsc lint config file
|
||||
TMP=.tsconfig-lint.json
|
||||
cat >${TMP} <<EOF
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"listFiles": false
|
||||
},
|
||||
"include": [
|
||||
EOF
|
||||
for file in "$@"; do
|
||||
echo " \"$file\"," >> ${TMP}
|
||||
done
|
||||
cat >>${TMP} <<EOF
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# run tsc lint
|
||||
${NPM} run tslint:exec > ./node_modules/.tslint.log
|
||||
TSC_ERROR=`tail -n +5 ./node_modules/.tslint.log`
|
||||
rm -f ./node_modules/.tslint.log
|
||||
|
||||
# remove tsc config file
|
||||
rm -f ${TMP}
|
||||
|
||||
# emit error if exists
|
||||
if [ "${TSC_ERROR}" != "" ]; then
|
||||
echoeol
|
||||
echo "${RED}⛔ Typescript complier lint (tsc) checksum failed!${NOCOLOR}"
|
||||
echoeol
|
||||
echo "${TSC_ERROR}"
|
||||
echoeol
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"packages": ["packages/*"],
|
||||
"version": "independent",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn run build:drip-table",
|
||||
"build:drip-table": "lerna run build --stream --scope=drip-table",
|
||||
"changelog": "lerna-changelog",
|
||||
"clean": "lerna clean -y",
|
||||
"lint": "yarn run gitlint && lerna run lint --stream --scope=drip-table",
|
||||
"gitlint": "sh ./bin/gitlint.sh || exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.6.0",
|
||||
"@rollup/plugin-eslint": "^8.0.1",
|
||||
"@types/cheerio": "0.22.22",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@typescript-eslint/eslint-plugin": "4.33.0",
|
||||
"@typescript-eslint/parser": "4.33.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"drip-table": "link:packages/drip-table",
|
||||
"eslint-config-lvmcn": "0.0.30",
|
||||
"eslint-formatter-pretty": "4.0.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-import-resolver-webpack": "^0.13.1",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-promise": "^5.1.1",
|
||||
"eslint-plugin-react": "^7.27.0",
|
||||
"eslint-plugin-unicorn": "^38.0.1",
|
||||
"eslint-plugin-unused-imports": "^1.1.1",
|
||||
"father-build": "1.17.2",
|
||||
"lerna": "3.22.1",
|
||||
"lerna-changelog": "1.0.1",
|
||||
"lint-staged": "10.0.7",
|
||||
"react": "17.0.2",
|
||||
"stylelint": "13.8.0",
|
||||
"stylelint-config-standard": "20.0.0",
|
||||
"stylelint-formatter-pretty": "2.1.1",
|
||||
"tsc-alias": "^1.4.2",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"files": [
|
||||
"README.*",
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://drip-table.jd.com/",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://coding.jd.com/drip/drip-table/issues"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
const path = require('path');
|
||||
|
||||
const rules = {
|
||||
"no-new-func": "off",
|
||||
"no-undefined": "error",
|
||||
"no-void": "off",
|
||||
"react/jsx-no-bind": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/sort-comp": "off",
|
||||
"unicorn/no-array-callback-reference": "off",
|
||||
"unicorn/no-array-for-each": "off",
|
||||
"unicorn/no-array-reduce": "off",
|
||||
"unicorn/prefer-switch": "off",
|
||||
};
|
||||
const extensions = [".js", ".jsx", ".jx", ".ts", ".tsx", ".tx"];
|
||||
|
||||
// http://eslint.org/docs/user-guide/configuring
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: "babel-eslint",
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
jsx: true,
|
||||
legacyDecorators: true,
|
||||
experimentalObjectRestSpread: true,
|
||||
},
|
||||
sourceType: "module",
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
},
|
||||
extends: [
|
||||
"lvmcn/javascript/react",
|
||||
],
|
||||
plugins: [
|
||||
"react",
|
||||
"import",
|
||||
"unicorn",
|
||||
"unused-imports",
|
||||
],
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
alias: {
|
||||
map: [
|
||||
['@', path.resolve(__dirname, './src')],
|
||||
],
|
||||
extensions: extensions,
|
||||
},
|
||||
node: {
|
||||
extensions: extensions,
|
||||
},
|
||||
},
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts", ".tsx"],
|
||||
},
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
noInlineConfig: true,
|
||||
rules,
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.ts", "*.tsx", "*.tx"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
jsx: true,
|
||||
legacyDecorators: true,
|
||||
experimentalObjectRestSpread: true,
|
||||
},
|
||||
sourceType: "module",
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
extends: [
|
||||
"lvmcn/typescript/react",
|
||||
],
|
||||
rules,
|
||||
},
|
||||
{
|
||||
files: ["*.d.ts"],
|
||||
rules: {
|
||||
"react/no-typos": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
ignorePatterns: [
|
||||
"/.eslintrc.js",
|
||||
"/dist",
|
||||
],
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
import { IBundleOptions } from 'father-build';
|
||||
import path from 'path';
|
||||
import eslint from '@rollup/plugin-eslint';
|
||||
|
||||
const options: IBundleOptions = {
|
||||
cjs: { type: 'rollup' },
|
||||
esm: {
|
||||
type: 'rollup',
|
||||
importLibToEs: true,
|
||||
},
|
||||
cssModules: true,
|
||||
extractCSS: true,
|
||||
extraBabelPlugins: [],
|
||||
extraRollupPlugins: [{
|
||||
before: "babel",
|
||||
plugins: [
|
||||
eslint({
|
||||
configFile: path.resolve(__dirname, '.eslintrc.js'),
|
||||
}),
|
||||
],
|
||||
}],
|
||||
pkgs: [
|
||||
'drip-table',
|
||||
],
|
||||
preCommit: {
|
||||
eslint: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default options;
|
|
@ -0,0 +1,77 @@
|
|||
# Drip-Table Development Guide
|
||||
|
||||
`Drip-Table` is the core library of the solution for building dynamic tables. It's main ability is to render a dynamic table automatically when received data which conforms to the `JSON Schema` standard.
|
||||
|
||||
## Directory
|
||||
|
||||
- update continually
|
||||
|
||||
```
|
||||
├── src // source code
|
||||
├── components // common components that drip-table depends on
|
||||
│ ├── ErrorBoundary // error notification component
|
||||
│ ├── Highlight // highlight component
|
||||
│ └── RichText // HTML character rendering component
|
||||
├── drip-table // drip-table main component
|
||||
│ ├─ components // built-in common components
|
||||
│ | ├── image // picture display component
|
||||
│ | ├── link // link operation component
|
||||
│ | ├── render-html // custom HTML render component
|
||||
│ | ├── text // text display component
|
||||
│ | ├── components.ts // common type definition
|
||||
│ | └── index.ts // export
|
||||
| ├── header // header of table
|
||||
| ├── utils // util functions
|
||||
| └── virtual-table // virtual scrollable table
|
||||
├── drip-table-provider // drip-table entry
|
||||
├── context.ts // export global context
|
||||
├── hooks.ts // global states
|
||||
├── index.ts // export
|
||||
├── shims-styles.d.ts // style type definition
|
||||
└── types.ts // export type definition
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Steps
|
||||
1. create a new branch that names to express the features simply.
|
||||
2. coding in local branch.
|
||||
3. add documents of new features in `docs/drip-table` directory.
|
||||
4. add features in `docs/drip-table/changelog` file.
|
||||
5. submit code and merge into `master` branch.
|
||||
6. modify version infomation in `package.json` file.
|
||||
7. submit and push code.
|
||||
8. release new version.
|
||||
|
||||
### Cautions
|
||||
- Use React.memo to wrap a functional component, and compare props to update the wrapped component.
|
||||
- Merge related states instead of using too many useStates.
|
||||
- Do not use arrow functions to assgin to event functions as values directly. (Reference: [How to read an often-changing value from useCallback?](https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback))
|
||||
- Either class component or functional component while definiting a component as much as possible.
|
||||
|
||||
## Release
|
||||
|
||||
add owner in npm depository for the first time.
|
||||
|
||||
```sh
|
||||
npm owner add [username] drip-table
|
||||
```
|
||||
|
||||
### Version number modification rules
|
||||
|
||||
- Major version number(1): Required. Modify when there are major changes in the functional modules, such as adding multiple modules or changing the architecture. Whether to modify this version number is determined by the project manager.
|
||||
|
||||
- Sub version number(2): Required. Modify when the function has a certain increase or change, such as adding authority management, adding custom views, etc. Whether to modify this version number is determined by the project manager.
|
||||
|
||||
- Phase version number(3): Required. Generally, a revised version is released when a bug is fixed or a small change is made. The time interval is not limited. When a serious bug is fixed, a revised version can be released. Whether to modify this version number is determined by the project manager.
|
||||
|
||||
- Date version number(051021): Optional. The date version number is used to record the date of the modification of the project, and the modification needs to be recorded every day. Whether to modify this version number is determined by developers.
|
||||
|
||||
- Greek letter version number(beta): Optional. This version number is used to mark which pharse of development the current software is in, and it needs to be modified when the software enters to another pharse. Whether to modify this version number is determined by the project manager.
|
||||
|
||||
```sh
|
||||
git commit -m 'release: drip-table 1.2.3'
|
||||
lerna run build --stream --scope=drip-table
|
||||
cd packages/drip-table
|
||||
npm publish --access=public
|
||||
```
|
|
@ -0,0 +1,78 @@
|
|||
# Drip-Table 开发文档
|
||||
|
||||
`Drip-Table` 是动态列表解决方案的核心库,其主要能力是支持符合 `JSON Schema` 标准的数据自动渲染列表内容。
|
||||
|
||||
## 目录结构
|
||||
|
||||
- 持续更新
|
||||
|
||||
```
|
||||
├── src // 源代码
|
||||
├── components // drip-table 依赖的通用组件
|
||||
│ ├── ErrorBoundary // 错误提示组件
|
||||
│ ├── Highlight // 高亮组件
|
||||
│ └── RichText // HTML 字符渲染组件
|
||||
├── drip-table // drip-table 主要渲染逻辑
|
||||
│ ├─ components // 内置通用组件
|
||||
│ | ├── image // 图片展示组件
|
||||
│ | ├── link // 链接操作组件
|
||||
│ | ├── render-html // 自定义 HTML 渲染组件
|
||||
│ | ├── text // 文本展示组件
|
||||
│ | ├── components.ts // 组件通用类型定义
|
||||
│ | └── index.ts // 组件导出
|
||||
| ├── header // 表格头部渲染
|
||||
| ├── utils // 工具函数库
|
||||
| └── virtual-table // 虚拟滚动表格
|
||||
├── drip-table-provider // drip-table 入口文件
|
||||
├── context.ts // 全局上下文导出
|
||||
├── hooks.ts // 全局属性状态管理
|
||||
├── index.ts // 导出
|
||||
├── shims-styles.d.ts // 样式类型定义
|
||||
└── types.ts // 全局变量类型定义
|
||||
```
|
||||
|
||||
## 开发
|
||||
|
||||
### 开发步骤
|
||||
1. 新建本地分支,命名可大概说明需求新特性;
|
||||
2. 在本地分支上修改对应代码;
|
||||
3. 在 `docs/drip-table` 目录下添加相应的文档说明;
|
||||
4. 在 `docs/drip-table/changelog` 目录下修改版本更改说明;
|
||||
5. 提交代码,合并至 `master` 分支;
|
||||
6. 修改 `package.json` 修改版本信息;
|
||||
7. 提交代码;
|
||||
8. 打包发布;
|
||||
|
||||
### 注意事项
|
||||
- 每个函数式组件 export 之前使用 React.memo 包裹,浅比较 props 来更新包裹的组件
|
||||
- 尽量合并相关的 state,不要使用多个 useState。
|
||||
- 事件函数不要用箭头函数直接赋值。(参考:[如何从 useCallback 读取一个经常变化的值?](https://react.docschina.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback))
|
||||
- 单个组件中不要同时使用 class component 与 function component。
|
||||
|
||||
## 发布
|
||||
|
||||
npm 添加 账号,仅第一次添加即可
|
||||
|
||||
```sh
|
||||
npm owner add [username] drip-table
|
||||
```
|
||||
|
||||
### 版本号定修改规则
|
||||
|
||||
- 主版本号(1):必填,当功能模块有较大的变动,比如增加多个模块或者整体架构发生变化。此版本号由项目决定是否修改。
|
||||
|
||||
- 子版本号(2):必填,当功能有一定的增加或变化,比如增加了对权限控制、增加自定义视图等功能。此版本号由项目决定是否修改。
|
||||
|
||||
- 阶段版本号(3):必填,一般是问题修复或是一些小的变动,要经常发布修订版,时间间隔不限,修复一个严重的问题即可发布一个修订版。此版本号由项目经理决定是否修改。
|
||||
|
||||
- 日期版本号(051021): 可选,用于记录修改项目的当前日期,每天对项目的修改都需要更改日期版本号。此版本号由开发人员决定是否修改。
|
||||
|
||||
- 希腊字母版本号(beta): 可选,此版本号用于标注当前版本的软件处于哪个开发阶段,当软件进入到另一个阶段时需要修改此版本号。此版本号由项目决定是否修改。
|
||||
|
||||
|
||||
```sh
|
||||
git commit -m 'release: drip-table 1.2.3'
|
||||
lerna run build --stream --scope=drip-table
|
||||
cd packages/drip-table
|
||||
npm publish --access=public
|
||||
```
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"name": "drip-table",
|
||||
"version": "1.0.0",
|
||||
"description": "A tiny and powerful enterprise-class solution for building tables.",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"typings": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "father-build && tsc-alias -p tsconfig.json",
|
||||
"analyze": "ANALYZE=1 dumi dev",
|
||||
"prepare": "yarn build",
|
||||
"postpublish": "git push --tags",
|
||||
"lint": "yarn run eslint && yarn run tslint && yarn run stylelint",
|
||||
"lint:fix": "yarn run eslint:fix && yarn run stylelint:fix",
|
||||
"tslint": "sh ../../bin/tslint.sh **/*.ts",
|
||||
"tslint:commit": "sh ./bin/tslint.sh",
|
||||
"tslint:exec": "tsc --project .tsconfig-lint.json --skipLibCheck --noEmit",
|
||||
"eslint": "eslint \"src/**/*.{js,jsx,ts,tsx,json}\" --format pretty",
|
||||
"eslint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx,json}\" --format pretty --fix",
|
||||
"eslint:commit": "eslint --format pretty",
|
||||
"stylelint": "stylelint \"src/**/*.{less,sass,scss,css,vue}\" --custom-formatter=../../node_modules/stylelint-formatter-pretty",
|
||||
"stylelint:fix": "stylelint \"src/**/*.{less,sass,scss,css,vue}\" --custom-formatter=../../node_modules/stylelint-formatter-pretty --fix",
|
||||
"stylelint:commit": "stylelint --custom-formatter=../../node_modules/stylelint-formatter-pretty"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "1.0.0-rc.3",
|
||||
"lodash": "4.17.20",
|
||||
"lodash.get": "^4.4.2",
|
||||
"rc-resize-observer": "^1.0.1",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-window": "^1.8.6",
|
||||
"viewerjs": "1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cheerio": "0.22.22",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"father-build": "1.17.2",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"keywords": [
|
||||
"DripTable",
|
||||
"Render",
|
||||
"TableRender",
|
||||
"Drip Design",
|
||||
"Json Schema",
|
||||
"React"
|
||||
],
|
||||
"files": [
|
||||
"*.md",
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://drip-table.jd.com/",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://coding.jd.com/drip/drip-table/issues"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React, { ErrorInfo } from 'react';
|
||||
import { DripTableRecordTypeBase, DripTableDriver } from '@/types';
|
||||
|
||||
class ErrorBoundary<RecordType extends DripTableRecordTypeBase> extends React.Component<
|
||||
{ driver: DripTableDriver<RecordType> },
|
||||
{ hasError: boolean; errorInfo: string }
|
||||
> {
|
||||
public state = { hasError: false, errorInfo: '' };
|
||||
|
||||
public static getDerivedStateFromError(error: Error) {
|
||||
return { hasError: true, errorInfo: error.message };
|
||||
}
|
||||
|
||||
public componentDidCatch(error: unknown, errorInfo: ErrorInfo) {
|
||||
console.error(error, errorInfo);
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.state.hasError) {
|
||||
const Result = this.props.driver.components.Result;
|
||||
// You can render any custom fallback UI
|
||||
return (
|
||||
<Result
|
||||
status="error"
|
||||
title="Something went wrong."
|
||||
extra={this.state.errorInfo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export interface HighlightProps {
|
||||
content: string;
|
||||
keywords: string[];
|
||||
color?: string;
|
||||
tagName?: keyof React.ReactHTML;
|
||||
tagAttrs?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ContentPart {
|
||||
highlight: boolean;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高亮文本
|
||||
*
|
||||
* @export
|
||||
* @class Highlight
|
||||
* @extends {React.Component<HighlightProps, {}>}
|
||||
*/
|
||||
export default class Highlight extends React.PureComponent<HighlightProps> {
|
||||
private get contents(): ContentPart[] {
|
||||
const contents: ContentPart[] = [];
|
||||
let { content, keywords } = this.props;
|
||||
keywords = keywords
|
||||
.filter(kw => kw)
|
||||
.sort((s1, s2) => s2.length - s1.length);
|
||||
if (keywords.length > 0) {
|
||||
while (content) {
|
||||
const [keyword, index] = this.searchString(content, keywords);
|
||||
if (keyword) {
|
||||
if (index > 0) {
|
||||
contents.push({
|
||||
highlight: false,
|
||||
text: content.slice(0, Math.max(0, index)),
|
||||
});
|
||||
}
|
||||
contents.push({
|
||||
highlight: true,
|
||||
text: keyword,
|
||||
});
|
||||
content = content.slice(index + keyword.length);
|
||||
} else {
|
||||
contents.push({
|
||||
highlight: false,
|
||||
text: content,
|
||||
});
|
||||
content = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索匹配字符串
|
||||
*
|
||||
* @private
|
||||
* @param {string} content 内容文本
|
||||
* @param {string[]} keywords 关键字列表(按长度降序排列)
|
||||
* @returns {[string, number]} 匹配到的关键字, 匹配到的位置
|
||||
*
|
||||
* @memberOf Highlight
|
||||
*/
|
||||
private searchString(content: string, keywords: string[]): [string, number] {
|
||||
let keyword = '';
|
||||
let foundIndex = -1;
|
||||
for (const kw of keywords) {
|
||||
const index = content.indexOf(kw);
|
||||
if (index >= 0 && (index < foundIndex || foundIndex === -1)) {
|
||||
keyword = kw;
|
||||
foundIndex = index;
|
||||
}
|
||||
}
|
||||
return [keyword, foundIndex];
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const { color = 'red', tagName = 'span', tagAttrs } = this.props;
|
||||
const children = this.contents.map((content, i) => (
|
||||
content.highlight
|
||||
? <span key={i} style={{ color }}>{ content.text }</span>
|
||||
: <span key={i}>{ content.text }</span>
|
||||
));
|
||||
return React.createElement(tagName, tagAttrs, children);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React, { CSSProperties } from 'react';
|
||||
import cheerio from 'cheerio';
|
||||
import ViewerJS from 'viewerjs';
|
||||
import Highlight, { HighlightProps } from '@/components/Highlight';
|
||||
import 'viewerjs/dist/viewer.css';
|
||||
|
||||
interface RichTextProps {
|
||||
html: string;
|
||||
tagNames?: (keyof React.ReactHTML)[];
|
||||
singleLine?: boolean;
|
||||
maxLength?: number;
|
||||
highlight?: Omit<HighlightProps, 'content' | 'tagName'>;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const SAFE_TAG_NAME: NonNullable<RichTextProps['tagNames']> = [
|
||||
'a',
|
||||
'abbr',
|
||||
'address',
|
||||
'area',
|
||||
'article',
|
||||
'aside',
|
||||
'audio',
|
||||
'b',
|
||||
'base',
|
||||
'bdi',
|
||||
'bdo',
|
||||
'big',
|
||||
'blockquote',
|
||||
// 'body',
|
||||
'br',
|
||||
'button',
|
||||
// 'canvas',
|
||||
'caption',
|
||||
'cite',
|
||||
'code',
|
||||
'col',
|
||||
'colgroup',
|
||||
'data',
|
||||
'datalist',
|
||||
'dd',
|
||||
'del',
|
||||
'details',
|
||||
'dfn',
|
||||
'dialog',
|
||||
'div',
|
||||
'dl',
|
||||
'dt',
|
||||
'em',
|
||||
'embed',
|
||||
'fieldset',
|
||||
'figcaption',
|
||||
'figure',
|
||||
'footer',
|
||||
// 'form',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'head',
|
||||
'header',
|
||||
'hgroup',
|
||||
'hr',
|
||||
'html',
|
||||
'i',
|
||||
// 'iframe',
|
||||
'img',
|
||||
// 'input',
|
||||
'ins',
|
||||
'kbd',
|
||||
'keygen',
|
||||
'label',
|
||||
'legend',
|
||||
'li',
|
||||
// 'link',
|
||||
'main',
|
||||
'map',
|
||||
'mark',
|
||||
'menu',
|
||||
'menuitem',
|
||||
'meta',
|
||||
'meter',
|
||||
'nav',
|
||||
// 'noscript',
|
||||
'object',
|
||||
'ol',
|
||||
'optgroup',
|
||||
'option',
|
||||
'output',
|
||||
'p',
|
||||
'param',
|
||||
'picture',
|
||||
'pre',
|
||||
'progress',
|
||||
'q',
|
||||
'rp',
|
||||
'rt',
|
||||
'ruby',
|
||||
's',
|
||||
'samp',
|
||||
'slot',
|
||||
// 'script',
|
||||
'section',
|
||||
'select',
|
||||
'small',
|
||||
'source',
|
||||
'span',
|
||||
'strong',
|
||||
'style',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'template',
|
||||
'tbody',
|
||||
'td',
|
||||
'textarea',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'time',
|
||||
'title',
|
||||
'tr',
|
||||
'track',
|
||||
'u',
|
||||
'ul',
|
||||
'var',
|
||||
'video',
|
||||
'wbr',
|
||||
'webview',
|
||||
];
|
||||
|
||||
interface ReducerRenderValue {
|
||||
elements: (JSX.Element | string)[];
|
||||
maxLength: number;
|
||||
tagNames: NonNullable<RichTextProps['tagNames']>;
|
||||
singleLine: NonNullable<RichTextProps['singleLine']>;
|
||||
highlight: RichTextProps['highlight'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 高亮文本
|
||||
*
|
||||
* @export
|
||||
* @class RichText
|
||||
* @extends {React.PureComponent<RichTextProps>}
|
||||
*/
|
||||
export default class RichText extends React.PureComponent<RichTextProps> {
|
||||
private viewer!: ViewerJS;
|
||||
|
||||
/**
|
||||
* 迭代渲染一个节点
|
||||
*
|
||||
* @private
|
||||
* @param {ReducerRenderValue} prevVal 迭代器当前数据
|
||||
* @param {CheerioElement} el 节点原始数据
|
||||
* @param {number} key 节点唯一标识
|
||||
* @param {number} maxLength 最大剩余渲染文本长度
|
||||
* @returns {ReducerRenderValue} 迭代器当前数据
|
||||
*
|
||||
* @memberOf RichText
|
||||
*/
|
||||
private reducerRenderEl = (prevVal: ReducerRenderValue, el: cheerio.Element, key: number): ReducerRenderValue => {
|
||||
const { tagNames, singleLine, maxLength, highlight } = prevVal;
|
||||
if (el.type === 'text') {
|
||||
let text = el.data || '';
|
||||
if (singleLine) {
|
||||
text = text.replace(/[\r\n]/ug, '$nbsp');
|
||||
}
|
||||
if (prevVal.maxLength >= 0) {
|
||||
text = text.slice(0, Math.max(0, prevVal.maxLength));
|
||||
if (text.length > 0 && text.length === prevVal.maxLength) {
|
||||
text += '...';
|
||||
}
|
||||
prevVal.maxLength = Math.max(prevVal.maxLength - text.length, 0);
|
||||
}
|
||||
prevVal.elements.push(
|
||||
highlight
|
||||
? React.createElement(Highlight, { ...highlight, key, content: text })
|
||||
: text,
|
||||
);
|
||||
return prevVal;
|
||||
}
|
||||
if (tagNames.includes(el.tagName as never)) {
|
||||
const tagName = el.tagName as NonNullable<HighlightProps['tagName']>;
|
||||
const { attribs = {}, children } = el;
|
||||
const style: React.CSSProperties = {};
|
||||
if (attribs.style) {
|
||||
attribs.style.split(';').forEach((s: string) => {
|
||||
const [k, v] = s.split(':');
|
||||
if (v) {
|
||||
style[k.trim().replace(/-([a-z])/ug, (_: string, c: string) => c.toUpperCase())] = v.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (tagName === 'img') {
|
||||
style.maxWidth = '100%';
|
||||
}
|
||||
if (singleLine) {
|
||||
if (tagName === 'br') {
|
||||
prevVal.elements.push(<span key={key}> </span>);
|
||||
return prevVal;
|
||||
}
|
||||
style.display = 'inline';
|
||||
}
|
||||
const filterAttrs = (rec: Record<string, unknown>, excludeKeys: string[]) =>
|
||||
Object.fromEntries(Object.entries(rec)
|
||||
.filter(([k, v]) => !excludeKeys.includes(k)));
|
||||
const props: Record<string, unknown> = {
|
||||
...filterAttrs(attribs, ['key', 'class', 'onclick']),
|
||||
key,
|
||||
style,
|
||||
className: attribs.class,
|
||||
src: attribs.src,
|
||||
width: attribs.width,
|
||||
height: attribs.height,
|
||||
onClick: new Function(attribs.onclick),
|
||||
};
|
||||
if (tagName === 'a') {
|
||||
props.href = attribs.href;
|
||||
props.target = attribs.target;
|
||||
}
|
||||
let content: (JSX.Element | string | null)[] | undefined;
|
||||
if (children) {
|
||||
const res = children.reduce<ReducerRenderValue>(this.reducerRenderEl, {
|
||||
elements: [],
|
||||
maxLength,
|
||||
tagNames,
|
||||
singleLine,
|
||||
highlight,
|
||||
});
|
||||
if (res.elements.length > 0) {
|
||||
content = res.elements;
|
||||
}
|
||||
prevVal.maxLength = res.maxLength;
|
||||
}
|
||||
prevVal.elements.push(React.createElement(tagName, props, content));
|
||||
return prevVal;
|
||||
}
|
||||
return prevVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理页面,绑定图片查看器
|
||||
*
|
||||
* @private
|
||||
* @param {HTMLDivElement} el 页面根节点
|
||||
* @returns {void}
|
||||
*
|
||||
* @memberOf RichText
|
||||
*/
|
||||
private onRef = (el: HTMLDivElement | null) => {
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
if (this.viewer) {
|
||||
this.viewer.destroy();
|
||||
}
|
||||
this.viewer = new ViewerJS(el);
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.viewer) {
|
||||
this.viewer.update();
|
||||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element | null {
|
||||
const { html, maxLength = -1, tagNames = SAFE_TAG_NAME, singleLine = false, highlight, style } = this.props;
|
||||
const $ = cheerio.load(html);
|
||||
const body = $('body')[0];
|
||||
return (
|
||||
<div ref={this.onRef} style={style}>
|
||||
{
|
||||
body && body.type === 'tag'
|
||||
? body.children.reduce<ReducerRenderValue>(this.reducerRenderEl, {
|
||||
elements: [],
|
||||
maxLength,
|
||||
tagNames,
|
||||
singleLine,
|
||||
highlight,
|
||||
}).elements
|
||||
: void 0
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface IDripTableContext {
|
||||
readonly _CTX_SOURCE: 'CONTEXT' | 'PROVIDER';
|
||||
loading: boolean;
|
||||
api: CallableFunction | CallableFunction[] | null;
|
||||
tab: number; // 如果api是数组,需要在最顶层感知tab,来知道到底点击搜索调用的是啥api
|
||||
extraData: null; // 需要用到的 dataSource 以外的扩展返回值
|
||||
pagination: {
|
||||
current: number;
|
||||
total: number;
|
||||
pageSize: number;
|
||||
};
|
||||
tableSize: 'default';
|
||||
checkPassed: boolean;
|
||||
selectedRowKeys: React.Key[];
|
||||
setTableState: CallableFunction;
|
||||
}
|
||||
|
||||
export const DripTableContext = createContext<IDripTableContext>({
|
||||
loading: false,
|
||||
api: null,
|
||||
tab: 0,
|
||||
extraData: null,
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
},
|
||||
tableSize: 'default',
|
||||
checkPassed: true,
|
||||
selectedRowKeys: [],
|
||||
_CTX_SOURCE: 'CONTEXT',
|
||||
setTableState: () => false,
|
||||
});
|
||||
|
||||
export const DripTableStoreContext = createContext({});
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import { DripTableRecordTypeBase } from '@/types';
|
||||
import { useState, useTable } from '@/hooks';
|
||||
import { DripTableContext, IDripTableContext } from '@/context';
|
||||
import DripTable, { DripTableProps } from '@/drip-table';
|
||||
|
||||
/**
|
||||
* 暴露给外部直接操作实例的接口
|
||||
*/
|
||||
export interface DripTableContainerHandle extends IDripTableContext {
|
||||
/**
|
||||
* 通过接口选择行
|
||||
*
|
||||
* @param indexes 行号数组
|
||||
*/
|
||||
select: (indexes: number[]) => void;
|
||||
}
|
||||
|
||||
// 组件提供给外部的公共接口
|
||||
const createTableContext = <RecordType extends DripTableRecordTypeBase>(props: DripTableProps<RecordType>): DripTableContainerHandle => {
|
||||
const initialState = useTable();
|
||||
const [state, setState] = useState(initialState);
|
||||
|
||||
const select = (indexes: number[]) => {
|
||||
let selectedKeys: React.Key[] = [];
|
||||
const { dataSource, rowKey } = props;
|
||||
if (dataSource && rowKey) {
|
||||
indexes.forEach((index) => {
|
||||
const data = dataSource[index];
|
||||
if (data) {
|
||||
const value = data[rowKey];
|
||||
const key = typeof value === 'string' || typeof value === 'number'
|
||||
? value
|
||||
: index;
|
||||
selectedKeys.push(key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
selectedKeys = [...indexes];
|
||||
}
|
||||
setState({ selectedRowKeys: selectedKeys });
|
||||
};
|
||||
|
||||
const handler: DripTableContainerHandle = {
|
||||
...state,
|
||||
setTableState: setState,
|
||||
select,
|
||||
_CTX_SOURCE: 'PROVIDER', // context 来源于 drip-table-provider
|
||||
};
|
||||
return handler;
|
||||
};
|
||||
|
||||
export interface DripTableProviderProps {}
|
||||
|
||||
const findChildReactNode = (children: React.ReactNode | undefined, filter: (child: React.ReactNode) => boolean): React.ReactNode => {
|
||||
const stack = [children];
|
||||
while (stack.length > 0) {
|
||||
const child = stack.pop();
|
||||
if (Array.isArray(child)) {
|
||||
stack.push(...child);
|
||||
} else if (child && filter(child)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return void 0;
|
||||
};
|
||||
|
||||
const DripTableContainer: React.ForwardRefRenderFunction<DripTableContainerHandle, React.PropsWithChildren<DripTableProviderProps>> = (props, ref) => {
|
||||
const tableChild = findChildReactNode(
|
||||
props.children,
|
||||
(child) => {
|
||||
const childType = child && typeof child === 'object' && 'type' in child ? child.type : void 0;
|
||||
const childName = typeof childType === 'function' ? childType.name : childType;
|
||||
return childName === DripTable.name;
|
||||
},
|
||||
);
|
||||
const tableProps: DripTableProps<DripTableRecordTypeBase> | undefined = tableChild && typeof tableChild === 'object' && 'type' in tableChild
|
||||
? tableChild.props
|
||||
: void 0;
|
||||
if (tableProps) {
|
||||
const ConfigProvider = tableProps.driver.components.ConfigProvider;
|
||||
const context = createTableContext(tableProps);
|
||||
useImperativeHandle(ref, () => context);
|
||||
return (
|
||||
<ConfigProvider locale={tableProps?.driver.locale}>
|
||||
<DripTableContext.Provider {...props} value={context} />
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
return <div />;
|
||||
};
|
||||
|
||||
const DripTableProvider = forwardRef(DripTableContainer);
|
||||
|
||||
const withTable = <T extends DripTableRecordTypeBase>(Component: React.FC<DripTableProps<T>>) => (props: DripTableProps<T>) => (
|
||||
<DripTableProvider>
|
||||
<Component {...props} />
|
||||
</DripTableProvider>
|
||||
);
|
||||
|
||||
export { DripTableProvider, withTable };
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import { DripTableDriver, EventLike } from '@/types';
|
||||
import { DripTableBuiltInComponentEvent } from '.';
|
||||
|
||||
export interface DripTableComponentSchema {
|
||||
/** 唯一标识,不做展示用 */
|
||||
$id: string;
|
||||
/** 组件类型唯一标识 */
|
||||
type: string;
|
||||
/** 表头,组件名 */
|
||||
title: string;
|
||||
/** 表格列宽 */
|
||||
width?: string | number;
|
||||
/** 表格列对齐 */
|
||||
align?: 'left' | 'center' | 'right';
|
||||
/** 列数据在数据项中对应的路径,支持通过数组查询嵌套路径 */
|
||||
dataIndex: string | string[];
|
||||
default?: string;
|
||||
/** 表头说明 */
|
||||
description?: string;
|
||||
/** 是否固定列 */
|
||||
fixed?: boolean;
|
||||
}
|
||||
|
||||
export interface DripTableComponentProps<
|
||||
RecordType,
|
||||
Schema extends DripTableComponentSchema = DripTableComponentSchema,
|
||||
ComponentEvent extends EventLike = never,
|
||||
Ext = unknown,
|
||||
> {
|
||||
/**
|
||||
* 底层渲染驱动
|
||||
*/
|
||||
driver: DripTableDriver<RecordType>;
|
||||
/**
|
||||
* 当前渲染列参数
|
||||
*/
|
||||
schema: Schema;
|
||||
/**
|
||||
* 当前渲染行数据结构 `list[i]`
|
||||
*/
|
||||
data: RecordType;
|
||||
/**
|
||||
* 当前渲染单元格数据 `data[schema.dataIndex]`
|
||||
*/
|
||||
value: unknown;
|
||||
/**
|
||||
* 额外透传数据
|
||||
*/
|
||||
ext?: Ext;
|
||||
/**
|
||||
* 是否处于预览模式(不响应事件)
|
||||
*/
|
||||
preview?: boolean | {
|
||||
/**
|
||||
* 自定义列渲染函数
|
||||
*/
|
||||
columnRenderer?: (columnSchema: Schema, columnElement: JSX.Element) => JSX.Element;
|
||||
};
|
||||
onClick?: (name: string) => void;
|
||||
/**
|
||||
* 自定义事件触发器
|
||||
*/
|
||||
fireEvent: (event: ComponentEvent | DripTableBuiltInComponentEvent) => void;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : helloqian12138 (johnhello12138@163.com)
|
||||
* @modifier : helloqian12138 (johnhello12138@163.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { get } from '@/drip-table/utils';
|
||||
import { DripTableComponentProps, DripTableComponentSchema } from '../component';
|
||||
|
||||
export interface DTCImageSchema extends DripTableComponentSchema {
|
||||
'ui:type': 'image';
|
||||
/** 兜底图案 */
|
||||
noDataUrl?: string;
|
||||
popover?: boolean;
|
||||
previewImg?: boolean;
|
||||
// fullScreen?: boolean;
|
||||
imgWidth?: number;
|
||||
imgHeight?: number;
|
||||
}
|
||||
|
||||
interface DTCImageProps<RecordType> extends DripTableComponentProps<RecordType, DTCImageSchema> { }
|
||||
|
||||
interface DTCImageState { }
|
||||
|
||||
export default class DTCImage<RecordType> extends React.PureComponent<DTCImageProps<RecordType>, DTCImageState> {
|
||||
private DEFAULT_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==';
|
||||
|
||||
private get value() {
|
||||
const schema = this.props.schema;
|
||||
const dataIndex = schema.dataIndex;
|
||||
return get(this.props.data, dataIndex, '');
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { popover, imgWidth, imgHeight, noDataUrl, previewImg } = this.props.schema;
|
||||
const Popover = this.props.driver.components.Popover;
|
||||
const Image = this.props.driver.components.Image;
|
||||
const imgFragment = (
|
||||
<Image
|
||||
width={imgWidth}
|
||||
height={imgHeight}
|
||||
src={this.value}
|
||||
preview={this.props.preview ? false : previewImg}
|
||||
fallback={noDataUrl || this.DEFAULT_IMG}
|
||||
/>
|
||||
);
|
||||
return popover && !this.props.preview
|
||||
? (
|
||||
<Popover content={(<img src={this.value} />)}>
|
||||
{ imgFragment }
|
||||
</Popover>
|
||||
)
|
||||
: imgFragment;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : helloqian12138 (johnhello12138@163.com)
|
||||
* @modifier : helloqian12138 (johnhello12138@163.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
import DTCImage from './image';
|
||||
import DTCLink, { DTCLinkEvent } from './link';
|
||||
import DTCRenderHTML from './render-html';
|
||||
import DTCText from './text';
|
||||
import DTCTag from './tag';
|
||||
|
||||
export type { DripTableComponentProps, DripTableComponentSchema } from './component';
|
||||
|
||||
export type DripTableBuiltInComponentEvent =
|
||||
| DTCLinkEvent;
|
||||
|
||||
const DripTableBuiltInComponents = {
|
||||
image: DTCImage,
|
||||
links: DTCLink,
|
||||
text: DTCText,
|
||||
tag: DTCTag,
|
||||
'render-html': DTCRenderHTML,
|
||||
};
|
||||
export default DripTableBuiltInComponents;
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { DripTableComponentProps, DripTableComponentSchema } from '../component';
|
||||
|
||||
export interface DTCLinkSchema extends DripTableComponentSchema {
|
||||
'ui:type': 'link';
|
||||
mode: 'single' | 'multiple';
|
||||
name?: string;
|
||||
label?: string;
|
||||
href?: string;
|
||||
event?: string;
|
||||
target?: string;
|
||||
operates?: {
|
||||
name?: string;
|
||||
label?: string;
|
||||
href?: string;
|
||||
event?: string;
|
||||
target?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface DTCLinkEvent {
|
||||
type: 'drip-link-click';
|
||||
payload: string;
|
||||
}
|
||||
|
||||
interface DTCLinkProps<RecordType> extends DripTableComponentProps<RecordType, DTCLinkSchema> { }
|
||||
|
||||
interface DTCLinkState {}
|
||||
|
||||
export default class DTCLink<RecordType> extends React.PureComponent<DTCLinkProps<RecordType>, DTCLinkState> {
|
||||
private get configured() {
|
||||
const schema = this.props.schema;
|
||||
if (schema.mode === 'multiple') {
|
||||
if (schema.operates) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (schema.mode === 'single' && (schema.href || schema.event)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const { schema } = this.props;
|
||||
if (!this.configured) {
|
||||
return <div style={{ color: 'red' }}>属性配置错误</div>;
|
||||
}
|
||||
if (schema.mode === 'single') {
|
||||
const event = schema.event;
|
||||
if (event) {
|
||||
return (
|
||||
<a
|
||||
onClick={() => {
|
||||
if (this.props.preview) {
|
||||
return;
|
||||
}
|
||||
this.props.fireEvent({ type: 'drip-link-click', payload: event });
|
||||
}}
|
||||
>
|
||||
{ schema.label }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <a href={schema.href} target={schema.target}>{ schema.label }</a>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
schema.operates?.map((config, index) => {
|
||||
const event = config.event;
|
||||
if (event) {
|
||||
return (
|
||||
<a
|
||||
style={{ marginRight: '5px' }}
|
||||
key={config.name || index}
|
||||
onClick={() => {
|
||||
if (this.props.preview) {
|
||||
return;
|
||||
}
|
||||
this.props.fireEvent({ type: 'drip-link-click', payload: event });
|
||||
}}
|
||||
>
|
||||
{ config.label }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <a style={{ marginRight: '5px' }} key={config.name || index} href={config.href} target={config.target}>{ config.label }</a>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import RichText from '@/components/RichText';
|
||||
import { DripTableComponentProps, DripTableComponentSchema } from '../component';
|
||||
|
||||
export interface DTCRenderHTMLSchema extends DripTableComponentSchema {
|
||||
'ui:type': 'render-html';
|
||||
paramName: string | string[];
|
||||
render: string;
|
||||
}
|
||||
|
||||
interface DTCRenderHTMLProps<RecordType> extends DripTableComponentProps<RecordType, DTCRenderHTMLSchema> { }
|
||||
|
||||
interface DTCRenderHTMLState { }
|
||||
|
||||
export default class DTCRenderHTML<RecordType> extends React.PureComponent<DTCRenderHTMLProps<RecordType>, DTCRenderHTMLState> {
|
||||
public render(): JSX.Element {
|
||||
const { data, schema } = this.props;
|
||||
try {
|
||||
const html = new Function('rec', schema.render)(data);
|
||||
if (typeof html === 'object') {
|
||||
return (
|
||||
<div>{ Object.prototype.toString.call(html) }</div>
|
||||
);
|
||||
}
|
||||
return <RichText html={html || ''} />;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return <div style={{ color: 'red' }}>渲染异常</div>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : helloqian12138 (johnhello12138@163.com)
|
||||
* @modifier : helloqian12138 (johnhello12138@163.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { get } from '@/drip-table/utils';
|
||||
import { DripTableComponentProps, DripTableComponentSchema } from '../component';
|
||||
|
||||
export interface DTCTagSchema extends DripTableComponentSchema {
|
||||
'ui:type': 'tag';
|
||||
/** 模式 */
|
||||
// mode?: 'single' | 'multi';
|
||||
/** 字体颜色 */
|
||||
color?: string;
|
||||
/** 边框颜色 */
|
||||
borderColor?: string;
|
||||
/** 背景色 */
|
||||
backgroundColor?: string;
|
||||
/** 圆角半径 */
|
||||
radius?: number;
|
||||
/** 前缀 */
|
||||
prefix?: string;
|
||||
/** 后缀 */
|
||||
subfix?: string;
|
||||
/** 静态文案 */
|
||||
content?: string;
|
||||
/** 枚举 */
|
||||
tagOptions?: { label: string; value: string | number }[];
|
||||
}
|
||||
|
||||
interface DTCTagProps<RecordType> extends DripTableComponentProps<RecordType, DTCTagSchema> { }
|
||||
|
||||
interface DTCTagState { }
|
||||
|
||||
export default class DTCTag<RecordType> extends React.PureComponent<DTCTagProps<RecordType>, DTCTagState> {
|
||||
private get value() {
|
||||
const schema = this.props.schema;
|
||||
const dataIndex = schema.dataIndex;
|
||||
return get(this.props.data, dataIndex, '');
|
||||
}
|
||||
|
||||
public render() {
|
||||
const Tag = this.props.driver.components.Tag;
|
||||
const schema = this.props.schema;
|
||||
const value = this.value;
|
||||
return (
|
||||
<div>
|
||||
{ schema.prefix || '' }
|
||||
<Tag
|
||||
color={schema.color}
|
||||
style={{
|
||||
color: schema.color,
|
||||
borderColor: schema.borderColor,
|
||||
backgroundColor: schema.backgroundColor,
|
||||
borderRadius: schema.radius,
|
||||
}}
|
||||
>
|
||||
{ schema.content || schema.tagOptions?.find(item => item.value === value)?.label || value }
|
||||
</Tag>
|
||||
{ schema.subfix || '' }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
.word-break {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.word-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.max-row {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { DripTableComponentProps, DripTableComponentSchema } from '../component';
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface DTCTextSchema extends DripTableComponentSchema {
|
||||
'ui:type': 'text';
|
||||
/** 字体大小 */
|
||||
fontSize?: string;
|
||||
/** 最大行数,超出展示... */
|
||||
maxRow?: number;
|
||||
/** 行高 */
|
||||
lineHeight?: number;
|
||||
/** 展示模式:单行文本、多行文本、自定义文本 */
|
||||
mode?: 'single' | 'multiple' | 'custom';
|
||||
/** 自定义文本 -- 渲染格式/代码 */
|
||||
format?: string;
|
||||
/** 兜底文案 */
|
||||
noDataValue?: string;
|
||||
/** 前缀文案 */
|
||||
prefix?: string;
|
||||
/** 后缀文案 */
|
||||
subfix?: string;
|
||||
enumValue?: string[];
|
||||
enumLabel?: string[];
|
||||
tooltip?: boolean | string;
|
||||
ellipsis?: boolean;
|
||||
/** 字段配置 */
|
||||
params?: {
|
||||
dataIndex: string;
|
||||
/** 前缀文案 */
|
||||
prefix?: string;
|
||||
/** 后缀文案 */
|
||||
subfix?: string;
|
||||
enumValue?: string[];
|
||||
enumLabel?: string[];
|
||||
tooltip?: boolean | string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface DTCTextProps<RecordType> extends DripTableComponentProps<RecordType, DTCTextSchema> { }
|
||||
|
||||
interface DTCTextState {}
|
||||
|
||||
export default class DTCText<RecordType> extends React.PureComponent<DTCTextProps<RecordType>, DTCTextState> {
|
||||
private get configured() {
|
||||
const schema = this.props.schema;
|
||||
if (schema.mode === 'custom') {
|
||||
if (schema.format) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (schema.mode === 'multiple') {
|
||||
if (schema.params) {
|
||||
return schema.params.length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (schema.mode === 'single') {
|
||||
if (typeof schema.dataIndex === 'object') {
|
||||
return Object.keys(schema.dataIndex).length > 0;
|
||||
}
|
||||
return !!schema.dataIndex;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private get fontSize() {
|
||||
let fontSize = String(this.props.schema.fontSize || '').trim();
|
||||
if ((/^[0-9]+$/uig).test(fontSize)) {
|
||||
fontSize += 'px';
|
||||
}
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public get classNames() {
|
||||
const schema = this.props.schema;
|
||||
let classNames = styles['word-break'];
|
||||
if (schema.ellipsis) {
|
||||
classNames += ` ${styles['word-ellipsis']}`;
|
||||
}
|
||||
if (schema.maxRow) {
|
||||
classNames += ` ${styles['max-row']}`;
|
||||
}
|
||||
return classNames;
|
||||
}
|
||||
|
||||
private get lineHeight() {
|
||||
return this.props.schema.lineHeight || 1.5;
|
||||
}
|
||||
|
||||
public get styles(): React.CSSProperties {
|
||||
const schema = this.props.schema;
|
||||
const wrapperStyles: React.CSSProperties = {
|
||||
fontSize: this.fontSize,
|
||||
lineHeight: this.lineHeight,
|
||||
};
|
||||
if (schema.maxRow) {
|
||||
wrapperStyles.WebkitLineClamp = schema.maxRow;
|
||||
wrapperStyles.maxHeight = `${schema.maxRow * this.lineHeight}em`;
|
||||
}
|
||||
return wrapperStyles;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const { schema, data } = this.props;
|
||||
const { mode,
|
||||
dataIndex,
|
||||
noDataValue,
|
||||
format,
|
||||
prefix,
|
||||
subfix,
|
||||
params,
|
||||
enumValue,
|
||||
enumLabel,
|
||||
tooltip } = schema;
|
||||
if (!this.configured) {
|
||||
return <div style={{ color: 'red' }}>未配置字段</div>;
|
||||
}
|
||||
if (mode === 'custom') {
|
||||
return (
|
||||
<pre style={{ fontSize: this.fontSize }}>
|
||||
{
|
||||
(format || '')
|
||||
.replace(/\{\{(.+?)\}\}/uig, (s, s1) => {
|
||||
try {
|
||||
const text = new Function('rec', `return ${s1}`)(data);
|
||||
if (typeof text === 'string') {
|
||||
return text;
|
||||
}
|
||||
return JSON.stringify(text);
|
||||
} catch {}
|
||||
return '';
|
||||
})
|
||||
}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
if (mode === 'single') {
|
||||
const noDataStr = noDataValue || '';
|
||||
let value = data[dataIndex as string];
|
||||
if (enumValue && enumLabel) {
|
||||
const index = enumValue.indexOf(value);
|
||||
value = enumLabel[index];
|
||||
}
|
||||
const contentStr = `${prefix || ''} ${value || noDataStr} ${subfix || ''}`;
|
||||
const Tooltip = this.props.driver.components.Tooltip;
|
||||
return (
|
||||
<div style={this.styles} className={this.classNames}>
|
||||
{ tooltip
|
||||
? (
|
||||
<Tooltip title={typeof tooltip === 'string' ? tooltip : contentStr} placement="top">
|
||||
{ contentStr }
|
||||
</Tooltip>
|
||||
)
|
||||
: contentStr }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (mode === 'multiple') {
|
||||
const noDataStr = noDataValue || '';
|
||||
const Tooltip = this.props.driver.components.Tooltip;
|
||||
return (
|
||||
<div style={this.styles} className={this.classNames}>
|
||||
{ (params || []).map((config, i) => {
|
||||
let value = data[config.dataIndex];
|
||||
if (enumValue && enumLabel) {
|
||||
const index = enumValue.indexOf(value);
|
||||
value = enumLabel[index];
|
||||
}
|
||||
const contentStr = `${config.prefix || ''} ${value || noDataStr} ${config.subfix || ''}`;
|
||||
return (
|
||||
<div key={i}>
|
||||
{ tooltip
|
||||
? (
|
||||
<Tooltip title={typeof tooltip === 'string' ? tooltip : contentStr} placement="top">
|
||||
{ contentStr }
|
||||
</Tooltip>
|
||||
)
|
||||
: contentStr }
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div />;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
margin-bottom: 0;
|
||||
line-height: 32px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
min-width: 72px;
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DripTableDriver, DripTableRecordTypeBase } from '@/types';
|
||||
import { DripTableProps } from '@/index';
|
||||
import RichText from '@/components/RichText';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
type Config = {
|
||||
type: 'title' | 'search' | 'addButton' | 'null';
|
||||
/**
|
||||
* 跨度;取值 0-24
|
||||
*/
|
||||
span: number;
|
||||
/**
|
||||
* 宽度;如果是string,可以是px也可以是%
|
||||
*/
|
||||
width?: number | string;
|
||||
position: 'topLeft' | 'topCenter' | 'topRight';
|
||||
};
|
||||
|
||||
interface TitleConfig extends Config {
|
||||
type: 'title';
|
||||
title: string;
|
||||
html?: boolean;
|
||||
}
|
||||
|
||||
interface SearchConfig extends Config {
|
||||
type: 'search';
|
||||
placeholder?: string;
|
||||
allowClear?: boolean;
|
||||
searchBtnText?: string;
|
||||
searchStyle?: React.CSSProperties;
|
||||
searchClassName?: string;
|
||||
size?: 'large' | 'middle' | 'small';
|
||||
searchKeys?: { label: string; value: number | string }[];
|
||||
searchKeyValue?: number | string;
|
||||
props?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface AddButtonConfig extends Config {
|
||||
type: 'addButton';
|
||||
showIcon?: boolean;
|
||||
addBtnText?: string;
|
||||
addBtnStyle?: React.CSSProperties;
|
||||
addBtnClassName?: string;
|
||||
}
|
||||
|
||||
interface NullConfig extends Config {
|
||||
type: 'null';
|
||||
}
|
||||
|
||||
export interface DripTableHeaderProps<RecordType extends DripTableRecordTypeBase> {
|
||||
driver: DripTableDriver<RecordType>;
|
||||
title?: TitleConfig | NullConfig;
|
||||
search?: SearchConfig | NullConfig;
|
||||
addButton?: AddButtonConfig | NullConfig;
|
||||
onSearch?: DripTableProps<RecordType>['onSearch'];
|
||||
onAddButtonClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||
}
|
||||
|
||||
const Header = <RecordType extends DripTableRecordTypeBase>(props: DripTableHeaderProps<RecordType>) => {
|
||||
const Button = props.driver.components.Button;
|
||||
const Col = props.driver.components.Col;
|
||||
const Input = props.driver.components.Input;
|
||||
const PlusOutlined = props.driver.icons.PlusOutlined;
|
||||
const Row = props.driver.components.Row;
|
||||
const Select = props.driver.components.Select;
|
||||
const TableSearch = props.driver.components.TableSearch;
|
||||
|
||||
const [searchStr, setSearchStr] = useState('');
|
||||
const [searchKey, setSearchKey] = useState<SearchConfig['searchKeyValue']>(void 0);
|
||||
|
||||
if (!props.title && !props.search && !props.addButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderColumnContent = (config: TitleConfig | AddButtonConfig | SearchConfig | NullConfig) => {
|
||||
if (config.type === 'title') {
|
||||
return config.html
|
||||
? <RichText html={config.title} />
|
||||
: <h3 className={styles['header-title']}>{ config.title }</h3>;
|
||||
}
|
||||
if (config.type === 'search') {
|
||||
if (TableSearch) {
|
||||
return (
|
||||
<TableSearch
|
||||
{...config.props}
|
||||
driver={props.driver}
|
||||
onSearch={(searchParams) => { props.onSearch?.(searchParams); }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={config.searchStyle} className={`${styles['search-container']} ${config.searchClassName}`}>
|
||||
{ config.searchKeys && (
|
||||
<Select
|
||||
defaultValue={config.searchKeyValue}
|
||||
className={styles['search-select']}
|
||||
value={searchKey}
|
||||
onChange={value => setSearchKey(value)}
|
||||
>
|
||||
{ config.searchKeys.map((item, i) => <Select.Option key={i} value={item.value}>{ item.label }</Select.Option>) }
|
||||
</Select>
|
||||
) }
|
||||
<Input.Search
|
||||
allowClear={config.allowClear}
|
||||
placeholder={config.placeholder}
|
||||
enterButton={config.searchBtnText || true}
|
||||
size={config.size}
|
||||
value={searchStr}
|
||||
onChange={e => setSearchStr(e.target.value.trim())}
|
||||
onSearch={(value) => { props.onSearch?.({ key: searchKey, value }); }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (config.type === 'addButton') {
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={config.showIcon && <PlusOutlined />}
|
||||
style={config.addBtnStyle}
|
||||
className={config.addBtnClassName}
|
||||
onClick={props.onAddButtonClick}
|
||||
>
|
||||
{ config.addBtnText }
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderColumn = (position: 'topLeft' | 'topCenter' | 'topRight') => {
|
||||
const config = [props.title, props.search, props.addButton].find(item => item && item.position === position);
|
||||
if (!config) {
|
||||
return <Col span={0} />;
|
||||
}
|
||||
const span = config.span || 8;
|
||||
return (
|
||||
<Col span={span} style={{ width: config.width }}>
|
||||
{ renderColumnContent(config) }
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['header-container']}>
|
||||
<Row>
|
||||
{ renderColumn('topLeft') }
|
||||
{ renderColumn('topCenter') }
|
||||
{ renderColumn('topRight') }
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
.drip-table-wrapper {
|
||||
background: #ffffff;
|
||||
padding: 0 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mb2 {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.mr {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
margin: 12px auto;
|
||||
}
|
||||
|
||||
.header-container.left {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.header-container.right {
|
||||
margin-right: 0;
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { ColumnConfig, DripTableDriver, DripTableReactComponentProps, DripTableRecordTypeBase, DripTableSchema, EventLike } from '@/types';
|
||||
import { useState, useTable } from '@/hooks';
|
||||
import ErrorBoundary from '@/components/ErrorBoundary';
|
||||
|
||||
import Header from './header';
|
||||
import VirtualTable from './virtual-table';
|
||||
import DripTableBuiltInComponents, { DripTableBuiltInComponentEvent, DripTableComponentProps, DripTableComponentSchema } from './components';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
export interface DripTableProps<RecordType extends DripTableRecordTypeBase, CustomComponentEvent extends EventLike = never, Ext = unknown> {
|
||||
/**
|
||||
* 底层组件驱动
|
||||
*/
|
||||
driver: DripTableDriver<RecordType>;
|
||||
/**
|
||||
* 样式表类名
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* 自定义样式表
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
/**
|
||||
* 表单 Schema
|
||||
*/
|
||||
schema: DripTableSchema;
|
||||
/**
|
||||
* 表格行主键
|
||||
*/
|
||||
rowKey?: string;
|
||||
/**
|
||||
* 数据源
|
||||
*/
|
||||
dataSource: RecordType[];
|
||||
/**
|
||||
* 当前选中的行键
|
||||
*/
|
||||
selectedRowKeys?: React.Key[];
|
||||
/**
|
||||
* 附加数据
|
||||
*/
|
||||
ext?: Ext;
|
||||
/**
|
||||
* 数据源总条数
|
||||
*/
|
||||
total?: number;
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
currentPage?: number;
|
||||
/**
|
||||
* 加载中
|
||||
*/
|
||||
loading?: boolean;
|
||||
/**
|
||||
* 组件库
|
||||
*/
|
||||
components?: {
|
||||
[libName: string]: {
|
||||
[componentName: string]:
|
||||
new (props: DripTableComponentProps<RecordType, DripTableComponentSchema, CustomComponentEvent, Ext>)
|
||||
=> React.PureComponent<DripTableComponentProps<RecordType, DripTableComponentSchema, CustomComponentEvent, Ext>>;
|
||||
};
|
||||
};
|
||||
/** 生命周期 */
|
||||
componentDidMount?: () => void;
|
||||
componentDidUpdate?: () => void;
|
||||
componentWillUnmount?: () => void;
|
||||
/**
|
||||
* 点击行
|
||||
*/
|
||||
onRowClick?: (record: RecordType | RecordType[], index?: number | string | (number | string)[]) => void;
|
||||
/**
|
||||
* 双击行
|
||||
*/
|
||||
onRowDoubleClick?: (record: RecordType | RecordType[], index?: number | string | (number | string)[]) => void;
|
||||
/**
|
||||
* 选择行变化
|
||||
*/
|
||||
onSelectionChange?: (selectedKeys: React.Key[], selectedRows: RecordType[]) => void;
|
||||
/**
|
||||
* 搜索触发
|
||||
*/
|
||||
onSearch?: (searchParams: { key?: number | string; value: string } | Record<string, unknown>) => void;
|
||||
/**
|
||||
* 点击添加按钮触发
|
||||
*/
|
||||
onAddButtonClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||
/**
|
||||
* 过滤器触发
|
||||
*/
|
||||
onFilter?: (column: ColumnConfig) => void;
|
||||
/**
|
||||
* 页码/页大小变化
|
||||
*/
|
||||
onPageChange?: (currentPage: number, pageSize: number) => void;
|
||||
/**
|
||||
* 通用事件机制
|
||||
*/
|
||||
onEvent?: (event: DripTableBuiltInComponentEvent | CustomComponentEvent, record: RecordType, index: number) => void;
|
||||
}
|
||||
|
||||
const DripTable = <RecordType extends Record<string, unknown>, CustomComponentEvent extends EventLike = never, Ext = unknown>
|
||||
(props: DripTableProps<RecordType, CustomComponentEvent, Ext>): JSX.Element => {
|
||||
const Table = props.driver.components?.Table;
|
||||
const Popover = props.driver.components?.Popover;
|
||||
const QuestionCircleOutlined = props.driver.icons?.QuestionCircleOutlined;
|
||||
type TableColumn = NonNullable<DripTableReactComponentProps<typeof Table>['columns']>[number];
|
||||
|
||||
const initialState = useTable();
|
||||
const paginationConfig = props.schema?.configs?.pagination || {} as { current: number; total: number; pageSize: number };
|
||||
initialState.pagination.pageSize = paginationConfig.pageSize || 10;
|
||||
const [tableState, setTableState] = initialState._CTX_SOURCE === 'CONTEXT' ? useState(initialState) : [initialState, initialState.setTableState];
|
||||
|
||||
const rootRef = useRef<HTMLDivElement>(null); // ProTable组件的ref
|
||||
|
||||
const {
|
||||
rowKey = '$id',
|
||||
} = props;
|
||||
const dataSource = props.dataSource.map((item, index) => ({
|
||||
...item,
|
||||
[rowKey]: typeof item[rowKey] === 'undefined' ? index : item[rowKey],
|
||||
}));
|
||||
|
||||
/**
|
||||
* 根据组件类型,生成表格渲染器
|
||||
* @param schema Schema
|
||||
* @returns 表格
|
||||
*/
|
||||
const renderGenerator = (schema: ColumnConfig): (value: unknown, record: RecordType, index: number) => JSX.Element | string | null => {
|
||||
const BuiltInComponent = DripTableBuiltInComponents[schema['ui:type']] as new() => React.PureComponent<DripTableComponentProps<RecordType>>;
|
||||
if (BuiltInComponent) {
|
||||
return (value, record, index) => (
|
||||
<BuiltInComponent
|
||||
driver={props.driver}
|
||||
value={value}
|
||||
data={record}
|
||||
schema={{ ...schema, ...schema['ui:props'] }}
|
||||
ext={props.ext}
|
||||
fireEvent={event => props.onEvent?.(event, record, index)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const [libName, componentName] = schema['ui:type'].split('::');
|
||||
if (libName && componentName) {
|
||||
const ExtraComponent = props.components?.[libName]?.[componentName];
|
||||
if (ExtraComponent) {
|
||||
return (value, record, index) => (
|
||||
<ExtraComponent
|
||||
driver={props.driver}
|
||||
value={value}
|
||||
data={record}
|
||||
schema={schema}
|
||||
ext={props.ext}
|
||||
fireEvent={event => props.onEvent?.(event, record, index)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return value => JSON.stringify(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据列 Schema,生成表格列配置
|
||||
* @param schemaColumn Schema Column
|
||||
* @returns 表格列配置
|
||||
*/
|
||||
const columnGenerator = (schemaColumn: ColumnConfig): TableColumn => {
|
||||
let width = String(schemaColumn.width).trim();
|
||||
if ((/^[0-9]+$/uig).test(width)) {
|
||||
width += 'px';
|
||||
}
|
||||
const column: TableColumn = {
|
||||
width,
|
||||
align: schemaColumn.align,
|
||||
title: schemaColumn.title,
|
||||
dataIndex: schemaColumn.dataIndex,
|
||||
fixed: schemaColumn.fixed,
|
||||
};
|
||||
if (schemaColumn.description) {
|
||||
column.title = (
|
||||
<div>
|
||||
<span style={{ marginRight: '6px' }}>{ schemaColumn.title }</span>
|
||||
<Popover placement="top" title="" content={schemaColumn.description}>
|
||||
<QuestionCircleOutlined />
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (props.schema.configs.ellipsis) {
|
||||
column.ellipsis = true;
|
||||
}
|
||||
if (!column.render) {
|
||||
column.render = renderGenerator(schemaColumn);
|
||||
}
|
||||
return column;
|
||||
};
|
||||
|
||||
const tableProps: Parameters<DripTableDriver<RecordType>['components']['Table']>[0] = {
|
||||
rowKey,
|
||||
columns: props.schema.columns?.map(columnGenerator) || [],
|
||||
dataSource,
|
||||
pagination: props.schema.configs.pagination === false
|
||||
? false as const
|
||||
: {
|
||||
onChange: (page, pageSize) => {
|
||||
if (pageSize === void 0) {
|
||||
pageSize = tableState.pagination.pageSize;
|
||||
}
|
||||
setTableState({ pagination: { ...tableState.pagination, current: page, pageSize } });
|
||||
props.onPageChange?.(page, pageSize);
|
||||
},
|
||||
size: props.schema.configs.pagination?.size === void 0 ? 'small' : props.schema.configs.pagination.size,
|
||||
pageSize: tableState.pagination.pageSize,
|
||||
total: props.total === void 0 ? dataSource.length : props.total,
|
||||
current: props.currentPage || tableState.pagination.current,
|
||||
position: [props.schema.configs.pagination?.position || 'bottomRight'],
|
||||
showLessItems: props.schema.configs.pagination?.showLessItems,
|
||||
showQuickJumper: props.schema.configs.pagination?.showQuickJumper,
|
||||
showSizeChanger: props.schema.configs.pagination?.showSizeChanger,
|
||||
},
|
||||
loading: props.loading,
|
||||
size: props.schema.configs.size,
|
||||
bordered: props.schema.configs.bordered,
|
||||
innerBordered: props.schema.configs.innerBordered,
|
||||
// ellipsis: schema.configs.ellipsis,
|
||||
sticky: props.schema.configs.isVirtualList ? false : props.schema.configs.sticky,
|
||||
rowSelection: props.schema.configs.rowSelection && !props.schema.configs.isVirtualList
|
||||
? {
|
||||
selectedRowKeys: props.selectedRowKeys || tableState.selectedRowKeys,
|
||||
onChange: (selectedKeys, selectedRows) => {
|
||||
setTableState({ selectedRowKeys: [...selectedKeys] });
|
||||
props.onSelectionChange?.(selectedKeys, selectedRows);
|
||||
},
|
||||
}
|
||||
: void 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<ErrorBoundary driver={props.driver}>
|
||||
<div
|
||||
className={`${styles['drip-table-wrapper']} ${props.className || ''}`}
|
||||
style={props.style}
|
||||
ref={rootRef}
|
||||
>
|
||||
{
|
||||
props.schema.configs.header
|
||||
? (
|
||||
<Header
|
||||
{...(typeof props.schema.configs.header !== 'boolean' ? props.schema.configs.header : {})}
|
||||
driver={props.driver}
|
||||
onSearch={props.onSearch}
|
||||
onAddButtonClick={props.onAddButtonClick}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{
|
||||
props.schema.configs.isVirtualList
|
||||
? (
|
||||
<VirtualTable
|
||||
{...tableProps}
|
||||
driver={props.driver}
|
||||
scroll={{ y: props.schema.configs.scrollY || 300, x: '100vw' }}
|
||||
/>
|
||||
)
|
||||
: <Table {...tableProps} />
|
||||
}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default DripTable;
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取对象指定下标值
|
||||
* @param data 基础对象
|
||||
* @param indexes 下标或下标数组
|
||||
* @param defaultValue 默认值
|
||||
* @returns 值
|
||||
*/
|
||||
export const get = (data: unknown, indexes: string | string[], defaultValue: unknown = void 0) => {
|
||||
if (typeof data !== 'object' || !data) {
|
||||
return void 0;
|
||||
}
|
||||
if (typeof indexes === 'string') {
|
||||
return data[indexes];
|
||||
}
|
||||
if (Array.isArray(indexes)) {
|
||||
return indexes.reduce((d, key) => (d ? d[key] : void 0), data);
|
||||
}
|
||||
return defaultValue;
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* This file is part of the drip-table launch.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
.virtual-table-cell {
|
||||
box-sizing: border-box;
|
||||
padding: 12px 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { VariableSizeGrid } from 'react-window';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
import { DripTableDriver, DripTableRecordTypeBase } from '../..';
|
||||
|
||||
import styles from './index.module.css';
|
||||
import { get } from '../utils';
|
||||
|
||||
// 根据size来控制行高
|
||||
const rowHeightMap = {
|
||||
small: 49,
|
||||
middle: 54,
|
||||
large: 88,
|
||||
};
|
||||
|
||||
function VirtualTable<RecordType extends DripTableRecordTypeBase>(props: { driver: DripTableDriver<RecordType> } & Parameters<DripTableDriver<RecordType>['components']['Table']>[0]) {
|
||||
const { columns = [], scroll, size, driver } = props;
|
||||
const Table = driver.components.Table;
|
||||
const [tableWidth, setTableWidth] = useState(0);
|
||||
|
||||
const rowHeight = rowHeightMap[size || 'middle'] || rowHeightMap.middle;
|
||||
|
||||
// 减去已经设定的宽度,剩下的宽度均分
|
||||
const initWidthColumn = columns.filter(c => c.width && c.width !== 'undefined');
|
||||
const widthColumnCount = columns.length - initWidthColumn.length;
|
||||
const initWidth = initWidthColumn.reduce((summary, c) => summary + ((typeof c.width === 'string' ? Number.parseFloat(c.width) : c.width) || 0), 0);
|
||||
const restWidth = tableWidth - initWidth;
|
||||
// 如果当设定宽度大于table宽度,则默认剩余平均宽度为100
|
||||
const restWidthAvg = restWidth > 0 ? Math.floor(restWidth / widthColumnCount) : 100;
|
||||
const mergedColumns = columns.map((column) => {
|
||||
if (column.width && column.width !== 'undefined') {
|
||||
if (typeof column.width === 'string') {
|
||||
column.width = Number(column.width.replace('px', ''));
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
return {
|
||||
...column,
|
||||
width: restWidthAvg,
|
||||
};
|
||||
});
|
||||
|
||||
const fixedColumns = mergedColumns.filter(c => c.fixed);
|
||||
const fixedColumnsWidth = fixedColumns.reduce((summary, c) => summary + ((typeof c.width === 'string' ? Number.parseFloat(c.width) : c.width) || 0), 0);
|
||||
|
||||
const gridRef = useRef<VariableSizeGrid>(null);
|
||||
const fixedGridRef = useRef<VariableSizeGrid>(null);
|
||||
// const [refConnector] = useState((): { scrollLeft: number } => {
|
||||
// const connector = { scrollLeft: 0 };
|
||||
// Object.defineProperty(connector, 'scrollLeft', {
|
||||
// get: () => null,
|
||||
// set: (scrollLeft: number) => { gridRef.current?.scrollTo({ scrollLeft }); },
|
||||
// });
|
||||
// return connector;
|
||||
// });
|
||||
|
||||
const resetVirtualGrid = () => {
|
||||
gridRef.current?.resetAfterIndices({
|
||||
columnIndex: 0,
|
||||
rowIndex: 0,
|
||||
shouldForceUpdate: true,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => resetVirtualGrid, [tableWidth]);
|
||||
|
||||
const renderVirtualList: NonNullable<Parameters<DripTableDriver<RecordType>['components']['Table']>[0]['components']>['body'] = (rawData, { scrollbarSize, ref, onScroll }) => {
|
||||
// if (ref && 'current' in ref) {
|
||||
// ref.current = refConnector;
|
||||
// }
|
||||
const totalHeight = rawData.length * rowHeight;
|
||||
|
||||
const renderCell = ({ columnIndex, rowIndex, style }: { columnIndex: number; rowIndex: number; style: React.CSSProperties }) => {
|
||||
const columnItem = mergedColumns[columnIndex];
|
||||
const dataItem = rawData[rowIndex];
|
||||
const value = columnItem.dataIndex ? get(dataItem, columnItem.dataIndex) : dataItem;
|
||||
return (
|
||||
<div className={styles['virtual-table-cell']} style={style}>
|
||||
{
|
||||
columnItem.render
|
||||
? columnItem.render(value, dataItem, rowIndex)
|
||||
: String(value)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const scrollY = (typeof scroll?.y === 'string' ? Number.parseFloat(scroll?.y) : scroll?.y) || 0;
|
||||
// 暂时用盖住的方式来展示,背景色也强制白色,层级999应该暂时满足了
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
{
|
||||
fixedColumns.length > 0
|
||||
? (
|
||||
<VariableSizeGrid
|
||||
ref={fixedGridRef}
|
||||
style={{ overflowY: 'hidden', position: 'absolute', top: 0, left: 0, zIndex: 999, width: fixedColumnsWidth, background: '#fff' }}
|
||||
className="virtual-grid"
|
||||
columnCount={fixedColumns.length}
|
||||
columnWidth={(index: number) => {
|
||||
const width = Number.parseFloat(String(fixedColumns[index].width)) || 0;
|
||||
return totalHeight > scrollY && index === fixedColumns.length - 1
|
||||
? width - scrollbarSize - 1
|
||||
: width;
|
||||
}}
|
||||
height={scrollY}
|
||||
rowCount={rawData.length}
|
||||
rowHeight={() => rowHeight}
|
||||
width={fixedColumnsWidth}
|
||||
>
|
||||
{ renderCell }
|
||||
</VariableSizeGrid>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<VariableSizeGrid
|
||||
ref={gridRef}
|
||||
className="virtual-grid"
|
||||
columnCount={mergedColumns.length}
|
||||
columnWidth={(index: number) => {
|
||||
const { width } = mergedColumns[index];
|
||||
return totalHeight > scrollY && index === mergedColumns.length - 1
|
||||
? (width as number) - scrollbarSize - 1
|
||||
: (width as number);
|
||||
}}
|
||||
height={scrollY}
|
||||
rowCount={rawData.length}
|
||||
rowHeight={() => rowHeight}
|
||||
width={tableWidth}
|
||||
onScroll={({ scrollLeft, scrollTop, scrollUpdateWasRequested }: { scrollLeft: number; scrollTop: number; scrollUpdateWasRequested: boolean }) => {
|
||||
onScroll({ scrollLeft });
|
||||
if (!scrollUpdateWasRequested) {
|
||||
fixedGridRef.current?.scrollTo({ scrollLeft: 0, scrollTop });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ renderCell }
|
||||
</VariableSizeGrid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ResizeObserver
|
||||
onResize={({ width }) => {
|
||||
setTableWidth(width);
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
{...props}
|
||||
columns={mergedColumns}
|
||||
components={{
|
||||
body: renderVirtualList,
|
||||
}}
|
||||
/>
|
||||
</ResizeObserver>
|
||||
);
|
||||
}
|
||||
|
||||
export default VirtualTable;
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import { useReducer, useContext, SetStateAction } from 'react';
|
||||
import { DripTableContext, DripTableStoreContext } from './context';
|
||||
|
||||
// 使用最顶层组件的 setState
|
||||
export const useTable = () => useContext(DripTableContext);
|
||||
|
||||
// 组件最顶层传入的所有props
|
||||
export const useStore = () => useContext(DripTableStoreContext);
|
||||
|
||||
/**
|
||||
* 使用状态对象,设置属性时可传入部分
|
||||
* @param initState 初始状态
|
||||
* @returns [状态对象, 状态转移函数]
|
||||
*/
|
||||
export const useState = <T>(initState: T): [T, (action: SetStateAction<Partial<T>>) => void] => useReducer(
|
||||
(state: T, action: SetStateAction<Partial<T>>): T => {
|
||||
const data = typeof action === 'function'
|
||||
? action(state)
|
||||
: action;
|
||||
return { ...state, ...data };
|
||||
},
|
||||
initState,
|
||||
);
|
|
@ -0,0 +1,6 @@
|
|||
export * from './drip-table-provider';
|
||||
export * from './types';
|
||||
export { default as builtInComponents } from './drip-table/components';
|
||||
export type { DripTableComponentProps, DripTableComponentSchema } from './drip-table/components';
|
||||
export type { DripTableProps } from './drip-table';
|
||||
export { default } from './drip-table';
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://ace.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2020 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
declare module '*.css' {
|
||||
interface IClassNames {
|
||||
[className: string]: string;
|
||||
}
|
||||
const classNames: IClassNames;
|
||||
export = classNames;
|
||||
}
|
||||
|
||||
declare module '*.less' {
|
||||
interface IClassNames {
|
||||
[className: string]: string;
|
||||
}
|
||||
const classNames: IClassNames;
|
||||
export = classNames;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
const content: string;
|
||||
export = content;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const content: string;
|
||||
export = content;
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
/**
|
||||
* This file is part of the drip-table project.
|
||||
* @link : https://drip-table.jd.com/
|
||||
* @author : Emil Zhai (root@derzh.com)
|
||||
* @modifier : Emil Zhai (root@derzh.com)
|
||||
* @copyright: Copyright (c) 2021 JD Network Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { DripTableHeaderProps } from './drip-table/header';
|
||||
import { DripTableComponentSchema } from './drip-table/components';
|
||||
|
||||
export interface StringDataSchema {
|
||||
type: 'string';
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
pattern?: string;
|
||||
default?: string;
|
||||
enumValue?: string[];
|
||||
enumLabel?: string[];
|
||||
transform?: ('trim' | 'toUpperCase' | 'toLowerCase')[];
|
||||
}
|
||||
|
||||
export interface NumberDataSchema {
|
||||
type: 'number' | 'integer';
|
||||
minimum?: number;
|
||||
exclusiveMinimum?: number;
|
||||
maximum?: number;
|
||||
exclusiveMaximum?: number;
|
||||
default?: number;
|
||||
enumValue?: number[];
|
||||
enumLabel?: string[];
|
||||
}
|
||||
|
||||
export interface BooleanDataSchema {
|
||||
type: 'boolean';
|
||||
default?: boolean;
|
||||
checkedValue?: string | number;
|
||||
uncheckedValue?: string | number;
|
||||
}
|
||||
|
||||
export interface NullDataSchema {
|
||||
type: 'null';
|
||||
default?: undefined | null;
|
||||
}
|
||||
|
||||
export interface ObjectDataSchema {
|
||||
type: 'object';
|
||||
default?: Record<string, unknown>;
|
||||
properties?: {
|
||||
[key: string]: DataSchema;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ArrayDataSchema {
|
||||
type: 'array';
|
||||
items?: DataSchema | DataSchema[];
|
||||
default?: unknown[];
|
||||
}
|
||||
|
||||
export type DataSchema =
|
||||
| StringDataSchema
|
||||
| NumberDataSchema
|
||||
| BooleanDataSchema
|
||||
| ObjectDataSchema
|
||||
| NullDataSchema
|
||||
| ArrayDataSchema;
|
||||
|
||||
export interface UISchema {
|
||||
/**
|
||||
* 若自定义开发的业务组件以`命名空间::组件名称`格式填写;通过 components 属性传入组件库实现
|
||||
* 系统支持的通用组件目前有:文本组件、图文组件和自定义组件(通过代码实现的)
|
||||
*/
|
||||
'ui:type': string;
|
||||
'ui:props': {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export type ColumnConfig = DripTableComponentSchema & UISchema & DataSchema & {
|
||||
/**
|
||||
* 是否支持过滤
|
||||
*/
|
||||
filtered?: boolean | {
|
||||
/**
|
||||
* 从 icons 属性传入,或者 antd 自带 icon
|
||||
*/
|
||||
icon?: string;
|
||||
options?: { label: string; value: string }[];
|
||||
resetText?: string;
|
||||
confirmText?: string;
|
||||
};
|
||||
/** 是否支持排序 */
|
||||
sorter?: boolean | {
|
||||
ascendIcon?: string;
|
||||
descendIcon?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DripTableSchema {
|
||||
'$schema': 'http://json-schema.org/draft/2019-09/schema#'
|
||||
| 'http://json-schema.org/draft-07/schema#'
|
||||
| 'http://json-schema.org/draft-06/schema#'
|
||||
| 'http://json-schema.org/draft-04/schema#';
|
||||
configs: {
|
||||
/** 是否展示表格边框 */
|
||||
bordered?: boolean;
|
||||
/** 是否展示表格内部边框 */
|
||||
innerBordered?: boolean;
|
||||
/** 是否展示搜索栏以及配置 */
|
||||
header?: boolean | Omit<DripTableHeaderProps<DripTableRecordTypeBase>, 'driver' | 'onSearch' | 'onAddButtonClick'>;
|
||||
/** 是否展示分页以及配置 */
|
||||
pagination?: false | {
|
||||
size?: 'small' | 'default';
|
||||
pageSize: number;
|
||||
position?: 'bottomLeft' | 'bottomCenter' | 'bottomRight';
|
||||
showLessItems?: boolean;
|
||||
showQuickJumper?: boolean;
|
||||
showSizeChanger?: boolean;
|
||||
};
|
||||
size?: 'small' | 'middle' | 'large' | undefined;
|
||||
/** 粘性头部 */
|
||||
sticky?: boolean;
|
||||
/** 是否支持选择栏 */
|
||||
rowSelection?: boolean;
|
||||
/** 是否平均列宽 */
|
||||
ellipsis?: boolean;
|
||||
/** 无数据提示 */
|
||||
nodata?: {
|
||||
image: string;
|
||||
text: string;
|
||||
};
|
||||
/** 是否开启虚拟列表 */
|
||||
isVirtualList?: boolean;
|
||||
/** 虚拟列表滚动高度 */
|
||||
scrollY?: number;
|
||||
};
|
||||
columns: ColumnConfig[];
|
||||
}
|
||||
|
||||
export type DripTableRecordTypeBase = Record<string, unknown>;
|
||||
|
||||
export type DripTableReactComponent<P> = (props: React.PropsWithChildren<P>) => React.ReactElement | null;
|
||||
export type DripTableReactComponentProps<T> = T extends DripTableReactComponent<infer P> ? P : never;
|
||||
|
||||
export interface DripTableDriver<RecordType> {
|
||||
/**
|
||||
* 组件库
|
||||
*/
|
||||
components: {
|
||||
Button: DripTableReactComponent<{
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
type?: 'primary';
|
||||
icon?: React.ReactNode;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||
}>;
|
||||
Col: DripTableReactComponent<{
|
||||
style?: React.CSSProperties;
|
||||
span?: number;
|
||||
}>;
|
||||
ConfigProvider: DripTableReactComponent<Record<string, unknown>>;
|
||||
Image: DripTableReactComponent<{
|
||||
width?: number;
|
||||
height?: number;
|
||||
src?: string;
|
||||
preview?: boolean;
|
||||
fallback?: string;
|
||||
}>;
|
||||
Input: {
|
||||
Search: DripTableReactComponent<{
|
||||
style?: React.CSSProperties;
|
||||
allowClear?: boolean;
|
||||
placeholder?: string;
|
||||
enterButton?: string | true;
|
||||
size?: 'large' | 'middle' | 'small';
|
||||
value?: string;
|
||||
onChange?: React.ChangeEventHandler<HTMLInputElement>;
|
||||
onSearch?: (value: string) => void;
|
||||
}>;
|
||||
};
|
||||
Popover: DripTableReactComponent<{
|
||||
placement?: 'top';
|
||||
title?: string;
|
||||
content?: React.ReactNode;
|
||||
}>;
|
||||
Result: DripTableReactComponent<{
|
||||
status?: 'error';
|
||||
title?: string;
|
||||
extra?: string;
|
||||
}>;
|
||||
Row: DripTableReactComponent<{
|
||||
style?: React.CSSProperties;
|
||||
}>;
|
||||
Select: DripTableReactComponent<{
|
||||
className?: string;
|
||||
defaultValue?: string | number;
|
||||
value?: string | number;
|
||||
onChange?: (value: string | number) => void;
|
||||
}> & {
|
||||
Option: DripTableReactComponent<Record<string, unknown> & { value: string | number; children: React.ReactNode }>;
|
||||
};
|
||||
Table: DripTableReactComponent<{
|
||||
rowKey?: string;
|
||||
columns?: {
|
||||
width?: string | number;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
title?: string | JSX.Element;
|
||||
dataIndex?: string | string[];
|
||||
fixed?: boolean;
|
||||
ellipsis?: boolean;
|
||||
render?: (value: unknown, record: RecordType, rowIndex: number) => React.ReactNode;
|
||||
}[];
|
||||
dataSource?: RecordType[];
|
||||
pagination?: false | {
|
||||
onChange?: (page: number, pageSize?: number) => void;
|
||||
size?: 'small' | 'default';
|
||||
pageSize?: number;
|
||||
total?: number;
|
||||
current?: number;
|
||||
position?: ('topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight')[];
|
||||
showLessItems?: boolean;
|
||||
showQuickJumper?: boolean;
|
||||
showSizeChanger?: boolean;
|
||||
};
|
||||
loading?: boolean;
|
||||
size?: 'large' | 'middle' | 'small';
|
||||
bordered?: boolean;
|
||||
innerBordered?: boolean;
|
||||
sticky?: boolean;
|
||||
rowSelection?: {
|
||||
selectedRowKeys?: React.Key[];
|
||||
onChange?: (selectedKeys: React.Key[], selectedRows: RecordType[]) => void;
|
||||
};
|
||||
scroll?: {
|
||||
x?: string | number;
|
||||
y?: string | number;
|
||||
};
|
||||
components?: {
|
||||
body?: (data: readonly RecordType[], info: {
|
||||
scrollbarSize: number;
|
||||
ref: React.Ref<{
|
||||
scrollLeft: number;
|
||||
}>;
|
||||
onScroll: (info: {
|
||||
currentTarget?: HTMLElement;
|
||||
scrollLeft?: number;
|
||||
}) => void;
|
||||
}) => React.ReactNode;
|
||||
};
|
||||
}>;
|
||||
TableSearch?: DripTableReactComponent<Record<string, unknown> & {
|
||||
driver: DripTableDriver<RecordType>;
|
||||
onSearch: (searchParams: Record<string, unknown>) => void;
|
||||
}>;
|
||||
Tag: DripTableReactComponent<{
|
||||
style?: React.CSSProperties;
|
||||
color?: string;
|
||||
}>;
|
||||
Tooltip: DripTableReactComponent<{
|
||||
title: React.ReactNode | (() => React.ReactNode);
|
||||
placement?: 'top';
|
||||
}>;
|
||||
Typography: {
|
||||
Text: DripTableReactComponent<{
|
||||
style?: React.CSSProperties;
|
||||
ellipsis?: boolean;
|
||||
copyable?: boolean | {
|
||||
text?: string;
|
||||
onCopy?: () => void;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
message: {
|
||||
success: (message: string) => void;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* 图标库
|
||||
*/
|
||||
icons: {
|
||||
PlusOutlined: DripTableReactComponent<unknown>;
|
||||
QuestionCircleOutlined: DripTableReactComponent<unknown>;
|
||||
};
|
||||
/**
|
||||
* 组件本地化翻译
|
||||
*/
|
||||
locale: unknown;
|
||||
}
|
||||
|
||||
export type EventLike<T = { type: string }> = T extends { type: string } ? T : never;
|
||||
export interface DripTableCustomEvent<TN> extends EventLike<{ type: 'custom' }> { name: TN }
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"lib": ["dom", "esnext"],
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noImplicitReturns": true,
|
||||
"importHelpers": true,
|
||||
"listFiles": true,
|
||||
"noEmit": false,
|
||||
"removeComments": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"allowJs": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@@/*": ["src/.umi/*"]
|
||||
},
|
||||
"types": ["node"],
|
||||
"jsx": "react",
|
||||
"jsxFactory": "React.createElement",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": false,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "es5",
|
||||
"moduleResolution": "node",
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"drip-table": ["./packages/drip-table/src"]
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react/prop-types": 0
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
Loading…
Reference in New Issue