心情总结:
对于ant.design pro 框架一开始学习特别的苦恼也特别迷茫,经过这两个小练习也能让我终于理解的它的韵味-我就是个小哈批哟
咱开始解谜了哟!!!
登录页面(样式不美但有功能呀!!!)
1.index.txs
import React, { Component } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { Form, Icon, Input, Button, message } from 'antd';
import { connect } from 'dva';
import router from 'umi/router';
import { Dispatch } from 'redux';
import { UserInforData } from './data.d';
import S from './index.less';
//profilUserInfor是namespace的名称 UserInforData来自data.d.ts
//主要是连接model的数据
@connect(({ profilUserInfor }: { profilUserInfor: UserInforData }) => ({
profilUserInfor,
}))
class Mylogin extends Component<{ profilUserInfor: UserInforData; dispatch: Dispatch<any>; }> {
// componentDidMount() {
// const { dispatch } = this.props;
// dispatch({
// //effects 这个*后面的名称
// type: 'profilUserInfor/fetchUserInfor',
// });
// }
//表单提交
handleSubmit = e => {
//组织表单默认行为
e.preventDefault();
//传值
const { form, dispatch } = this.props;
//点击按钮的时候提交表单里面的内容
this.props.form.validateFields((err, values) => {
console.log('在index界面打印',values);
//判断是否有错误, 如果没有错误就将内容传输给model
if (!err) {
dispatch({
//model的namespance名称/effect后面的名称
type: 'profilUserInfor/fetchUserInfor',
//传表单里面所有的值
payload: {
...values,
}
}).then((res: { code: string; message: {} | null | undefined; }) => {
if (res.code === 'ok') {
message.success(res.message);
setTimeout(() => {
router.push('/reportfrom/daily');
}, 500)
} else {
message.error(res.message);
}
});;
}
});
};
render() {
//获取表单里面的数据(表单用法可查ant.design组件的表单
const { getFieldDecorator } = this.props.form;
return (
<PageHeaderWrapper>
// 表单提交函数 this.handleSubmit
<Form onSubmit={this.handleSubmit} className={S.login_form}>
<Form.Item>
//表单用法可以百度总的获取数据
{getFieldDecorator('userName', {
//当输入框里面的内容为空的时候出现提示
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="请输入"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('passWord', {
//当输入框里面的内容为空的时候出现提示
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="请输入数字密码"
/>,
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className={S.login_form_button}>Login</Button>
</Form.Item>
</Form>
</PageHeaderWrapper>
);
}
}
export default Form.create()(Mylogin);
2.model.ts文件
import { AnyAction, Reducer } from 'redux';
import { EffectsCommandMap } from 'dva';
import { UserInforData } from './data.d';
//引入服务端接口???好像是这样
import { queryUserInfor } from './service';
export type Effect = (
action: AnyAction,
effects: EffectsCommandMap & { select: <T>(func: (state: UserInforData) => T) => T },
) => void;
export interface ModelType {
namespace: string;
state: UserInforData;
effects: {
fetchUserInfor: Effect;
};
reducers: {
show: Reducer<UserInforData>;
};
}
const Model: ModelType = {
//model:命名
namespace: 'profilUserInfor',
state: {
UserInforData: null,
},
effects: {
*fetchUserInfor({ payload }, { call, put }) {
console.log('在model里面打印', payload);
const res = yield call(queryUserInfor, payload);
console.log(res);
//一般有关于交互部分为了规范代码所以不建议写在这边,将数据返回再到页面点击按钮的函数那边dispatch后边跟个回调函数
if (res) {
return res;
}
},
},
//做数据的更改
reducers: {
show(state, { payload }) {
return {
...state,
...payload,
};
},
},
};
export default Model;
3._mock.ts
//请求函数里面要用的引入哦
import { Request, Response } from 'express';
//与提交的密码做对比的数据
const UserInfordata = [
{
userName: 'zhangsan',
passWord: '123456',
},
{
userName: 'wangwu',
passWord: '14789',
},
{
userName: 'laowang',
passWord: '36987',
},
];
function getUserRule(req: Request, res: Response) {
//接受传值
const { body } = req;
//拷贝数组
let userList = UserInfordata;
//返回最后给出的结果
let resul_success = {
code: 'ok',
message: '登录成功',
data: userList,
};
let resul_error = {
code: 'error',
message: '登录失败',
data: [],
};
console.log('没有筛选前', userList);
//筛选部分
if (body.userName && body.passWord) {
console.log('得到提交数据: 用户名, ' + body.userName + '密码, ' + body.passWord);
//筛选输入值是否满足mock已存的数据
userList = userList.filter(item => {
return item.userName === body.userName && item.passWord == body.passWord;
});
}
//返回筛选部分是否有满足
if (userList.length === 0) {
return res.json(resul_error);
} else {
console.log('登录成功');
console.log(userList);
return res.json(resul_success);
// result.code = 'success'
// result.message = '登录成功'
// result.data = userList
// console.log(result)
}
// return res.json(result);
}
//请求
export default {
'POST /api/userInfor': getUserRule,
// 'POST /api/userInfor': getUserRule,
};
4.service.ts
import request from '@/utils/request';
export async function queryUserInfor(params) {
//从model里面传过来的params
console.log('service里面打印', params);
return request('/api/userInfor', {
method: 'POST',
data: params, //get请求 键名用params post请求 键名用data
});
}
5.data.d.ts
//mock里面数据的类型定义
export interface UserInfor {
userName: string;
passWord: string;
}
export interface UserInforData {
code: string;
message: string;
data: [];
}
以上是可以做到登录页面了,对于登录页面是一定要做到校验那两个输入框的不然空了也可以进去呢
2.搜索功能(对于下拉框啦,时间那边没有处理到位,要是有好办法扣我呀)
1.index.tsx
import React, { Component } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { Select, Form, DatePicker, Button, Table } from 'antd';
import { Dispatch } from 'redux';
import S from './index.less';
import { AdvancedProfileData } from './data.d';
import { connect } from 'dva';
const { RangePicker } = DatePicker;
//表格的表头信息
const columns = [
{
title: '日期',
dataIndex: 'date',
width: 150,
fixed: 'left',
defaultSortOrder: 'descend',
},
{
title: '游戏',
dataIndex: 'name',
width: 150,
fixed: 'left',
defaultSortOrder: 'descend',
},
{
title: '累计用户数',
dataIndex: 'totalUsers',
filterMultiple: false,
sorter: (a: { totalUsers: number; }, b: { totalUsers: number; }) => a.totalUsers - b.totalUsers,
},
{
title: '日活跃用户',
dataIndex: 'DAU',
filterMultiple: false,
sorter: (a: { DAU: number; }, b: { DAU: number; }) => a.DAU - b.DAU,
},
{
title: '新增用户数',
dataIndex: 'newPercent',
filterMultiple: false,
sorter: (a: { newPercent: number; }, b: { newPercent: number; }) => a.newPercent - b.newPercent,
},
{
title: '人均停留时长',
dataIndex: 'duration',
filterMultiple: false,
sorter: (a: { duration: number; }, b: { duration: number; }) => a.duration - b.duration,
},
{
title: '新增次留',
dataIndex: 'derivedRate2',
filterMultiple: false,
sorter: (a: { derivedRate2: number; }, b: { derivedRate2: number; }) => a.derivedRate2 - b.derivedRate2,
},
{
title: '新增三留',
dataIndex: 'derivedRate3',
filterMultiple: false,
sorter: (a: { derivedRate3: number; }, b: { derivedRate3: number; }) => a.derivedRate3 - b.derivedRate3,
},
{
title: '新增七留',
dataIndex: 'derivedRate7',
filterMultiple: false,
sorter: (a: { derivedRate7: number; }, b: { derivedRate7: number; }) => a.derivedRate7 - b.derivedRate7,
},
{
title: '导出率(总)',
dataIndex: 'derivedRateAld',
filterMultiple: false,
sorter: (a: { derivedRateAld: number; }, b: { derivedRateAld: number; }) => a.derivedRateAld - b.derivedRateAld,
},
{
title: '流量主ARPU',
dataIndex: 'ARPU',
filterMultiple: false,
sorter: (a: { ARPU: number; }, b: { ARPU: number; }) => a.ARPU - b.ARPU,
},
{
title: '流量主收入',
dataIndex: 'adIncome',
filterMultiple: false,
sorter: (a: { adIncome: number; }, b: { adIncome: number; }) => a.adIncome - b.adIncome,
},
{
title: 'banner收入',
dataIndex: 'bannerIncome',
filterMultiple: false,
sorter: (a: { bannerIncome: number; }, b: { bannerIncome: number; }) => a.bannerIncome - b.bannerIncome,
},
{
title: '视频收入',
dataIndex: 'videoIncome',
filterMultiple: false,
sorter: (a: { videoIncome: number; }, b: { videoIncome: number; }) => a.videoIncome - b.videoIncome,
},
{
title: '插屏广告收入',
dataIndex: 'interstitialARPU',
filterMultiple: false,
sorter: (a: { interstitialARPU: number; }, b: { interstitialARPU: number; }) => a.interstitialARPU - b.interstitialARPU,
},
{
title: '3日LTV',
dataIndex: 'LTV3',
filterMultiple: false,
sorter: (a: { LTV3: number; }, b: { LTV3: number; }) => a.LTV3 - b.LTV3,
},
{
title: '7日LTV',
dataIndex: 'LTV7',
filterMultiple: false,
sorter: (a: { LTV7: number; }, b: { LTV7: number; }) => a.LTV7 - b.LTV7,
},
{
title: '15日LTV',
dataIndex: 'LTV15',
filterMultiple: false,
sorter: (a: { LTV15: number; }, b: { LTV15: number; }) => a.LTV15 - b.LTV15,
},
];
//关联model
@connect(({ profileAndadvanced }: { profileAndadvanced: AdvancedProfileData }) => ({
profileAndadvanced,
}))
class Daily extends Component<{profileAndadvanced: AdvancedProfileData;dispatch: Dispatch<any>;}> {
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'profileAndadvanced/fetchAdvanced',
});
}
//提交按钮
handleSubmit = e => {
e.preventDefault();
const { form, dispatch } = this.props;
form.validateFields((err: any, values: any) => {
if (!err) {
dispatch({
type: 'profileAndadvanced/searchGame',
payload: {
...values,
}
});
}
});
};
render() {
const { profileAndadvanced } = this.props;
const { gameTable, games } = profileAndadvanced;
const { getFieldDecorator } = this.props.form;
//样式调整
const formItemLayout = {
layout: 'inline',
};
//在上面表单校验是写在表单里面,当然也可以写在外面呢
const config = {
rules: [{ type: 'object', required: true, message: 'Please select time!' }],
};
const rangeConfig = {
rules: [{ type: 'array', required: true, message: 'Please select time!' }],
};
const { Option } = Select;
return (
<>
<PageHeaderWrapper className={S.daily}>
<div className={S.daily_wrap}>
<Form {...formItemLayout} className={S.formWrap} onSubmit={this.handleSubmit}>
<Form.Item label="游戏:">
{getFieldDecorator(
'gameName',
)(
<Select
showSearch
style={{ width: 200 }}
placeholder="选择游戏"
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{games.map((game, index) => {
return <Option key={index} value={game.name}>{game.name}</Option>;
})}
</Select>)}
</Form.Item>
<Form.Item label="日期">
{getFieldDecorator(
'rangeTimePicker',
//这就是上面说的校验哦,具体内容是在上头哟
rangeConfig,
)(<RangePicker showTime format="YYYY-MM-DD"/>)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
查询
</Button>
</Form.Item>
</Form>
<Table scroll={{ x: true }} columns={columns} dataSource={gameTable} rowKey='appid' />
</div>
</PageHeaderWrapper>
</>
);
}
}
export default Form.create()(Daily);
2.model.ts
import { AnyAction, Reducer } from 'redux';
import { EffectsCommandMap } from 'dva';
import { AdvancedProfileData } from './data.d';
import { queryAdvancedProfile,searchInfor} from './service';
export type Effect = (
action: AnyAction,
effects: EffectsCommandMap & { select: <T>(func: (state: AdvancedProfileData) => T) => T },
) => void;
export interface ModelType {
namespace: string;
state: AdvancedProfileData;
effects: {
fetchAdvanced: Effect;
};
reducers: {
show: Reducer<AdvancedProfileData>;
};
}
function number(_n) {
return String(_n).length === 1 ? `0${_n}` : _n
}
const Model: ModelType = {
namespace: 'profileAndadvanced',
state: {
gameTable: [],
games: [],
},
effects: {
*fetchAdvanced({ payload }, { call, put }) {
const response = yield call(queryAdvancedProfile);
yield put({
type: 'show',
payload: response,
});
},
*searchGame({ payload }, { call, put }) {
var s = new Date(payload.rangeTimePicker[0]._d);
var e = new Date(payload.rangeTimePicker[1]._d);
let Arr=[1]
let obj={
gameName:payload.gameName,
rangeTimePicker: [
s.getFullYear() + '-' + number(s.getMonth() + 1) + '-' + number(s.getDate()),
e.getFullYear() + '-' + number(e.getMonth() + 1) + '-' + number(e.getDate())
]
}
const res = yield call(searchInfor,obj);
console.log(obj)
if(res.success){
console.log('res---------------------',res)
yield put({
type: 'show',
payload: {
gameTable: res.data,
},
});
}
},
},
reducers: {
show(state, { payload }) {
return {
...state,
...payload,
};
},
// changegame() {
// },
}
}
export default Model;
3._mock.ts
import { Request, Response } from 'express';
const gameTable = [
{
date: '2019-12-17',
appid: '1',
totalUsers: 11,
totalDAU: 990818,
DAU: 62765,
DAU2: 64692,
DNU: 42691,
derivedCount2: 34944,
newPercent: 0.6802,
duration: 224,
retention2: 0,
retention3: 0,
retention7: 0,
ARPU: 0.1063,
bannerIncome: '6048.86',
videoIncome: '621.85',
interstitialIncome: '0.00',
LTV3: '0.2490',
LTV7: '0.2835',
LTV15: '0.3196',
name: '贪吃龙进化',
bannerARPU: '0.0964',
interstitialARPU: '0.0000',
videoARPU: '0.0099',
adIncome: '6670.71',
derivedRate2: '54.02',
derivedRateNewUser: null,
derivedRateOldUser: 'NaN',
derivedRateAld: null,
conversationRate: null,
kValue: null,
avgDAU: '1.2559',
derivedSellARPU: null,
},
{
date: '2019-12-17',
appid: '2',
totalUsers: 11,
totalDAU: 990818,
DAU: 62765,
DAU2: 64692,
DNU: 42691,
derivedCount2: 34944,
newPercent: 0.6802,
duration: 224,
retention2: 0,
retention3: 0,
retention7: 0,
ARPU: 0.1063,
bannerIncome: '6048.86',
videoIncome: '621.85',
interstitialIncome: '0.00',
LTV3: '0.2490',
LTV7: '0.2835',
LTV15: '0.3196',
name: '贪吃龙进化',
bannerARPU: '0.0964',
interstitialARPU: '0.0000',
videoARPU: '0.0099',
adIncome: '6670.71',
derivedRate2: '54.02',
derivedRateNewUser: null,
derivedRateOldUser: 'NaN',
derivedRateAld: null,
conversationRate: null,
kValue: null,
avgDAU: '1.2559',
derivedSellARPU: null,
},
{
date: '2019-12-17',
appid: '3',
totalUsers: 11,
totalDAU: 990818,
DAU: 62765,
DAU2: 64692,
DNU: 42691,
derivedCount2: 34944,
newPercent: 0.6802,
duration: 224,
retention2: 0,
retention3: 0,
retention7: 0,
ARPU: 0.1063,
bannerIncome: '6048.86',
videoIncome: '621.85',
interstitialIncome: '0.00',
LTV3: '0.2490',
LTV7: '0.2835',
LTV15: '0.3196',
name: '狂扁英雄',
bannerARPU: '0.0964',
interstitialARPU: '0.0000',
videoARPU: '0.0099',
adIncome: '6670.71',
derivedRate2: '54.02',
derivedRateNewUser: null,
derivedRateOldUser: 'NaN',
derivedRateAld: null,
conversationRate: null,
kValue: null,
avgDAU: '1.2559',
derivedSellARPU: null,
},
{
date: '2019-12-17',
appid: '4',
totalUsers: 11,
totalDAU: 990818,
DAU: 62765,
DAU2: 64692,
DNU: 42691,
derivedCount2: 34944,
newPercent: 0.6802,
duration: 224,
retention2: 0,
retention3: 0,
retention7: 0,
ARPU: 0.1063,
bannerIncome: '6048.86',
videoIncome: '621.85',
interstitialIncome: '0.00',
LTV3: '0.2490',
LTV7: '0.2835',
LTV15: '0.3196',
name: '大鱼吃小鱼总动员',
bannerARPU: '0.0964',
interstitialARPU: '0.0000',
videoARPU: '0.0099',
adIncome: '6670.71',
derivedRate2: '54.02',
derivedRateNewUser: null,
derivedRateOldUser: 'NaN',
derivedRateAld: null,
conversationRate: null,
kValue: null,
avgDAU: '1.2559',
derivedSellARPU: null,
},
{
date: '2019-12-17',
appid: '5',
totalUsers: 788907,
totalDAU: 990818,
DAU: 62765,
DAU2: 64692,
DNU: 42691,
derivedCount2: 34944,
newPercent: 0.6802,
duration: 224,
retention2: 0,
retention3: 0,
retention7: 0,
ARPU: 0.1063,
bannerIncome: '6048.86',
videoIncome: '621.85',
interstitialIncome: '0.00',
LTV3: '0.2490',
LTV7: '0.2835',
LTV15: '0.3196',
name: '侠盗猎手',
bannerARPU: '0.0964',
interstitialARPU: '0.0000',
videoARPU: '0.0099',
adIncome: '6670.71',
derivedRate2: '54.02',
derivedRateNewUser: null,
derivedRateOldUser: 'NaN',
derivedRateAld: null,
conversationRate: null,
kValue: null,
avgDAU: '1.2559',
derivedSellARPU: null,
},
];
const games = [
{
id: '1',
name: '侠盗猎手',
},
{
id: '2',
name: '僵尸娃娃机',
},
{
id: '3',
name: '口袋怪兽超进化',
},
{
id: '4',
name: '大鱼吃小鱼总动员',
},
{
id: '5',
name: '天天来爬山',
},
{
id: '6',
name: '毁灭病毒大作战',
},
{
id: '7',
name: '狂扁英雄',
},
{
id: '8',
name: '紧急营救',
},
{
id: '9',
name: '贪吃小恐龙',
},
{
id: '10',
name: '贪吃的恐龙',
},
{
id: '11',
name: '贪吃龙进化',
},
{
id: '12',
name: '饥饿飞龙',
},
{
id: '13',
name: '饿龙传说',
},
{
id: '14',
name: '饿龙传说(备用3)',
},
{
id: '15',
name: '魔法拼拼',
},
];
//过滤
let GameTable: GameTable[] = [];
function getRule(req: Request, res: Response) {
const { params } = req;
console.log(params)
const { gameName, rangeTimePicker } = req.query;
console.log(req.query)
let _data= gameTable.filter(function (obj) {
let s1 = Number(rangeTimePicker[0].split('-').join(''))//ks
let e1 = Number(rangeTimePicker[1].split('-').join(''))//js
let b1 = Number(obj.date.split('-').join('')) //date
// console.log(s1 <= b1 && e1 >= b1 && gameName === obj.name)
if (s1 <= b1 && e1 >= b1 && gameName === obj.name) {
return obj
}
if (gameName) {
if (gameName === obj.name && s1 <= b1 && e1 >= b1) return obj
}
if (!gameName && s1 <= b1 && e1 >= b1) {
return obj
}
});
console.log(_data)
let dataSource = GameTable;
if (params.rangeTimePicker) {
const rangeTimePicker = params.rangeTimePicker.split('-');
let filterDataSource: GameTable[] = [];
rangeTimePicker.forEach((s: string) => {
filterDataSource = filterDataSource.concat(
dataSource.filter(item => {
if (parseInt(`${item.rangeTimePicker}`, 10) === parseInt(s.split('')[0], 10)) {
return true;
}
return false;
}),
);
});
dataSource = filterDataSource;
}
if (params.name) {
dataSource = dataSource.filter(data => data.name.includes(params.name || ''));
}
let pageSize = 10;
if (params.pageSize) {
pageSize = parseInt(`${params.pageSize}`, 0);
}
const result = {
data: _data,
total:_data.length,
success: true,
pageSize,
current: parseInt(`${params.currentPage}`, 10) || 1,
};
return res.json(result);
}
const getProfileAdvancedData = {
gameTable,
games,
};
export default {
'GET /api/reportfrom/daily': getProfileAdvancedData,
'POST /api/reportfrom/daily': getRule,
};
4.service.ts
import request from '@/utils/request';
import { GameTable } from "./data.d";
export async function queryAdvancedProfile(params: ParamsType) {
return request('/api/reportfrom/daily', { params });
}
export async function searchInfor(params: ParamsType) {
const { gameName, rangeTimePicker } = params;
return request('/api/reportfrom/daily', {
method: 'POST',
params: {
gameName,
rangeTimePicker
},
});
}
5.data.d.ts
//表格里的数据类型
export interface GameTable {
date: number;
appid: string;
totalUsers: number;
totalDAU: number;
DAU: number;
DAU2: number;
DNU: number;
derivedCount2: number;
newPercent: number;
duration: number;
retention2: number;
retention3: number;
retention7: number;
ARPU: number;
bannerIncome: number;
videoIncome: number;
interstitialIncome: number;
LTV3: number;
LTV7: number;
LTV15: number;
name: number;
bannerARPU: number;
interstitialARPU: number;
videoARPU: number;
adIncome: number;
derivedRate2: number;
derivedRateNewUser: null;
derivedRateOldUser: NaN;
derivedRateAld: null;
conversationRate: null;
kValue: null;
avgDAU: number;
derivedSellARPU: null;
}
//下拉框里面的数据类型
export interface Games {
id: string;
name: string;
}
export interface AdvancedProfileData {
gameTable: GameTable[];
games: Games[];
}
对于搜索功能其实我也就做到了筛选下拉框的值,时间部分我太菜了还没做这个哟
一旦了真的理解思路看其他项目也就不会那么迷茫了,嘻嘻
菜鸟的世界就是如此的模糊,求大佬带带飞