10-6 添加类别和删除类别

120 阅读2分钟

在本节中,我们将实现类别模块的添加和删除功能。以下是详细的实现步骤:

一、更新 Item 组件

1. 添加编辑状态图标

pages/Category/Item.tsx 中,根据编辑状态显示加号或减号图标:

import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { ICategory } from '@/models/category';
import { viewportWidth } from '@/utils/index';

export const parentWidth = viewportWidth - 10;
export const itemWidth = parentWidth / 4;
export const itemHeight = 48;
export const margin = 5;

interface IProps {
  data: ICategory;
  isEdit: boolean;
  selected: boolean;
  onPress: () => void;
  onLongPress: () => void;
}

class Item extends React.Component<IProps> {
  render() {
    const { data, isEdit, selected, onPress, onLongPress } = this.props;

    return (
      <TouchableOpacity
        activeOpacity={0.8}
        onPress={onPress}
        onLongPress={onLongPress}
        style={styles.itemWrapper}
      >
        <View style={styles.item}>
          <Text style={styles.text}>{data.name}</Text>
          {isEdit && (
            <View style={styles.icon}>
              <Text style={styles.iconText}>{selected ? '-' : '+'}</Text>
            </View>
          )}
        </View>
      </TouchableOpacity>
    );
  }
}

const styles = StyleSheet.create({
  itemWrapper: {
    width: itemWidth,
    height: itemHeight,
  },
  item: {
    flex: 1,
    margin: margin,
    backgroundColor: '#fff',
    borderRadius: 3,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'relative',
  },
  text: {
    fontSize: 12,
  },
  icon: {
    position: 'absolute',
    top: -5,
    right: -5,
    height: 16,
    width: 16,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f86442',
    borderRadius: 8,
  },
  iconText: {
    lineHeight: 15,
    color: '#fff',
    fontSize: 12,
  },
});

export default Item;

二、更新分类页面

1. 处理类别点击和长按事件

pages/Category/index.tsx 中,处理类别点击和长按事件:

import React, { Component } from 'react';
import { View, Text, StyleSheet, ScrollView, Platform } from 'react-native';
import _ from 'lodash';
import Item from './Item';
import Touchable from '@/components/Touchable';
import moment from 'moment';
import { connect, ConnectedProps } from 'react-redux';
import { RootState } from '@/models/index';
import { ICategory } from '@/models/category';
import { RootStackNavigation } from '@/navigator/index';
import { editMode } from '@/utils/editMode';
import { fixedItems } from '@/constants';
import HeaderRightBtn from './HeaderRightBtn';

const marginHorizontal = 5;
const itemWidth = (viewportWidth - marginHorizontal * 2) / 4;

const mapStateToProps = ({ category }: RootState) => ({
  categorys: category.categorys,
  myCategorys: category.myCategorys,
  isEdit: category.isEdit,
});

const connector = connect(mapStateToProps);

type ModelState = ConnectedProps<typeof connector>;

interface IProps extends ModelState {
  navigation: RootStackNavigation;
}

interface IState {
  myCategorys: ICategory[];
}

class Category extends Component<IProps, IState> {
  state = {
    myCategorys: this.props.myCategorys,
  };

  componentDidMount() {
    const { categorys } = this.props;
    const classifyGroup = _.groupBy(categorys, 'classify');
    this.classifyGroup = classifyGroup;
  }

  onPress = (item: ICategory, selected: boolean) => {
    const { isEdit } = this.props;
    const { myCategorys } = this.state;

    if (isEdit) {
      if (selected) {
        this.setState({
          myCategorys: myCategorys.filter(
            (selectedItem) => selectedItem.id !== item.id,
          ),
        });
      } else {
        this.setState({
          myCategorys: myCategorys.concat(item),
        });
      }
    }
  };

  onLongPress = (index: number) => {
    const { isEdit, dispatch } = this.props;

    if (!isEdit) {
      dispatch({
        type: 'category/toggle',
      });
    }
  };

  render() {
    const { isEdit, categorys } = this.props;
    const { myCategorys } = this.state;

    return (
      <ScrollView style={styles.container}>
        <View>
          <Text style={styles.classifyName}>
            我的分类<Text style={styles.tips}>长按可拖动顺序</Text>
          </Text>
          <View style={styles.classifyView}>
            {myCategorys.map((item, index) => (
              <Item
                key={item.id}
                data={item}
                isEdit={isEdit}
                selected
                onPress={() => this.onPress(item, true)}
                onLongPress={() => this.onLongPress(index)}
              />
            ))}
          </View>
        </View>
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f3f6f6',
  },
  classifyName: {
    fontSize: 16,
    marginBottom: 8,
    marginTop: 14,
    marginLeft: 10,
  },
  tips: {
    color: '#666',
  },
  classifyView: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    padding: 5,
  },
});

export default connector(Category);

2. 保存数据到本地存储

models/category.ts 中,更新 toggle 方法,保存数据到本地存储:

*toggle(_, { put, select }) {
  const category: CategoryModelState = yield select((state: RootState) => state.category);
  yield put({
    type: 'setState',
    payload: {
      isEdit: !category.isEdit,
    },
  });

  if (category.isEdit) {
    storage.save({
      key: 'myCategorys',
      data: category.myCategorys,
    });
  }
},

三、适配设备平台

navigator/index.tsx 中适配设备平台:

<Stack.Navigator
  headerMode="float"
  screenOptions={{
    headerTintColor: '#333',
    headerBackTitleVisible: false,
    ...Platform.select({
      android: {
        headerStatusBarHeight: StatusBar.currentHeight,
      },
    }),
  }}
/>

四、总结

在本节中,我们实现了类别模块的添加和删除功能。通过动态显示加号和减号图标,用户可以方便地添加或删除类别。同时,我们还处理了长按事件切换编辑状态,并保存了数据到本地存储中。下一节,我们将学习如何实现类别的拖拽功能。