Commit 593349ab authored by 前端-许佳敏's avatar 前端-许佳敏

fix: 补充组件问题

parent 94c91d13
/*
* @Author: XieZhiXiong
* @Date: 2021-06-10 14:30:16
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-06-10 15:29:04
* @Description: 区域选择单个Select
*/
import React from 'react';
import { Select } from 'antd';
import { SelectProps } from 'antd/lib/select';
interface IProps extends SelectProps<string> {
/**
* 选项
*/
options: {
/**
* 描述
*/
label: string,
/**
* 值
*/
value: any,
}[],
}
const AreaSelectItem = (props: IProps) => {
const {
disabled,
value,
options,
} = props;
const current = options.find((item) => item.value === value);
if (!disabled) {
return (
<Select
style={{
width: '100%',
}}
{...props}
>
{options.map((item) => (
<Select.Option
key={item.value}
value={item.value}
>
{item.label}
</Select.Option>
))}
</Select>
);
}
return (
<div>{current?.label}</div>
);
};
export default AreaSelectItem;
/*
* @Author: XieZhiXiong
* @Date: 2021-06-10 14:27:37
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-06-10 16:00:07
* @Description: 区域选择
*/
import React, { useState, useEffect } from 'react';
import { Row, Col } from 'antd';
import SelectItem from './SelectItem';
import { getMemberAreaCity, getMemberAreaDistrict, getMemberAreaProvince } from '@/services/MemberV2Api';
function isObj(value) {
return typeof value === 'object';
};
type AreaCodeType = {
/**
* 区域编码
*/
value: string
/**
* 区域名称
*/
label: string
}
const AreaSelect = (props) => {
const {
mutators,
editable,
} = props;
const value = isObj(props.value) ? props.value : {};
const XComponentProps = props.props['x-component-props'] || {};
const SelectProps = {
...XComponentProps,
disabled: !editable || XComponentProps.disabled,
};
const [provinceList, setProvinceList] = useState<AreaCodeType[]>([]);
const [cityList, setCityList] = useState<AreaCodeType[]>([]);
const [districtList, setDistrictList] = useState<AreaCodeType[]>([]);
const [provinceLoading, setProvinceLoading] = useState(false);
const [cityLoading, setCityLoading] = useState(false);
const [districtLoading, setDistrictLoading] = useState(false);
const [provinceValue, setProvinceValue] = useState<string | undefined>('');
const [cityValue, setCityValue] = useState<string | undefined>('');
const [districtValue, setDistrictValue] = useState<string | undefined>('');
const getProvinceList = async () => {
setProvinceLoading(true);
const res = await getMemberAreaProvince();
if (res.code === 1000) {
setProvinceList(res.data.map((item) => ({ label: item.name, value: item.code })));
}
setProvinceLoading(false);
};
const getCityList = async (code?: string) => {
if (!code) {
return;
}
setCityLoading(true);
const res = await getMemberAreaCity({ code });
if (res.code === 1000) {
setCityList(res.data.map((item) => ({ label: item.name, value: item.code })));
}
setCityLoading(false);
};
const getDistrictList = async (code?: string) => {
if (!code) {
return;
}
setDistrictLoading(true);
const res = await getMemberAreaDistrict({ code });
if (res.code === 1000) {
setDistrictList(res.data.map((item) => ({ label: item.name, value: item.code })));
}
setDistrictLoading(false);
};
useEffect(() => {
const {
provinceCode,
cityCode,
districtCode,
} = value;
setProvinceValue(provinceCode);
setCityValue(cityCode);
setDistrictValue(districtCode);
}, [value]);
useEffect(() => {
getProvinceList();
}, []);
useEffect(() => {
getCityList(provinceValue);
}, [provinceValue]);
useEffect(() => {
getDistrictList(cityValue);
}, [cityValue]);
const handleProvinceChange = (next: string) => {
setProvinceValue(next);
setCityValue(undefined);
setCityList([]);
setDistrictList([]);
setDistrictValue(undefined);
mutators.change({ ...value, provinceCode: next, cityCode: undefined, districtCode: undefined });
};
const handleCityChange = (next: string) => {
setCityValue(next);
setDistrictList([]);
setDistrictValue(undefined);
mutators.change({ ...value, cityCode: next, districtCode: undefined });
};
const handleDistrictChange = (next: string) => {
setDistrictValue(next);
mutators.change({ ...value, districtCode: next });
};
return (
<div
style={{
width: '100%',
}}
>
<Row gutter={16}>
<Col span={8}>
<SelectItem
placeholder="- 省 -"
allowClear
{...SelectProps}
value={provinceValue}
options={provinceList}
loading={provinceLoading}
onChange={handleProvinceChange}
/>
</Col>
<Col span={8}>
<SelectItem
placeholder="- 市 -"
allowClear
{...SelectProps}
value={cityValue}
options={cityList}
loading={cityLoading}
onChange={handleCityChange}
/>
</Col>
<Col span={8}>
<SelectItem
placeholder="- 县 / 区 -"
allowClear
{...SelectProps}
value={districtValue}
options={districtList}
loading={districtLoading}
onChange={handleDistrictChange}
/>
</Col>
</Row>
</div>
);
};
AreaSelect.isFieldComponent = true;
export default AreaSelect
import React from 'react';
import {
Steps,
Tabs,
} from 'antd';
import MellowCard from '@/components/MellowCard';
import styles from './index.less';
interface AuditProcessProp {
outerVerifyCurrent?: number;
innerVerifyCurrent?: number;
outerVerifySteps?: { step: number, stepName: string, roleName: string }[];
innerVerifySteps?: { step: number, stepName: string, roleName: string }[];
};
const AuditProcess: React.FC<AuditProcessProp> = ({
outerVerifyCurrent = 0,
innerVerifyCurrent = 0,
outerVerifySteps = [],
innerVerifySteps = [],
}) => (
<MellowCard>
<Tabs onChange={() => {}}>
<Tabs.TabPane tab="外部审核流程" key="1">
<Steps style={{ marginTop: 30 }} progressDot current={outerVerifyCurrent}>
{outerVerifySteps.map(item => (
<Steps.Step
key={item.step}
title={item.stepName}
description={item.roleName}
/>
))}
</Steps>
</Tabs.TabPane>
<Tabs.TabPane tab="内部审核流程" key="2">
<Steps style={{ marginTop: 30 }} progressDot current={innerVerifyCurrent}>
{innerVerifySteps.map(item => (
<Steps.Step
key={item.step}
title={item.roleName}
description={item.stepName}
/>
))}
</Steps>
</Tabs.TabPane>
</Tabs>
</MellowCard>
);
export default AuditProcess;
\ No newline at end of file
.basicInfo {
.descriptions {
:global {
.ant-descriptions-item-label {
flex: 0 0 128px;
color: rgba(107, 119, 140, 1);
}
}
}
}
\ No newline at end of file
import React, { useState } from 'react';
import {
Row,
Col,
Descriptions,
} from 'antd';
import MellowCard from '@/components/MellowCard';
import {
MEMBER_TYPE_CHANNEL_CORPORATE,
MEMBER_TYPE_CHANNEL_INDIVIDUAL,
} from '@/constants/const/member';
import { renderFieldTypeContent } from '../../utils';
import PicWrap from '../PicWrap';
import FlowRecords, { InnerHistoryItem, OuterHistoryItem } from '../FlowRecords';
import styles from './index.less';
interface BasicInfoProps {
basic?: {
account?: string,
phone?: string,
email?: string,
created?: string,
};
channel?: {
memberType?: number,
level?: string,
type?: string,
areas?: string[],
desc?: string,
};
extra?: {
groupName: string,
elements: {
/**
* 注册资料id
*/
id?: number,
/**
* 字段名称
*/
fieldName?: string,
/**
* 中文名称
*/
fieldLocalName?: string,
/**
* 字段类型
*/
fieldType?: string,
/**
* 字段类型附加属性(该参数为map)
*/
attr?: { [key: string]: any },
/**
* 字段长度
*/
fieldLength?: number,
/**
* 是否可为空0-不能为空1-可以为空
*/
fieldEmpty?: number,
/**
* 字段顺序
*/
fieldOrder?: number,
/**
* 帮助信息
*/
fieldRemark?: string,
/**
* 枚举标签列表
*/
fieldEnum?: {
value?: number,
label?: string,
}[],
/**
* 字段校验规则枚举:0-无校验规则,1-邮箱规则,2-手机号码规则,3-身份证规则,4-电话号码规则
*/
ruleEnum?: number,
/**
* 校验规则的正则表达式
*/
pattern?: string,
/**
* 校验错误的提示语
*/
msg?: string,
/**
* 值
*/
fieldValue?: any,
/**
* 是否禁用
*/
disabled?: boolean,
}[],
}[];
outerHistory?: OuterHistoryItem[];
innerHistory?: InnerHistoryItem[];
channelRender?: React.ReactNode; // 自定义渲染渠道信息
}
const BasicInfo: React.FC<BasicInfoProps> = ({
basic = {},
channel = {},
extra = [],
outerHistory = [],
innerHistory = [],
channelRender,
}) => {
return (
<div className={styles.basicInfo}>
<Row gutter={[0, 24]}>
<Col span={24}>
<MellowCard
title="基本信息"
>
<Descriptions column={2} className={styles.descriptions}>
<Descriptions.Item label="登录账户">{basic.account}</Descriptions.Item>
<Descriptions.Item label="注册手机号">{basic.phone}</Descriptions.Item>
<Descriptions.Item label="注册邮箱">{basic.email}</Descriptions.Item>
<Descriptions.Item label="申请时间">{basic.created}</Descriptions.Item>
</Descriptions>
</MellowCard>
</Col>
{
(
channel.memberType === MEMBER_TYPE_CHANNEL_CORPORATE ||
channel.memberType === MEMBER_TYPE_CHANNEL_INDIVIDUAL
) && (
<Col span={24}>
<MellowCard
title="渠道信息"
>
{!channelRender ? (
<Descriptions column={2} className={styles.descriptions}>
<Descriptions.Item label="渠道级别">{channel.level}</Descriptions.Item>
<Descriptions.Item label="渠道类型">{channel.type}</Descriptions.Item>
<Descriptions.Item label="代理地市">
<Row gutter={[16, 16]} style={{ flex: 1 }}>
{
channel.areas ?
channel.areas.map(item => (
<Col key={item} span={12}>{item}</Col>
)) :
null
}
</Row>
</Descriptions.Item>
<Descriptions.Item label="渠道描述">{channel.desc}</Descriptions.Item>
</Descriptions>
) : channelRender}
</MellowCard>
</Col>
)
}
{extra.map((item, index) => (
<Col key={index} span={24}>
<MellowCard
title={item.groupName}
>
<Row gutter={20}>
<Col span={12}>
<Descriptions column={1} className={styles.descriptions}>
{item.elements.map((ele, index) => (index + 1) % 2 !== 0 ? (
<Descriptions.Item key={index} label={ele.fieldLocalName}>
{renderFieldTypeContent(ele.fieldType as any, ele.fieldValue)}
</Descriptions.Item>
) : null)}
</Descriptions>
</Col>
<Col span={12}>
<Descriptions column={1} className={styles.descriptions}>
{item.elements.map((ele, index) => (index + 1) % 2 === 0 ? (
<Descriptions.Item key={index} label={ele.fieldLocalName}>
{renderFieldTypeContent(ele.fieldType as any, ele.fieldValue)}
</Descriptions.Item>
) : null)}
</Descriptions>
</Col>
</Row>
</MellowCard>
</Col>
))}
<Col span={24}>
<FlowRecords
outerHistory={outerHistory}
innerHistory={innerHistory}
/>
</Col>
</Row>
</div>
);
};
export default BasicInfo;
@import '../../../../global/styles/utils.less';
.equityInfo {
.container {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 24px;
height: 192px;
background: #ffffff;
border-radius: 8px;
&-title {
margin-bottom: 20px;
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: rgba(23, 43, 77, 1);
}
&-content {
}
}
.tofo {
display: flex;
padding: 0 0 4px;
margin: 0;
min-height: 101px;
overflow-x: auto;
.silkyScrollbar();
&-item {
flex: 0 0 33.333%;
list-style: none;
&-logo {
width: 40px;
height: 40px;
margin: 0 auto 4px;
text-align: center;
> img {
width: 100%;
height: 100%;
}
}
&-title {
margin-bottom: 9px;
line-height: 22px;
font-size: 12px;
font-weight: 400;
color: rgba(23, 43, 77, 1);
text-align: center;
:global {
.anticon {
margin-left: 4px;
}
}
}
&-extra {
text-align: center;
}
&-tag {
line-height: 22px;
padding: 0 6px;
font-size: 12px;
font-weight: 400;
text-align: center;
border-radius: 4px;
&-price {
color: rgba(101, 84, 192, 1);
background: rgba(234, 230, 255, 1);
}
&-recurrence {
color: rgba(230, 63, 59, 1);
background: rgba(255, 235, 230, 1);
}
&-integral {
color: rgba(255, 153, 31, 1);
background: rgba(255, 250, 230, 1);
}
}
}
}
.exhibition {
display: flex;
align-items: center;
&-left {
flex: 1;
}
&-right {
flex-shrink: 0;
}
&-title {
line-height: 20px;
margin-bottom: 24px;
font-size: 12px;
font-weight: 400;
color: rgba(107, 119, 140, 1);
}
&-amount {
font-size: 24px;
font-weight: 500;
color: rgba(23, 43, 77, 1);
line-height: 24px;
> span {
margin-left: 8px;
line-height: 12px;
font-size: 12px;
font-weight: 400;
color: rgba(107, 119, 140, 1);
}
}
&-logo {
width: 72px;
height: 72px;
> img {
width: 100%;
height: 100%;
}
}
}
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import {
Row,
Col,
Tabs,
Tooltip,
} from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import PolymericTable from '@/components/PolymericTable';
import { EditableColumns } from '@/components/PolymericTable/interface';
import MellowCard from '@/components/MellowCard';
import styles from './index.less';
import equity_1 from '@/asserts/equity-1.png';
import equity_2 from '@/asserts/equity-2.png';
import equity_3 from '@/asserts/equity-3.png';
import equity_4 from '@/asserts/equity-4.png';
import equity_5 from '@/asserts/equity-5.png';
const imgMap = {
1: equity_1,
2: equity_2,
3: equity_3,
4: equity_4,
5: equity_5,
}
const { TabPane } = Tabs;
const PAGE_SIZE = 8;
export interface ReceivedData {
id?: number;
createTime?: string;
ruleName?: string;
score?: number;
remark?: string;
};
export interface UsageData {
id?: number;
createTime?: string;
rightTypeName?: string;
spendTypeName?: string;
point?: number;
remark?: string;
};
export interface FetchParams {
current: number;
pageSize: number;
};
export interface EquityInfoProps {
equityInfo?: {
sumReturnMoney?: number, // 累计返现金额
sumUsedPoint?: number, // 已用积分
sumPoint?: number, // 累计积分
rights?: {
acquireWay: string,
id: number,
name: string,
paramWay: string,
parameter: string,
remark: string,
rightTypeEnum: number,
status: number,
}[];
};
fetchReceivedList?: (params: FetchParams) => Promise<{ data: ReceivedData[] , totalCount: number }>;
fetchUsageList?: (params: FetchParams) => Promise<{ data: UsageData[] , totalCount: number }>;
}
const equityTxtMap = {
1: '折扣',
2: '返现',
3: '积分',
};
const clsMap = {
1: 'tofo-item-tag-price',
2: 'tofo-item-tag-recurrence',
3: 'tofo-item-tag-integral',
};
const EquityInfo: React.FC<EquityInfoProps> = ({
equityInfo = {},
fetchReceivedList,
fetchUsageList,
}) => {
const [receivedPage, setReceivedPage] = useState(1);
const [receivedSize, setReceivedSize] = useState(PAGE_SIZE);
const [receivedTotal, setReceivedTotal] = useState(0);
const [receivedList, setReceivedList] = useState<ReceivedData[]>([]);
const [receivedListLoading, setReceivedListLoading] = useState(false);
const [usagePage, setUsagePage] = useState(1);
const [usageSize, setUsageSize] = useState(PAGE_SIZE);
const [usageTotal, setUsageTotal] = useState(0);
const [usageList, setUsageList] = useState<UsageData[]>([]);
const [usageListLoading, setUsageListLoading] = useState(false);
const receivedColumns: EditableColumns[] = [
{
title: 'ID',
dataIndex: 'id',
align: 'center',
},
{
title: '会员权益名称',
dataIndex: 'rightTypeName ',
align: 'center',
},
{
title: '获取数量',
dataIndex: 'point',
align: 'center',
},
{
title: '获取时间',
dataIndex: 'createTime',
align: 'center',
},
{
title: '备注',
dataIndex: 'remark',
align: 'center',
ellipsis: true,
},
];
const usageColumns: EditableColumns[] = [
{
title: 'ID',
dataIndex: 'id',
align: 'center',
},
{
title: '会员权益名称',
dataIndex: 'rightTypeName',
align: 'center',
},
{
title: '会员权益使用名称',
dataIndex: 'spendTypeName',
align: 'center',
},
{
title: '使用数量',
dataIndex: 'point',
align: 'center',
},
{
title: '使用时间',
dataIndex: 'createTime',
align: 'center',
},
{
title: '备注',
dataIndex: 'remark',
align: 'center',
ellipsis: true,
},
];
const getReceivedList = (params?) => {
if (fetchReceivedList) {
if (receivedListLoading) {
return;
}
setReceivedListLoading(true);
fetchReceivedList({
current: receivedPage,
pageSize: receivedSize,
...params,
}).then(res => {
const { data = [], totalCount = 0 } = (res || {});
setReceivedList(data);
setReceivedTotal(totalCount);
}).finally(() => {
setReceivedListLoading(false);
});
}
};
const getUsageList = (params?) => {
if (fetchUsageList) {
if (usageListLoading) {
return;
}
setUsageListLoading(true);
fetchUsageList({
current: usagePage,
pageSize: usageSize,
...params,
}).then(res => {
const { data = [], totalCount = 0 } = (res || {});
setUsageList(data);
setUsageTotal(totalCount);
}).finally(() => {
setUsageListLoading(false);
});
}
};
useEffect(() => {
getReceivedList();
}, []);
const handleTabChange = key => {
switch (key) {
case '1':
getReceivedList();
break;
case '2':
getUsageList();
break;
default:
break;
}
};
const handleReceivedPaginationChange = (page: number, size: number) => {
setReceivedPage(page);
setReceivedSize(size);
getReceivedList({
current: page,
pageSize: size,
});
};
const handleUsagePaginationChange = (page: number, size: number) => {
setUsagePage(page);
setUsageSize(size);
getUsageList({
current: page,
pageSize: size,
});
};
return (
<div className={styles.equityInfo}>
<Row gutter={[0, 24]}>
<Col span={24}>
<Row gutter={24}>
<Col span={8}>
<div className={styles.container}>
<div className={styles['container-title']}>
当前权益
</div>
<div className={styles['container-content']}>
<ul className={styles.tofo}>
{equityInfo.rights ? equityInfo.rights.map(item => (
<li key={item.id} className={styles['tofo-item']}>
<div className={styles['tofo-item-logo']}>
<img src={imgMap[item.rightTypeEnum]} />
</div>
<div className={styles['tofo-item-title']}>
{item.name}
<Tooltip title={item.remark}>
<QuestionCircleOutlined />
</Tooltip>
</div>
<div className={styles['tofo-item-extra']}>
<span
className={classNames(styles['tofo-item-tag'], styles[clsMap[item.rightTypeEnum]])}
>
{item.parameter}% {equityTxtMap[item.rightTypeEnum] || ''}
</span>
</div>
</li>
)) : null}
</ul>
</div>
</div>
</Col>
<Col span={8}>
<div className={styles.container}>
<div className={styles['container-content']}>
<div className={styles.exhibition}>
<div className={styles['exhibition-left']}>
<div className={styles['exhibition-title']}>
累计返现金额
</div>
<div className={styles['exhibition-amount']}>
{equityInfo?.sumReturnMoney}
<span>CNY</span>
</div>
</div>
<div className={styles['exhibition-right']}>
<div className={styles['exhibition-logo']}>
<img src={imgMap['4']} />
</div>
</div>
</div>
</div>
</div>
</Col>
<Col span={8}>
<div className={styles.container}>
<div className={styles['container-content']}>
<div className={styles.exhibition}>
<div className={styles['exhibition-left']}>
<div className={styles['exhibition-title']}>
已用积分/总积分
</div>
<div className={styles['exhibition-amount']}>
{equityInfo?.sumUsedPoint}/{equityInfo?.sumPoint}
</div>
</div>
<div className={styles['exhibition-right']}>
<div className={styles['exhibition-logo']}>
<img src={imgMap['5']} />
</div>
</div>
</div>
</div>
</div>
</Col>
</Row>
</Col>
<Col span={24}>
<MellowCard
title="活跃分获取记录"
>
<Tabs onChange={handleTabChange}>
<TabPane tab="权益获取记录" key="1">
<PolymericTable
dataSource={receivedList}
columns={receivedColumns}
loading={receivedListLoading}
pagination={{
pageSize: receivedSize,
total: receivedTotal,
}}
onPaginationChange={handleReceivedPaginationChange}
/>
</TabPane>
<TabPane tab="权益使用记录" key="2">
<PolymericTable
dataSource={usageList}
columns={usageColumns}
loading={usageListLoading}
pagination={{
pageSize: usageSize,
total: usageTotal,
}}
onPaginationChange={handleUsagePaginationChange}
/>
</TabPane>
</Tabs>
</MellowCard>
</Col>
</Row>
</div>
);
};
export default EquityInfo;
import React from 'react';
import {
Tabs,
Badge,
} from 'antd';
import PolymericTable from '@/components/PolymericTable';
import { EditableColumns } from '@/components/PolymericTable/interface';
import MellowCard from '@/components/MellowCard';
import StatusTag from '@/components/StatusTag';
import {
MEMBER_INNER_STATUS_BADGE_COLOR,
MEMBER_OUTER_STATUS_TYPE,
} from '../../constant';
export interface InnerHistoryItem {
createTime?: string,
id?: number,
innerStatus?: number,
innerStatusName?: string,
operation?: string,
operatorJobTitle?: string,
operatorName?: string,
operatorOrgName?: string,
remark?: string,
};
export interface OuterHistoryItem {
createTime?: string,
id?: number,
operation?: string,
operatorRoleName?: string,
outerStatus?: number,
outerStatusName?: string,
remark?: string,
};
interface FlowRecordsProps {
outerHistory?: OuterHistoryItem[];
innerHistory?: InnerHistoryItem[];
};
const FlowRecords: React.FC<FlowRecordsProps> = ({ outerHistory = [], innerHistory = [] }) => {
const outerColumns: EditableColumns<OuterHistoryItem>[] = [
{
title: '序号',
dataIndex: 'index',
align: 'center',
render: (text, record, index) => index + 1,
},
{
title: '操作角色',
dataIndex: 'operatorRoleName',
align: 'center',
},
{
title: '状态',
dataIndex: 'outerStatusName',
align: 'center',
render: (text, record) => (
<StatusTag type={MEMBER_OUTER_STATUS_TYPE[record.outerStatus as number]} title={text} />
),
},
{
title: '操作',
dataIndex: 'operation',
align: 'center',
},
{
title: '操作时间',
dataIndex: 'createTime',
align: 'center',
ellipsis: true,
},
{
title: '审核意见',
dataIndex: 'remark',
align: 'center',
ellipsis: true,
},
];
const innerColumns: EditableColumns<InnerHistoryItem>[] = [
{
title: '序号',
dataIndex: 'index',
align: 'center',
render: (text, record, index) => index + 1,
},
{
title: '操作人',
dataIndex: 'operatorName',
align: 'center',
},
{
title: '部门',
dataIndex: 'operatorOrgName',
align: 'center',
},
{
title: '职位',
dataIndex: 'operatorJobTitle',
align: 'center',
},
{
title: '状态',
dataIndex: 'innerStatusName',
align: 'center',
render: (text, record) => (
<Badge color={MEMBER_INNER_STATUS_BADGE_COLOR[record.innerStatus as number] || '#606266'} text={text} />
),
},
{
title: '操作',
dataIndex: 'operation',
align: 'center',
},
{
title: '操作时间',
dataIndex: 'createTime',
align: 'center',
ellipsis: true,
},
{
title: '审核意见',
dataIndex: 'remark',
align: 'center',
ellipsis: true,
},
];
return (
<MellowCard>
<Tabs onChange={() => { }}>
<Tabs.TabPane tab="流转记录" key="1">
<PolymericTable
dataSource={outerHistory}
columns={outerColumns}
loading={false}
pagination={null}
/>
</Tabs.TabPane>
<Tabs.TabPane tab="内部单据流转记录" key="2">
<PolymericTable
dataSource={innerHistory}
columns={innerColumns}
loading={false}
pagination={null}
/>
</Tabs.TabPane>
</Tabs>
</MellowCard>
);
};
export default FlowRecords;
\ No newline at end of file
.head {
display: flex;
align-items: center;
font-size: 18px;
font-weight: 500;
&-prefix {
width: 48px;
height: 48px;
line-height: 48px;
border-radius: 4px;
border: 1px solid #DFE1E6;
color: #fff;
text-align: center;
background-color: #8777D9;
}
&-name {
color: #303133;
margin: 0 8px 0 12px;
}
}
import React from 'react';
import LevelBrand from '../LevelBrand';
import styles from './index.less';
export interface HeadInfoProps {
info: {
name?: string,
level?: number,
};
extra?: React.ReactNode;
};
const HeadInfo: React.FC<HeadInfoProps> = ({ info, extra }) => (
<div className={styles.head}>
<div className={styles['head-prefix']}>
{ info && info.name && info.name.length ? info.name[0] : '' }
</div>
<div className={styles['head-name']}>
{info.name || ''}
</div>
{!extra ? (
<LevelBrand level={info.level} />
) : extra}
</div>
);
export default HeadInfo;
\ No newline at end of file
.brand {
display: inline-block;
width: 54px;
height: 16px;
line-height: 16px;
> img {
width: 100%;
height: 100%;
}
}
\ No newline at end of file
import React from 'react';
import p_level1 from '@/asserts/level1.png';
import p_level2 from '@/asserts/level2.png';
import p_level3 from '@/asserts/level3.png';
import p_level4 from '@/asserts/level4.png';
import styles from './index.less';
enum levelEnum {
'青铜会员' = 1,
'白银会员' = 2,
'黄金会员' = 3,
'钻石会员' = 4,
}
export interface LevelBrandProps {
level: levelEnum;
};
const PIC_MAP = {
1: p_level1,
2: p_level2,
3: p_level3,
4: p_level4,
};
const LevelBrand: React.FC<LevelBrandProps> = ({ level }) => {
const current = PIC_MAP[level] || '';
return (
<div className={styles.brand}>
{current && <img src={current} />}
</div>
);
};
export default LevelBrand;
\ No newline at end of file
@card-prefix: card;
.levelInfo {
.infoWrap {
display: flex;
&-left {
margin-right: 40px;
flex-shrink: 0;
}
&-right {
flex: 1;
}
}
.card {
width: 338px;
height: 190px;
padding: 32px 24px 16px 24px;
background: linear-gradient(135deg, rgba(255, 235, 225, 1) 0%, rgba(251, 196, 167, 1) 100%);
box-shadow: 0 1px 2px 0 rgba(23, 43, 77, 0.12);
border-radius: 8px;
position: relative;
z-index: 0;
&-name {
min-height: 33px;
margin-bottom: 48px;
line-height: 33px;
font-size: 24px;
font-weight: 500;
color: rgba(164, 114, 104, 1);
}
&-progress {
padding: 0 72px 0 0;
:global {
.ant-progress-bg {
background: rgba(164, 114, 104, 1) !important;
}
.ant-progress-inner {
background-color: rgb(233, 191, 169, 1) !important;
}
}
}
&-txt {
display: flex;
justify-content: space-between;
}
&-experience {
font-size: 12px;
font-weight: 400;
color: rgba(164, 114, 104, 1);
line-height: 20px;
}
&-higher {
font-size: 12px;
font-weight: 400;
color: rgba(186, 140, 115, 1);
line-height: 20px;
}
&-level2 {
background: linear-gradient(135deg, rgba(233, 244, 255, 1) 0%, rgba(164, 185, 223, 1) 100%);
box-shadow: 0 1px 2px 0 rgba(23, 43, 77, 0.12);
.@{card-prefix} {
&-name {
color: rgba(91, 111, 146, 1);
}
&-progress {
:global {
.ant-progress-bg {
background: rgba(87, 108, 143, 1) !important;
}
.ant-progress-inner {
background-color: rgba(171, 192, 220, 1) !important;
}
}
}
&-experience {
color: rgba(91, 111, 146, 1);
}
&-higher {
color: rgba(133, 150, 179, 1);
}
}
}
&-level3 {
background: linear-gradient(135deg, rgba(255, 245, 222, 1) 0%, rgba(212, 191, 146, 1) 100%);
box-shadow: 0 1px 2px 0 rgba(23, 43, 77, 0.12);
.@{card-prefix} {
&-name {
color: rgba(141, 107, 56, 1);
}
&-progress {
:global {
.ant-progress-bg {
background: rgba(141, 107, 56, 1) !important;
}
.ant-progress-inner {
background-color: rgba(214, 195, 157, 1) !important;
}
}
}
&-experience {
color: rgba(141, 107, 56, 1);
}
&-higher {
color: rgba(174, 154, 113, 1);
}
}
}
&-level4 {
background: linear-gradient(135deg, rgba(232, 239, 255, 1) 0%, rgba(157, 162, 194, 1) 100%);
box-shadow: 0 1px 2px 0 rgba(23, 43, 77, 0.12);
.@{card-prefix} {
&-name {
color: rgba(89, 91, 113, 1);
}
&-progress {
:global {
.ant-progress-bg {
background: rgba(89, 91, 113, 1) !important;
}
.ant-progress-inner {
background-color: rgba(167, 170, 198, 1) !important;
}
}
}
&-experience {
color: rgba(89, 91, 113, 1);
}
&-higher {
color: rgba(134, 136, 166, 1);
}
}
}
&::after {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 100px;
z-index: -1;
width: 100px;
transform: skewX(24deg);
background: linear-gradient(rgba(255, 255, 255, .2), rgba(255, 255, 255, 0));
}
}
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import {
Row,
Col,
Progress,
} from 'antd';
import classNames from 'classnames';
import { MiniArea } from '@/components/Charts';
import PolymericTable from '@/components/PolymericTable';
import { EditableColumns } from '@/components/PolymericTable/interface';
import MellowCard from '@/components/MellowCard';
import styles from './index.less';
const PAGE_SIZE = 8;
export interface DataProps {
id?: number;
createTime?: string;
ruleName?: string;
score?: number;
remark?: string;
};
export interface FetchParams {
current: number;
pageSize: number;
};
export interface LevelInfoProps {
levelInfo?: {
level?: string,
score?: number,
nextLevel?: string,
nextScore?: number,
};
chartData?: {
x: React.ReactText,
y: number,
}[];
fetchList?: (params: FetchParams) => Promise<{ data: DataProps[], totalCount: number }>;
}
const LevelInfo: React.FC<LevelInfoProps> = ({
levelInfo = {},
chartData = [],
fetchList,
}) => {
const score = levelInfo.score || 0;
const nextScore = levelInfo.nextScore || 0;
const [page, setPage] = useState(1);
const [size, setSize] = useState(PAGE_SIZE);
const [total, setTotal] = useState(0);
const [list, setList] = useState<DataProps[]>([]);
const [listLoading, setListLoading] = useState(false);
const columns: EditableColumns[] = [
{
title: 'ID',
dataIndex: 'id',
align: 'center',
},
{
title: '获取项目',
dataIndex: 'ruleName',
align: 'center',
},
{
title: '获取分值',
dataIndex: 'score',
align: 'center',
},
{
title: '获取时间',
dataIndex: 'createTime',
align: 'center',
},
{
title: '备注',
dataIndex: 'remark',
align: 'center',
ellipsis: true,
},
];
const getHistoryList = (params?) => {
if (fetchList) {
setListLoading(true);
fetchList({
current: page,
pageSize: size,
...params,
}).then(res => {
const { data = [], totalCount = 0 } = (res || {});
setList(data);
setTotal(totalCount);
}).finally(() => {
setListLoading(false);
});
}
};
useEffect(() => {
getHistoryList();
}, []);
const handlePaginationChange = (current: number, pageSize: number) => {
setPage(page);
setSize(size);
getHistoryList({
current,
pageSize,
});
};
return (
<div className={styles.levelInfo}>
<Row gutter={[0, 24]}>
<Col span={24}>
<MellowCard
title="会员等级"
headStyle={{
borderBottom: 'none',
}}
>
<div className={styles.infoWrap}>
<div className={styles['infoWrap-left']}>
<div className={classNames(styles.card, styles['card-level1'])}>
<div className={styles['card-name']}>{levelInfo?.level}</div>
<div className={styles['card-progress']}>
<Progress
strokeWidth={4}
strokeLinecap="square"
showInfo={false}
percent={nextScore ? (score / nextScore) * 100 : 100}
/>
</div>
<div className={styles['card-txt']}>
<div className={styles['card-experience']}>
{score}/{nextScore}
</div>
<div className={styles['card-higher']}>
{levelInfo.nextLevel}
</div>
</div>
<div className={styles['card-higher']}>
当前活跃分
</div>
</div>
</div>
<div className={styles['infoWrap-right']}>
<MiniArea
animate={false}
line
borderWidth={2}
height={180}
padding={[10, 20, 50, 60]}
scale={{
x: {
alias: `${new Date().getFullYear()}年`, // 别名
},
y: {
tickCount: 5,
alias: '活跃分', // 别名
},
}}
xAxis={{
tickLine: undefined,
label: undefined,
title: {
style: {
fontSize: 12,
fill: '#C0C4CC',
fontWeight: 400,
rotate: 90,
},
},
}}
yAxis={{
tickLine: undefined,
label: {
offset: 10,
},
title: {
style: {
fontSize: 12,
fill: '#C0C4CC',
fontWeight: 400,
rotate: 90,
},
},
grid: {
line: {
type: 'line',
style: {
stroke: "#d9d9d9",
lineWidth: 1,
lineDash: [2, 2]
}
}
},
}}
color="l(90) 0:#AAC5FC 1:#FFFFFF"
data={chartData}
/>
</div>
</div>
</MellowCard>
</Col>
<Col span={24}>
<MellowCard
title="活跃分获取记录"
headStyle={{
borderBottom: 'none',
}}
>
<PolymericTable
dataSource={list}
columns={columns}
loading={listLoading}
pagination={{
pageSize: size,
total,
}}
onPaginationChange={handlePaginationChange}
/>
</MellowCard>
</Col>
</Row>
</div>
);
};
export default LevelInfo;
.list {
display: flex;
align-items: center;
flex-wrap: wrap;
padding: 0;
margin: 0;
width: 100%;
overflow-x: auto;
&-item {
width: 175px;
height: 120px;
padding: 0 28px;
margin-bottom: 10px;
background: rgba(255, 255, 255, 1);
border: 1px solid rgba(235,236,240,1);
overflow: hidden;
list-style: none;
&:not(:last-child) {
margin-right: 16px;
}
> img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
\ No newline at end of file
import React from 'react';
import styles from './index.less';
interface PicWrapProps {
pics: string[];
};
const PicWrap: React.FC<PicWrapProps> = ({ pics = [] }) => (
<ul className={styles.list}>
{pics.map((item, index) => (
<li key={index} className={styles['list-item']}>
<img src={item} />
</li>
))}
</ul>
);
export default PicWrap;
\ No newline at end of file
.sincerityInfo {
.tofo {
height: 100%;
:global {
.antd-card {
height: 100%;
}
}
&-item {
width: 50%;
padding: 55px 24px;
&-logo {
width: 72px;
height: 72px;
}
}
}
}
.contentBox {
position: relative;
padding-right: 72px;
background: #ffffff;
&-main {
}
&-extra {
position: absolute;
top: 0;
right: 0;
bottom: 0;
}
.title {
margin-bottom: 20px;
line-height: 20px;
font-size: 12px;
font-weight: 400;
color: rgba(107, 119, 140, 1);
:global {
.anticon {
margin-left: 6px;
}
}
}
.txt {
line-height: 32px;
font-size: 24px;
font-weight: 500;
color: rgba(23, 43, 77, 1);
}
}
.record {
&-tabs {
:global {
.ant-tabs-nav::before {
border-bottom: none;
}
}
}
&-btns {
margin-bottom: 16px;
}
&-row {
:global {
.ant-table-cell:first-child {
background-color: rgba(250, 251, 252, 1);
}
}
}
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import {
Row,
Col,
Tabs,
Card,
Tooltip,
Radio,
Button,
Spin,
} from 'antd';
import {
QuestionCircleOutlined,
SmileFilled,
MehFilled,
FrownFilled,
} from '@ant-design/icons';
import { isJSONStr } from '@/utils';
import PolymericTable from '@/components/PolymericTable';
import { EditableColumns } from '@/components/PolymericTable/interface';
import { Pie } from '@/components/Charts';
import MellowCard from '@/components/MellowCard';
import styles from './index.less';
import equity_1 from '@/asserts/equity-1.png';
import equity_2 from '@/asserts/equity-2.png';
import equity_3 from '@/asserts/equity-3.png';
import equity_4 from '@/asserts/equity-4.png';
import equity_5 from '@/asserts/equity-5.png';
const imgMap = {
1: equity_1,
2: equity_2,
3: equity_3,
4: equity_4,
5: equity_5,
}
const { TabPane } = Tabs;
interface ContentBoxProps {
title?: string;
desc?: string;
content?: React.ReactNode;
extra?: React.ReactNode;
};
const ContentBox: React.FC<ContentBoxProps> = ({
title = '我是标题',
content,
extra,
desc,
}) => (
<div className={styles.contentBox}>
<div className={styles['contentBox-main']}>
<div className={styles.title}>
{title}
<Tooltip title={desc}>
<QuestionCircleOutlined />
</Tooltip>
</div>
<div className={styles.txt}>
{content}
</div>
</div>
<div className={styles['contentBox-extra']}>
{extra}
</div>
</div>
);
interface MoodProps {
type: 'smile' | 'notBad' | 'sad';
};
const Mood: React.FC<MoodProps> = ({ type = 'smile' }) => {
let node: any = null;
switch (type) {
case 'smile':
node = <><SmileFilled style={{ color: '#41CC9E', marginRight: 4 }} /> 好评</>;
break;
case 'notBad':
node = <><MehFilled style={{ color: '#FFC400', marginRight: 4 }} /> 中评</>;
break;
case 'sad':
node = <><FrownFilled style={{ color: '#EF6260', marginRight: 4 }} /> 差评</>;
break;
default:
break;
}
return node;
};
const PAGE_SIZE = 5;
export interface BasicInfo {
pieData: {
x: string,
y: number,
}[],
items: {
id: number,
creditTypeName: string,
remark: string,
creditPoint: number,
currentPoint: number,
}[],
loading?: boolean,
};
export interface EstimateSumItems {
id?: number,
title?: JSX.Element,
star?: number,
last7days?: number,
last30days?: number,
last180days?: number,
before180days?: number,
sum?: number,
};
export interface EstimateSum {
dataSource: EstimateSumItems[],
loading?: boolean,
};
export interface ComplaintSum {
dataSource: EstimateSumItems,
loading?: boolean,
};
export interface FetchParams {
current: number;
pageSize: number;
};
export interface SalesProps {
id?: number;
createTime?: string;
star?: number;
comment?: string;
product?: string;
byMemberName?: string;
remark?: string;
};
export interface ComplaintProps {
id?: number;
createTime?: string;
content?: string;
reason?: string;
byMemberName?: string;
remark?: string;
};
interface SincerityInfoProps {
basicInfo?: BasicInfo;
salesEstimateSum?: EstimateSum;
fetchSalesList?: (params: FetchParams) => Promise<{ data: SalesProps[] , totalCount: number }>;
afterEstimateSum?: EstimateSum;
fetchAfterList?: (params: FetchParams) => Promise<{ data: SalesProps[] , totalCount: number }>;
complaintSum?: ComplaintSum;
fetchComplaintList?: (params: FetchParams) => Promise<{ data: ComplaintProps[] , totalCount: number }>;
};
const SincerityInfo: React.FC<SincerityInfoProps> = ({
basicInfo = {},
salesEstimateSum = {},
fetchSalesList,
afterEstimateSum = {},
fetchAfterList,
complaintSum = {},
fetchComplaintList,
}) => {
const creditData = basicInfo.pieData || [];
const integralItems = basicInfo.items || [];
const { dataSource: complaintSumData } = complaintSum;
const [salesEvaluate, setSalesEvaluate] = useState<EstimateSumItems[]>([]);
const [salesEvaluatePie, setSalesEvaluatePie] = useState<{ x: string, y: number }[]>([]);
const [afterEvaluate, setAfterEvaluate] = useState<EstimateSumItems[]>([]);
const [afterEvaluatePie, setAfterEvaluatePie] = useState<{ x: string, y: number }[]>([]);
const [salesTabKey, setSalesTabKey] = useState('evaluateSum');
const [afterTabKey, setAfterTabKey] = useState('evaluateSum');
const [salesPage, setSalesPage] = useState(1);
const [salesSize, setSalesSize] = useState(PAGE_SIZE);
const [salesTotal, setSalesTotal] = useState(0);
const [salesList, setSalesList] = useState<any[]>([]);
const [salesListLoading, setSalesListLoading] = useState(false);
const [afterPage, setAfterPage] = useState(1);
const [afterSize, setAfterSize] = useState(PAGE_SIZE);
const [afterTotal, setAfterTotal] = useState(0);
const [afterList, setAfterList] = useState<any>([]);
const [afterListLoading, setAfterListLoading] = useState(false);
const [complainPage, setComplainPage] = useState(1);
const [complainSize, setComplainSize] = useState(PAGE_SIZE);
const [complainTotal, setComplainTotal] = useState(0);
const [complainList, setComplainList] = useState<any[]>([]);
const [complainListLoading, setComplainListLoading] = useState(false);
const exchangeMood = (star: number): React.ReactNode => {
let node: any = null;
switch (star) {
case 1:
case 2: {
node = (
<>
<Mood type="sad" />
<span>差评</span>
</>
);
break;
}
case 3: {
node = (
<>
<Mood type="notBad" />
<span>中评</span>
</>
);
break;
}
case 4:
case 5: {
node = (
<>
<Mood type="smile" />
<span>好评</span>
</>
);
break;
}
default:
break;
}
return node;
};
const recordColumns: EditableColumns[] = [
{
title: '投诉单号',
dataIndex: 'remark',
},
{
title: '摘要',
dataIndex: 'content',
ellipsis: true,
},
{
title: '投诉方',
dataIndex: 'byMemberName',
},
{
title: '投诉原因',
dataIndex: 'reason',
ellipsis: true,
},
{
title: '交易单据时间',
dataIndex: 'createTime',
align: 'center',
ellipsis: true,
},
];
const evaluateColumns: EditableColumns[] = [
{
title: ' ',
dataIndex: 'title',
align: 'center',
},
{
title: '最近7天',
dataIndex: 'last7days',
align: 'center',
},
{
title: '最近30天',
dataIndex: 'last30days',
align: 'center',
},
{
title: '最近180天',
dataIndex: 'last180days',
align: 'center',
},
{
title: '180天前',
dataIndex: 'before180days',
align: 'center',
},
];
const evaluateRecordColumns: EditableColumns[] = [
{
title: '评论',
dataIndex: 'star',
align: 'center',
render: (text) => exchangeMood(text),
},
{
title: '评价内容',
dataIndex: 'comment',
align: 'center',
ellipsis: true,
},
{
title: '交易商品',
dataIndex: 'product',
align: 'center',
ellipsis: true,
render: text => {
const product = isJSONStr(text) || {};
return product.productName || '';
},
},
{
title: '评价方',
dataIndex: 'byMemberName',
align: 'center',
},
{
title: '交易时间',
dataIndex: 'createTime',
align: 'center',
},
{
title: '订单号',
dataIndex: 'remark',
align: 'center',
},
];
const summaryEvaluate = (items: EstimateSumItems[]): EstimateSumItems[] => {
// 顺序写死的 1:表示好评,2:表示中评,3:表示差评
// 根据 1、2星级为差评,3星级为中评,4、5星级为好评往里边塞数据
const source = items || [];
const ret = [
{
id: 1,
title: (<Mood type="smile" />),
},
{
id: 2,
title: (<Mood type="notBad" />),
},
{
id: 3,
title: (<Mood type="sad" />),
},
];
for (let i = 0; i < source.length; i++) {
const item = source[i];
const { star, ...rest } = item;
let target: any = null;
switch (item.star) {
case 1:
case 2: {
target = ret[2];
break;
}
case 3: {
target = ret[1];
break;
}
case 4:
case 5: {
target = ret[0];
break;
}
default:
break;
}
if (!target) {
continue;
}
// 大于 2 表示已经添加过一次数据,之后就累加上去,否则直接赋值
if (Object.keys(target).length <= 2) {
target = Object.assign(target, rest);
} else {
for (const key in target) {
if (!Object.prototype.hasOwnProperty.call(target, key)) {
continue;
}
// 排除 id、title 固定的 key
if (key !== 'id' && key !== 'title') {
target[key] += item[key];
}
}
}
}
return ret;
};
// 取得评价统计 Pie 饼图数据
const getSummaryEvaluatePie = (data: EstimateSumItems[]) => {
const source = data || [];
const count = source.reduce((pre, now) => (now.sum || 0) + pre, 0);
const good = source[0] && source[0].sum ? source[0].sum : 0;
const notBad = source[1] && source[1].sum ? source[1].sum : 0;
const bad = source[2] && source[2].sum ? source[2].sum : 0;
const ret = [
{
x: `好评 ${count > 0 ? (good / count * 100).toFixed(2) : '0'}%`,
y: good,
},
{
x: `中评 ${count > 0 ? (notBad / count * 100).toFixed(2) : 0}%`,
y: notBad,
},
{
x: `差评 ${count > 0 ? (bad / count * 100).toFixed(2) : 0}%`,
y: bad,
},
];
return ret;
};
// 获取交易评价记录列表
const getSalesRecordList = (extraParams: {[key: string]: any} = {}) => {
if (fetchSalesList) {
setSalesListLoading(true);
fetchSalesList({
current: salesPage,
pageSize: salesSize,
...extraParams,
}).then(res => {
const { data = [], totalCount = 0 } = (res || {});
setSalesList(data);
setSalesTotal(totalCount);
}).finally(() => {
setSalesListLoading(false);
});
}
};
// 获取售后评价记录列表
const getAfterRecordList = (extraParams: {[key: string]: any} = {}) => {
if (fetchAfterList) {
setAfterListLoading(true);
fetchAfterList({
current: afterPage,
pageSize: afterSize,
...extraParams,
}).then(res => {
const { data = [], totalCount = 0 } = (res || {});
setAfterList(data);
setAfterTotal(totalCount);
}).finally(() => {
setAfterListLoading(false);
});
}
};
// 获取投诉评价记录列表
const getComplaintList = () => {
if (fetchComplaintList) {
setComplainListLoading(true);
fetchComplaintList({
current: complainPage,
pageSize: complainSize,
}).then(res => {
const { data = [], totalCount = 0 } = (res || {});
setComplainList(data);
setComplainTotal(totalCount);
}).finally(() => {
setComplainListLoading(false);
});
}
};
useEffect(() => {
getComplaintList();
}, []);
// 监听交易评价数据改变,组合所需数据
useEffect(() => {
const evaluate = salesEstimateSum && salesEstimateSum.dataSource ? summaryEvaluate(salesEstimateSum.dataSource) : [];
const evaluatePie = getSummaryEvaluatePie(evaluate);
setSalesEvaluate(evaluate);
setSalesEvaluatePie(evaluatePie);
}, [salesEstimateSum.dataSource]);
// 监听售后评价数据改变,组合所需数据
useEffect(() => {
const evaluate = afterEstimateSum && afterEstimateSum.dataSource ? summaryEvaluate(afterEstimateSum.dataSource) : [];
const evaluatePie = getSummaryEvaluatePie(evaluate);
setAfterEvaluate(evaluate);
setAfterEvaluatePie(evaluatePie);
}, [afterEstimateSum.dataSource]);
const handleSalesTabChange = key => {
if (key === 'evaluateRecord') {
getSalesRecordList();
}
setSalesTabKey(key);
};
const handleAfterTabChange = key => {
if (key === 'evaluateRecord') {
getAfterRecordList();
}
setAfterTabKey(key);
};
const handleSalesPaginationChange = (page: number, size: number) => {
setSalesPage(page);
setSalesSize(size);
getSalesRecordList();
};
const handleAfterPaginationChange = (page: number, size: number) => {
setAfterPage(page);
setAfterSize(size);
getAfterRecordList();
};
const handleComplainPaginationChange = (page: number, size: number) => {
setComplainPage(page);
setComplainSize(size);
getComplaintList();
};
const handleSalesRadioChange = value => {
setSalesPage(1);
getSalesRecordList({
starLevel: value,
});
};
const handleAfterRadioChange = value => {
setAfterPage(1);
getAfterRecordList({
starLevel: value,
});
};
return (
<div className={styles.sincerityInfo}>
<Row gutter={[0, 24]}>
<Col span={24}>
<Spin spinning={Boolean(basicInfo.loading)}>
<Row gutter={24}>
<Col flex="386px">
<MellowCard
title="信用积分"
fullHeight
>
<Pie
hasLegend
subTitle="信用积分"
total={() => creditData.reduce((pre, now) => now.y + pre, 0)}
data={creditData}
height={178}
colors={['#6C9CEB', '#8777D9', '#FFC400', '#41CC9E']}
/>
</MellowCard>
</Col>
{basicInfo.loading === false && (
<Col flex="1">
<div className={styles.tofo}>
<MellowCard fullHeight>
{integralItems.map((item, index) => (
<Card.Grid key={item.id} className={styles['tofo-item']}>
<ContentBox
title={item.creditTypeName}
desc={`${item.remark}(${item.creditPoint})`}
content={item.currentPoint}
extra={
<img
className={styles['tofo-item-logo']}
src={imgMap[index+1]}
/>
}
/>
</Card.Grid>
))}
</MellowCard>
</div>
</Col>
)}
</Row>
</Spin>
</Col>
<Col span={24}>
<MellowCard>
<Tabs
className={styles['record-tabs']}
activeKey={salesTabKey}
onChange={handleSalesTabChange}
tabBarExtraContent={
salesTabKey === 'evaluateRecord' ? (
<>
<Button type="text" onClick={() => handleSalesRadioChange(3)}>好评</Button>
<Button type="text" onClick={() => handleSalesRadioChange(2)}>中评</Button>
<Button type="text" onClick={() => handleSalesRadioChange(1)}>差评</Button>
<Button type="text" onClick={() => handleSalesRadioChange(0)}>全部</Button>
</>
) : null
}
>
<TabPane tab="交易评价统计" key="evaluateSum">
<Spin spinning={Boolean(salesEstimateSum.loading)}>
<Row gutter={24}>
<Col span={8}>
<Pie
hasLegend
subTitle="累计评价"
total={() => salesEvaluatePie.reduce((pre, now) => now.y + pre, 0)}
data={salesEvaluatePie}
height={178}
colProps={{
span: 8,
}}
colors={['#41CC9E', '#FFC400', '#EF6260']}
/>
</Col>
<Col span={16}>
<PolymericTable
dataSource={salesEvaluate}
columns={evaluateColumns}
loading={false}
pagination={null}
rowClassName={() => styles['record-row']}
/>
</Col>
</Row>
</Spin>
</TabPane>
<TabPane tab="交易评价记录" key="evaluateRecord">
<PolymericTable
rowKey="remark"
dataSource={salesList}
columns={evaluateRecordColumns}
loading={salesListLoading}
pagination={{
pageSize: salesSize,
total: salesTotal,
}}
onPaginationChange={handleSalesPaginationChange}
/>
</TabPane>
</Tabs>
</MellowCard>
</Col>
<Col span={24}>
<MellowCard>
<Tabs
className={styles['record-tabs']}
activeKey={afterTabKey}
onChange={handleAfterTabChange}
tabBarExtraContent={
afterTabKey === 'evaluateRecord' ? (
<>
<Button type="text" onClick={() => handleAfterRadioChange(3)}>好评</Button>
<Button type="text" onClick={() => handleAfterRadioChange(2)}>中评</Button>
<Button type="text" onClick={() => handleAfterRadioChange(1)}>差评</Button>
<Button type="text" onClick={() => handleAfterRadioChange(0)}>全部</Button>
</>
) : null
}
>
<TabPane tab="售后评价统计" key="evaluateSum">
<Spin spinning={Boolean(afterEstimateSum.loading)}>
<Row gutter={24}>
<Col span={8}>
<Pie
hasLegend
subTitle="累计评价"
total={() => afterEvaluatePie.reduce((pre, now) => now.y + pre, 0)}
data={afterEvaluatePie}
height={178}
colProps={{
span: 8,
}}
colors={['#41CC9E', '#FFC400', '#EF6260']}
/>
</Col>
<Col span={16}>
<PolymericTable
dataSource={afterEvaluate}
columns={evaluateColumns}
loading={false}
pagination={null}
rowClassName={() => styles['record-row']}
/>
</Col>
</Row>
</Spin>
</TabPane>
<TabPane tab="售后评价记录" key="evaluateRecord">
<PolymericTable
rowKey="remark"
dataSource={afterList}
columns={evaluateRecordColumns}
loading={afterListLoading}
pagination={{
pageSize: afterSize,
total: afterTotal,
}}
onPaginationChange={handleAfterPaginationChange}
/>
</TabPane>
</Tabs>
</MellowCard>
</Col>
<Col span={24}>
<MellowCard title="投诉记录">
<div className={styles['record-btns']}>
<Radio.Group
buttonStyle="solid"
onChange={() => { }}
>
<Button type="text">全部({complaintSumData?.sum})</Button>
<Button type="text">最近7天({complaintSumData?.last7days})</Button>
<Button type="text">最近30天({complaintSumData?.last30days})</Button>
<Button type="text">最近180天({complaintSumData?.last180days})</Button>
<Button type="text">180天前({complaintSumData?.before180days})</Button>
</Radio.Group>
</div>
<PolymericTable
rowKey="remark"
dataSource={complainList}
columns={recordColumns}
loading={complainListLoading}
pagination={{
pageSize: complainSize,
total: complainTotal,
}}
onPaginationChange={handleComplainPaginationChange}
/>
</MellowCard>
</Col>
</Row>
</div>
);
};
export default SincerityInfo;
.tag {
line-height: 22px;
padding: 0 8px;
font-size: 12px;
font-weight: 400;
color: #00B37A;
background: #EBF7F2;
border-radius: 4px;
&__success {
color: #00B37A;
background: #EBF7F2;
}
&__warnning {
color: #FF991F;
background: #FFFAE6;
}
&__default {
color: #606266;
background: #F4F5F7;
}
&__danger {
color: #E63F3B;
background: #FFEBE6;
}
&__primary {
color: #3F7ED2;
background: #F0F8FF;
}
}
\ No newline at end of file
/*
* @Author: XieZhiXiong
* @Date: 2020-08-31 17:52:14
* @LastEditors: XieZhiXiong
* @LastEditTime: 2020-09-08 16:32:03
* @Description: 状态 tag
*/
import React from 'react';
import classNames from 'classnames';
import styles from './index.less';
interface StatusTagProps {
type: 'success' | 'warnning' | 'default' | 'danger' | 'primary';
title?: string;
};
const StatusTag: React.FC<StatusTagProps> = ({ type, title }) => {
const cls = classNames(styles.tag, styles[`tag__${type}`]);
return (
<span className={cls}>{title}</span>
);
};
export default StatusTag;
\ No newline at end of file
import {
MEMBER_INNER_STATUS_TO_BE_COMMIT,
MEMBER_INNER_STATUS_COMMIT_NOT_PASSED,
MEMBER_INNER_STATUS_TO_BE_VERIFY_STEP1,
MEMBER_INNER_STATUS_VERIFY_STEP1_NOT_PASSED,
MEMBER_INNER_STATUS_TO_BE_VERIFY_STEP2,
MEMBER_INNER_STATUS_VERIFY_STEP2_NOT_PASSED,
MEMBER_INNER_STATUS_TO_CONFIRM,
MEMBER_INNER_STATUS_VERIFY_NOT_PASSED,
MEMBER_INNER_STATUS_VERIFY_PASSED,
MEMBER_OUTER_STATUS_TO_PLATFORM_VERIFY,
MEMBER_OUTER_STATUS_PLATFORM_VERIFYING,
MEMBER_OUTER_STATUS_PLATFORM_VERIFY_PASSED,
MEMBER_OUTER_STATUS_PLATFORM_VERIFY_NOT_PASSED,
MEMBER_OUTER_STATUS_DEPOSITING,
MEMBER_OUTER_STATUS_DEPOSITORY_PASSED,
MEMBER_OUTER_STATUS_DEPOSITORY_NOT_PASSED,
MEMBER_OUTER_STATUS_MODIFYING,
MEMBER_OUTER_STATUS_MODIFY_PASSED,
MEMBER_OUTER_STATUS_MODIFY_NOT_PASSED,
MEMBER_STATUS_NORMAL,
MEMBER_STATUS_FROZEN,
} from '@/constants/const/member';
export const STATUS_COLOR_MAP = {
0: '#669EDE',
1: '#41CC9E',
2: '#EF6260',
};
export const STATUS_COLOR_TXT = {
0: '待审核',
1: '审核通过',
2: '冻结',
};
// 会员状态 StatusTag map
export const MEMBER_STATUS_TAG_MAP = {
[MEMBER_STATUS_NORMAL]: 'success',
[MEMBER_STATUS_FROZEN]: 'default'
};
// 会员外部状态 StatusTag map
export const MEMBER_OUTER_STATUS_TYPE = {
[MEMBER_OUTER_STATUS_TO_PLATFORM_VERIFY]: 'default',
[MEMBER_OUTER_STATUS_PLATFORM_VERIFYING]: 'warning',
[MEMBER_OUTER_STATUS_PLATFORM_VERIFY_PASSED]: 'success',
[MEMBER_OUTER_STATUS_PLATFORM_VERIFY_NOT_PASSED]: 'danger',
[MEMBER_OUTER_STATUS_DEPOSITING]: 'warning',
[MEMBER_OUTER_STATUS_DEPOSITORY_PASSED]: 'success',
[MEMBER_OUTER_STATUS_DEPOSITORY_NOT_PASSED]: 'danger',
[MEMBER_OUTER_STATUS_MODIFYING]: 'warning',
[MEMBER_OUTER_STATUS_MODIFY_PASSED]: 'success',
[MEMBER_OUTER_STATUS_MODIFY_NOT_PASSED]: 'danger',
};
// 会员内部状态 Tag badge map
export const MEMBER_INNER_STATUS_BADGE_COLOR = {
[MEMBER_INNER_STATUS_TO_BE_COMMIT]: 'grey',
[MEMBER_INNER_STATUS_COMMIT_NOT_PASSED]: 'red',
[MEMBER_INNER_STATUS_TO_BE_VERIFY_STEP1]: 'orange',
[MEMBER_INNER_STATUS_VERIFY_STEP1_NOT_PASSED]: 'red',
[MEMBER_INNER_STATUS_TO_BE_VERIFY_STEP2]: 'orange',
[MEMBER_INNER_STATUS_VERIFY_STEP2_NOT_PASSED]: 'red',
[MEMBER_INNER_STATUS_TO_CONFIRM]: 'blue',
[MEMBER_INNER_STATUS_VERIFY_NOT_PASSED]: 'red',
[MEMBER_INNER_STATUS_VERIFY_PASSED]: 'green',
};
\ No newline at end of file
import { ISchema } from '@formily/antd';
import { FORM_FILTER_PATH } from '@/formSchema/const';
export const auditSchema: ISchema = {
type: 'object',
properties: {
MEGA_LAYOUT: {
type: 'object',
'x-component': 'mega-layout',
properties: {
topLayout: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
grid: true,
},
properties: {
ctl: {
type: 'object',
'x-component': 'ControllerBtns',
},
name: {
type: 'string',
'x-component': 'Search',
'x-component-props': {
placeholder: '搜索',
tip: '输入 会员名称 进行搜索',
},
},
},
},
[FORM_FILTER_PATH]: {
type: 'object',
'x-component': 'flex-layout',
'x-component-props': {
colStyle: {
marginLeft: 20,
},
},
properties: {
memberTypeId: {
type: 'string',
default: undefined,
enum: [],
'x-component-props': {
placeholder: '会员类型(全部)',
allowClear: true,
style: {
width: 160,
},
},
},
// status: {
// type: 'string',
// default: undefined,
// enum: [],
// 'x-component-props': {
// placeholder: '会员状态(全部)',
// allowClear: true,
// },
// },
roleId: {
type: 'string',
default: undefined,
enum: [],
'x-component-props': {
placeholder: '会员角色(全部)',
allowClear: true,
style: {
width: 160,
},
},
},
level: {
type: 'string',
default: undefined,
enum: [],
'x-component-props': {
placeholder: '会员等级(全部)',
allowClear: true,
style: {
width: 160,
},
},
},
source: {
type: 'string',
default: undefined,
enum: [],
'x-component-props': {
placeholder: '申请来源(全部)',
allowClear: true,
style: {
width: 160,
},
},
},
'[startDate, endDate]': {
type: 'string',
default: '',
'x-component': 'dateSelect',
'x-component-props': {
placeholder: '时间范围(全部)',
allowClear: true,
style: {
width: 160,
},
},
},
submit: {
'x-component': 'Submit',
'x-mega-props': {
span: 1,
},
'x-component-props': {
children: '查询',
},
},
},
},
},
},
},
};
export const auditModalSchema: ISchema = {
type: 'object',
properties: {
MEGA_LAYOUT: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
labelAlign: 'top',
},
properties: {
agree: {
type: 'string',
default: 1,
enum: [
{ label: '审核通过', value: 1 },
{ label: '审核不通过', value: 0 },
],
'x-component': 'radio',
'x-component-props': {},
},
reason: {
type: 'string',
title: '审核不通过原因',
'x-component': 'textarea',
required: true,
'x-component-props': {
placeholder: '在此输入你的内容,最长120个字符,60个汉字',
rows: 5,
},
'x-rules': [
{
limitByte: true, // 自定义校验规则
maxByte: 120,
}
],
},
},
},
},
};
import React from 'react';
import { PATTERN_MAPS } from '@/constants/regExp';
import PicWrap from './components/PicWrap';
export function coverColFiltersItem(
data: Array<{[key: string]: any}>,
dataIndex: string,
item: {[key: string]: any}
) {
const index = data.findIndex(i => i.dataIndex === dataIndex);
if (index !== -1) {
data.splice(index, 1, {
...data[index],
filters: item,
});
}
};
// 初始化内部流转记录
export function normalizeInnerHistory(source: {[key: string]: any}[]) {
const ret: any[] = [];
if (!Array.isArray(source)) {
return ret;
}
source.forEach(item => {
const atom = {
id: item.id,
operator: item.operatorName,
org: item.operatorOrgName,
jobTitle: item.operatorJobTitle,
innerStatusName: item.innerStatusName,
innerStatus: item.innerStatus,
operation: item.operation,
operateTime: item.createTime,
reason: item.remark,
};
ret.push(atom);
});
return ret;
};
// 初始化内部流转记录
export function normalizeOuterHistory(source: {[key: string]: any}[]) {
const ret: any[] = [];
if (!Array.isArray(source)) {
return ret;
}
source.forEach(item => {
const atom = {
id: item.id,
roleName: item.operatorRoleName,
status: item.outerStatus,
statusName: item.outerStatusName,
operation: item.operation,
operateTime: item.createTime,
reason: item.remark,
};
ret.push(atom);
});
return ret;
};
export type ElementType = {
/**
* 注册资料id
*/
id?: number,
/**
* 字段名称
*/
fieldName?: string,
/**
* 中文名称
*/
fieldLocalName?: string,
/**
* 字段类型
*/
fieldType?: string,
/**
* 字段类型附加属性(该参数为map)
*/
attr?: { [key: string]: any },
/**
* 字段长度
*/
fieldLength?: number,
/**
* 是否可为空0-不能为空1-可以为空
*/
fieldEmpty?: number,
/**
* 字段顺序
*/
fieldOrder?: number,
/**
* 帮助信息
*/
fieldRemark?: string,
/**
* 枚举标签列表
*/
fieldEnum?: {
value?: number,
label?: string,
}[],
/**
* 字段校验规则枚举:0-无校验规则,1-邮箱规则,2-手机号码规则,3-身份证规则,4-电话号码规则
*/
ruleEnum?: number,
/**
* 校验规则的正则表达式
*/
pattern?: string,
/**
* 校验错误的提示语
*/
msg?: string,
/**
* 值
*/
fieldValue?: any,
/**
* 是否禁用
*/
disabled?: boolean,
}
export type GroupItem = {
/**
* 组名
*/
groupName: string,
/**
* 元素
*/
elements: ElementType[],
}
export type FieldType = 'string' | 'long' | 'upload' | 'radio' | 'select' | 'checkbox' | 'area' | string & {};
// 判断表单元素是否真的有值
const fieldHasValue = (fieldType: FieldType, value: any): boolean => {
switch (fieldType) {
case 'string':
case 'long':
case 'radio':
case 'select':
return !!value;
case 'upload':
case 'checkbox':
return value && value.length > 0;
case 'area':
return value && !!value.provinceCode;
default:
return true;
}
};
// 字段校验规则枚举:0-无校验规则,1-邮箱规则,2-手机号码规则,3-身份证规则,4-电话号码规则
const RULE_REG_MAP = {
1: PATTERN_MAPS.email,
2: PATTERN_MAPS.phone,
3: PATTERN_MAPS.identity,
4: PATTERN_MAPS.tel,
};
const getFieldType = (field: ElementType, editable: boolean = true) => {
const isDisabled = (!editable && fieldHasValue(field.fieldType as string, field.fieldValue)) || !!field.disabled;
// 默认是 输入框
let description: { [key: string]: any } = {
'x-component-props': {
placeholder: field.fieldRemark,
disabled: isDisabled,
},
};
// 公共的属性
const common = {
type: 'string',
required: field.fieldEmpty === 0,
title: field.fieldLocalName,
default: field.fieldValue,
'x-rules': [
(
field.ruleEnum
? {
pattern: RULE_REG_MAP[field.ruleEnum],
message: field.msg,
}
: null
),
(
field.pattern
? {
pattern: field.pattern,
message: field.msg,
}
: null
),
].filter(Boolean),
};
switch (field.fieldType as FieldType) {
case 'upload': {
description = {
'x-component': 'CustomUpload',
'x-component-props': {
showDesc: false,
disabled: isDisabled,
},
};
break;
}
case 'radio': {
description = {
'x-component': 'RadioGroup',
enum: field.fieldEnum,
'x-component-props': {
disabled: isDisabled
},
};
break;
}
case 'select': {
description = {
enum: field.fieldEnum,
'x-component-props': {
disabled: isDisabled,
},
};
break;
}
case 'checkbox': {
description = {
'x-component': 'CheckboxGroup',
enum: field.fieldEnum,
'x-component-props': {
disabled: isDisabled,
},
};
break;
}
case 'area': {
description = {
'x-component': 'AreaSelect',
'x-component-props': {
disabled: isDisabled,
},
// 这里判断 省级 是否有值,没值给 undefined
// 后台没值是返回 {provinceCode: '', cityCode: '', districtCode: ''}
default: field.fieldValue ? field.fieldValue.provinceCode ? field.fieldValue : undefined : undefined,
};
break;
}
default:
break;
}
return Object.assign({}, common, description);
};
/**
* 根据后台生成注册资料 schema
* @param elements
* @param editable 有值的元素是否可编辑
* @returns
*/
export function createMemberSchema(elements: ElementType[], editable: boolean = true) {
const components = {};
if (!Array.isArray(elements)) {
return components;
}
for (let item of elements) {
components[item.fieldName as string] = getFieldType(item, editable);
}
return components;
};
// 根据 fieldType 渲染对应的内容
export function renderFieldTypeContent(fieldType: FieldType, fieldValue: any): React.ReactNode {
// 默认渲染 string
let node: React.ReactNode = fieldValue;
switch (fieldType) {
case 'upload':
node = (
<PicWrap
pics={[fieldValue]}
/>
);
break;
default:
break;
}
return node;
};
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment