├── .editorconfig
├── .eslintrc
├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── config
├── dev.js
├── index.js
└── prod.js
├── global.d.ts
├── image
├── multiselect.png
└── radio.png
├── package.json
├── project.config.json
├── src
├── actions
│ └── counter.ts
├── app.scss
├── app.tsx
├── components
│ ├── Tree.tsx
│ ├── TreeItem.tsx
│ ├── index.tsx
│ ├── interface.tsx
│ ├── style.scss
│ └── utils.scss
├── constants
│ └── counter.ts
├── index.html
├── index.ts
├── pages
│ └── index
│ │ ├── index.scss
│ │ └── index.tsx
├── reducers
│ ├── counter.ts
│ └── index.ts
└── store
│ └── index.ts
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["taro"],
3 | "rules": {
4 | "no-unused-vars": ["error", { "varsIgnorePattern": "Taro" }],
5 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".tsx"] }]
6 | },
7 | "parser": "@typescript-eslint/parser",
8 | "parserOptions": {
9 | "ecmaFeatures": {
10 | "jsx": true
11 | },
12 | "useJSXTextNode": true,
13 | "project": "./tsconfig.json"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | deploy_versions/
3 | .temp/
4 | .rn_temp/
5 | node_modules/
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2
3 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # taro-tree
2 | 基于 taro 的 Tree 组件,可用于小程序、h5
3 |
4 | ## 🔨示例
5 |
6 |
7 |
8 |
9 |
10 | ## 🍭 API
11 | | 属性 | 说明 | 类型 | 默认值 |
12 | | --- | --- | --- | --- |
13 | | dataSource | 数据源 | array (必填) | [ ] |
14 | | value | 选中值 | 多选情况下为数组 (必填) | |
15 | | onChange | 选中的回调 | function (必填) | |
16 | | multiple | 多选 | boolean | false |
17 | | loadData | 异步加载数据 | function,返回值需要为 promise 对象 | |
18 |
19 |
20 |
21 | #### dataSource
22 |
23 | ```js
24 | [{
25 | label: '水果',
26 | value: 'fruit',
27 | isLeaf: true, // 是否有子节点
28 | disabled: true, // 是否禁用
29 | children: [], // 子节点
30 | }]
31 | ```
32 |
33 |
34 |
--------------------------------------------------------------------------------
/config/dev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | NODE_ENV: '"development"'
4 | },
5 | defineConstants: {
6 | },
7 | weapp: {},
8 | h5: {}
9 | }
10 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 |
4 | const config = {
5 | projectName: 'taro-tree',
6 | date: '2019-9-9',
7 | designWidth: 750,
8 | deviceRatio: {
9 | '640': 2.34 / 2,
10 | '750': 1,
11 | '828': 1.81 / 2
12 | },
13 | sourceRoot: 'src',
14 | outputRoot: 'dist',
15 | plugins: {
16 | babel: {
17 | sourceMap: true,
18 | presets: [
19 | ['env', {
20 | modules: false
21 | }]
22 | ],
23 | plugins: [
24 | 'transform-decorators-legacy',
25 | 'transform-class-properties',
26 | 'transform-object-rest-spread'
27 | ]
28 | }
29 | },
30 | defineConstants: {
31 | },
32 | copy: {
33 | patterns: [
34 | ],
35 | options: {
36 | }
37 | },
38 | weapp: {
39 | module: {
40 | postcss: {
41 | autoprefixer: {
42 | enable: true,
43 | config: {
44 | browsers: [
45 | 'last 3 versions',
46 | 'Android >= 4.1',
47 | 'ios >= 8'
48 | ]
49 | }
50 | },
51 | pxtransform: {
52 | enable: true,
53 | config: {
54 |
55 | }
56 | },
57 | url: {
58 | enable: true,
59 | config: {
60 | limit: 10240 // 设定转换尺寸上限
61 | }
62 | },
63 | cssModules: {
64 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
65 | config: {
66 | namingPattern: 'module', // 转换模式,取值为 global/module
67 | generateScopedName: '[name]__[local]___[hash:base64:5]'
68 | }
69 | }
70 | }
71 | }
72 | },
73 | h5: {
74 | publicPath: '/',
75 | staticDirectory: 'static',
76 | module: {
77 | postcss: {
78 | autoprefixer: {
79 | enable: true,
80 | config: {
81 | browsers: [
82 | 'last 3 versions',
83 | 'Android >= 4.1',
84 | 'ios >= 8'
85 | ]
86 | }
87 | },
88 | cssModules: {
89 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
90 | config: {
91 | namingPattern: 'module', // 转换模式,取值为 global/module
92 | generateScopedName: '[name]__[local]___[hash:base64:5]'
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | if (process.env.TARO_BUILD_TYPE === 'ui') {
101 | Object.assign(config.h5, {
102 | enableSourceMap: false,
103 | enableExtract: false,
104 | enableDll: false
105 | })
106 | config.h5.webpackChain = chain => {
107 | chain.plugins.delete('htmlWebpackPlugin')
108 | chain.plugins.delete('addAssetHtmlWebpackPlugin')
109 | chain.merge({
110 | output: {
111 | path: path.join(process.cwd(), 'dist', 'h5'),
112 | filename: 'index.js',
113 | libraryTarget: 'umd',
114 | library: 'taro-ui-sample'
115 | },
116 | externals: {
117 | nervjs: 'commonjs2 nervjs',
118 | classnames: 'commonjs2 classnames',
119 | '@tarojs/components': 'commonjs2 @tarojs/components',
120 | '@tarojs/taro-h5': 'commonjs2 @tarojs/taro-h5',
121 | 'weui': 'commonjs2 weui'
122 | }
123 | })
124 | }
125 | }
126 |
127 |
128 | if (process.env.TARO_BUILD_TYPE === 'ui') {
129 | Object.assign(config.h5, {
130 | enableSourceMap: false,
131 | enableExtract: false,
132 | enableDll: false
133 | })
134 | config.h5.webpackChain = chain => {
135 | chain.plugins.delete('htmlWebpackPlugin')
136 | chain.plugins.delete('addAssetHtmlWebpackPlugin')
137 | chain.merge({
138 | output: {
139 | path: path.join(process.cwd(), 'dist', 'h5'),
140 | filename: 'index.js',
141 | libraryTarget: 'umd',
142 | library: 'taro-ui-sample'
143 | },
144 | externals: {
145 | nervjs: 'commonjs2 nervjs',
146 | classnames: 'commonjs2 classnames',
147 | '@tarojs/components': 'commonjs2 @tarojs/components',
148 | '@tarojs/taro-h5': 'commonjs2 @tarojs/taro-h5',
149 | 'weui': 'commonjs2 weui'
150 | }
151 | })
152 | }
153 | }
154 |
155 | module.exports = function (merge) {
156 | if (process.env.NODE_ENV === 'development') {
157 | return merge({}, config, require('./dev'))
158 | }
159 |
160 | return merge({}, config, require('./prod'))
161 | }
162 |
--------------------------------------------------------------------------------
/config/prod.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | NODE_ENV: '"production"'
4 | },
5 | defineConstants: {
6 | },
7 | weapp: {},
8 | h5: {
9 | /**
10 | * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。
11 | * 参考代码如下:
12 | * webpackChain (chain) {
13 | * chain.plugin('analyzer')
14 | * .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
15 | * }
16 | */
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.png";
2 | declare module "*.gif";
3 | declare module "*.jpg";
4 | declare module "*.jpeg";
5 | declare module "*.svg";
6 | declare module "*.css";
7 | declare module "*.less";
8 | declare module "*.scss";
9 | declare module "*.sass";
10 | declare module "*.styl";
11 |
12 | // @ts-ignore
13 | declare const process: {
14 | env: {
15 | TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt';
16 | [key: string]: any;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/image/multiselect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dolov/taro-tree/af8d0d1729a2180437366223ea75dd559111923e/image/multiselect.png
--------------------------------------------------------------------------------
/image/radio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dolov/taro-tree/af8d0d1729a2180437366223ea75dd559111923e/image/radio.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "taro-tree",
3 | "version": "1.0.9",
4 | "private": false,
5 | "description": "基于 taro 的 Tree 组件,适用于小程序、h5",
6 | "templateInfo": {
7 | "name": "redux",
8 | "typescript": true,
9 | "css": "sass"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/Dolov/taro-tree"
14 | },
15 | "main": "dist/index.js",
16 | "files": [
17 | "dist",
18 | "@types"
19 | ],
20 | "scripts": {
21 | "build:weapp": "taro build --type weapp",
22 | "build:swan": "taro build --type swan",
23 | "build:alipay": "taro build --type alipay",
24 | "build:tt": "taro build --type tt",
25 | "build:h5": "taro build --type h5",
26 | "build:rn": "taro build --type rn",
27 | "dev:weapp": "npm run build:weapp -- --watch",
28 | "dev:swan": "npm run build:swan -- --watch",
29 | "dev:alipay": "npm run build:alipay -- --watch",
30 | "dev:tt": "npm run build:tt -- --watch",
31 | "dev:h5": "npm run build:h5 -- --watch",
32 | "dev:rn": "npm run build:rn -- --watch",
33 | "build": "TARO_BUILD_TYPE=ui taro build --ui",
34 | "prepublish": "npm run build"
35 | },
36 | "author": "",
37 | "license": "MIT",
38 | "dependencies": {
39 | "@tarojs/async-await": "1.3.13",
40 | "@tarojs/components": "1.3.13",
41 | "@tarojs/redux": "1.3.13",
42 | "@tarojs/redux-h5": "1.3.13",
43 | "@tarojs/router": "1.3.13",
44 | "@tarojs/taro": "1.3.13",
45 | "@tarojs/taro-alipay": "1.3.13",
46 | "@tarojs/taro-h5": "1.3.13",
47 | "@tarojs/taro-swan": "1.3.13",
48 | "@tarojs/taro-tt": "1.3.13",
49 | "@tarojs/taro-weapp": "1.3.13",
50 | "nerv-devtools": "^1.4.0",
51 | "nervjs": "^1.4.0",
52 | "redux": "^4.0.0",
53 | "redux-logger": "^3.0.6",
54 | "redux-thunk": "^2.3.0",
55 | "taro-ui": "^2.2.2"
56 | },
57 | "devDependencies": {
58 | "@tarojs/plugin-babel": "1.3.13",
59 | "@tarojs/plugin-csso": "1.3.13",
60 | "@tarojs/plugin-sass": "1.3.13",
61 | "@tarojs/plugin-uglifyjs": "1.3.13",
62 | "@tarojs/webpack-runner": "1.3.13",
63 | "@types/react": "^16.4.8",
64 | "@types/webpack-env": "^1.13.6",
65 | "@typescript-eslint/parser": "^1.6.0",
66 | "babel-eslint": "^8.2.3",
67 | "babel-plugin-transform-class-properties": "^6.24.1",
68 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
69 | "babel-plugin-transform-jsx-stylesheet": "^0.6.5",
70 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
71 | "babel-preset-env": "^1.6.1",
72 | "eslint": "^5.16.0",
73 | "eslint-config-taro": "1.3.13",
74 | "eslint-plugin-import": "^2.12.0",
75 | "eslint-plugin-react": "^7.8.2",
76 | "eslint-plugin-react-hooks": "^1.6.1",
77 | "eslint-plugin-taro": "1.3.13",
78 | "stylelint": "9.3.0",
79 | "stylelint-config-taro-rn": "1.3.13",
80 | "stylelint-taro-rn": "1.3.13",
81 | "typescript": "^3.0.1"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "miniprogramRoot": "dist/",
3 | "projectname": "taro-tree",
4 | "description": "基于 taro 的 Tree 组件,适用于小程序",
5 | "appid": "wx6260da497cbf05de",
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": false,
9 | "postcss": false,
10 | "minified": false
11 | },
12 | "compileType": "miniprogram",
13 | "simulatorType": "wechat",
14 | "simulatorPluginLibVersion": {},
15 | "condition": {}
16 | }
--------------------------------------------------------------------------------
/src/actions/counter.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ADD,
3 | MINUS
4 | } from '../constants/counter'
5 |
6 | export const add = () => {
7 | return {
8 | type: ADD
9 | }
10 | }
11 | export const minus = () => {
12 | return {
13 | type: MINUS
14 | }
15 | }
16 |
17 | // 异步的action
18 | export function asyncAdd () {
19 | return dispatch => {
20 | setTimeout(() => {
21 | dispatch(add())
22 | }, 2000)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/app.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dolov/taro-tree/af8d0d1729a2180437366223ea75dd559111923e/src/app.scss
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import '@tarojs/async-await'
2 | import Taro, { Component, Config } from '@tarojs/taro'
3 | import { Provider } from '@tarojs/redux'
4 |
5 | import Index from './pages/index'
6 |
7 | import configStore from './store'
8 |
9 | import './app.scss'
10 | import 'taro-ui/dist/style/index.scss'
11 |
12 | // 如果需要在 h5 环境中开启 React Devtools
13 | // 取消以下注释:
14 | // if (process.env.NODE_ENV !== 'production' && process.env.TARO_ENV === 'h5') {
15 | // require('nerv-devtools')
16 | // }
17 |
18 | const store = configStore()
19 |
20 | class App extends Component {
21 |
22 | /**
23 | * 指定config的类型声明为: Taro.Config
24 | *
25 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型
26 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string
27 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型
28 | */
29 | config: Config = {
30 | pages: [
31 | 'pages/index/index'
32 | ],
33 | window: {
34 | backgroundTextStyle: 'light',
35 | navigationBarBackgroundColor: '#fff',
36 | navigationBarTitleText: 'WeChat',
37 | navigationBarTextStyle: 'black'
38 | }
39 | }
40 |
41 | componentDidMount () {}
42 |
43 | componentDidShow () {}
44 |
45 | componentDidHide () {}
46 |
47 | componentDidCatchError () {}
48 |
49 | // 在 App 类中的 render() 函数没有实际作用
50 | // 请勿修改此函数
51 | render () {
52 | return (
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | Taro.render(, document.getElementById('app'))
61 |
--------------------------------------------------------------------------------
/src/components/Tree.tsx:
--------------------------------------------------------------------------------
1 | import Taro from '@tarojs/taro'
2 | import { View, Text } from '@tarojs/components'
3 | import TreeItem from './TreeItem'
4 | import { ITree } from './interface'
5 |
6 |
7 | export default class Tree extends Taro.PureComponent {
8 |
9 | static options = {
10 | styleIsolation: 'shared'
11 | }
12 |
13 | state = {
14 |
15 | }
16 |
17 | render() {
18 | const { dataSource, value: selectedValue, onChange, multiple, loadData, treeDefaultExpandAll } = this.props
19 | if (!Array.isArray(dataSource)) return null
20 | return (
21 |
22 | {dataSource.map(item => {
23 | const { value } = item
24 | return (
25 |
34 | )
35 | })}
36 |
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/src/components/TreeItem.tsx:
--------------------------------------------------------------------------------
1 | import Taro from '@tarojs/taro'
2 | import { View, Text } from '@tarojs/components'
3 | import { AtCheckbox, AtIcon } from 'taro-ui'
4 | import cls from 'classnames'
5 | import { ITreeItem } from './interface'
6 | import Tree from './Tree'
7 |
8 |
9 | const iconSize = 20
10 | const iconColor = '#C7C7CC'
11 | const checkIconSize = 18
12 | const checkIconColor = '#1890FF'
13 |
14 |
15 |
16 | interface ITreeItemState {
17 | visible: boolean;
18 | loading: boolean;
19 | }
20 |
21 | export default class TreeItem extends Taro.PureComponent {
22 |
23 | static options = {
24 | styleIsolation: 'shared'
25 | }
26 |
27 | constructor(props) {
28 | super(props)
29 | const { treeDefaultExpandAll, data={} } = props
30 | const { children } = data
31 | const visible = treeDefaultExpandAll && Array.isArray(children) && children.length > 0
32 | this.state = {
33 | visible,
34 | loading: false,
35 | }
36 | }
37 |
38 | loaded = false
39 |
40 | handleToggleMore = async () => {
41 | const { visible } = this.state
42 | const { data, loadData } = this.props
43 | const { children } = data
44 | const hasChild = Array.isArray(children) && children.length > 0
45 | if (hasChild || this.loaded) {
46 | this.setState({
47 | visible: !visible,
48 | })
49 | return
50 | }
51 | if (typeof loadData === 'function') {
52 | this.setState({loading: true})
53 | await loadData(data)
54 | this.setState({
55 | visible: true,
56 | loading: false
57 | }, () => {
58 | this.loaded = true
59 | })
60 | } else {
61 | this.setState({
62 | visible: !visible,
63 | })
64 | }
65 | }
66 |
67 | handleRadioTreeChange = (value, disabled) => {
68 | if (disabled) return
69 | const { onChange } = this.props
70 | onChange(value)
71 | }
72 |
73 | render() {
74 | const { visible, loading } = this.state
75 | const { selectedValue, onChange, multiple, data, treeDefaultExpandAll, loadData } = this.props
76 | const { label, children, value, isLeaf, disabled } = data || {}
77 | const moreIcon = loading ? 'loading-3': visible ? 'chevron-down': 'chevron-up'
78 | const isRenderLeafIcon = isLeaf || (Array.isArray(children) && children.length > 0)
79 | const checked = selectedValue === value
80 | return (
81 |
82 |
83 | {/* 多选树 */}
84 | {multiple&&(
85 | )}
94 | {/* 单选树 */}
95 | {!multiple&&(
96 | this.handleRadioTreeChange(value, disabled)}
98 | className="tree-item-radio"
99 | >
100 |
101 | {label}
102 |
103 | {checked&&(
104 |
105 | )}
106 | )}
107 | {/* 展开收起加载中状态下的图标 */}
108 | {isRenderLeafIcon&&(
109 |
110 |
111 | )}
112 |
113 | {/* 子级树 */}
114 |
117 |
125 |
126 |
127 | )
128 | }
129 | }
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | import Taro from '@tarojs/taro'
2 | import { View } from '@tarojs/components'
3 | import cls from 'classnames'
4 | import Tree from './Tree'
5 | import { ITree } from './interface'
6 | import './style.scss'
7 |
8 | const clsPrefix = 'cp-tree'
9 |
10 |
11 | export default class TreeSelect extends Taro.PureComponent {
12 |
13 | static options = {
14 | styleIsolation: 'shared'
15 | }
16 |
17 | static defaultProps = {
18 | multiple: false,
19 | dataSource: [],
20 | treeDefaultExpandAll: true,
21 | }
22 |
23 | state = {
24 |
25 | }
26 |
27 | render() {
28 | const { dataSource, multiple, value, onChange, treeDefaultExpandAll, loadData } = this.props
29 | return (
30 |
34 |
42 |
43 | )
44 | }
45 | }
--------------------------------------------------------------------------------
/src/components/interface.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | interface ITree {
4 | value: any;
5 | onChange: Function;
6 | dataSource: Array;
7 | multiple?: boolean;
8 | loadData?: Function;
9 | treeDefaultExpandAll?: boolean;
10 | }
11 |
12 |
13 |
14 |
15 | interface Ichild {
16 | label: string,
17 | value: string | number,
18 | isLeaf?: boolean;
19 | children?: Array,
20 | }
21 |
22 |
23 |
24 | interface ITreeItem {
25 | [otherProps: string]: any;
26 | }
27 |
28 |
29 |
30 | export {
31 | ITree,
32 | Ichild,
33 | ITreeItem,
34 | }
--------------------------------------------------------------------------------
/src/components/style.scss:
--------------------------------------------------------------------------------
1 |
2 | // @import "~taro-ui/dist/style/components/icon.scss";
3 | // @import "~taro-ui/dist/style/components/checkbox.scss";
4 | @import './utils.scss';
5 |
6 | $cls-prefix: 'cp-tree';
7 |
8 | .#{$cls-prefix} {
9 | .tree {
10 | padding-left: 36px;
11 | &-item-content {
12 | position: relative;
13 | }
14 | &-item-more {
15 | width: 80px;
16 | height: 90px;
17 | position: absolute;
18 | padding-right: 16px;
19 | top: 0;
20 | right: 0;
21 | z-index: 10;
22 | display: flex;
23 | align-items: center;
24 | justify-content: flex-end;
25 | }
26 | }
27 | }
28 |
29 | .#{$cls-prefix}-radio {
30 | .tree-item-radio {
31 | color: #333;
32 | font-size: 32px;
33 | padding: 24rpx 32rpx 24rpx 0;
34 | @include hairline-bottom(#d6e4ef);
35 | .label {
36 | padding-right: 24px;
37 | }
38 | .checked {
39 | color: #1890FF;
40 | }
41 | .disabled {
42 | opacity: 0.3;
43 | }
44 | }
45 | }
46 |
47 |
48 | .#{$cls-prefix}-multiple {
49 | .at-checkbox::before {
50 | border-top: 0;
51 | }
52 | }
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/components/utils.scss:
--------------------------------------------------------------------------------
1 | @mixin hairline-common() {
2 | content: '';
3 | position: absolute;
4 | transform-origin: center;
5 | box-sizing: border-box;
6 | pointer-events: none;
7 | }
8 |
9 | @mixin hairline-base(
10 | $color: $color-border-light,
11 | $style: solid
12 | ) {
13 | @include hairline-common();
14 |
15 | top: -50%;
16 | left: -50%;
17 | right: -50%;
18 | bottom: -50%;
19 | border: 0 $style $color;
20 | transform: scale(0.5);
21 | }
22 |
23 | @mixin hairline-surround(
24 | $color: $color-border-light,
25 | $style: solid,
26 | $width: 1PX
27 | ) {
28 | position: relative;
29 |
30 | &::after {
31 | @include hairline-base($color, $style);
32 |
33 | border-width: $width;
34 | }
35 | }
36 |
37 | @mixin hairline-top(
38 | $color: $color-border-light,
39 | $style: solid,
40 | $width: 1PX
41 | ) {
42 | position: relative;
43 |
44 | &::after {
45 | @include hairline-base($color, $style);
46 |
47 | border-top-width: $width;
48 | }
49 | }
50 |
51 | @mixin hairline-bottom(
52 | $color: $color-border-light,
53 | $style: solid,
54 | $width: 1PX
55 | ) {
56 | position: relative;
57 |
58 | &::after {
59 | @include hairline-base($color, $style);
60 |
61 | border-bottom-width: $width;
62 | }
63 | }
64 |
65 | @mixin hairline-left(
66 | $color: $color-border-light,
67 | $style: solid,
68 | $width: 1PX
69 | ) {
70 | position: relative;
71 |
72 | &::after {
73 | @include hairline-base($color, $style);
74 |
75 | border-left-width: $width;
76 | }
77 | }
78 |
79 | @mixin hairline-right(
80 | $color: $color-border-light,
81 | $style: solid,
82 | $width: 1PX
83 | ) {
84 | position: relative;
85 |
86 | &::after {
87 | @include hairline-base($color, $style);
88 |
89 | border-right-width: $width;
90 | }
91 | }
92 |
93 | @mixin hairline-top-bottom(
94 | $color: $color-border-light,
95 | $style: solid,
96 | $width: 1PX
97 | ) {
98 | position: relative;
99 |
100 | &::after {
101 | @include hairline-base($color, $style);
102 |
103 | border-top-width: $width;
104 | border-bottom-width: $width;
105 | }
106 | }
107 |
108 | @mixin hairline-bottom-relative(
109 | $color: $color-border-light,
110 | $style: solid,
111 | $width: 1PX,
112 | $left: 0
113 | ) {
114 | position: relative;
115 |
116 | &::after {
117 | @include hairline-common();
118 |
119 | top: auto;
120 | left: $left;
121 | right: 0;
122 | bottom: 0;
123 | transform: scaleY(0.5);
124 | border-bottom: $width $style $color;
125 | }
126 | }
127 |
128 | @mixin hairline-top-relative(
129 | $color: $color-border-light,
130 | $style: solid,
131 | $width: 1PX,
132 | $left: 0
133 | ) {
134 | position: relative;
135 |
136 | &::before {
137 | @include hairline-common();
138 |
139 | top: 0;
140 | left: $left;
141 | right: 0;
142 | bottom: auto;
143 | transform: scaleY(0.5);
144 | border-top: $width $style $color;
145 | }
146 | }
147 |
148 | @mixin hairline-left-relative(
149 | $color: $color-border-light,
150 | $style: solid,
151 | $width: 1PX,
152 | $top: 0
153 | ) {
154 | position: relative;
155 |
156 | &::after {
157 | @include hairline-common();
158 |
159 | top: $top;
160 | left: 0;
161 | right: auto;
162 | bottom: 0;
163 | transform: scaleX(0.5);
164 | border-left: $width $style $color;
165 | }
166 | }
167 |
168 | @mixin hairline-right-relative(
169 | $color: $color-border-light,
170 | $style: solid,
171 | $width: 1PX,
172 | $top: 0
173 | ) {
174 | position: relative;
175 |
176 | &::after {
177 | @include hairline-common();
178 |
179 | top: $top;
180 | left: auto;
181 | right: 0;
182 | bottom: 0;
183 | transform: scaleX(0.5);
184 | border-right: $width $style $color;
185 | }
186 | }
187 |
188 |
189 | .none {
190 | display: none;
191 | }
--------------------------------------------------------------------------------
/src/constants/counter.ts:
--------------------------------------------------------------------------------
1 | export const ADD = 'ADD'
2 | export const MINUS = 'MINUS'
3 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import TreeSelect from './components'
2 |
3 |
4 | export default TreeSelect
--------------------------------------------------------------------------------
/src/pages/index/index.scss:
--------------------------------------------------------------------------------
1 | .index {
2 | flex-direction: column;
3 | width: 100%;
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/index/index.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentClass } from 'react'
2 | import Taro, { Component, Config } from '@tarojs/taro'
3 | import { View, Button, Text } from '@tarojs/components'
4 | import Tree from '../../components'
5 |
6 | import './index.scss'
7 |
8 | const dataSource = [
9 | {
10 | label: '水果',
11 | value: '001',
12 | children: [
13 | {
14 | label: '苹果',
15 | value: '001_001',
16 | disabled: true,
17 | },
18 | {
19 | label: '梨子',
20 | value: '001_002',
21 | isLeaf: true,
22 | }
23 | ]
24 | },
25 | {
26 | label: '蔬菜',
27 | value: '002',
28 | children: [
29 | {
30 | label: '白菜',
31 | value: '002_001'
32 | },
33 | {
34 | label: '萝卜',
35 | value: '002_002'
36 | }
37 | ]
38 | }
39 | ]
40 |
41 | class Index extends Component {
42 |
43 | config: Config = {
44 | navigationBarTitleText: '首页'
45 | }
46 |
47 | state = {
48 | value: null,
49 | }
50 |
51 | onChange = val => {
52 | this.setState({
53 | value: val
54 | })
55 | }
56 |
57 | loadData = data => {
58 | return new Promise(resolve => {
59 | setTimeout(() => {
60 | resolve()
61 | data.children = [{
62 | label: '冰糖雪梨',
63 | value: 'test',
64 | }]
65 | this.forceUpdate()
66 | }, 100)
67 | })
68 | }
69 |
70 | render () {
71 | const { value } = this.state
72 | return (
73 |
74 |
81 |
82 | )
83 | }
84 | }
85 |
86 |
87 | export default Index as ComponentClass
88 |
--------------------------------------------------------------------------------
/src/reducers/counter.ts:
--------------------------------------------------------------------------------
1 | import { ADD, MINUS } from '../constants/counter'
2 |
3 | const INITIAL_STATE = {
4 | num: 0
5 | }
6 |
7 | export default function counter (state = INITIAL_STATE, action) {
8 | switch (action.type) {
9 | case ADD:
10 | return {
11 | ...state,
12 | num: state.num + 1
13 | }
14 | case MINUS:
15 | return {
16 | ...state,
17 | num: state.num - 1
18 | }
19 | default:
20 | return state
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import counter from './counter'
3 |
4 | export default combineReducers({
5 | counter
6 | })
7 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux'
2 | import thunkMiddleware from 'redux-thunk'
3 | import rootReducer from '../reducers'
4 |
5 | const composeEnhancers =
6 | typeof window === 'object' &&
7 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
8 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
9 | // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
10 | }) : compose
11 |
12 | const middlewares = [
13 | thunkMiddleware
14 | ]
15 |
16 | if (process.env.NODE_ENV === 'development' && process.env.TARO_ENV !== 'quickapp') {
17 | middlewares.push(require('redux-logger').createLogger())
18 | }
19 |
20 | const enhancer = composeEnhancers(
21 | applyMiddleware(...middlewares),
22 | // other store enhancers if any
23 | )
24 |
25 | export default function configStore () {
26 | const store = createStore(rootReducer, enhancer)
27 | return store
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "removeComments": false,
6 | "preserveConstEnums": true,
7 | "moduleResolution": "node",
8 | "experimentalDecorators": true,
9 | "noImplicitAny": false,
10 | "allowSyntheticDefaultImports": true,
11 | "outDir": "lib",
12 | "noUnusedLocals": true,
13 | "noUnusedParameters": true,
14 | "strictNullChecks": true,
15 | "sourceMap": true,
16 | "baseUrl": ".",
17 | "rootDir": ".",
18 | "jsx": "preserve",
19 | "jsxFactory": "Taro.createElement",
20 | "allowJs": true,
21 | "resolveJsonModule": true,
22 | "typeRoots": [
23 | "node_modules/@types"
24 | ]
25 | },
26 | "exclude": [
27 | "node_modules",
28 | "dist"
29 | ],
30 | "compileOnSave": false
31 | }
32 |
--------------------------------------------------------------------------------