├── .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 | --------------------------------------------------------------------------------