一、布局

1.1 div、span

布局基本都用DIV、span,scss样式的语法基本都用 className={style.···}

1.2 psd

外层的包裹元素尽可能的减少和有效利用,Photoshop的psd原图以50%与开发后进行对比,尽量提高还原度。

1.3 rem、px

布局使用rem();字体大小为px;它们之间的比例为2:1。

二、图片(img/svg)

2.1 img

两者均保存在src/app/config文件夹中,通过对应引入路径,抛出路径,再经过一系列的引入方可使用:
image.png

在index.ts中:
image.png

image.png

variable.ts则是配置相关信息,其中在开发过程引入图片路径则需添加相关路径信息
image.png

引入使用(包含路由功能):

  1. import ImgView from 'yuejia/component/ImgView';
  1. import Link from 'yuejia/component/Link'; //引入路由组件
  1. <ImgView
  2. className={style.rankBgChoose}
  3. completeView={config.variable.app.imgHost + config.imgFiles.rankChoose}
  4. onClick={() => {
  5. Link.go('/rankingFilter'); //路由
  6. model.customer.channelListRequest.filter.TypeFitter.clear();
  7. model.customer.channelListRequest.filter.ProjectService.clear();
  8. }}
  9. />

2.2 svg

image.png

svg图片的使用:

  1. import Icon from 'yuejia/component/Icon';

classnames组件的引入与使用

  1. import * as classnames from 'classnames';

通过classnames,改变icon的样式效果

  1. <div
  2. onClick={() => this.isIconActive(txt)}
  3. className={style.rankListTitleBox}
  4. key={txt}
  5. >
  6. <span className={txt === '报备经纪人数' ? style.rankListTitleBoxSpan : ''}>{txt}</span>
  7. <Icon
  8. className={classnames(style.iconUp, type2 === txt && typeS2 ? style.iconUpActive : '')}
  9. src={config.svgFiles.rankUp}
  10. />
  11. <Icon
  12. className={classnames(style.iconDown, type2 === txt && !typeS2 ? style.iconDownActive : '')}
  13. src={config.svgFiles.rankDown}
  14. />
  15. </div>
  1. .iconUp {
  2. position: absolute;
  3. top: rem(22);
  4. right: rem(15);
  5. fill: rgba(255, 255, 255, 0.5);
  6. width: rem(22);
  7. height: rem(22);
  8. }
  9. .iconUpActive {
  10. position: absolute;
  11. fill: #fff;
  12. top: rem(22);
  13. right: rem(15);
  14. width: rem(22);
  15. height: rem(22);
  16. }
  17. .iconDown {
  18. position: absolute;
  19. top: rem(22);
  20. right: rem(0);
  21. fill: rgba(255, 255, 255, 0.5);
  22. width: rem(22);
  23. height: rem(22);
  24. }
  25. .iconDownActive {
  26. position: absolute;
  27. top: rem(22);
  28. right: rem(0);
  29. fill: #fff;
  30. width: rem(22);
  31. height: rem(22);
  32. }

2.3 背景图片的引入

  1. <div className={style.rankBg} style={{
  2. backgroundImage: `url(${config.variable.app.imgHost + config.imgFiles.rankbg})`
  3. }}>

三、切换、交互、逻辑

3.1 status的使用流程

  1. 通过在interface State中申明类型;
    2 .constructor中, this.state中初始化 status: 0,;
    3. 通过handleChecked触发,在 public handleChecked 中写判断,通过0和1实现状态的改变
    this.setState({
    status,
    })
    4. render()中解构,const { status} = this.state;
    5. 使用status,className里面可根据status === 0/1 显示不同样式;同时通过 handleChecked(0/1) 的点击显示不同的页面: { status ? () :() }
  1. interface State {
  2. /** 经纪人和置业顾问的切换 */
  3. status: number;
  4. /** 经纪人维度排序类型 */
  5. type1: string;
  6. /** 经纪人维度排序方式 */
  7. typeS1: boolean;
  8. /** 置业顾问维度排序类型 */
  9. type2: string;
  10. /** 置业顾问维度排序方式 */
  11. typeS2: boolean;
  12. /** 判断箭头的显示与隐藏 */
  13. iconActive: boolean;
  14. /** 时间的筛选 */
  15. isDate: boolean;
  16. /** 判断角色渲染对应数据 */
  17. roleData: any;
  18. /** 表头列表 */
  19. tabs: string[];
  20. }
  1. constructor(props: PageProps<Match>, state: State) {
  2. super(props, state);
  3. this.init();
  4. const role = config.variable.account.userRole;
  5. let temptab = tabList[0];
  6. let tempType1 = '报备量';
  7. if (role === '销售经理') {
  8. temptab = tabList[1];
  9. tempType1 = '分配量';
  10. }
  11. this.state = {
  12. status: 0, //经纪人和置业顾问的切换
  13. type1: tempType1,
  14. typeS1: true,
  15. type2: '分配量',
  16. typeS2: true,
  17. iconActive: true,
  18. isDate: false,
  19. roleData: [],
  20. tabs: temptab,
  21. };
  22. this.handleChecked = this.handleChecked.bind(this);
  23. this.isIconActive = this.isIconActive.bind(this);
  24. }
  1. /** 经纪人维度和置业顾问维度的切换 */
  2. public handleChecked = (index: number) => {
  3. const status = (index === 0) ? 0 : 1;
  4. this.setState({
  5. status,
  6. });
  7. }
  1. const { status, type1, typeS1, type2, typeS2, tabs } = this.state;
  1. <div className={style.rankBgMenuBox1}>
  2. <div
  3. className={status === 0 ? style.pactive : ''}
  4. onClick={() => this.handleChecked(0)}>
  5. 经纪人维度
  6. </div>
  7. </div>
  8. <div className={style.rankBgMenuBox1}>
  9. <div
  10. className={status === 1 ? style.pactive : ''}
  11. onClick={() => this.handleChecked(1)}>
  12. 置业顾问维度
  13. </div>
  14. </div>
  1. {status ? (
  2. <div className={style.rankListTitle}>
  3. {tabList[2].map((txt: string) => (
  4. <div
  5. onClick={() => this.isIconActive(txt)}
  6. className={style.rankListTitleBox}
  7. key={txt}
  8. >
  9. <span className={txt === '报备经纪人数' ? style.rankListTitleBoxSpan : ''}>{txt}</span>
  10. <Icon
  11. className={classnames(style.iconUp, type2 === txt && typeS2 ? style.iconUpActive : '')}
  12. src={config.svgFiles.rankUp}
  13. />
  14. <Icon
  15. className={classnames(style.iconDown, type2 === txt && !typeS2 ? style.iconDownActive : '')}
  16. src={config.svgFiles.rankDown}
  17. />
  18. </div>
  19. ))}
  20. </div>
  21. ) : (
  22. <div className={style.rankListTitle}>
  23. {tabs.map((txt: string) => (
  24. <div
  25. onClick={() => this.isIconActive(txt)}
  26. className={style.rankListTitleBox}
  27. key={txt}
  28. >
  29. <span>{txt}</span>
  30. <Icon
  31. className={classnames(style.iconUp, type1 === txt && typeS1 ? style.iconUpActive : '')}
  32. src={config.svgFiles.rankUp}
  33. />
  34. <Icon
  35. className={classnames(style.iconDown, type1 === txt && !typeS1 ? style.iconDownActive : '')}
  36. src={config.svgFiles.rankDown}
  37. />
  38. </div>
  39. ))}
  40. </div>
  41. )}

3.2 其他功能的实现

3.2.1 tab根据获取的用户角色(userRole),显示不同的列表,通过 tabList[2].map((txt: string) => ()遍历渲染,其他的tabs(temptab)等,解构后遍历渲染。

  1. const tabList = [
  2. ['报备量', '分配量', '到访量', '认购量', '签约量'],
  3. ['分配量', '到访量', '认购量', '签约量'],
  4. ['分配量', '到访量', '认购量', '签约量', '报备经纪人数'],
  5. ];
  1. constructor(props: PageProps<Match>, state: State) {
  2. super(props, state);
  3. this.init();
  4. const role = config.variable.account.userRole;
  5. let temptab = tabList[0];
  6. let tempType1 = '报备量';
  7. if (role === '销售经理') {
  8. temptab = tabList[1];
  9. tempType1 = '分配量';
  10. }
  1. {status ? (
  2. <div className={style.rankListTitle}>
  3. {tabList[2].map((txt: string) => (
  4. <div
  5. onClick={() => this.isIconActive(txt)}
  6. className={style.rankListTitleBox}
  7. key={txt}
  8. >
  9. <span className={txt === '报备经纪人数' ? style.rankListTitleBoxSpan : ''}>{txt}</span>
  10. <Icon
  11. className={classnames(style.iconUp, type2 === txt && typeS2 ? style.iconUpActive : '')}
  12. src={config.svgFiles.rankUp}
  13. />
  14. <Icon
  15. className={classnames(style.iconDown, type2 === txt && !typeS2 ? style.iconDownActive : '')}
  16. src={config.svgFiles.rankDown}
  17. />
  18. </div>
  19. ))}
  20. </div>
  21. ) : (

3.2.2 type1, typeS1, type2, typeS2 的对应切换

  /** 经纪人维度排序类型 */
  type1: string;
  /** 经纪人维度排序方式 */
  typeS1: boolean;
  /** 置业顾问维度排序类型 */
  type2: string;
  /** 置业顾问维度排序方式 */
  typeS2: boolean;
  /** 判断箭头的显示与隐藏 */
/** 箭头上下的切换 */
  public isIconActive = (type: string) => {
    const { status, type1, typeS1, type2, typeS2 } = this.state;
    if (status) {
      this.setState({
        type2: type,
        typeS2: type2 === type ? !typeS2 : true,
      });
    } else {
      this.setState({
        type1: type,
        typeS1: type1 === type ? !typeS1 : true,
      });
    }
  }

3.2.3 public pageActive(): void { }:

image.png

比如里面的 isDate 就是通过在此进行判断和生效的

public pageActive(): void {
    //请求接口
    // console.log('dasda');
    const startDate = dateFormat(model.customer.channelListRequest.filter.startDateService1.value.get(), 'yyMMdd');
    console.log(startDate);
    if (startDate) {
      this.setState({
        isDate: true
      });
    }
  }

3.2.4 筛选条件:

通过筛选页面给出的接口,

private sendParams = (): FilterParams => {
    const { status, type1, type2, typeS1, typeS2 } = this.state;
    const filter = model.customer.channelListRequest.filter;
    const startDate = dateFormat(filter.startDateService1.value.get(), 'yyMMdd');
    const endDate = dateFormat(filter.endDateService2.value.get(), 'yyMMdd');
    // 这个是项目筛选的
    const ProjectService = filter.ProjectService.value;
    const projectServiceV = ProjectService.get()[0] && ProjectService.get()[0].value;
    // 这个是类型筛选的
    const typeFitter = filter.TypeFitter.value;
    const typeFitterV = typeFitter.get()[0] && typeFitter.get()[0].value;
    return {
         //startDate,
         //endDate,
      //reportType: status === 0 ? 1 : 2,
      //categoryCode: Number(typeFitterV) ? Number(typeFitterV) : undefined,
      //projectInfoId: Number(projectServiceV) ? Number(projectServiceV) : undefined,
      //orderType: status === 0 ? Number(!typeS1) : Number(!typeS2),
      //sortType: status === 0 ? type1 : type2,
    };
  }

例:3.2.3里面的 isDate,写在div中的判断:显示与否。

{this.state.isDate ? (<div>{`${startDate}至${endDate}`}</div>) : (<></>)}

四、数据接口、渲染

4.1 model组件

4.1.1 MVC模式,模型(model)-视图(view)-控制器(controller)

image.png

4.1.2 项目排行.ts

引入组件 ,其中 SendParams 是数据接口组件

import Request, { Event } from 'yuejia/app/request';
import Toast from 'yuejia/component/Toast';
import ObArray from 'yuejia/app/datatype/obArray';
import { SendParams } from 'yuejia/component/ListView';
import Filter from './筛选';
import { Value } from 'yuejia/_base/action/Choice';

定义筛选数据的类型:

export interface FilterParams {
  /** 开始日期 */
  startDate?: string;
  /** 结束日期 */
  endDate?: string;
  /** 渠道类型 */
  categoryCode: number | undefined;
  /** 报表类型 */
  reportType: number;
  /** 项目id */
  projectInfoId: number | undefined;
  /** 排序字段 */
  sortType: string;
  /** 升降序 */
  orderType: number;
}

定义筛选数据的类型:

export interface FilterParams {
  /** 开始日期 */
  startDate?: string;
  /** 结束日期 */
  endDate?: string;
  /** 渠道类型 */
  categoryCode: number | undefined;
  /** 报表类型 */
  reportType: number;
  /** 项目id */
  projectInfoId: number | undefined;
  /** 排序字段 */
  sortType: string;
  /** 升降序 */
  orderType: number;
}

列表:

// 如果是获取数据类型的请求就需要data,并且正确设置类型和初始化;如果是请求类的一般就不需要data
  public data: ObArray<Data> = new ObArray({ request: this });
  public startDate: string | undefined = undefined;
  public endDate: string | undefined = undefined;
  /** 筛选 */
  public filter: Filter = new Filter();
  public total: number = 0;

  private getTypeNum(type: string): number {
    switch (type) {
      case '报备量':
        return 1;
      case '分配量':
        return 2;
      case '到访量':
        return 3;
      case '认购量':
        return 4;
      case '签约量':
        return 5;
      case '报备经纪人数':
        return 6;
      default:
        return 0;
    }
  }

发送数据(重要):

/** 发送请求 */
  public async send(params: Params): Promise<void> {
    this.action({
      config: {
        url: '/channel/ranking/reportdata',
        method: 'post',
        data: {
          categoryCode: params.categoryCode,
          reportType: params.reportType,
          projectInfoId: params.projectInfoId,
          page: params.listviewParams.start,
          rows: params.listviewParams.size,
          startDate: params.startDate ? params.startDate : undefined,
          endDate: params.endDate ? params.endDate : undefined,
          sortType: this.getTypeNum(params.sortType),
          orderType: params.orderType,
        }
      }
    }).then((response) => {
      if (response.retCode === '0000') {
        const state = params.listviewParams.state;

        if (state === 'init') {
          this.data.set(response.result);
        } else {
          const oldData = this.data.get();
          const newData = oldData.concat(response.result);
          this.data.set(newData);
        }
      } else {
        this.state.set('fail');
        Toast.show(response.retMsg);
      }
    }).catch((err) => {
      Toast.show('网络繁忙');
      throw err;
    });
  }

4.2 index.tsx

4.2.1 ListView组件里

  1. 在ListView组件外层,包裹一层div,有利于样式的调整;

  2. 通过params接收数据,传递数据,渲染数据;
    params={
    this.sendParams()
    }
    ···

3 在此之后,通过遍历数据等进行数据渲染。 const item: any = data[params.index];
···

4 其中,可使用 <> </> 组合键将 div 包含在一个标签里。

<div className={style.body}>
  <ListView
    request={getReport}
    limit={this.limit} //限制分页里面的条数
    rowHeight={94}    //可自动获取、可手动固定值
    dataLength={data.length}
    params={
      this.sendParams()  //传值
  }
  emptyRender={(
               <StateView state={'empty'} className={style.emptyView} />
      )}
   failRender={(
               <FailView />
     )}
    loadingRender={(
               <StateView state={'loading'} />
      )}
    >
             {(params: any, i) => {
        const item: any = data[params.index];
        return (
          <div className={style.rankItemCon}>
          <div className={style.rankListNum} key={`${item.brokerName}_${i}`}>
            <div className={style.rankListNumP}>{item.brokerName}</div>
              <div className={style.rankListNumSpan}>
                {userRole === '现管' ? status === 0 ? (
                  <>
                    <span>
                        <div className={style.itemFirNum}>
                        {item.recordTotal}
                        </div>
                    </span>
                    <span>{item.distributionTotal}</span>
                    <span>{item.visitTotal}</span>
                    <span>{item.subscriptionTotal}</span>
                    <span>{item.tradeTotal}</span>
                  </>
                ) : (
                ) : status === 0 ? ()
            }}
        </ListView>
</div>