feat: 常用联系人; 催缴任务

This commit is contained in:
uiuJun 2025-08-18 11:36:25 +08:00
parent 66ab87094c
commit e66ec57f52
11 changed files with 502 additions and 18 deletions

41
src/gen/ApiTypes.d.ts vendored
View File

@ -18,7 +18,8 @@ declare namespace ApiTypes {
"ownership_info"?: string[]; // 产权信息 "ownership_info"?: string[]; // 产权信息
"house_relation"?: string; // 房客关系,[enum:HouseOccupantsHouseRelationEnum] "house_relation"?: string; // 房客关系,[enum:HouseOccupantsHouseRelationEnum]
"relation_with_owner"?: string; // 与产权人关系,[enum:HouseOccupantsRelationWithOwnerEnum] "relation_with_owner"?: string; // 与产权人关系,[enum:HouseOccupantsRelationWithOwnerEnum]
"is_live_in"?: number; // 是否在居住中 "is_contact"?: boolean; // 是否是常用联系人
"is_live_in"?: boolean; // 是否在居住中
"move_in_date"?: Date; // 入住时间 "move_in_date"?: Date; // 入住时间
"status"?: string; // 状态,[enum:HouseOccupantsStatusEnum] "status"?: string; // 状态,[enum:HouseOccupantsStatusEnum]
"unbound_time"?: Date; // 解绑时间 "unbound_time"?: Date; // 解绑时间
@ -29,6 +30,10 @@ declare namespace ApiTypes {
type Delete = { type Delete = {
"id": number; // id "id": number; // id
}; };
type ChangeIsContact = {
"id": number; // id
"is_contact": boolean; // 是否是常用联系人
};
} }
namespace HouseRegisters { namespace HouseRegisters {
type List = { type List = {
@ -408,6 +413,40 @@ declare namespace ApiTypes {
}; };
} }
} }
namespace Collcetion {
namespace HouseCollectionRecords {
type List = {
"collection_tasks_id"?: number; // 房屋崔缴任务id,[ref:house_collection_tasks]
"asset_houses_id"?: number; // 房屋id,[ref:asset_houses]
"channel"?: string; // 通知渠道,[enum:HouseCollectionTasksChannelEnum]
"status"?: string; // 通知状态,[enum:HouseCollectionRecordsStatusEnum]
};
type Show = {
"id": number; // id
};
type Delete = {
"id": number; // id
};
}
namespace HouseCollectionTasks {
type List = {
"task_name"?: string; // 模糊搜索:名称
"status"?: string; // 状态,[enum:HouseCollectionTasksStatusEnum]
};
type Show = {
"id": number; // id
};
type SoftDelete = {
"id": number; // id
};
type Restore = {
"id": number; // id
};
type Delete = {
"id": number; // id
};
}
}
namespace Common { namespace Common {
namespace Admins { namespace Admins {
type List = { type List = {

View File

@ -16,6 +16,9 @@ export const Apis = {
Delete(data: ApiTypes.Archive.HouseOccupants.Delete): Promise<MyResponseType> { Delete(data: ApiTypes.Archive.HouseOccupants.Delete): Promise<MyResponseType> {
return request('admin/archive/house_occupants/delete', { data }); return request('admin/archive/house_occupants/delete', { data });
}, },
ChangeIsContact(data: ApiTypes.Archive.HouseOccupants.ChangeIsContact): Promise<MyResponseType> {
return request('admin/archive/house_occupants/change_is_contact', { data });
},
}, },
HouseRegisters: { HouseRegisters: {
List(data?: ApiTypes.Archive.HouseRegisters.List): Promise<MyResponseType> { List(data?: ApiTypes.Archive.HouseRegisters.List): Promise<MyResponseType> {
@ -211,6 +214,36 @@ export const Apis = {
}, },
}, },
}, },
Collcetion: {
HouseCollectionRecords: {
List(data?: ApiTypes.Collcetion.HouseCollectionRecords.List): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_records/list', { data });
},
Show(data: ApiTypes.Collcetion.HouseCollectionRecords.Show): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_records/show', { data });
},
Delete(data: ApiTypes.Collcetion.HouseCollectionRecords.Delete): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_records/delete', { data });
},
},
HouseCollectionTasks: {
List(data?: ApiTypes.Collcetion.HouseCollectionTasks.List): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_tasks/list', { data });
},
Show(data: ApiTypes.Collcetion.HouseCollectionTasks.Show): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_tasks/show', { data });
},
SoftDelete(data: ApiTypes.Collcetion.HouseCollectionTasks.SoftDelete): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_tasks/soft_delete', { data });
},
Restore(data: ApiTypes.Collcetion.HouseCollectionTasks.Restore): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_tasks/restore', { data });
},
Delete(data: ApiTypes.Collcetion.HouseCollectionTasks.Delete): Promise<MyResponseType> {
return request('admin/collcetion/house_collection_tasks/delete', { data });
},
},
},
Common: { Common: {
Admins: { Admins: {
List(data?: ApiTypes.Common.Admins.List): Promise<MyResponseType> { List(data?: ApiTypes.Common.Admins.List): Promise<MyResponseType> {

View File

@ -126,7 +126,7 @@ export const BannersTypeEnum= {
// 缓存类型 // 缓存类型
export const CacheTypeEnum= { export const CacheTypeEnum= {
'MobilePhoneVerificationCode': {"text":"手机验证码","color":"#a6e8bc","value":"MobilePhoneVerificationCode"}, 'MobilePhoneVerificationCode': {"text":"手机验证码","color":"#f8c2db","value":"MobilePhoneVerificationCode"},
}; };
// CompaniesMerchantTypeEnum // CompaniesMerchantTypeEnum
@ -188,6 +188,39 @@ export const HouseBillsTypeEnum= {
'SharedElectricityFee': {"text":"公摊电费","color":"#ec4899","value":"SharedElectricityFee"}, 'SharedElectricityFee': {"text":"公摊电费","color":"#ec4899","value":"SharedElectricityFee"},
}; };
// HouseCollectionRecordsCollectionResultEnum
export const HouseCollectionRecordsCollectionResultEnum= {
'PromiseToPay': {"text":"承诺缴费","color":"#4caf50","value":"PromiseToPay"},
'RefuseToPay': {"text":"拒绝缴费","color":"#f44336","value":"RefuseToPay"},
'NotReached': {"text":"未联系到","color":"#9e9e9e","value":"NotReached"},
'NeedFollowUp': {"text":"需要跟进","color":"#ff9800","value":"NeedFollowUp"},
};
// HouseCollectionRecordsSmsStatusEnum
export const HouseCollectionRecordsStatusEnum= {
'NotNotified': {"text":"未通知","color":"#808080","value":"NotNotified"},
'Notified': {"text":"已通知","color":"#00cc00","value":"Notified"},
'Failed': {"text":"失败","color":"#ff0000","value":"Failed"},
};
// HouseCollectionTasksChannelEnum
export const HouseCollectionTasksChannelEnum= {
'SMS': {"text":"短信","color":"#1E90FF","value":"SMS"},
'MiniProgram': {"text":"小程序","color":"#00BFFF","value":"MiniProgram"},
'OfficialAccount': {"text":"公众号","color":"#32CD32","value":"OfficialAccount"},
'PhoneCall': {"text":"电话催缴","color":"#FF8C00","value":"PhoneCall"},
'Visit': {"text":"上门催缴","color":"#8B4513","value":"Visit"},
'WeChat': {"text":"微信联系","color":"#20B2AA","value":"WeChat"},
'WrittenNotice': {"text":"书面通知","color":"#708090","value":"WrittenNotice"},
};
// HouseCollectionTasksStatusEnum
export const HouseCollectionTasksStatusEnum= {
'Processing': {"text":"进行中","color":"#ffcc00","value":"Processing"},
'Completed': {"text":"已完成","color":"#00cc00","value":"Completed"},
'Failed': {"text":"失败","color":"#ff0000","value":"Failed"},
};
// HouseOccupantsCardTypeEnum // HouseOccupantsCardTypeEnum
export const HouseOccupantsCardTypeEnum= { export const HouseOccupantsCardTypeEnum= {
'MainlandID': {"text":"中国大陆居民身份证","color":"#2db7f5","value":"MainlandID"}, 'MainlandID': {"text":"中国大陆居民身份证","color":"#2db7f5","value":"MainlandID"},
@ -295,7 +328,7 @@ export const HouseWorkOrdersAssignStatusEnum= {
export const HouseWorkOrdersLevelEnum= { export const HouseWorkOrdersLevelEnum= {
'Urgent': {"text":"紧急","color":"#ff0000","value":"Urgent"}, 'Urgent': {"text":"紧急","color":"#ff0000","value":"Urgent"},
'High': {"text":"高","color":"#ff7f00","value":"High"}, 'High': {"text":"高","color":"#ff7f00","value":"High"},
'Normal': {"text":"普通","color":"#00cc00","value":"Normal"}, 'Medium': {"text":"中","color":"#00cc00","value":"Medium"},
'Low': {"text":"低","color":"#999999","value":"Low"}, 'Low': {"text":"低","color":"#999999","value":"Low"},
}; };

View File

@ -57,7 +57,10 @@ export default function Show({ title = '房屋档案' }) {
children: ( children: (
<OccupantsNow <OccupantsNow
item={{ ...data, asset_houses_id: id }} item={{ ...data, asset_houses_id: id }}
reload={() => loadShow()} reload={() => {
loadShow();
loadPendingCount();
}}
/> />
), ),
}, },

View File

@ -6,7 +6,7 @@ import {
HouseOccupantsStatusEnum, HouseOccupantsStatusEnum,
} from '@/gen/Enums'; } from '@/gen/Enums';
import { ProTable } from '@ant-design/pro-components'; import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd'; import { Popconfirm, Space, Tag } from 'antd';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import AddOccupant from './modals/AddOccupant'; import AddOccupant from './modals/AddOccupant';
import AddRent from './modals/AddRent'; import AddRent from './modals/AddRent';
@ -23,6 +23,11 @@ export default function Index({ ...rest }) {
actionLooks?.current.reloadAndRest(); actionLooks?.current.reloadAndRest();
}, [rest.loadmore]); }, [rest.loadmore]);
const handleReload = () => {
actionLooks?.current.reload();
rest.reload?.(); // 调用父组件的reload函数更新Pending计数
};
return ( return (
<> <>
<ProTable<Record<any, any>> <ProTable<Record<any, any>>
@ -68,8 +73,9 @@ export default function Index({ ...rest }) {
search: false, search: false,
}), }),
MyColumns.EnumTag({ MyColumns.EnumTag({
title: // title:
rest?.item?.status === 'Rented' ? '与租客关系' : '与业主关系', // rest?.item?.status === 'Rented' ? '与租客关系' : '与业主关系',
title: '关系说明',
dataIndex: 'relation_with_owner', dataIndex: 'relation_with_owner',
valueEnum: HouseOccupantsRelationWithOwnerEnum, valueEnum: HouseOccupantsRelationWithOwnerEnum,
search: false, search: false,
@ -82,13 +88,6 @@ export default function Index({ ...rest }) {
title: '电话', title: '电话',
dataIndex: 'phone', dataIndex: 'phone',
}, },
// {
// title: '是否入住',
// dataIndex: 'is_live_in',
// render(_, record) {
// return `${record?.is_live_in ? '是' : '-'} `;
// },
// },
{ {
title: '入住日期', title: '入住日期',
dataIndex: 'is_live_in', dataIndex: 'is_live_in',
@ -96,6 +95,46 @@ export default function Index({ ...rest }) {
return `${record?.move_in_date || '未入住'}`; return `${record?.move_in_date || '未入住'}`;
}, },
}, },
{
title: '常用联系人',
render: (_, item, index, action) =>
item?.is_contact ? (
<Popconfirm
title="设置常用联系人"
description="您确认设置常用联系人吗?"
onConfirm={() => {
Apis.Archive.HouseOccupants.ChangeIsContact({
id: item.id,
is_contact: false,
}).then(() => action?.reload());
}}
okText="是"
cancelText="否"
>
<Tag color="green" style={{ cursor: 'pointer' }}>
</Tag>
</Popconfirm>
) : (
<Popconfirm
title="取消"
description="您确认取消常用联系人吗?"
onConfirm={() => {
Apis.Archive.HouseOccupants.ChangeIsContact({
id: item.id,
is_contact: true,
}).then(() => action?.reload());
}}
okText="是"
cancelText="否"
>
<Tag color="gray" style={{ cursor: 'pointer' }}>
</Tag>
</Popconfirm>
),
search: false,
},
MyColumns.EnumTag({ MyColumns.EnumTag({
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
@ -114,7 +153,7 @@ export default function Index({ ...rest }) {
{item?.move_in_date && ( {item?.move_in_date && (
<MoveOut item={item} reload={action?.reload} title="搬离" /> <MoveOut item={item} reload={action?.reload} title="搬离" />
)} )}
<RemoveOwner item={item} reload={action?.reload} title="移除" /> <RemoveOwner item={item} reload={handleReload} title="移除" />
</Space> </Space>
), ),
}), }),

View File

@ -138,6 +138,12 @@ export default function Update(props: MyBetaModalFormProps) {
: [,]; : [,];
}, },
}, },
{
title: '是否联系',
dataIndex: 'is_contact',
colProps: { span: 12 },
valueType: 'switch',
},
], ],
}, },
]} ]}

View File

@ -5,8 +5,8 @@ import { message, Modal } from 'antd';
export default function MoveOut(props: MyBetaModalFormProps) { export default function MoveOut(props: MyBetaModalFormProps) {
const handleRemoveClick = () => { const handleRemoveClick = () => {
Modal.confirm({ Modal.confirm({
title: '确认移除', title: '解除申请',
content: '确定要提交移除申请吗?', content: '确定要解除人房关系吗?',
okText: '确定', okText: '确定',
cancelText: '取消', cancelText: '取消',
// centered: true, // centered: true,
@ -16,7 +16,7 @@ export default function MoveOut(props: MyBetaModalFormProps) {
house_occupants_id: props?.item?.id, house_occupants_id: props?.item?.id,
}); });
props.reload?.(); props.reload?.();
message.success(props.title + '成功'); message.success('申请成功,等待审核');
} catch (error) { } catch (error) {
message.error('操作失败'); message.error('操作失败');
} }

View File

@ -0,0 +1,52 @@
import { MyPageContainer, usePageTabs } from '@/common';
import { Apis } from '@/gen/Apis';
import { ProCard } from '@ant-design/pro-components';
import { useParams } from '@umijs/max';
import { Tabs } from 'antd';
import { useEffect, useState } from 'react';
import CollectionList from './components/CollectionList';
import CollectionTaskInfo from './components/CollectionTaskInfo';
export default function Show({ title }: { title?: string } = {}) {
const { id } = useParams<{ id: string }>();
const [data, setShow] = useState<any>({});
// 注册当前页面为标签页
const { addTab } = usePageTabs({
tabKey: `collection-task-${id}`,
tabLabel: `${data?.task_name} ${data?.id}` || title || '任务详情',
});
const loadShow = () => {
let paramsId: any = { id: id ?? 0 };
Apis.Collcetion.HouseCollectionTasks.Show(paramsId).then((res) => {
setShow(res?.data);
});
};
useEffect(() => {
loadShow();
}, [id]);
let items = [
{
label: '通知记录',
key: '1',
closable: false,
children: (
<CollectionList
item={{ ...data, collection_tasks_id: id }}
reload={() => loadShow()}
/>
),
},
];
return (
<MyPageContainer title={title}>
<CollectionTaskInfo item={data} reload={loadShow} />
<ProCard style={{ marginTop: 16 }}>
<Tabs type="card" items={items} defaultActiveKey="1" size="small" />
</ProCard>
</MyPageContainer>
);
}

View File

@ -0,0 +1,95 @@
import { MyColumns, MyProTableProps } from '@/common';
import { Apis } from '@/gen/Apis';
import {
HouseCollectionRecordsStatusEnum,
HouseCollectionTasksChannelEnum,
} from '@/gen/Enums';
import BannerShow from '@/pages/banner/modals/BannerShow';
import BannerUpdate from '@/pages/banner/modals/BannerUpdate';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import { useEffect, useRef } from 'react';
export default function Index({ ...rest }) {
const actionLooks = useRef<any>();
useEffect(() => {
actionLooks?.current.reloadAndRest();
}, [rest.loadmore]);
return (
<>
<ProTable<Record<any, any>>
{...MyProTableProps.props}
actionRef={actionLooks}
request={async (params, sort) =>
MyProTableProps.request(
{
...params,
collection_tasks_id: rest.item?.collection_tasks_id,
},
sort,
Apis.Collcetion.HouseCollectionRecords.List,
)
}
// toolBarRender={(action) => [
// <BannerCreate
// key="AddOccupant"
// item={rest.item}
// reload={action?.reload}
// title="添加住户"
// />,
// ]}
// search={false}
options={false}
columns={[
MyColumns.ID(),
{
title: '房屋名称',
dataIndex: ['asset_house', 'full_name'],
search: {
transform: (value) => {
return { full_name: value };
},
},
},
MyColumns.EnumTag({
title: '通知方式',
dataIndex: 'channel',
valueEnum: HouseCollectionTasksChannelEnum,
search: false,
}),
{
title: '未缴金额',
dataIndex: 'total_unpaid_amount',
search: false,
},
{
title: '通知时间',
dataIndex: 'notified_time',
valueType: 'dateTime',
search: false,
},
MyColumns.EnumTag({
title: '状态',
dataIndex: 'status',
valueEnum: HouseCollectionRecordsStatusEnum,
search: false,
}),
{
title: '失败原因',
dataIndex: 'failed_reason',
search: false,
},
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<BannerShow item={item} reload={action?.reload} />
<BannerUpdate item={item} reload={action?.reload} />
</Space>
),
}),
]}
/>
</>
);
}

View File

@ -0,0 +1,67 @@
import { MyBetaModalFormProps, renderTextHelper } from '@/common';
import {
HouseCollectionTasksChannelEnum,
HouseCollectionTasksStatusEnum,
} from '@/gen/Enums';
import { ProCard, ProDescriptions } from '@ant-design/pro-components';
import { Space, Tag } from 'antd';
export default function info(props: MyBetaModalFormProps) {
const { item } = props;
return (
<Space direction="vertical" style={{ width: '100%' }}>
<ProCard title="基本信息">
<ProDescriptions bordered>
<ProDescriptions.Item label="ID">{item?.id}</ProDescriptions.Item>
<ProDescriptions.Item label="任务名称">
{item?.task_name}
</ProDescriptions.Item>
<ProDescriptions.Item label="渠道">
{!item?.channel || !Array.isArray(item?.channel) ? (
'-'
) : (
<Space>
{item.channel.map((channelValue: string) => {
const channelInfo =
HouseCollectionTasksChannelEnum[
channelValue as keyof typeof HouseCollectionTasksChannelEnum
];
return (
<Tag key={channelValue} color={channelInfo?.color}>
{channelInfo?.text || channelValue}
</Tag>
);
})}
</Space>
)}
</ProDescriptions.Item>
<ProDescriptions.Item label="状态">
<renderTextHelper.Tag
Enums={HouseCollectionTasksStatusEnum}
value={item?.status}
key="status"
/>
</ProDescriptions.Item>
<ProDescriptions.Item label="房屋数">
{item?.total_houses || '-'}
</ProDescriptions.Item>
<ProDescriptions.Item label="住户数">
{item?.total_notified || '-'}
</ProDescriptions.Item>
<ProDescriptions.Item label="创建人">
{item?.company_employee?.name || '-'}-
{item?.company_employee?.phone || '-'}
</ProDescriptions.Item>
<ProDescriptions.Item label="创建时间">
{item?.created_at || '-'}
</ProDescriptions.Item>
<ProDescriptions.Item label="完成时间">
{item?.completed_time || '-'}
</ProDescriptions.Item>
</ProDescriptions>
</ProCard>
</Space>
);
}

View File

@ -0,0 +1,117 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
usePageTabs,
} from '@/common';
import { Apis } from '@/gen/Apis';
import {
HouseCollectionTasksChannelEnum,
HouseCollectionTasksStatusEnum,
} from '@/gen/Enums';
import { ProTable } from '@ant-design/pro-components';
import { useNavigate } from '@umijs/max';
import { Space, Tag } from 'antd';
export default function CollectionTaskList({ title = '通知任务' }) {
const navigate = useNavigate();
// 注册当前页面为标签页
usePageTabs({
tabKey: 'collection-tasks',
tabLabel: title,
});
return (
<MyPageContainer
title={title}
enableTabs={true}
tabKey="collection-tasks"
tabLabel={title}
>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(
params,
sort,
Apis.Collcetion.HouseCollectionTasks.List,
)
}
columns={[
MyColumns.ID(),
{
title: '任务名称',
dataIndex: 'task_name',
search: false,
},
{
title: '催缴渠道',
dataIndex: 'channel',
search: false,
render: (_, record: any) => {
if (!record.channel || !Array.isArray(record.channel)) {
return '-';
}
return record.channel.map((channelValue: string) => {
const channelInfo =
HouseCollectionTasksChannelEnum[
channelValue as keyof typeof HouseCollectionTasksChannelEnum
];
return (
<Tag key={channelValue} color={channelInfo?.color}>
{channelInfo?.text || channelValue}
</Tag>
);
});
},
},
MyColumns.EnumTag({
title: '状态',
dataIndex: 'status',
valueEnum: HouseCollectionTasksStatusEnum,
}),
{
title: '房屋数',
dataIndex: 'total_houses',
search: false,
},
{
title: '住户数',
dataIndex: 'total_notified',
search: false,
},
{
title: '发起人',
dataIndex: 'CompanyEmployee',
search: false,
render: (_, record: any) =>
`${record?.company_employee?.name}-${record?.company_employee?.phone}`,
},
{
title: '完成时间',
dataIndex: 'completed_time',
valueType: 'dateTime',
search: false,
},
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index) => (
<Space key={index}>
<MyButtons.View
title="查看详情"
onClick={() => {
navigate(`/collection_task/${item.id}`);
}}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}