fix:更新员工端权限
This commit is contained in:
parent
d898d299ed
commit
78ab79c80b
@ -49,3 +49,7 @@ export function GetFromNow(time: string) {
|
||||
//获取时间距离现在的时间
|
||||
return time ? dayjs(time).fromNow() : '';
|
||||
}
|
||||
|
||||
export function getTodayDate(): string {
|
||||
return dayjs().format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
2
src/common/utils/mapConfig.ts
Normal file
2
src/common/utils/mapConfig.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const mapKey = '4ce26ecef55ae1ec47910a72a098efc0';
|
||||
// export const mapKey = '35fe6f4443fddf86cdcca3b97fd8c872';
|
||||
109
src/components/ModalsAssetsProjectSelectList.tsx
Normal file
109
src/components/ModalsAssetsProjectSelectList.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyColumns,
|
||||
MyProTableProps,
|
||||
} from '@/common';
|
||||
import { MyModal } from '@/components/MyModal';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { AssetProjectsPropertyTypeEnum } from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { useRef, useState } from 'react';
|
||||
interface DataType {
|
||||
key?: React.Key;
|
||||
id?: React.Key;
|
||||
}
|
||||
export default function SurveysSelectList(
|
||||
props: MyBetaModalFormProps & {
|
||||
onChange?: (selectedRows: DataType[]) => void;
|
||||
type?: 'checkbox' | 'radio';
|
||||
},
|
||||
) {
|
||||
const modalRef = useRef<any>();
|
||||
// const [selectedDataRow, setSelectedDataRow] = useState<any>({});
|
||||
const [getSelectedRow, setSelectedRow] = useState<any>([]);
|
||||
const rowSelection: any = {
|
||||
onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
|
||||
console.log(selectedRows, 'selectedRows[0]');
|
||||
setSelectedRow(selectedRows);
|
||||
},
|
||||
getCheckboxProps: (record: any) => ({
|
||||
disabled: record.deleted_at,
|
||||
checked: props?.item?.some((item: any) => {
|
||||
console.log(item, record);
|
||||
return item?.id === record?.id;
|
||||
}),
|
||||
}),
|
||||
defaultSelectedRowKeys: props?.item?.map((item: any) => item?.id) || [],
|
||||
};
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
title={'选择关联项目'}
|
||||
width="1000px"
|
||||
myRef={modalRef}
|
||||
size="middle"
|
||||
onOpen={() => {
|
||||
setSelectedRow(props?.item);
|
||||
console.log(props?.item, 'props?.item?.id');
|
||||
}}
|
||||
node={
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(params, sort, Apis.Asset.AssetProjects.List)
|
||||
}
|
||||
rowSelection={{
|
||||
type: props?.type ? props?.type : 'checkbox',
|
||||
...rowSelection,
|
||||
}}
|
||||
options={false}
|
||||
tableAlertOptionRender={() => {
|
||||
return (
|
||||
<MyButtons.Default
|
||||
key="okSelect"
|
||||
size="middle"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
let res: any = getSelectedRow;
|
||||
props?.onChange?.(res);
|
||||
modalRef.current?.close();
|
||||
}}
|
||||
title="确定选项"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
columns={[
|
||||
MyColumns.ID({
|
||||
search: false,
|
||||
}),
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
MyColumns.EnumTag({
|
||||
title: '类型',
|
||||
dataIndex: 'property_type',
|
||||
valueEnum: AssetProjectsPropertyTypeEnum,
|
||||
search: false,
|
||||
}),
|
||||
{
|
||||
title: '地址',
|
||||
render: (_, i: any) => {
|
||||
return `${i?.province || ''} ${i?.city || ''} ${
|
||||
i?.district || ''
|
||||
}${i?.address || ''}`;
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
MyColumns.DeletedAt({
|
||||
title: '启/禁用',
|
||||
dataIndex: 'deleted_at',
|
||||
search: false,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
}
|
||||
></MyModal>
|
||||
);
|
||||
}
|
||||
@ -75,6 +75,7 @@ export const SysSelects = {
|
||||
valueType: 'select',
|
||||
hideInTable: hideInTable,
|
||||
formItemProps: { ...(required ? rulesHelper.number : {}) },
|
||||
...rest,
|
||||
fieldProps: {
|
||||
mode: 'multiple',
|
||||
showSearch: false,
|
||||
@ -82,9 +83,37 @@ export const SysSelects = {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
...rest?.fieldProps,
|
||||
},
|
||||
request: async () => (await Apis.Permission.Roles.Select()).data,
|
||||
};
|
||||
},
|
||||
SysEmployeeRoles(props?: PropsType): ReturnType {
|
||||
const {
|
||||
title = '员工角色',
|
||||
key = 'employee_roles_id',
|
||||
required = true,
|
||||
hideInTable = true,
|
||||
...rest
|
||||
} = props ?? {};
|
||||
|
||||
return {
|
||||
title: title,
|
||||
key: key,
|
||||
valueType: 'select',
|
||||
hideInTable: hideInTable,
|
||||
formItemProps: { ...(required ? rulesHelper.number : {}) },
|
||||
...rest,
|
||||
fieldProps: {
|
||||
mode: 'multiple',
|
||||
showSearch: false,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
...rest?.fieldProps,
|
||||
},
|
||||
request: async () => (await Apis.Company.EmployeeRoles.Select()).data,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
23
src/gen/ApiTypes.d.ts
vendored
23
src/gen/ApiTypes.d.ts
vendored
@ -1138,6 +1138,28 @@ declare namespace ApiTypes {
|
||||
"company_name"?: string; // 模糊搜索:名称
|
||||
};
|
||||
}
|
||||
namespace EmployeeRoles {
|
||||
type List = {
|
||||
"name"?: string; // 模糊搜索:名称
|
||||
};
|
||||
type Store = {
|
||||
"name": string; // 角色名称
|
||||
};
|
||||
type Update = {
|
||||
"id": number; // ID
|
||||
"name": string; // 角色名称
|
||||
};
|
||||
type Delete = {
|
||||
"id": number; // ID
|
||||
};
|
||||
type GetPermissions = {
|
||||
"id": number; // 角色ID
|
||||
};
|
||||
type SetPermissions = {
|
||||
"id": number; // ID
|
||||
"permissions_ids": string[]; // 权限ID
|
||||
};
|
||||
}
|
||||
namespace OrganizationProjects {
|
||||
type List = {
|
||||
"organizations_id"?: number; // 组织id,[ref:organizations]
|
||||
@ -1882,6 +1904,7 @@ declare namespace ApiTypes {
|
||||
namespace Roles {
|
||||
type List = {
|
||||
"name"?: string; // 模糊搜索:名称
|
||||
"guard_name"?: string; // 角色类型
|
||||
};
|
||||
type Store = {
|
||||
"name": string; // 角色名称
|
||||
|
||||
@ -633,6 +633,32 @@ export const Apis = {
|
||||
return request('company/company/company_receipt_accounts/select', { data });
|
||||
},
|
||||
},
|
||||
EmployeeRoles: {
|
||||
List(data?: ApiTypes.Company.EmployeeRoles.List): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/list', { data });
|
||||
},
|
||||
Store(data: ApiTypes.Company.EmployeeRoles.Store): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/store', { data });
|
||||
},
|
||||
Update(data: ApiTypes.Company.EmployeeRoles.Update): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/update', { data });
|
||||
},
|
||||
Delete(data: ApiTypes.Company.EmployeeRoles.Delete): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/delete', { data });
|
||||
},
|
||||
Select(): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/select', {});
|
||||
},
|
||||
GetPermissions(data: ApiTypes.Company.EmployeeRoles.GetPermissions): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/get_permissions', { data });
|
||||
},
|
||||
SetPermissions(data: ApiTypes.Company.EmployeeRoles.SetPermissions): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/set_permissions', { data });
|
||||
},
|
||||
PermissionTree(): Promise<MyResponseType> {
|
||||
return request('company/company/employee_roles/permission_tree', {});
|
||||
},
|
||||
},
|
||||
OrganizationProjects: {
|
||||
List(data?: ApiTypes.Company.OrganizationProjects.List): Promise<MyResponseType> {
|
||||
return request('company/company/organization_projects/list', { data });
|
||||
|
||||
1374
src/gen/Enums.ts
1374
src/gen/Enums.ts
File diff suppressed because it is too large
Load Diff
68
src/pages/attendance/attendance_configs/index.tsx
Normal file
68
src/pages/attendance/attendance_configs/index.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { MyColumns, MyPageContainer, MyProTableProps } from '@/common';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { Space } from 'antd';
|
||||
import Create from './modals/Create';
|
||||
import Update from './modals/Update';
|
||||
|
||||
export default function Index({ title = '打卡配置' }) {
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={title}
|
||||
enableTabs={true}
|
||||
tabKey="attendance_configs"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
// search={false}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(
|
||||
params,
|
||||
sort,
|
||||
Apis.Attendance.AttendanceConfigs.List,
|
||||
)
|
||||
}
|
||||
headerTitle="打卡参数配置"
|
||||
toolBarRender={(action) => [
|
||||
<Create key="Create" reload={action?.reload} title={title} />,
|
||||
]}
|
||||
search={false}
|
||||
columns={[
|
||||
MyColumns.ID({
|
||||
search: false,
|
||||
}),
|
||||
{
|
||||
title: '可打卡范围(米内)',
|
||||
dataIndex: 'check_in_range',
|
||||
search: false,
|
||||
},
|
||||
MyColumns.Boolean({
|
||||
dataIndex: 'require_photo',
|
||||
title: '是否要求拍照打卡',
|
||||
search: false,
|
||||
}),
|
||||
// MyColumns.Boolean({
|
||||
// dataIndex: 'allow_out_range_checkin',
|
||||
// title: '是否允许范围外打卡',
|
||||
// search: false,
|
||||
// }),
|
||||
// MyColumns.IsEnabled({
|
||||
// onRestore: Apis.Attendance.AttendanceConfigs.Enable,
|
||||
// onSoftDelete: Apis.Attendance.AttendanceConfigs.Enable,
|
||||
// search: false,
|
||||
// }),
|
||||
// MyColumns.CreatedAt(),
|
||||
MyColumns.UpdatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
<Update item={item} reload={action?.reload} title={title} />
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
63
src/pages/attendance/attendance_configs/modals/Create.tsx
Normal file
63
src/pages/attendance/attendance_configs/modals/Create.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Attendance.AttendanceConfigs.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={`添加${props.title}`}
|
||||
wrapperCol={{ span: 24 }}
|
||||
width="500px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
trigger={<MyButtons.Create title={`配置`} />}
|
||||
onFinish={async (values) =>
|
||||
Apis.Attendance.AttendanceConfigs.Store(values)
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
key: 'check_in_range',
|
||||
title: '可打卡范围',
|
||||
valueType: 'digit',
|
||||
fieldProps: {
|
||||
suffix: '米内',
|
||||
style: { width: '100%' },
|
||||
},
|
||||
formItemProps: { ...rulesHelper.number },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
title: '是否要求拍照打卡',
|
||||
key: 'require_photo',
|
||||
valueType: 'switch',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
// {
|
||||
// title: '是否允许范围外打卡',
|
||||
// key: 'allow_out_range_checkin',
|
||||
// valueType: 'switch',
|
||||
// colProps: { span: 12 },
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
66
src/pages/attendance/attendance_configs/modals/Update.tsx
Normal file
66
src/pages/attendance/attendance_configs/modals/Update.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Attendance.AttendanceConfigs.UpdateConfig>
|
||||
{...MyModalFormProps.props}
|
||||
title={`打卡配置`}
|
||||
trigger={<MyButtons.Edit title={`修改`} />}
|
||||
wrapperCol={{ span: 24 }}
|
||||
key={new Date().getTime()}
|
||||
width="500px"
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open && props.item) {
|
||||
form.setFieldsValue(props.item);
|
||||
}
|
||||
}}
|
||||
onFinish={async (values) =>
|
||||
Apis.Attendance.AttendanceConfigs.UpdateConfig({
|
||||
...values,
|
||||
id: props.item?.id ?? 1,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '编辑成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
key: 'check_in_range',
|
||||
title: '可打卡范围',
|
||||
valueType: 'digit',
|
||||
fieldProps: {
|
||||
suffix: '米内',
|
||||
style: { width: '100%' },
|
||||
},
|
||||
formItemProps: { ...rulesHelper.number },
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
title: '是否要求拍照打卡',
|
||||
key: 'require_photo',
|
||||
valueType: 'switch',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
// {
|
||||
// title: '是否允许范围外打卡',
|
||||
// key: 'allow_out_range_checkin',
|
||||
// valueType: 'switch',
|
||||
// colProps: { span: 12 },
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
116
src/pages/attendance/attendance_employees/index.tsx
Normal file
116
src/pages/attendance/attendance_employees/index.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import {
|
||||
MyColumns,
|
||||
MyImportModal,
|
||||
MyPageContainer,
|
||||
MyProTableProps,
|
||||
renderTextHelper,
|
||||
useCurrentPermissions,
|
||||
} from '@/common';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { CompanyEmployeesTypeEnum } from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { Space } from 'antd';
|
||||
import Change from './modals/Change';
|
||||
import EmployeeCreate from './modals/EmployeeCreate';
|
||||
import EmployeeUpdate from './modals/EmployeeUpdate';
|
||||
|
||||
export default function Index({ title = '外包人员' }) {
|
||||
const getCurrentPermissions = useCurrentPermissions();
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={title}
|
||||
enableTabs={true}
|
||||
tabKey="attendance_employees"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
// search={false}
|
||||
headerTitle="外包人员列表"
|
||||
tooltip="通过企微同步的员工信息的修改,需在企微修改后,同步到系统,才能生效。"
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(
|
||||
{
|
||||
type: 'External',
|
||||
...params,
|
||||
},
|
||||
sort,
|
||||
Apis.Company.CompanyEmployees.List,
|
||||
)
|
||||
}
|
||||
toolBarRender={(action) => [
|
||||
<MyImportModal
|
||||
key="ImportHouse"
|
||||
title="导入外部人员"
|
||||
type="default"
|
||||
size="middle"
|
||||
templateApi={Apis.Company.CompanyEmployees.DownloadTemplate}
|
||||
importApi={Apis.Company.CompanyEmployees.Import}
|
||||
reload={action?.reload}
|
||||
/>,
|
||||
<EmployeeCreate key="Create" reload={action?.reload} title="员工" />,
|
||||
]}
|
||||
columns={[
|
||||
MyColumns.ID({ search: false }),
|
||||
{
|
||||
title: '所在组织',
|
||||
dataIndex: 'organization_path',
|
||||
search: {
|
||||
transform: (value) => {
|
||||
return { organization_name: value };
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roles',
|
||||
renderText: renderTextHelper.TagList,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '岗位',
|
||||
dataIndex: ['position', 'name'],
|
||||
search: false,
|
||||
},
|
||||
MyColumns.EnumTag({
|
||||
title: '来源',
|
||||
dataIndex: 'type',
|
||||
valueEnum: CompanyEmployeesTypeEnum,
|
||||
search: false,
|
||||
}),
|
||||
MyColumns.SoftDelete({
|
||||
title: '启/禁用',
|
||||
onRestore: Apis.Company.CompanyEmployees.Restore,
|
||||
onSoftDelete: Apis.Company.CompanyEmployees.SoftDelete,
|
||||
search: false,
|
||||
setPermissions: getCurrentPermissions({
|
||||
enableDisable: true,
|
||||
}),
|
||||
}),
|
||||
MyColumns.UpdatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
<EmployeeUpdate
|
||||
item={item}
|
||||
reload={action?.reload}
|
||||
title={title}
|
||||
/>
|
||||
|
||||
<Change item={item} reload={action?.reload} title={title} />
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
68
src/pages/attendance/attendance_employees/modals/Change.tsx
Normal file
68
src/pages/attendance/attendance_employees/modals/Change.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { CompanyEmployeesTypeEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Company.CompanyEmployees.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={`组织调整`}
|
||||
trigger={
|
||||
<MyButtons.Edit
|
||||
title="组织"
|
||||
disabled={
|
||||
props.item?.type !== CompanyEmployeesTypeEnum.External.value
|
||||
}
|
||||
type="primary"
|
||||
/>
|
||||
}
|
||||
wrapperCol={{ span: 24 }}
|
||||
width="500px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Company.CompanyEmployees.Update({
|
||||
...values,
|
||||
id: props.item?.id ?? 0,
|
||||
name: props.item?.name ?? '',
|
||||
phone: props.item?.phone ?? '',
|
||||
type: props.item?.type ?? '',
|
||||
organizations_id:
|
||||
values?.organizations_id?.[values.organizations_id.length - 1],
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.OrganizationsTree({
|
||||
title: '选择组织',
|
||||
key: 'organizations_id',
|
||||
params: { companies_id: props?.item?.companies_id },
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
},
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { SysSelects } from '@/components/SysSelects';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { CompanyEmployeesTypeEnum, SexEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Company.CompanyEmployees.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={`添加外部人员`}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
labelAlign="left"
|
||||
width="500px"
|
||||
key={new Date().getTime()}
|
||||
trigger={<MyButtons.Create title={`外部人员`} />}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Company.CompanyEmployees.Store({
|
||||
...values,
|
||||
companies_id: values?.companies_id || props?.item?.id,
|
||||
type: CompanyEmployeesTypeEnum.External.value,
|
||||
password: 'Gc#123',
|
||||
organizations_id:
|
||||
values?.organizations_id?.[values.organizations_id.length - 1],
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.OrganizationsTree({
|
||||
title: '选择组织',
|
||||
key: 'organizations_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
|
||||
{
|
||||
key: 'name',
|
||||
title: '姓名',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: '手机号',
|
||||
valueType: 'number',
|
||||
fieldProps: {
|
||||
maxLength: 11,
|
||||
},
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.phone },
|
||||
},
|
||||
MyFormItems.EnumRadio({
|
||||
key: 'sex',
|
||||
title: '性别',
|
||||
colProps: { span: 24 },
|
||||
valueEnum: SexEnum,
|
||||
required: true,
|
||||
}),
|
||||
Selects?.Positions({
|
||||
title: '岗位',
|
||||
key: 'positions_id',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
SysSelects.SysRoles(),
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
colProps: { span: 24 },
|
||||
valueType: 'textarea',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { SexEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Company.CompanyEmployees.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={`编辑员工`}
|
||||
trigger={
|
||||
<MyButtons.Default
|
||||
title="编辑"
|
||||
type="primary"
|
||||
// variant="solid"
|
||||
size="small"
|
||||
// disabled={
|
||||
// props.item?.type !== CompanyEmployeesTypeEnum.External.value
|
||||
// }
|
||||
/>
|
||||
}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
labelAlign="left"
|
||||
width="500px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open && props.item) {
|
||||
form.setFieldsValue({
|
||||
...props.item,
|
||||
roles_id: props.item?.roles?.map((item: any) => item.value),
|
||||
positions_id: props.item?.positions_id ?? '',
|
||||
});
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Company.CompanyEmployees.Update({
|
||||
...values,
|
||||
id: props.item?.id ?? 0,
|
||||
type: props.item?.type,
|
||||
password: null,
|
||||
organizations_id: props.item?.organizations_id ?? '',
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
key: 'name',
|
||||
title: '姓名',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: '手机号',
|
||||
valueType: 'number',
|
||||
fieldProps: {
|
||||
maxLength: 11,
|
||||
},
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.phone },
|
||||
},
|
||||
MyFormItems.EnumRadio({
|
||||
key: 'sex',
|
||||
title: '性别',
|
||||
colProps: { span: 24 },
|
||||
valueEnum: SexEnum,
|
||||
required: true,
|
||||
}),
|
||||
// MyFormItems.EnumRadio({
|
||||
// key: 'type',
|
||||
// title: '来源',
|
||||
// colProps: { span: 24 },
|
||||
// valueEnum: CompanyEmployeesTypeEnum,
|
||||
// required: true,
|
||||
// }),
|
||||
Selects?.Positions({
|
||||
title: '岗位',
|
||||
key: 'positions_id',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
params: {
|
||||
name: props.item?.position?.name ?? '',
|
||||
},
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
placeholder: '请输入关键字搜索',
|
||||
},
|
||||
}),
|
||||
|
||||
// {
|
||||
// key: 'password',
|
||||
// title: '密码',
|
||||
// colProps: { span: 24 },
|
||||
// valueType: 'password',
|
||||
// },
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
colProps: { span: 24 },
|
||||
valueType: 'textarea',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
109
src/pages/attendance/attendance_employees_tracks/index.tsx
Normal file
109
src/pages/attendance/attendance_employees_tracks/index.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import {
|
||||
MyButtons,
|
||||
MyColumns,
|
||||
MyPageContainer,
|
||||
MyProTableProps,
|
||||
renderTextHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { CompanyEmployeesTypeEnum } from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { Space } from 'antd';
|
||||
|
||||
export default function Index({ title = '员工轨迹' }) {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={title}
|
||||
enableTabs={true}
|
||||
tabKey="attendance_employees"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
// search={false}
|
||||
headerTitle="员工轨迹列表"
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(
|
||||
{
|
||||
type: 'External',
|
||||
...params,
|
||||
},
|
||||
sort,
|
||||
Apis.Company.CompanyEmployees.List,
|
||||
)
|
||||
}
|
||||
columns={[
|
||||
MyColumns.ID({ search: false }),
|
||||
Selects?.OrganizationSearch({
|
||||
title: '所属组织',
|
||||
key: 'organizations_id',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
},
|
||||
search: {
|
||||
transform: (value) => {
|
||||
return {
|
||||
organization_name:
|
||||
value.length > 0 ? value[value.length - 1] : '',
|
||||
};
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
title: '所在组织',
|
||||
dataIndex: 'organization_path',
|
||||
// search: {
|
||||
// transform: (value) => {
|
||||
// return { organization_name: value };
|
||||
// },
|
||||
// },
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roles',
|
||||
renderText: renderTextHelper.TagList,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '岗位',
|
||||
dataIndex: ['position', 'name'],
|
||||
search: false,
|
||||
},
|
||||
MyColumns.EnumTag({
|
||||
title: '来源',
|
||||
dataIndex: 'type',
|
||||
valueEnum: CompanyEmployeesTypeEnum,
|
||||
search: false,
|
||||
}),
|
||||
MyColumns.UpdatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index) => (
|
||||
<Space key={index}>
|
||||
<MyButtons.Default
|
||||
title="查看轨迹"
|
||||
type="primary"
|
||||
onClick={() =>
|
||||
navigate(`/attendance/employee_tracks/${item.id}`)
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Company.CompanyEmployees.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={`组织调整`}
|
||||
trigger={<MyButtons.Default title="组织调整" type="link" />}
|
||||
wrapperCol={{ span: 24 }}
|
||||
width="500px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Company.CompanyEmployees.Update({
|
||||
...values,
|
||||
id: props.item?.id ?? 0,
|
||||
name: props.item?.name ?? '',
|
||||
phone: props.item?.phone ?? '',
|
||||
type: props.item?.type,
|
||||
organizations_id:
|
||||
values?.organizations_id?.[values.organizations_id.length - 1],
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.OrganizationsTree({
|
||||
title: '选择组织',
|
||||
key: 'organizations_id',
|
||||
params: { companies_id: props?.item?.companies_id },
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
},
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { SysSelects } from '@/components/SysSelects';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { CompanyEmployeesTypeEnum, SexEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Company.CompanyEmployees.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={`添加外部人员`}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
labelAlign="left"
|
||||
width="500px"
|
||||
trigger={<MyButtons.Create title={`外部人员`} />}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Company.CompanyEmployees.Store({
|
||||
...values,
|
||||
companies_id: values?.companies_id || props?.item?.id,
|
||||
type: CompanyEmployeesTypeEnum.External.value,
|
||||
password: 'Gc#123',
|
||||
organizations_id:
|
||||
values?.organizations_id?.[values.organizations_id.length - 1],
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.OrganizationsTree({
|
||||
title: '选择组织',
|
||||
key: 'organizations_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
|
||||
{
|
||||
key: 'name',
|
||||
title: '姓名',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: '手机号',
|
||||
valueType: 'number',
|
||||
fieldProps: {
|
||||
maxLength: 11,
|
||||
},
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.phone },
|
||||
},
|
||||
MyFormItems.EnumRadio({
|
||||
key: 'sex',
|
||||
title: '性别',
|
||||
colProps: { span: 24 },
|
||||
valueEnum: SexEnum,
|
||||
required: true,
|
||||
}),
|
||||
Selects?.Positions({
|
||||
title: '岗位',
|
||||
key: 'positions_id',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
SysSelects.SysRoles(),
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
colProps: { span: 24 },
|
||||
valueType: 'textarea',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
|
||||
import { Selects } from '@/components/Select';
|
||||
import { SysSelects } from '@/components/SysSelects';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { SexEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Company.CompanyEmployees.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={`编辑员工`}
|
||||
trigger={<MyButtons.Edit />}
|
||||
wrapperCol={{ span: 24 }}
|
||||
width="500px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open && props.item) {
|
||||
form.setFieldsValue({
|
||||
...props.item,
|
||||
roles_id: props.item?.roles?.map((item: any) => item.value),
|
||||
});
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Company.CompanyEmployees.Update({
|
||||
...values,
|
||||
id: props.item?.id ?? 0,
|
||||
type: props.item?.type,
|
||||
organizations_id:
|
||||
values?.organizations_id?.[values.organizations_id.length - 1] ||
|
||||
props.item?.organizations_id,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
key: 'name',
|
||||
title: '姓名',
|
||||
colProps: { span: 8 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: '手机号',
|
||||
valueType: 'number',
|
||||
fieldProps: {
|
||||
maxLength: 11,
|
||||
},
|
||||
colProps: { span: 10 },
|
||||
formItemProps: { ...rulesHelper.phone },
|
||||
},
|
||||
MyFormItems.EnumRadio({
|
||||
key: 'sex',
|
||||
title: '性别',
|
||||
colProps: { span: 6 },
|
||||
valueEnum: SexEnum,
|
||||
required: true,
|
||||
}),
|
||||
{
|
||||
key: 'password',
|
||||
title: '密码',
|
||||
valueType: 'password',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
placeholder: '不修改密码请留空',
|
||||
},
|
||||
},
|
||||
Selects?.Positions({
|
||||
title: '岗位',
|
||||
key: 'positions_id',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
},
|
||||
}),
|
||||
SysSelects.SysRoles(),
|
||||
// {
|
||||
// key: 'password',
|
||||
// title: '密码',
|
||||
// colProps: { span: 24 },
|
||||
// valueType: 'password',
|
||||
// },
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
colProps: { span: 24 },
|
||||
valueType: 'textarea',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
120
src/pages/attendance/attendance_records/index.tsx
Normal file
120
src/pages/attendance/attendance_records/index.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import { MyColumns, MyPageContainer, MyProTableProps } from '@/common';
|
||||
import { MyExport } from '@/components/MyExport';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import {
|
||||
AttendanceRecordsCheckinTypeEnum,
|
||||
AttendanceRecordsStatusEnum,
|
||||
} from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { Image } from 'antd';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Index({ title = '打卡记录' }) {
|
||||
const [getParams, setParams] = useState({});
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={title}
|
||||
enableTabs={true}
|
||||
tabKey="attendance_records"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(
|
||||
params,
|
||||
sort,
|
||||
Apis.Attendance.AttendanceRecords.List,
|
||||
(e) => {
|
||||
setParams(e);
|
||||
},
|
||||
)
|
||||
}
|
||||
headerTitle={title}
|
||||
toolBarRender={() => [
|
||||
<MyExport
|
||||
key="export"
|
||||
item={getParams}
|
||||
download={Apis.Attendance.AttendanceRecords}
|
||||
/>,
|
||||
]}
|
||||
columns={[
|
||||
MyColumns.ID({ search: false }),
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
hidden: true,
|
||||
}),
|
||||
{
|
||||
title: '关联项目',
|
||||
dataIndex: ['asset_project', 'name'],
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '员工',
|
||||
dataIndex: ['company_employee', 'name'],
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '打卡时间',
|
||||
dataIndex: 'checkin_time',
|
||||
valueType: 'dateRange',
|
||||
hidden: true,
|
||||
},
|
||||
MyColumns.EnumTag({
|
||||
title: '打卡类型',
|
||||
dataIndex: 'checkin_type',
|
||||
valueEnum: AttendanceRecordsCheckinTypeEnum,
|
||||
}),
|
||||
MyColumns.EnumTag({
|
||||
title: '打卡结果',
|
||||
dataIndex: 'status',
|
||||
valueEnum: AttendanceRecordsStatusEnum,
|
||||
}),
|
||||
{
|
||||
title: '打卡时间',
|
||||
dataIndex: 'checkin_time',
|
||||
search: false,
|
||||
render: (_, item: any) => {
|
||||
return item?.status === 'CheckIn'
|
||||
? item?.shift_periods?.work_start_time
|
||||
: item?.shift_periods?.work_end_time;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '员工打卡时间',
|
||||
dataIndex: 'checkin_time',
|
||||
search: false,
|
||||
},
|
||||
|
||||
{
|
||||
title: '打卡拍照',
|
||||
dataIndex: 'checkin_time',
|
||||
search: false,
|
||||
render: (_, item: any) => {
|
||||
return (
|
||||
<Image.PreviewGroup
|
||||
preview={{
|
||||
onChange: (current, prev) =>
|
||||
console.log(
|
||||
`current index: ${current}, prev index: ${prev}`,
|
||||
),
|
||||
}}
|
||||
>
|
||||
{item?.photo?.map((res: any, index?: number) => {
|
||||
return (
|
||||
<Image key={`item_${index}`} height={30} src={res?.url} />
|
||||
);
|
||||
})}
|
||||
</Image.PreviewGroup>
|
||||
);
|
||||
},
|
||||
},
|
||||
// MyColumns.CreatedAt(),
|
||||
]}
|
||||
/>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
158
src/pages/attendance/attendance_schedules/index.tsx
Normal file
158
src/pages/attendance/attendance_schedules/index.tsx
Normal file
@ -0,0 +1,158 @@
|
||||
import {
|
||||
MyButtons,
|
||||
MyColumns,
|
||||
MyPageContainer,
|
||||
MyProTableProps,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { AttendanceSchedulesStatusEnum } from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { Space, Tooltip } from 'antd';
|
||||
import Update from './modals/Update';
|
||||
|
||||
export default function Index({ title = '排班管理' }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={title}
|
||||
enableTabs={true}
|
||||
tabKey="attendance_schedules"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(
|
||||
{ ...params, status: AttendanceSchedulesStatusEnum.Active.value },
|
||||
sort,
|
||||
Apis.Attendance.AttendanceSchedules.List,
|
||||
)
|
||||
}
|
||||
headerTitle="排班信息"
|
||||
toolBarRender={() => [
|
||||
<MyButtons.Default
|
||||
key="Create"
|
||||
size="middle"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
navigate('/attendance/attendance_schedules/pages/create');
|
||||
}}
|
||||
title="批量排班 / 批量调整"
|
||||
/>,
|
||||
]}
|
||||
columns={[
|
||||
// MyColumns.ID({
|
||||
// search: false,
|
||||
// }),
|
||||
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
hidden: true,
|
||||
}),
|
||||
{
|
||||
title: '排班日期',
|
||||
dataIndex: 'schedule_date',
|
||||
valueType: 'date',
|
||||
},
|
||||
{
|
||||
title: '关联项目',
|
||||
dataIndex: ['asset_project', 'name'],
|
||||
// search: {
|
||||
// transform: (value) => {
|
||||
// return { project_name: value };
|
||||
// },
|
||||
// },
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '员工',
|
||||
dataIndex: ['company_employee', 'name'],
|
||||
search: {
|
||||
transform: (value) => {
|
||||
return { employee_name: value };
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '班次',
|
||||
dataIndex: ['attendance_shift', 'name'],
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '时段要求',
|
||||
dataIndex: 'shift_periods',
|
||||
search: false,
|
||||
render: (_, item: any) => {
|
||||
const periods = item?.shift_periods || [];
|
||||
const periodTexts = periods.map((res: any) => {
|
||||
return `时段${
|
||||
res?.period_order
|
||||
}: ${res?.work_start_time?.substring(
|
||||
0,
|
||||
5,
|
||||
)}-${res?.work_end_time?.substring(0, 5)}`;
|
||||
});
|
||||
const allPeriodsText = periodTexts.join(' ');
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
periodTexts.map((text: string, index: number) => (
|
||||
<div key={index}>{text}</div>
|
||||
)) || ''
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: '150px',
|
||||
}}
|
||||
>
|
||||
{allPeriodsText}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
// MyColumns.EnumTag({
|
||||
// title: '状态',
|
||||
// dataIndex: 'status',
|
||||
// valueEnum: AttendanceSchedulesStatusEnum,
|
||||
// }),
|
||||
{
|
||||
title: '排班人',
|
||||
dataIndex: ['created_employee', 'name'],
|
||||
search: false,
|
||||
},
|
||||
// MyColumns.CreatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
<Update item={item} reload={action?.reload} title={title} />
|
||||
<MyButtons.Default
|
||||
disabled={item?.status === 'Cancelled'}
|
||||
isConfirm
|
||||
title="取消班次"
|
||||
danger
|
||||
description="是否确定取消?"
|
||||
onConfirm={() =>
|
||||
Apis.Attendance.AttendanceSchedules.Cancel({
|
||||
id: item.id,
|
||||
}).then(() => action?.reload())
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
117
src/pages/attendance/attendance_schedules/modals/Create.tsx
Normal file
117
src/pages/attendance/attendance_schedules/modals/Create.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm, EditableProTable } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [dataSource, setDataSource] = useState([]);
|
||||
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Attendance.AttendanceSchedules.BatchStore>
|
||||
{...MyModalFormProps.props}
|
||||
title={`批量${props.title}`}
|
||||
wrapperCol={{ span: 24 }}
|
||||
width="800px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
trigger={<MyButtons.Create title={`批量${props.title}`} />}
|
||||
onFinish={async (values) =>
|
||||
Apis.Attendance.AttendanceSchedules.BatchStore(values)
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success('新增成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
submitter={false}
|
||||
columns={[
|
||||
Selects?.OrganizationsTree({
|
||||
title: '选择组织',
|
||||
key: 'organizations_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
title: '排班日期',
|
||||
key: 'schedule_date',
|
||||
valueType: 'date',
|
||||
fieldProps: {
|
||||
format: 'YYYY-MM-DD',
|
||||
},
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
valueType: 'dependency',
|
||||
name: ['organizations_id', 'schedule_date'],
|
||||
colProps: { span: 24 },
|
||||
columns: ({ schedule_date, organizations_id }) => {
|
||||
return [
|
||||
{
|
||||
renderFormItem: () => {
|
||||
console.log(schedule_date, organizations_id);
|
||||
return (
|
||||
<MyButtons.Default
|
||||
type="primary"
|
||||
size="middle"
|
||||
title="获取员工列表"
|
||||
style={{ width: '100%' }}
|
||||
onClick={() => {
|
||||
// getEmployeesExternal(organizations_id || []); //获取外部员工
|
||||
Apis.Attendance.AttendanceSchedules.ShiftList({
|
||||
schedule_date: schedule_date,
|
||||
organizations_id:
|
||||
organizations_id?.[organizations_id?.length - 1],
|
||||
}).then((res) => {
|
||||
setDataSource(res?.data);
|
||||
console.log(res);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
renderFormItem: () => {
|
||||
return (
|
||||
<EditableProTable<any>
|
||||
headerTitle="可编辑表格"
|
||||
bordered
|
||||
recordCreatorProps={false}
|
||||
columns={[
|
||||
{
|
||||
title: '员工ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '员工',
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
rowKey="id"
|
||||
value={dataSource}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
98
src/pages/attendance/attendance_schedules/modals/Update.tsx
Normal file
98
src/pages/attendance/attendance_schedules/modals/Update.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Attendance.AttendanceSchedules.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={`调整班次`}
|
||||
trigger={
|
||||
<MyButtons.Default
|
||||
type="primary"
|
||||
title="调班"
|
||||
disabled={props.item?.status !== 'Active'}
|
||||
/>
|
||||
}
|
||||
key={new Date().getTime()}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
labelAlign="left"
|
||||
width="500px"
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open && props.item) {
|
||||
form.setFieldsValue(props.item);
|
||||
}
|
||||
}}
|
||||
onFinish={async (values) =>
|
||||
Apis.Attendance.AttendanceSchedules.Update({
|
||||
...values,
|
||||
id: props.item?.id ?? 0,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success('编辑成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.Employees({
|
||||
title: '员工信息',
|
||||
key: 'company_employees_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.number },
|
||||
required: true,
|
||||
fieldProps: {
|
||||
disabled: true,
|
||||
},
|
||||
}),
|
||||
|
||||
Selects?.AssetProjects({
|
||||
title: '项目信息',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.number },
|
||||
fieldProps: {
|
||||
disabled: true,
|
||||
},
|
||||
}),
|
||||
{
|
||||
title: '班次日期',
|
||||
key: 'schedule_date',
|
||||
valueType: 'date',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
fieldProps: {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
Selects?.AttendanceShiftsSelect({
|
||||
key: 'attendance_shifts_id',
|
||||
title: '选择班次',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.number },
|
||||
}),
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
304
src/pages/attendance/attendance_schedules/pages/create.tsx
Normal file
304
src/pages/attendance/attendance_schedules/pages/create.tsx
Normal file
@ -0,0 +1,304 @@
|
||||
import {
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
MyPageContainer,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import {
|
||||
BetaSchemaForm,
|
||||
EditableProTable,
|
||||
ProCard,
|
||||
} from '@ant-design/pro-components';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { Form, message, Space } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { useState } from 'react';
|
||||
let optionsData: any = [];
|
||||
export default function Index({ title = '批量排班' }) {
|
||||
const [form] = Form.useForm();
|
||||
const navigate = useNavigate();
|
||||
const [startDate, setStartDate] = useState([]);
|
||||
const [dataSource, setDataSource] = useState([]);
|
||||
|
||||
// 动态提取日期(排除 id 和 name 字段)
|
||||
const extractDates = (data: any) => {
|
||||
const firstPerson = data[0];
|
||||
return Object.keys(firstPerson).filter(
|
||||
(key) => key !== 'id' && key !== 'name' && !isNaN(Date.parse(key)),
|
||||
);
|
||||
};
|
||||
|
||||
const getOptionsData = async (params: any) => {
|
||||
if (optionsData?.length) {
|
||||
return optionsData || [];
|
||||
}
|
||||
let res: any = await Apis.Attendance.AttendanceShifts.Select({
|
||||
...params,
|
||||
name: params?.keyWords || undefined,
|
||||
});
|
||||
optionsData = [...res?.data, ...[{ id: -1, name: '取消班次' }]];
|
||||
console.log(optionsData, '1');
|
||||
return optionsData || [];
|
||||
};
|
||||
// 生成未来7天日期的函数
|
||||
const generateFutureDates = (startDate: string) => {
|
||||
const dates = [];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '员工ID',
|
||||
dataIndex: 'id',
|
||||
readonly: true,
|
||||
},
|
||||
{
|
||||
title: '员工',
|
||||
dataIndex: 'name',
|
||||
readonly: true,
|
||||
},
|
||||
];
|
||||
const start = dayjs(startDate);
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const currentDate = start.clone().add(i, 'day').format('YYYY-MM-DD');
|
||||
dates.push({
|
||||
title: currentDate,
|
||||
key: currentDate,
|
||||
dataIndex: currentDate,
|
||||
formItemProps: { ...rulesHelper.number },
|
||||
valueType: 'select',
|
||||
request: async (params: any) => {
|
||||
return getOptionsData(params);
|
||||
},
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
allowClear: false,
|
||||
// placeholder: '请选择班次',
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log(dates);
|
||||
if (startDate && dates?.length) {
|
||||
return [...columns, ...dates];
|
||||
} else {
|
||||
return columns;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={
|
||||
<Space
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
>
|
||||
{/* <LeftCircleOutlined size={34} /> */}
|
||||
{title}
|
||||
</Space>
|
||||
}
|
||||
enableTabs={false}
|
||||
tabKey="charge-standards-create"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProCard>
|
||||
<div
|
||||
style={{
|
||||
width: '1200px',
|
||||
minHeight: '83vh',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
>
|
||||
<BetaSchemaForm<ApiTypes.Contract.Contracts.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={title}
|
||||
// 基础表单
|
||||
layoutType="Form"
|
||||
labelCol={{ span: 24 }}
|
||||
wrapperCol={{ span: 24 }}
|
||||
labelAlign="left"
|
||||
width="900px"
|
||||
form={form}
|
||||
submitter={{
|
||||
render: (props) => {
|
||||
return [
|
||||
<MyButtons.Default
|
||||
key="Review"
|
||||
size="middle"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
props.submit();
|
||||
}}
|
||||
title="提交保存"
|
||||
/>,
|
||||
];
|
||||
},
|
||||
}}
|
||||
onFinish={async () => {
|
||||
const dates = extractDates(dataSource);
|
||||
const missingData: string[] = [];
|
||||
|
||||
// // 先检查所有数据是否完整
|
||||
for (const person of dataSource) {
|
||||
for (const date of dates) {
|
||||
const shiftId = person[date];
|
||||
if (shiftId === undefined || shiftId === null) {
|
||||
missingData.push(`员工 ${person?.name} 在 ${date}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有缺失数据,提示并停止执行(只提示一次)
|
||||
// if (missingData.length > 0) {
|
||||
// message.error(
|
||||
// `存在 ${missingData.length} 条未填写的排班数据,请完善后再提交`,
|
||||
// );
|
||||
// return false;
|
||||
// }
|
||||
// 数据完整,进行转换和提交
|
||||
const transformedData: any = dataSource.flatMap((person: any) => {
|
||||
console.log(person, 'person');
|
||||
|
||||
return dates.map((date) => {
|
||||
let startDateItem: any = {};
|
||||
if (person[date] === -1) {
|
||||
startDate?.flatMap((item: any) => {
|
||||
if (item?.id === person?.id) {
|
||||
startDateItem = item;
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(startDateItem, 'startDateItem');
|
||||
return {
|
||||
company_employees_id: person.id,
|
||||
attendance_shifts_id:
|
||||
person[date] === -1
|
||||
? startDateItem[date]
|
||||
: person[date] || 0,
|
||||
schedule_date: date,
|
||||
status: person[date] < 0 ? 'Cancelled' : '',
|
||||
};
|
||||
});
|
||||
});
|
||||
const filteredData = transformedData.filter(
|
||||
(item: any) =>
|
||||
item.attendance_shifts_id !== null &&
|
||||
item.attendance_shifts_id !== 0,
|
||||
);
|
||||
console.log(transformedData, startDate, 'startDate');
|
||||
|
||||
console.log(filteredData, 'filteredData');
|
||||
// return;
|
||||
Apis.Attendance.AttendanceSchedules.BatchStore({
|
||||
schedules: filteredData || [],
|
||||
})
|
||||
.then(() => {
|
||||
navigate(-1);
|
||||
message.success('提交成功');
|
||||
})
|
||||
.catch(() => false);
|
||||
console.log(transformedData);
|
||||
}}
|
||||
columns={[
|
||||
Selects?.OrganizationsTree({
|
||||
title: '第一步:选择组织',
|
||||
key: 'organizations_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
title: '第二步:选择开始日期',
|
||||
tooltip: '排班开始日期,最大排班日期为当前日期',
|
||||
key: 'schedule_date',
|
||||
valueType: 'date',
|
||||
fieldProps: {
|
||||
format: 'YYYY-MM-DD',
|
||||
style: { width: '100%' },
|
||||
},
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
valueType: 'dependency',
|
||||
name: ['organizations_id', 'schedule_date'],
|
||||
colProps: { span: 24 },
|
||||
columns: ({ schedule_date, organizations_id }) => {
|
||||
return [
|
||||
{
|
||||
renderFormItem: () => {
|
||||
console.log(schedule_date, organizations_id);
|
||||
return (
|
||||
<MyButtons.Default
|
||||
type="primary"
|
||||
size="middle"
|
||||
title="第三步:获取环卫人员信息"
|
||||
style={{ width: '100%' }}
|
||||
onClick={() => {
|
||||
// getEmployeesExternal(organizations_id || []); //获取外部员工
|
||||
Apis.Attendance.AttendanceSchedules.ShiftList({
|
||||
schedule_date: schedule_date,
|
||||
organizations_id:
|
||||
organizations_id?.[
|
||||
organizations_id?.length - 1
|
||||
],
|
||||
}).then((res) => {
|
||||
setStartDate(
|
||||
JSON.parse(JSON.stringify(res?.data)),
|
||||
);
|
||||
setDataSource(
|
||||
JSON.parse(JSON.stringify(res?.data)),
|
||||
);
|
||||
console.log(res);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
renderFormItem: () => {
|
||||
return (
|
||||
<ProCard bordered size="small">
|
||||
<EditableProTable<any>
|
||||
key={schedule_date}
|
||||
headerTitle="第四步:设置员工班次(按周)"
|
||||
bordered
|
||||
recordCreatorProps={false}
|
||||
columns={generateFutureDates(schedule_date || '')}
|
||||
rowKey="id"
|
||||
value={dataSource}
|
||||
onChange={(values) => {
|
||||
setDataSource(values);
|
||||
}}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
editableKeys: dataSource?.map(
|
||||
(item: any) => item?.id,
|
||||
),
|
||||
onValuesChange: (record, recordList: any) => {
|
||||
setDataSource(recordList);
|
||||
},
|
||||
onSave: async (rowKey, data, row) => {
|
||||
console.log(rowKey, data, row);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ProCard>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
154
src/pages/attendance/attendance_shifts/index.tsx
Normal file
154
src/pages/attendance/attendance_shifts/index.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import {
|
||||
MyButtons,
|
||||
MyColumns,
|
||||
MyPageContainer,
|
||||
MyProTableProps,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { Space, Tooltip } from 'antd';
|
||||
|
||||
export default function Index({ title = '班次管理' }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={title}
|
||||
enableTabs={true}
|
||||
tabKey="attendance_shifts"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(
|
||||
params,
|
||||
sort,
|
||||
Apis.Attendance.AttendanceShifts.List,
|
||||
)
|
||||
}
|
||||
headerTitle={title}
|
||||
toolBarRender={() => [
|
||||
<MyButtons.Default
|
||||
key="Create"
|
||||
size="middle"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
navigate('/attendance/attendance_shifts/pages/create');
|
||||
}}
|
||||
title="新增班次"
|
||||
/>,
|
||||
]}
|
||||
columns={[
|
||||
MyColumns.ID({
|
||||
search: false,
|
||||
}),
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
hidden: true,
|
||||
}),
|
||||
{
|
||||
title: '关联项目',
|
||||
dataIndex: ['asset_project', 'name'],
|
||||
// search: {
|
||||
// transform: (value) => {
|
||||
// return { project_name: value };
|
||||
// },
|
||||
// },
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '班次名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
|
||||
{
|
||||
title: '时段要求',
|
||||
dataIndex: 'attendance_shift_periods',
|
||||
search: false,
|
||||
render: (_, item: any) => {
|
||||
const periods = item?.attendance_shift_periods || [];
|
||||
const periodTexts = periods.map((res: any) => {
|
||||
return `时段${
|
||||
res?.period_order
|
||||
}: ${res?.work_start_time?.substring(
|
||||
0,
|
||||
5,
|
||||
)}-${res?.work_end_time?.substring(0, 5)}`;
|
||||
});
|
||||
const allPeriodsText = periodTexts.join(' ');
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
periodTexts.map((text: string, index: number) => (
|
||||
<div key={index}>{text}</div>
|
||||
)) || ''
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: '150px',
|
||||
}}
|
||||
>
|
||||
{allPeriodsText}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '可打卡时间范围',
|
||||
render(_, record) {
|
||||
return `${record?.allow_checkin_start} - ${record?.allow_checkin_end}`;
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
search: false,
|
||||
},
|
||||
MyColumns.Boolean({
|
||||
dataIndex: 'is_enabled',
|
||||
title: '启用',
|
||||
}),
|
||||
|
||||
// MyColumns.CreatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
<MyButtons.Default
|
||||
key="Update"
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
navigate(
|
||||
`/attendance/attendance_shifts/pages/update?id=${item.id}`,
|
||||
);
|
||||
}}
|
||||
title="编辑"
|
||||
/>
|
||||
|
||||
<MyButtons.Delete
|
||||
onConfirm={() =>
|
||||
Apis.Attendance.AttendanceShifts.Delete({
|
||||
id: item.id,
|
||||
}).then(() => action?.reload())
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
259
src/pages/attendance/attendance_shifts/pages/create.tsx
Normal file
259
src/pages/attendance/attendance_shifts/pages/create.tsx
Normal file
@ -0,0 +1,259 @@
|
||||
import {
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
MyPageContainer,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm, ProCard } from '@ant-design/pro-components';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { Form, message, Space } from 'antd';
|
||||
import { useState } from 'react';
|
||||
export default function Index({ title = '新增班次' }) {
|
||||
const [form] = Form.useForm();
|
||||
const navigate = useNavigate();
|
||||
const [dataSource, setDataSource] = useState<
|
||||
ApiTypes.Asset.AssetProjects.List[]
|
||||
>([]);
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={
|
||||
<Space
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
>
|
||||
{/* <LeftCircleOutlined size={34} /> */}
|
||||
{title}
|
||||
</Space>
|
||||
}
|
||||
enableTabs={false}
|
||||
tabKey="attendance-shifts-create"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProCard>
|
||||
<div style={{ width: 900, minHeight: '83vh', margin: '0 auto' }}>
|
||||
<BetaSchemaForm<ApiTypes.Attendance.AttendanceShifts.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={title}
|
||||
// 基础表单
|
||||
layoutType="Form"
|
||||
labelCol={{ span: 24 }}
|
||||
wrapperCol={{ span: 24 }}
|
||||
labelAlign="left"
|
||||
width="900px"
|
||||
form={form}
|
||||
onFinish={async (values: any) => {
|
||||
// 处理时间格式转换,将HH:mm转换为HH:mm:ss格式,秒数固定为00
|
||||
const formattedValues = { ...values };
|
||||
|
||||
// 处理work_start_time和work_end_time数组
|
||||
if (formattedValues.periods) {
|
||||
formattedValues.periods = formattedValues.periods.map(
|
||||
(period: any) => {
|
||||
return {
|
||||
...period,
|
||||
work_start_time: period.work_start_time
|
||||
? `${period.work_start_time}:00`
|
||||
: '',
|
||||
work_end_time: period.work_end_time
|
||||
? `${period.work_end_time}:00`
|
||||
: '',
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 处理可打卡开始和结束时间
|
||||
if (formattedValues.allow_checkin_start) {
|
||||
formattedValues.allow_checkin_start = `${formattedValues.allow_checkin_start}:00`;
|
||||
}
|
||||
if (formattedValues.allow_checkin_end) {
|
||||
formattedValues.allow_checkin_end = `${formattedValues.allow_checkin_end}:00`;
|
||||
}
|
||||
|
||||
Apis.Attendance.AttendanceShifts.Store({
|
||||
...formattedValues,
|
||||
// is_enabled: values.is_enabled ? true : false,
|
||||
is_enabled: true,
|
||||
})
|
||||
.then(() => {
|
||||
//提交
|
||||
navigate(-1);
|
||||
message.success('提交成功');
|
||||
})
|
||||
.catch(() => false);
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
title: '班次名称',
|
||||
key: 'name',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
fieldProps: {
|
||||
placeholder: '请输入(如:早班、晚班)',
|
||||
},
|
||||
},
|
||||
{
|
||||
valueType: 'formList',
|
||||
dataIndex: 'periods',
|
||||
title: '设置需打卡时段',
|
||||
formItemProps: { ...rulesHelper.array },
|
||||
fieldProps: {
|
||||
copyIconProps: false,
|
||||
// deleteIconProps: false,
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
valueType: 'group',
|
||||
colProps: { span: 24 },
|
||||
columns: [
|
||||
MyFormItems.EnumSelect({
|
||||
key: 'period_order',
|
||||
valueEnum: {
|
||||
1: '1',
|
||||
2: '2',
|
||||
3: '3',
|
||||
4: '4',
|
||||
5: '5',
|
||||
},
|
||||
required: true,
|
||||
colProps: { span: 4 },
|
||||
}),
|
||||
{
|
||||
key: 'work_start_time',
|
||||
valueType: 'time',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
placeholder: '请输入班次开始时间',
|
||||
format: 'HH:mm',
|
||||
},
|
||||
colProps: { span: 8 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
key: 'work_end_time',
|
||||
valueType: 'time',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
placeholder: '请输入班次开始时间',
|
||||
format: 'HH:mm',
|
||||
},
|
||||
colProps: { span: 8 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ valueType: 'divider' },
|
||||
{
|
||||
title: '可打卡开始时间',
|
||||
key: 'allow_checkin_start',
|
||||
valueType: 'time',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
format: 'HH:mm',
|
||||
},
|
||||
colProps: { span: 12 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
title: '可打卡结束时间',
|
||||
key: 'allow_checkin_end',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
format: 'HH:mm',
|
||||
},
|
||||
valueType: 'time',
|
||||
colProps: { span: 12 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
// {
|
||||
// title: '启用',
|
||||
// key: 'is_enabled',
|
||||
// valueType: 'switch',
|
||||
// colProps: { span: 24 },
|
||||
// },
|
||||
// {
|
||||
// colProps: { span: 24 },
|
||||
// title: '选择关联项目',
|
||||
// tooltip: '选择后对应项目的人员可排班',
|
||||
// key: 'asset_projects_id',
|
||||
// formItemProps: { ...rulesHelper.text },
|
||||
// renderFormItem: () => {
|
||||
// return (
|
||||
// <ProCard size="small" bordered>
|
||||
// <ProTable
|
||||
// {...MyProTableProps.props}
|
||||
// options={false}
|
||||
// size="small"
|
||||
// search={false}
|
||||
// pagination={false}
|
||||
// dataSource={dataSource || []}
|
||||
// headerTitle={
|
||||
// <MyAssetsProjectSelectList
|
||||
// key="select_project"
|
||||
// item={dataSource}
|
||||
// type="radio"
|
||||
// onChange={(e: any) => {
|
||||
// let row = e?.[0];
|
||||
// form.setFieldsValue({
|
||||
// asset_projects_id:
|
||||
// row?.asset_projects_id || row?.id,
|
||||
// });
|
||||
// setDataSource(e);
|
||||
// }}
|
||||
// />
|
||||
// }
|
||||
// columns={[
|
||||
// MyColumns.ID({
|
||||
// search: false,
|
||||
// }),
|
||||
// {
|
||||
// title: '名称',
|
||||
// dataIndex: 'name',
|
||||
// },
|
||||
// MyColumns.Option({
|
||||
// render: (_, item: any, index) => (
|
||||
// <Space key={index}>
|
||||
// <MyButtons.Delete
|
||||
// onConfirm={() => {
|
||||
// setDataSource(
|
||||
// dataSource.filter(
|
||||
// (res: any) => res?.id !== item?.id,
|
||||
// ),
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
// </Space>
|
||||
// ),
|
||||
// }),
|
||||
// ]}
|
||||
// />
|
||||
// </ProCard>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
|
||||
Selects?.AssetProjects({
|
||||
title: '关联项目',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ProCard>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
263
src/pages/attendance/attendance_shifts/pages/update.tsx
Normal file
263
src/pages/attendance/attendance_shifts/pages/update.tsx
Normal file
@ -0,0 +1,263 @@
|
||||
import {
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
MyPageContainer,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm, ProCard } from '@ant-design/pro-components';
|
||||
import { useNavigate, useSearchParams } from '@umijs/max';
|
||||
import { Form, message, Space } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function Index({ title = '编辑班次' }) {
|
||||
const [form] = Form.useForm();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get('id') ?? 0;
|
||||
const [data, setShow] = useState<any>({});
|
||||
const [dataSource, setDataSource] = useState<
|
||||
ApiTypes.Asset.AssetProjects.List[]
|
||||
>([]);
|
||||
|
||||
const loadShow = () => {
|
||||
Apis.Attendance.AttendanceShifts.Show({ id: Number(id) }).then((res) => {
|
||||
setShow(res?.data);
|
||||
setDataSource([res?.data?.asset_project]);
|
||||
|
||||
// 处理时间格式,将hh:mm:ss转换为hh:mm用于表单显示
|
||||
const formData = { ...res?.data };
|
||||
|
||||
// 处理可打卡开始和结束时间
|
||||
if (formData.allow_checkin_start) {
|
||||
formData.allow_checkin_start = formData.allow_checkin_start.slice(0, 5);
|
||||
}
|
||||
if (formData.allow_checkin_end) {
|
||||
formData.allow_checkin_end = formData.allow_checkin_end.slice(0, 5);
|
||||
}
|
||||
|
||||
// 处理时间段数组中的时间格式
|
||||
if (formData.attendance_shift_periods) {
|
||||
formData.periods = formData.attendance_shift_periods.map(
|
||||
(period: any) => {
|
||||
return {
|
||||
...period,
|
||||
work_start_time: period.work_start_time
|
||||
? period.work_start_time.slice(0, 5)
|
||||
: '',
|
||||
work_end_time: period.work_end_time
|
||||
? period.work_end_time.slice(0, 5)
|
||||
: '',
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
...formData,
|
||||
asset_projects_id: res?.data?.asset_project?.id,
|
||||
}); // 编辑赋值
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadShow();
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={
|
||||
<Space
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
>
|
||||
{/* <LeftCircleOutlined size={34} /> */}
|
||||
{title}
|
||||
</Space>
|
||||
}
|
||||
enableTabs={false}
|
||||
tabKey="attendance-shifts-update"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProCard>
|
||||
<div style={{ width: 900, minHeight: '83vh', margin: '0 auto' }}>
|
||||
<BetaSchemaForm<ApiTypes.Attendance.AttendanceShifts.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={title}
|
||||
// 基础表单
|
||||
layoutType="Form"
|
||||
labelCol={{ span: 24 }}
|
||||
wrapperCol={{ span: 24 }}
|
||||
labelAlign="left"
|
||||
width="900px"
|
||||
form={form}
|
||||
onFinish={async (values: any) => {
|
||||
// 处理时间格式转换,将HH:mm转换为HH:mm:ss格式,秒数固定为00
|
||||
const formattedValues = { ...values };
|
||||
|
||||
// 处理work_start_time和work_end_time数组
|
||||
if (formattedValues.periods) {
|
||||
formattedValues.periods = formattedValues.periods.map(
|
||||
(period: any) => {
|
||||
return {
|
||||
...period,
|
||||
work_start_time: period.work_start_time
|
||||
? `${period.work_start_time}:00`
|
||||
: '',
|
||||
work_end_time: period.work_end_time
|
||||
? `${period.work_end_time}:00`
|
||||
: '',
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 处理可打卡开始和结束时间
|
||||
if (formattedValues.allow_checkin_start) {
|
||||
formattedValues.allow_checkin_start = `${formattedValues.allow_checkin_start}:00`;
|
||||
}
|
||||
if (formattedValues.allow_checkin_end) {
|
||||
formattedValues.allow_checkin_end = `${formattedValues.allow_checkin_end}:00`;
|
||||
}
|
||||
|
||||
Apis.Attendance.AttendanceShifts.Update({
|
||||
...formattedValues,
|
||||
is_enabled: values.is_enabled ? true : false,
|
||||
id: data?.id,
|
||||
})
|
||||
.then(() => {
|
||||
//提交
|
||||
navigate(-1);
|
||||
message.success('编辑成功');
|
||||
})
|
||||
.catch(() => false);
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
title: '班次名称',
|
||||
key: 'name',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'is_enabled',
|
||||
valueType: 'switch',
|
||||
colProps: { span: 12 },
|
||||
fieldProps(form, config) {
|
||||
return {
|
||||
...config.fieldProps,
|
||||
style: { width: '100%' },
|
||||
checkedChildren: '启用',
|
||||
unCheckedChildren: '停用',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
valueType: 'formList',
|
||||
dataIndex: 'periods',
|
||||
title: '设置需打卡时段',
|
||||
formItemProps: { ...rulesHelper.array },
|
||||
fieldProps: {
|
||||
copyIconProps: false,
|
||||
// deleteIconProps: false,
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
valueType: 'group',
|
||||
colProps: { span: 24 },
|
||||
columns: [
|
||||
MyFormItems.EnumSelect({
|
||||
key: 'period_order',
|
||||
valueEnum: {
|
||||
1: '1',
|
||||
2: '2',
|
||||
3: '3',
|
||||
4: '4',
|
||||
5: '5',
|
||||
},
|
||||
required: true,
|
||||
colProps: { span: 4 },
|
||||
}),
|
||||
// {
|
||||
// key: 'period_order',
|
||||
// valueType: 'digit',
|
||||
// colProps: { span: 6 },
|
||||
// formItemProps: { ...rulesHelper.text },
|
||||
// fieldProps: {
|
||||
// style: { width: '100%' },
|
||||
// placeholder: '请输入班次序号',
|
||||
// },
|
||||
// },
|
||||
{
|
||||
key: 'work_start_time',
|
||||
valueType: 'time',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
placeholder: '请输入班次开始时间',
|
||||
format: 'HH:mm',
|
||||
},
|
||||
colProps: { span: 8 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
key: 'work_end_time',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
placeholder: '请输入班次开始时间',
|
||||
format: 'HH:mm',
|
||||
},
|
||||
valueType: 'time',
|
||||
colProps: { span: 8 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '可打卡开始时间',
|
||||
key: 'allow_checkin_start',
|
||||
valueType: 'time',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
format: 'HH:mm',
|
||||
},
|
||||
colProps: { span: 12 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
{
|
||||
title: '可打卡结束时间',
|
||||
key: 'allow_checkin_end',
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
format: 'HH:mm',
|
||||
},
|
||||
valueType: 'time',
|
||||
colProps: { span: 12 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
|
||||
Selects?.AssetProjects({
|
||||
title: '关联项目',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ProCard>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
289
src/pages/attendance/employee_tracks/$id.tsx
Normal file
289
src/pages/attendance/employee_tracks/$id.tsx
Normal file
@ -0,0 +1,289 @@
|
||||
import { MyPageContainer } from '@/common';
|
||||
import { getTodayDate } from '@/common/utils/day';
|
||||
import { mapKey } from '@/common/utils/mapConfig';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { LeftOutlined } from '@ant-design/icons';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { useNavigate, useParams } from '@umijs/max';
|
||||
import { Button, DatePicker, Space } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import * as L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import './style.scss';
|
||||
const dateFormat = 'YYYY/MM/DD';
|
||||
export default function Index({ title = '员工轨迹' }) {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const mapRef = useRef<HTMLDivElement>(null);
|
||||
const mapInstanceRef = useRef<L.Map | null>(null);
|
||||
// 初始化地图
|
||||
const initMap = () => {
|
||||
if (!mapRef.current) return;
|
||||
|
||||
// 创建地图实例
|
||||
const map = L.map(mapRef.current, {
|
||||
center: [30.258134, 120.19382669582967], // 默认中心点(长江三角洲)
|
||||
zoom: 10,
|
||||
maxZoom: 18,
|
||||
zoomControl: false,
|
||||
attributionControl: false,
|
||||
});
|
||||
|
||||
// 添加天地图底图层(矢量地图)
|
||||
L.tileLayer(
|
||||
`https://t{s}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${mapKey}`,
|
||||
{
|
||||
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
|
||||
attribution: '天地图',
|
||||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
},
|
||||
).addTo(map);
|
||||
|
||||
// 添加天地图文字描述层(矢量注记)
|
||||
L.tileLayer(
|
||||
`https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${mapKey}`,
|
||||
{
|
||||
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
|
||||
attribution: '天地图',
|
||||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
},
|
||||
).addTo(map);
|
||||
|
||||
mapInstanceRef.current = map;
|
||||
};
|
||||
|
||||
// 显示轨迹
|
||||
const showTrack = (data: any[]) => {
|
||||
if (!mapInstanceRef.current) return;
|
||||
|
||||
const map = mapInstanceRef.current;
|
||||
|
||||
// 首先清除所有旧的轨迹(无论是否有新数据)
|
||||
map.eachLayer((layer: L.Layer) => {
|
||||
if (layer instanceof L.Polyline || layer instanceof L.Marker) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有新的轨迹数据,直接返回
|
||||
if (!data || data.length === 0) return;
|
||||
|
||||
// 过滤有效坐标并保留原始数据
|
||||
const validPoints = data.filter(
|
||||
(point) => point.latitude && point.longitude,
|
||||
);
|
||||
|
||||
// 转换为Leaflet可识别的坐标格式
|
||||
const trackPoints: L.LatLngTuple[] = validPoints.map(
|
||||
(point) =>
|
||||
[
|
||||
parseFloat(point.latitude),
|
||||
parseFloat(point.longitude),
|
||||
] as L.LatLngTuple,
|
||||
);
|
||||
|
||||
// 如果过滤后没有有效坐标,返回
|
||||
if (trackPoints.length === 0) return;
|
||||
|
||||
// 创建轨迹线
|
||||
const trackLine = L.polyline(trackPoints, {
|
||||
color: '#f00',
|
||||
weight: 4,
|
||||
opacity: 1,
|
||||
}).addTo(map);
|
||||
|
||||
// 添加起点标记
|
||||
const startMarker = L.marker(trackPoints[0], {
|
||||
icon: L.divIcon({
|
||||
className: 'track-start-marker',
|
||||
html: "<img width='40' height='40' src='https://gc-prod-1385694945.cos.ap-shanghai.myqcloud.com/uploads/cs-test/01KH0R6TZSPBNF3X8ARGXE5R6C.png' />",
|
||||
}),
|
||||
}).addTo(map);
|
||||
|
||||
// 为起点添加弹窗
|
||||
const startPoint = validPoints[0];
|
||||
startMarker.bindPopup(`
|
||||
<div style="padding: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">起点</h4>
|
||||
<p><strong>序号:</strong> 1</p>
|
||||
<p><strong>时间:</strong> ${startPoint.track_time || '未知'}</p>
|
||||
<p><strong>坐标:</strong> ${startPoint.latitude}, ${
|
||||
startPoint.longitude
|
||||
}</p>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// 添加终点标记
|
||||
const endMarker = L.marker(trackPoints[trackPoints.length - 1], {
|
||||
icon: L.divIcon({
|
||||
className: 'track-end-marker',
|
||||
html: "<img width='40' height='40' src='https://gc-prod-1385694945.cos.ap-shanghai.myqcloud.com/uploads/cs-test/01KH0STS12KMARBH8F9SJVCN1S.png' />",
|
||||
}),
|
||||
}).addTo(map);
|
||||
|
||||
// 为终点添加弹窗
|
||||
const endPoint = validPoints[validPoints.length - 1];
|
||||
endMarker.bindPopup(`
|
||||
<div style="padding: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">终点</h4>
|
||||
<p><strong>序号:</strong> ${validPoints.length}</p>
|
||||
<p><strong>时间:</strong> ${endPoint.track_time || '未知'}</p>
|
||||
<p><strong>坐标:</strong> ${endPoint.latitude}, ${
|
||||
endPoint.longitude
|
||||
}</p>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// 为每个轨迹点添加序号标记和弹窗
|
||||
trackPoints.forEach((point, index) => {
|
||||
// 跳过起点和终点,只标记中间点
|
||||
if (index === 0 || index === trackPoints.length - 1) return;
|
||||
|
||||
const currentPoint = validPoints[index];
|
||||
const marker = L.marker(point, {
|
||||
icon: L.divIcon({
|
||||
className: 'track-point-marker',
|
||||
html: `<div style="width: 24px; height: 24px; border-radius: 50%; background-color: #3498db; color: white; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">${
|
||||
index + 1
|
||||
}</div>`,
|
||||
iconSize: [24, 24],
|
||||
}),
|
||||
}).addTo(map);
|
||||
|
||||
// 为中间点添加弹窗
|
||||
marker.bindPopup(`
|
||||
<div style="padding: 10px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">点位 ${index + 1}</h4>
|
||||
<p><strong>时间:</strong> ${currentPoint.track_time || '未知'}</p>
|
||||
<p><strong>坐标:</strong> ${currentPoint.latitude}, ${
|
||||
currentPoint.longitude
|
||||
}</p>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
// 自动调整地图视图以显示整个轨迹
|
||||
map.fitBounds(trackLine.getBounds());
|
||||
};
|
||||
|
||||
const loadShow = (day: any) => {
|
||||
// showTrack([
|
||||
// { latitude: 22.566729, longitude: 114.062952 },
|
||||
// { latitude: 22.567982, longitude: 114.06278 },
|
||||
// { latitude: 22.569394, longitude: 114.062694 },
|
||||
// ]);
|
||||
Apis.Attendance.AttendanceEmployeeTracks.Heatmap({
|
||||
company_employees_id: Number(id),
|
||||
date: day, // 转换为Date类型
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
// 假设API返回的数据结构为{ data: [{ latitude, longitude, time }, ...] }
|
||||
showTrack(res?.data);
|
||||
// setTrackData(res.data || []);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
// 初始化地图
|
||||
initMap();
|
||||
// 加载轨迹数据
|
||||
loadShow(new Date(getTodayDate()));
|
||||
|
||||
// 组件卸载时清理地图实例
|
||||
return () => {
|
||||
if (mapInstanceRef.current) {
|
||||
mapInstanceRef.current.remove();
|
||||
mapInstanceRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
// // 当轨迹数据变化时,显示轨迹
|
||||
// useEffect(() => {
|
||||
// if (trackData.length > 0) {
|
||||
// showTrack(trackData);
|
||||
// }
|
||||
// }, [trackData]);
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
enableTabs
|
||||
tabKey="attendance-employee-tracks"
|
||||
tabLabel={title}
|
||||
title={title}
|
||||
>
|
||||
<ProCard
|
||||
title={
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<LeftOutlined />}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
员工轨迹底图
|
||||
</Button>
|
||||
}
|
||||
headerBordered
|
||||
extra={
|
||||
<DatePicker
|
||||
defaultValue={dayjs(getTodayDate(), dateFormat)}
|
||||
format={dateFormat}
|
||||
onChange={(e: any, dateString: any) => {
|
||||
loadShow(dateString);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div
|
||||
ref={mapRef}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'calc(100vh - 200px)',
|
||||
borderRadius: '8px',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid #e8e8e8',
|
||||
}}
|
||||
/>
|
||||
<Space
|
||||
style={{
|
||||
paddingTop: '15px',
|
||||
fontSize: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span>起点:</span>
|
||||
<img
|
||||
src="https://gc-prod-1385694945.cos.ap-shanghai.myqcloud.com/uploads/cs-test/01KH0R6TZSPBNF3X8ARGXE5R6C.png"
|
||||
width="18"
|
||||
height="18"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span>终点:</span>
|
||||
<img
|
||||
src="https://gc-prod-1385694945.cos.ap-shanghai.myqcloud.com/uploads/cs-test/01KH0STS12KMARBH8F9SJVCN1S.png"
|
||||
width="18"
|
||||
height="18"
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</ProCard>
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
26
src/pages/attendance/employee_tracks/style.scss
Normal file
26
src/pages/attendance/employee_tracks/style.scss
Normal file
@ -0,0 +1,26 @@
|
||||
.track-start-marker {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 100px;
|
||||
font-size: 0.6em;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.track-end-marker {
|
||||
background-color: #f00;
|
||||
color: #fff;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 100px;
|
||||
font-size: 0.6em;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@ -130,8 +130,14 @@ export default function Index({ title = '员工管理' }) {
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '系统角色',
|
||||
dataIndex: 'roles',
|
||||
title: '员工端角色',
|
||||
dataIndex: 'employee_roles',
|
||||
renderText: renderTextHelper.TagList,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '系统后台角色',
|
||||
dataIndex: 'company_roles',
|
||||
renderText: renderTextHelper.TagList,
|
||||
hideInSearch: true,
|
||||
},
|
||||
|
||||
@ -90,7 +90,14 @@ export default function Create(props: MyBetaModalFormProps) {
|
||||
placeholder: '请输入关键字搜索',
|
||||
},
|
||||
}),
|
||||
SysSelects.SysRoles(),
|
||||
SysSelects.SysEmployeeRoles({
|
||||
title: '员工角色',
|
||||
required: false,
|
||||
}),
|
||||
SysSelects.SysRoles({
|
||||
title: '系统角色',
|
||||
required: false,
|
||||
}),
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
} from '@/common';
|
||||
|
||||
import { Selects } from '@/components/Select';
|
||||
import { SysSelects } from '@/components/SysSelects';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { SexEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
@ -29,7 +30,10 @@ export default function Update(props: MyBetaModalFormProps) {
|
||||
if (open && props.item) {
|
||||
form.setFieldsValue({
|
||||
...props.item,
|
||||
roles_id: props.item?.roles?.map((item: any) => item.value),
|
||||
roles_id: props.item?.company_roles?.map((item: any) => item.value),
|
||||
employee_roles_id: props.item?.employee_roles?.map(
|
||||
(item: any) => item.value,
|
||||
),
|
||||
positions_id: props.item?.positions_id ?? '',
|
||||
});
|
||||
}
|
||||
@ -92,7 +96,36 @@ export default function Update(props: MyBetaModalFormProps) {
|
||||
placeholder: '请输入关键字搜索',
|
||||
},
|
||||
}),
|
||||
|
||||
SysSelects.SysEmployeeRoles({
|
||||
title: '员工角色',
|
||||
required: false,
|
||||
fieldProps: {
|
||||
labelRender: (res: any) => {
|
||||
if (res?.label) {
|
||||
return res?.label;
|
||||
} else {
|
||||
return props.item?.employee_roles?.map(
|
||||
(item: any) => item.label,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
SysSelects.SysRoles({
|
||||
title: '系统角色',
|
||||
required: false,
|
||||
fieldProps: {
|
||||
labelRender: (res: any) => {
|
||||
if (res?.label) {
|
||||
return res?.label;
|
||||
} else {
|
||||
return props.item?.company_roles?.map(
|
||||
(item: any) => item.label,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
// {
|
||||
// key: 'password',
|
||||
// title: '密码',
|
||||
|
||||
53
src/pages/patrol/index.tsx
Normal file
53
src/pages/patrol/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { MyPageContainer } from '@/common';
|
||||
import MyPatrolWork from '@/pages/work_order/patrol_work';
|
||||
import { useSearchParams } from '@umijs/max';
|
||||
import { Tabs } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import MyPatrolLocations from './patrol_locations';
|
||||
import MyPatrolRoutes from './patrol_routes';
|
||||
import MyPatrolTasks from './patrol_tasks';
|
||||
|
||||
export default function Index({ title = '安防巡更' }) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [activeKey, setActiveKey] = useState('MyPatrolTasks');
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: '1',
|
||||
label: '巡更任务',
|
||||
children: <MyPatrolTasks />,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '路线配置',
|
||||
children: <MyPatrolRoutes />,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: '点位配置',
|
||||
children: <MyPatrolLocations />,
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: '巡更工单',
|
||||
children: <MyPatrolWork />,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams?.get('key')) {
|
||||
setActiveKey(searchParams?.get('key') || '1');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
title={title}
|
||||
enableTabs={true}
|
||||
tabKey="patrol_page"
|
||||
tabLabel={title}
|
||||
>
|
||||
<Tabs type="card" defaultActiveKey={activeKey} items={items} />
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
244
src/pages/patrol/patrol_locations/index.tsx
Normal file
244
src/pages/patrol/patrol_locations/index.tsx
Normal file
@ -0,0 +1,244 @@
|
||||
import { MyButtons, MyColumns, MyProTableProps } from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { message, Progress, Space } from 'antd';
|
||||
import { saveAs } from 'file-saver';
|
||||
import JSZip from 'jszip';
|
||||
import { useState } from 'react';
|
||||
import Create from './modals/Create';
|
||||
import Update from './modals/Update';
|
||||
interface DataType {
|
||||
key?: React.Key;
|
||||
id?: React.Key;
|
||||
}
|
||||
export default function Index({ title = '点位配置' }) {
|
||||
const [count, setCount] = useState(0);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [getSelectedRow, setSelectedRow] = useState<any>([]);
|
||||
const rowSelection: any = {
|
||||
selectedRowKeys,
|
||||
onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
|
||||
console.log(selectedRows, 'selectedRows[0]');
|
||||
if (selectedRowKeys.length > 10) {
|
||||
message.warning('最多批量下载10个');
|
||||
return;
|
||||
}
|
||||
setSelectedRowKeys(selectedRowKeys);
|
||||
setSelectedRow(selectedRows);
|
||||
},
|
||||
getCheckboxProps: (record: any) => ({
|
||||
disabled: !record.is_enabled,
|
||||
}),
|
||||
onSelectAll: (selected: boolean, selectedRows: DataType[]) => {
|
||||
if (selected && selectedRows.length > 10) {
|
||||
message.warning('全选数量超过10个!');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
defaultSelectedRowKeys: !count
|
||||
? []
|
||||
: getSelectedRow?.map((item: any) => item.id),
|
||||
};
|
||||
|
||||
// 模拟获取二维码接口
|
||||
const getQRCode = async (id?: string) => {
|
||||
let res = await Apis.Patrol.PatrolLocations.PatrolLocationQrCode({
|
||||
id: Number(id),
|
||||
});
|
||||
return res?.data?.qr_code;
|
||||
};
|
||||
|
||||
// 生成带海报的二维码
|
||||
const generatePosterQR = async (base64: string, res: any) => {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx: any = canvas.getContext('2d');
|
||||
|
||||
canvas.width = 500;
|
||||
canvas.height = 800;
|
||||
|
||||
// 海报样式
|
||||
ctx.fillStyle = '#1890ff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = 'bold 36px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(`${res?.name}`, canvas.width / 2, 130);
|
||||
|
||||
const qrImage = new Image();
|
||||
qrImage.onload = () => {
|
||||
const qrSize = 300;
|
||||
const qrX = (canvas.width - qrSize) / 2;
|
||||
const qrY = 210;
|
||||
ctx.drawImage(qrImage, qrX, qrY, qrSize, qrSize);
|
||||
|
||||
ctx.font = '20px Arial';
|
||||
ctx.fillText('扫描二维码完成签到', canvas.width / 2, 570);
|
||||
ctx.font = '16px Arial';
|
||||
ctx.fillText(res?.code, canvas.width / 2, 605);
|
||||
|
||||
resolve(canvas.toDataURL('image/png'));
|
||||
};
|
||||
qrImage.src = base64;
|
||||
});
|
||||
};
|
||||
|
||||
const download = async (type: number) => {
|
||||
if (!getSelectedRow?.length) {
|
||||
message.error('请选择巡逻位置!');
|
||||
return;
|
||||
}
|
||||
setDownloading(true);
|
||||
setDownloadProgress(0);
|
||||
try {
|
||||
const zip = new JSZip();
|
||||
const total = getSelectedRow.length;
|
||||
for (let i = 0; i < total; i++) {
|
||||
let res = getSelectedRow?.[i];
|
||||
if (res?.id) {
|
||||
await new Promise((resolve: any) => {
|
||||
setTimeout(resolve, 1000);
|
||||
});
|
||||
const qrCode = await getQRCode(res?.id);
|
||||
if (type === 1) {
|
||||
const base64Data = qrCode.split(',')[1];
|
||||
zip.file(`${res?.name}_${res?.code}.png`, base64Data, {
|
||||
base64: true,
|
||||
});
|
||||
}
|
||||
if (type === 2) {
|
||||
// 带海报的二维码
|
||||
const posterData: any = await generatePosterQR(qrCode, res);
|
||||
const fileData = posterData.split(',')[1];
|
||||
zip.file(`${res?.name}_${res?.code}.png`, fileData, {
|
||||
base64: true,
|
||||
});
|
||||
}
|
||||
setCount(i + 1);
|
||||
const progress = Math.round(((i + 1) / total) * 100);
|
||||
setDownloadProgress(progress);
|
||||
// 添加延迟
|
||||
}
|
||||
}
|
||||
const content = await zip.generateAsync({ type: 'blob' });
|
||||
saveAs(content, `扫码签到二维码.zip`);
|
||||
setTimeout(() => {
|
||||
setCount(0);
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error);
|
||||
message.error('下载失败,请重试');
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
setDownloadProgress(0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
loading={{
|
||||
spinning: downloading,
|
||||
tip: `正在生成二维码... ${downloadProgress}%`,
|
||||
indicator: (
|
||||
<Progress type="circle" percent={downloadProgress} size={20} />
|
||||
),
|
||||
}}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(params, sort, Apis.Patrol.PatrolLocations.List)
|
||||
}
|
||||
rowSelection={{ type: 'checkbox', ...rowSelection }}
|
||||
toolBarRender={(action) => [
|
||||
<MyButtons.Default
|
||||
key="download_qrcode"
|
||||
size="middle"
|
||||
title={`下载纯二维码${
|
||||
getSelectedRow?.length ? `${count}/${getSelectedRow?.length}` : ''
|
||||
}`}
|
||||
onClick={() => download(1)}
|
||||
/>,
|
||||
<MyButtons.Default
|
||||
key="download_qrcode"
|
||||
size="middle"
|
||||
title={`下载海报二维码${
|
||||
getSelectedRow?.length ? `${count}/${getSelectedRow?.length}` : ''
|
||||
}`}
|
||||
onClick={() => download(2)}
|
||||
/>,
|
||||
<Create key="Create" reload={action?.reload} title={title} />,
|
||||
]}
|
||||
columns={[
|
||||
MyColumns.ID({
|
||||
search: false,
|
||||
}),
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
hidden: true,
|
||||
}),
|
||||
{
|
||||
title: '关联项目',
|
||||
dataIndex: ['asset_project', 'name'],
|
||||
search: {
|
||||
transform: (value) => {
|
||||
return { project_name: value };
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '点位名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '位置编码',
|
||||
dataIndex: 'code',
|
||||
},
|
||||
MyColumns.Boolean({
|
||||
dataIndex: 'is_enabled',
|
||||
title: '启用/禁用',
|
||||
search: false,
|
||||
}),
|
||||
MyColumns.CreatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
<Update item={item} reload={action?.reload} title={title} />
|
||||
<MyButtons.Default
|
||||
title={item.is_enabled === 1 ? '禁用' : '启用'}
|
||||
type={item.is_enabled === 1 ? 'default' : 'primary'}
|
||||
danger={item.is_enabled === 1}
|
||||
isConfirm={true}
|
||||
description={
|
||||
item.is_enabled === 1
|
||||
? `确定要禁用点位吗?`
|
||||
: `确定要启用点位吗?`
|
||||
}
|
||||
onConfirm={() =>
|
||||
Apis.Patrol.PatrolLocations.Update({
|
||||
id: item.id ?? 0,
|
||||
is_enabled: item.is_enabled === 1 ? 0 : 1,
|
||||
name: item.name,
|
||||
code: item.code,
|
||||
asset_projects_id: item.asset_projects_id,
|
||||
}).then(() => action?.reload())
|
||||
}
|
||||
/>
|
||||
|
||||
<MyButtons.Delete
|
||||
onConfirm={() =>
|
||||
Apis.Patrol.PatrolLocations.Delete({ id: item.id }).then(() =>
|
||||
action?.reload(),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
80
src/pages/patrol/patrol_locations/modals/Create.tsx
Normal file
80
src/pages/patrol/patrol_locations/modals/Create.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Patrol.PatrolLocations.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={`添加${props.title}`}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
labelAlign="left"
|
||||
width="600px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
trigger={<MyButtons.Create title={`添加${props.title}`} />}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Patrol.PatrolLocations.Store({
|
||||
...values,
|
||||
longitude: values?.location ? values.location.split(',')[0] : '',
|
||||
latitude: values?.location ? values.location.split(',')[1] : '',
|
||||
is_enabled: values.is_enabled ? 1 : 0,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.AssetProjects({
|
||||
title: '关联项目',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
key: 'name',
|
||||
title: '点位名称',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
placeholder: '请输入点位名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '位置信息',
|
||||
key: 'remark',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
placeholder: '请输入点位的具体位置信息',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '是否启用',
|
||||
key: 'is_enabled',
|
||||
valueType: 'switch',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
86
src/pages/patrol/patrol_locations/modals/Update.tsx
Normal file
86
src/pages/patrol/patrol_locations/modals/Update.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Patrol.PatrolLocations.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={`编辑${props.title}`}
|
||||
trigger={<MyButtons.Edit />}
|
||||
key={new Date().getTime()}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
labelAlign="left"
|
||||
width="600px"
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open && props.item) {
|
||||
form.setFieldsValue({
|
||||
...props.item,
|
||||
location: `${props.item?.longitude || ''},${
|
||||
props.item?.latitude || ''
|
||||
}`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Patrol.PatrolLocations.Update({
|
||||
...values,
|
||||
longitude: values?.location ? values.location.split(',')[0] : '',
|
||||
latitude: values?.location ? values.location.split(',')[1] : '',
|
||||
is_enabled: values.is_enabled ? 1 : 0,
|
||||
id: props.item?.id ?? 0,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '编辑成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
key: 'name',
|
||||
title: '点位名称',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
placeholder: '请输入点位名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '位置信息',
|
||||
key: 'remark',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
placeholder: '请输入点位的具体位置信息',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '是否启用',
|
||||
key: 'is_enabled',
|
||||
valueType: 'switch',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
163
src/pages/patrol/patrol_routes/index.tsx
Normal file
163
src/pages/patrol/patrol_routes/index.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import { MyButtons, MyColumns, MyProTableProps } from '@/common';
|
||||
import { showTime } from '@/common/utils/day';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { PatrolRoutesGenerationMethodEnum } from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { Space, Tooltip } from 'antd';
|
||||
import Create from './modals/Create';
|
||||
import Update from './modals/Update';
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(params, sort, Apis.Patrol.PatrolRoutes.List)
|
||||
}
|
||||
toolBarRender={(action) => [
|
||||
<Create key="Create" reload={action?.reload} title="路线" />,
|
||||
]}
|
||||
columns={[
|
||||
MyColumns.ID({
|
||||
search: false,
|
||||
}),
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
hidden: true,
|
||||
}),
|
||||
{
|
||||
title: '关联项目',
|
||||
dataIndex: ['asset_project', 'name'],
|
||||
// search: {
|
||||
// transform: (value) => {
|
||||
// return { project_name: value };
|
||||
// },
|
||||
// },
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '路线名称',
|
||||
dataIndex: 'name',
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
title: '点位数',
|
||||
render: (_, item: any) => {
|
||||
const locations = item?.patrol_route_locations || [];
|
||||
const locationNames = locations
|
||||
.map((res: any) => {
|
||||
return res?.patrol_location?.name || '';
|
||||
})
|
||||
.filter(Boolean);
|
||||
const pointCount = locationNames.length;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
locationNames.map((name: string, index: number) => (
|
||||
<div key={index}>
|
||||
点位{index + 1}:{name}
|
||||
</div>
|
||||
)) || ''
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: '150px',
|
||||
}}
|
||||
>
|
||||
{pointCount}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
MyColumns.EnumTag({
|
||||
title: '巡更周期',
|
||||
dataIndex: 'generation_method',
|
||||
valueEnum: PatrolRoutesGenerationMethodEnum,
|
||||
}),
|
||||
{
|
||||
title: '周期',
|
||||
search: false,
|
||||
|
||||
render: (_, item: any) => {
|
||||
if (
|
||||
item?.generation_method ===
|
||||
PatrolRoutesGenerationMethodEnum.Daily.value
|
||||
) {
|
||||
return '每天';
|
||||
} else if (
|
||||
item?.generation_method ===
|
||||
PatrolRoutesGenerationMethodEnum.Weekly.value
|
||||
) {
|
||||
const dateArray = item?.date || [];
|
||||
if (dateArray.length === 0) return '每周';
|
||||
// 检查dateArray中的元素是否为对象,若是则提取weekday属性
|
||||
const weekdays = dateArray
|
||||
.map((res: any) => {
|
||||
if (typeof res === 'object' && res !== null) {
|
||||
return res?.weekday || '';
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.filter(Boolean);
|
||||
return `每周${weekdays.join('/')}`;
|
||||
} else if (
|
||||
item?.generation_method ===
|
||||
PatrolRoutesGenerationMethodEnum.Monthly.value
|
||||
) {
|
||||
const dateArray = item?.date || [];
|
||||
if (dateArray.length === 0) return '每月';
|
||||
return `每月${dateArray.join('/')}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '任务时段',
|
||||
search: false,
|
||||
render: (_, item: any) => {
|
||||
return `${showTime(item.task_start_time)}-${showTime(
|
||||
item.task_end_time,
|
||||
)}`;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '可执行时段',
|
||||
// search: false,
|
||||
// render: (_, item: any) => {
|
||||
// return `${showTime(item.clock_start_time)}-${showTime(
|
||||
// item.clock_end_time,
|
||||
// )}`;
|
||||
// },
|
||||
// },
|
||||
MyColumns.Boolean({
|
||||
dataIndex: 'is_enabled',
|
||||
title: '是否启用',
|
||||
}),
|
||||
MyColumns.CreatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
<Update item={item} reload={action?.reload} title="路线" />
|
||||
<MyButtons.Delete
|
||||
onConfirm={() =>
|
||||
Apis.Patrol.PatrolRoutes.Delete({ id: item.id }).then(() =>
|
||||
action?.reload(),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
324
src/pages/patrol/patrol_routes/modals/Create.tsx
Normal file
324
src/pages/patrol/patrol_routes/modals/Create.tsx
Normal file
@ -0,0 +1,324 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { PatrolRoutesGenerationMethodEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
const actionRef = useRef<any>();
|
||||
|
||||
// 使用useEffect添加样式,确保组件挂载时样式正确应用
|
||||
useEffect(() => {
|
||||
const styleId = 'custom-checkbox-styles';
|
||||
let styleElement = document.getElementById(styleId) as HTMLStyleElement;
|
||||
|
||||
if (!styleElement) {
|
||||
styleElement = document.createElement('style');
|
||||
styleElement.id = styleId;
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
|
||||
// 添加checkbox样式,确保每个选项宽度一致且label和checkbox在同一行,一行显示7个
|
||||
styleElement.textContent = `
|
||||
/* 为checkbox组的容器设置样式 */
|
||||
.custom-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 为每个checkbox选项设置固定宽度,使一行能显示7个,并确保在同一行 */
|
||||
.custom-checkbox-group .ant-checkbox-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: calc(14.2857% - 4px); /* 一行7个,减去间距 */
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保label和checkbox在同一行 */
|
||||
.custom-checkbox-group .ant-checkbox + span {
|
||||
display: inline;
|
||||
width: auto;
|
||||
margin-left: 4px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 确保整个选项作为一个整体居中 */
|
||||
.custom-checkbox-group .ant-checkbox-wrapper-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 调整checkbox本身的大小 */
|
||||
.custom-checkbox-group .ant-checkbox {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
`;
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (styleElement && styleElement.parentNode) {
|
||||
styleElement.parentNode.removeChild(styleElement);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Patrol.PatrolLocations.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={`添加${props.title}`}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 20 }}
|
||||
labelAlign="left"
|
||||
width="720px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
trigger={<MyButtons.Create title={`添加${props.title}`} />}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Patrol.PatrolRoutes.Store({
|
||||
...values,
|
||||
is_enabled: 1,
|
||||
patrol_location: values.patrol_location?.map(
|
||||
(item: any) => item.location_id,
|
||||
),
|
||||
clock_start_time: values.task_start_time,
|
||||
clock_end_time: values.task_end_time,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
title: '路线名称',
|
||||
key: 'name',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
valueType: 'dependency',
|
||||
name: ['asset_projects_id'],
|
||||
columns: ({ asset_projects_id }) => {
|
||||
return asset_projects_id
|
||||
? [
|
||||
{
|
||||
valueType: 'formList',
|
||||
dataIndex: 'patrol_location',
|
||||
title: '选择点位',
|
||||
formItemProps: { ...rulesHelper.array },
|
||||
fieldProps: {
|
||||
actionRef: actionRef,
|
||||
copyIconProps: false,
|
||||
addButtonProps: {
|
||||
children: '添加点位',
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
valueType: 'group',
|
||||
colProps: { span: 24 },
|
||||
columns: [
|
||||
Selects?.PatrolLocationsSelect({
|
||||
title: '',
|
||||
key: 'location_id',
|
||||
tooltip: '点位的巡更顺序未为【自上而下】',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
params: {
|
||||
asset_projects_id: asset_projects_id,
|
||||
},
|
||||
fieldProps: {
|
||||
mode: 'single',
|
||||
placeholder:
|
||||
'请选择巡逻点位(如空白,请在“点位配置”中添加)',
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'task_start_time',
|
||||
title: '任务时段',
|
||||
colProps: { span: 12 },
|
||||
valueType: 'time',
|
||||
required: true,
|
||||
formItemProps: {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
...rulesHelper.text,
|
||||
},
|
||||
fieldProps: {
|
||||
placeholder: '请设置任务开始时间',
|
||||
format: 'HH:mm',
|
||||
style: { width: '100%' },
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'task_end_time',
|
||||
valueType: 'time',
|
||||
colProps: { span: 12 },
|
||||
formItemProps: {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
...rulesHelper.text,
|
||||
},
|
||||
fieldProps: {
|
||||
placeholder: '请设置任务结束时间',
|
||||
format: 'HH:mm',
|
||||
style: { width: '100%' },
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '执行时段',
|
||||
// tooltip:
|
||||
// '任务的具体执行中,不能早于可执行开始时间,不能晚于可执行结束时间',
|
||||
// key: 'clock_start_time',
|
||||
// colProps: { span: 12 },
|
||||
// valueType: 'time',
|
||||
// required: true,
|
||||
// formItemProps: {
|
||||
// labelCol: { span: 8 },
|
||||
// wrapperCol: { span: 16 },
|
||||
// ...rulesHelper.text,
|
||||
// },
|
||||
// fieldProps: {
|
||||
// placeholder: '请设置任务可执行开始时间',
|
||||
// format: 'HH:mm',
|
||||
// style: { width: '100%' },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// key: 'clock_end_time',
|
||||
// valueType: 'time',
|
||||
// colProps: { span: 12 },
|
||||
// formItemProps: {
|
||||
// labelCol: { span: 8 },
|
||||
// wrapperCol: { span: 16 },
|
||||
// ...rulesHelper.text,
|
||||
// },
|
||||
// fieldProps: {
|
||||
// placeholder: '请设置任务可执行结束时间',
|
||||
// format: 'HH:mm',
|
||||
// style: { width: '100%' },
|
||||
// },
|
||||
// },
|
||||
MyFormItems.EnumRadio({
|
||||
key: 'generation_method',
|
||||
title: '巡更周期',
|
||||
tooltip: '每天巡更一次/每周x巡更一次/每月x号巡更一次',
|
||||
valueEnum: PatrolRoutesGenerationMethodEnum,
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
fieldProps: {
|
||||
onChange: () => {
|
||||
// 切换巡更周期时清空生成时段
|
||||
form.setFieldValue('date', undefined);
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
valueType: 'dependency',
|
||||
name: ['generation_method'],
|
||||
columns: ({ generation_method }) => {
|
||||
return generation_method === 'Weekly' ||
|
||||
generation_method === 'Monthly'
|
||||
? [
|
||||
{
|
||||
title: '生成时段',
|
||||
key: 'date',
|
||||
valueType: 'checkbox',
|
||||
formItemProps: { ...rulesHelper.array },
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
// 使用CSS方式设置每个checkbox选项的宽度
|
||||
className: 'custom-checkbox-group',
|
||||
},
|
||||
valueEnum: () => {
|
||||
if (generation_method === 'Weekly') {
|
||||
const weekDays = [
|
||||
{ label: '周一', value: 1 },
|
||||
{ label: '周二', value: 2 },
|
||||
{ label: '周三', value: 3 },
|
||||
{ label: '周四', value: 4 },
|
||||
{ label: '周五', value: 5 },
|
||||
{ label: '周六', value: 6 },
|
||||
{ label: '周日', value: 7 },
|
||||
];
|
||||
return weekDays.reduce((acc: any, day) => {
|
||||
acc[day.value] = { text: day.label };
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
if (generation_method === 'Monthly') {
|
||||
const monthDays = Array.from(
|
||||
{ length: 31 },
|
||||
(_, index) => {
|
||||
const day = index + 1;
|
||||
return {
|
||||
label: `${day < 10 ? `0${day}` : day}号`,
|
||||
value: day,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return monthDays.reduce((acc: any, day) => {
|
||||
acc[day.value] = { text: day.label };
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
338
src/pages/patrol/patrol_routes/modals/Update.tsx
Normal file
338
src/pages/patrol/patrol_routes/modals/Update.tsx
Normal file
@ -0,0 +1,338 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { PatrolRoutesGenerationMethodEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export default function Update(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
const actionRef = useRef<any>();
|
||||
|
||||
const loadShow = () => {
|
||||
Apis.Patrol.PatrolRoutes.Show({ id: props.item?.id ?? 0 }).then((res) => {
|
||||
form.setFieldsValue({
|
||||
...res?.data,
|
||||
patrol_location: res?.data?.patrol_route_locations?.map(
|
||||
(item: any) => ({
|
||||
location_id: item.patrol_locations_id,
|
||||
}),
|
||||
),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 使用useEffect添加样式,确保组件挂载时样式正确应用
|
||||
useEffect(() => {
|
||||
const styleId = 'custom-checkbox-styles';
|
||||
let styleElement = document.getElementById(styleId) as HTMLStyleElement;
|
||||
|
||||
if (!styleElement) {
|
||||
styleElement = document.createElement('style');
|
||||
styleElement.id = styleId;
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
|
||||
// 添加checkbox样式,确保每个选项宽度一致且label和checkbox在同一行,一行显示7个
|
||||
styleElement.textContent = `
|
||||
/* 为checkbox组的容器设置样式 */
|
||||
.custom-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 为每个checkbox选项设置固定宽度,使一行能显示7个,并确保在同一行 */
|
||||
.custom-checkbox-group .ant-checkbox-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: calc(14.2857% - 4px); /* 一行7个,减去间距 */
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保label和checkbox在同一行 */
|
||||
.custom-checkbox-group .ant-checkbox + span {
|
||||
display: inline;
|
||||
width: auto;
|
||||
margin-left: 4px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 确保整个选项作为一个整体居中 */
|
||||
.custom-checkbox-group .ant-checkbox-wrapper-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 调整checkbox本身的大小 */
|
||||
.custom-checkbox-group .ant-checkbox {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
`;
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (styleElement && styleElement.parentNode) {
|
||||
styleElement.parentNode.removeChild(styleElement);
|
||||
}
|
||||
};
|
||||
}, [props.item?.id]);
|
||||
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Patrol.PatrolLocations.Update>
|
||||
{...MyModalFormProps.props}
|
||||
title={`编辑${props.title}`}
|
||||
trigger={<MyButtons.Edit />}
|
||||
key={new Date().getTime()}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 20 }}
|
||||
labelAlign="left"
|
||||
width="720px"
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open && props.item) {
|
||||
props.item?.id ? loadShow() : null;
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) =>
|
||||
Apis.Patrol.PatrolRoutes.Update({
|
||||
...values,
|
||||
patrol_location: values.patrol_location?.map(
|
||||
(item: any) => item.location_id,
|
||||
),
|
||||
is_enabled: values.is_enabled ? 1 : 0,
|
||||
id: props.item?.id ?? 0,
|
||||
clock_start_time: values.task_start_time,
|
||||
clock_end_time: values.task_end_time,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '编辑成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
title: '路线名称',
|
||||
key: 'name',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
valueType: 'dependency',
|
||||
name: ['asset_projects_id'],
|
||||
columns: ({ asset_projects_id }) => {
|
||||
return asset_projects_id
|
||||
? [
|
||||
{
|
||||
valueType: 'formList',
|
||||
dataIndex: 'patrol_location',
|
||||
title: '选择点位',
|
||||
formItemProps: { ...rulesHelper.array },
|
||||
fieldProps: {
|
||||
actionRef: actionRef,
|
||||
copyIconProps: false,
|
||||
addButtonProps: {
|
||||
children: '添加点位',
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
valueType: 'group',
|
||||
colProps: { span: 24 },
|
||||
columns: [
|
||||
Selects?.PatrolLocationsSelect({
|
||||
title: '',
|
||||
key: 'location_id',
|
||||
colProps: { span: 24 },
|
||||
tooltip: '点位的巡更顺序未为【自上而下】',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
params: {
|
||||
asset_projects_id: asset_projects_id,
|
||||
},
|
||||
fieldProps: {
|
||||
mode: 'single',
|
||||
placeholder:
|
||||
'请选择巡逻点位(如空白,请在“点位配置”中添加)',
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'task_start_time',
|
||||
title: '任务时段',
|
||||
colProps: { span: 12 },
|
||||
valueType: 'time',
|
||||
required: true,
|
||||
formItemProps: {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
...rulesHelper.text,
|
||||
},
|
||||
fieldProps: {
|
||||
placeholder: '请设置任务开始时间',
|
||||
format: 'HH:mm',
|
||||
style: { width: '100%' },
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'task_end_time',
|
||||
valueType: 'time',
|
||||
colProps: { span: 12 },
|
||||
formItemProps: {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
...rulesHelper.text,
|
||||
},
|
||||
fieldProps: {
|
||||
placeholder: '请设置任务结束时间',
|
||||
format: 'HH:mm',
|
||||
style: { width: '100%' },
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '执行时段',
|
||||
// tooltip:
|
||||
// '任务的具体执行中,不能早于可执行开始时间,不能晚于可执行结束时间',
|
||||
// key: 'clock_start_time',
|
||||
// colProps: { span: 12 },
|
||||
// valueType: 'time',
|
||||
// required: true,
|
||||
// formItemProps: {
|
||||
// labelCol: { span: 8 },
|
||||
// wrapperCol: { span: 16 },
|
||||
// ...rulesHelper.text,
|
||||
// },
|
||||
// fieldProps: {
|
||||
// placeholder: '请设置任务可执行开始时间',
|
||||
// format: 'HH:mm',
|
||||
// style: { width: '100%' },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// key: 'clock_end_time',
|
||||
// valueType: 'time',
|
||||
// colProps: { span: 12 },
|
||||
// formItemProps: {
|
||||
// labelCol: { span: 8 },
|
||||
// wrapperCol: { span: 16 },
|
||||
// ...rulesHelper.text,
|
||||
// },
|
||||
// fieldProps: {
|
||||
// placeholder: '请设置任务可执行结束时间',
|
||||
// format: 'HH:mm',
|
||||
// style: { width: '100%' },
|
||||
// },
|
||||
// },
|
||||
MyFormItems.EnumRadio({
|
||||
key: 'generation_method',
|
||||
title: '巡更周期',
|
||||
tooltip: '每天巡更一次/每周x巡更一次/每月x号巡更一次',
|
||||
valueEnum: PatrolRoutesGenerationMethodEnum,
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
fieldProps: {
|
||||
onChange: () => {
|
||||
form.setFieldValue('date', undefined);
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
valueType: 'dependency',
|
||||
name: ['generation_method'],
|
||||
columns: ({ generation_method }) => {
|
||||
return generation_method === 'Weekly' ||
|
||||
generation_method === 'Monthly'
|
||||
? [
|
||||
{
|
||||
title: '生成时段',
|
||||
key: 'date',
|
||||
valueType: 'checkbox',
|
||||
formItemProps: { ...rulesHelper.array },
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
// 使用CSS方式设置每个checkbox选项的宽度
|
||||
className: 'custom-checkbox-group',
|
||||
},
|
||||
valueEnum: () => {
|
||||
if (generation_method === 'Weekly') {
|
||||
const weekDays = [
|
||||
{ label: '周一', value: 1 },
|
||||
{ label: '周二', value: 2 },
|
||||
{ label: '周三', value: 3 },
|
||||
{ label: '周四', value: 4 },
|
||||
{ label: '周五', value: 5 },
|
||||
{ label: '周六', value: 6 },
|
||||
{ label: '周日', value: 7 },
|
||||
];
|
||||
return weekDays.reduce((acc: any, day) => {
|
||||
acc[day.value] = { text: day.label };
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
if (generation_method === 'Monthly') {
|
||||
const monthDays = Array.from(
|
||||
{ length: 31 },
|
||||
(_, index) => {
|
||||
const day = index + 1;
|
||||
return {
|
||||
label: `${day < 10 ? `0${day}` : day}号`,
|
||||
value: day,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return monthDays.reduce((acc: any, day) => {
|
||||
acc[day.value] = { text: day.label };
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
139
src/pages/patrol/patrol_tasks/index.tsx
Normal file
139
src/pages/patrol/patrol_tasks/index.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import { MyButtons, MyColumns, MyProTableProps } from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { PatrolTasksCreateTypeEnum, PatrolTasksStatusEnum } from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { Space } from 'antd';
|
||||
import Create from './modals/Create';
|
||||
import PatrolOrderAssign from './modals/PatrolOrderAssign';
|
||||
import PatrolOrderShow from './modals/PatrolOrderShow';
|
||||
|
||||
export default function Index({ title = '巡更任务' }) {
|
||||
return (
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
request={async (params, sort) =>
|
||||
MyProTableProps.request(params, sort, Apis.Patrol.PatrolTasks.List)
|
||||
}
|
||||
toolBarRender={(action) => [
|
||||
<Create key="Create" reload={action?.reload} title={title} />,
|
||||
]}
|
||||
columns={[
|
||||
MyColumns.ID({
|
||||
search: false,
|
||||
}),
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
hidden: true,
|
||||
}),
|
||||
{
|
||||
title: '关联项目',
|
||||
dataIndex: ['asset_project', 'name'],
|
||||
search: {
|
||||
transform: (value) => {
|
||||
return { project_name: value };
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '路线名称',
|
||||
dataIndex: ['patrol_route', 'name'],
|
||||
search: {
|
||||
transform: (value) => {
|
||||
return { patrol_route_name: value };
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '任务时段',
|
||||
dataIndex: 'start_time',
|
||||
valueType: 'dateTime',
|
||||
render: (_, item: any) => {
|
||||
// 格式化开始时间(显示日期和时分)
|
||||
const formatStartTime = (timeStr: string) => {
|
||||
if (!timeStr) return '';
|
||||
const [date, time] = timeStr.split(' ');
|
||||
if (!time) return date;
|
||||
return `${date} ${time.split(':').slice(0, 2).join(':')}`;
|
||||
};
|
||||
|
||||
// 格式化结束时间(只显示时分)
|
||||
const formatEndTime = (timeStr: string) => {
|
||||
if (!timeStr) return '';
|
||||
const [, time] = timeStr.split(' ');
|
||||
if (!time) return '';
|
||||
return time.split(':').slice(0, 2).join(':');
|
||||
};
|
||||
|
||||
return `${formatStartTime(item.start_time)} - ${formatEndTime(
|
||||
item.end_time,
|
||||
)}`;
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '任务时段',
|
||||
dataIndex: 'date_time',
|
||||
valueType: 'dateRange',
|
||||
hideInTable: true,
|
||||
},
|
||||
MyColumns.EnumTag({
|
||||
title: '生成方式',
|
||||
dataIndex: 'create_type',
|
||||
valueEnum: PatrolTasksCreateTypeEnum,
|
||||
}),
|
||||
MyColumns.EnumTag({
|
||||
title: '任务状态',
|
||||
dataIndex: 'status',
|
||||
valueEnum: PatrolTasksStatusEnum,
|
||||
search: false,
|
||||
}),
|
||||
{
|
||||
title: '指派时间',
|
||||
dataIndex: 'assign_time',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '关联工单',
|
||||
dataIndex: 'house_work_orders_id',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '完成时间',
|
||||
dataIndex: 'complete_time',
|
||||
search: false,
|
||||
},
|
||||
MyColumns.CreatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
{item?.status === 'Unassigned' && (
|
||||
<PatrolOrderAssign
|
||||
item={item}
|
||||
reload={action?.reload}
|
||||
title="指派"
|
||||
/>
|
||||
)}
|
||||
{item.status !== 'Unassigned' && (
|
||||
<PatrolOrderShow
|
||||
item={item}
|
||||
reload={action?.reload}
|
||||
title="查看"
|
||||
/>
|
||||
)}
|
||||
<MyButtons.Delete
|
||||
onConfirm={() =>
|
||||
Apis.Patrol.PatrolTasks.Delete({ id: item.id }).then(() =>
|
||||
action?.reload(),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
125
src/pages/patrol/patrol_tasks/modals/Create.tsx
Normal file
125
src/pages/patrol/patrol_tasks/modals/Create.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyFormItems,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { HouseWorkOrdersLevelEnum } from '@/gen/Enums';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Patrol.PatrolTasks.Store>
|
||||
{...MyModalFormProps.props}
|
||||
title={`添加临时任务`}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 20 }}
|
||||
labelAlign="left"
|
||||
width="600px"
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
trigger={<MyButtons.Create title={`临时任务`} />}
|
||||
onFinish={async (values) =>
|
||||
Apis.Patrol.PatrolTasks.Store({
|
||||
...values,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success('添加成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
{
|
||||
valueType: 'dependency',
|
||||
name: ['asset_projects_id'],
|
||||
columns: ({ asset_projects_id }) => {
|
||||
return asset_projects_id
|
||||
? [
|
||||
Selects?.PatrolRoutesSelect({
|
||||
title: '选择路线',
|
||||
key: 'patrol_routes_id',
|
||||
params: {
|
||||
asset_projects_id: asset_projects_id,
|
||||
},
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
}),
|
||||
]
|
||||
: [
|
||||
Selects?.PatrolRoutesSelect({
|
||||
title: '选择路线',
|
||||
key: 'patrol_routes_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
fieldProps: { disabled: true },
|
||||
}),
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
key: 'start_time',
|
||||
valueType: 'dateTime',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
placeholder: '请设置开始时间',
|
||||
disabledDate: (current: any) => {
|
||||
// 只能选择当天及之后的时间
|
||||
return current && current < dayjs().startOf('day');
|
||||
},
|
||||
},
|
||||
formItemProps: {
|
||||
...rulesHelper.text,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
key: 'end_time',
|
||||
valueType: 'dateTime',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
placeholder: '请设置结束日期',
|
||||
disabledDate: (current: any) => {
|
||||
//选择时间必须在开始时间之后,且为同一天
|
||||
return (
|
||||
current && current < dayjs(form.getFieldValue('start_time'))
|
||||
);
|
||||
},
|
||||
},
|
||||
formItemProps: {
|
||||
...rulesHelper.text,
|
||||
},
|
||||
},
|
||||
MyFormItems.EnumRadio({
|
||||
key: 'level',
|
||||
title: '任务等级',
|
||||
colProps: { span: 24 },
|
||||
valueEnum: HouseWorkOrdersLevelEnum,
|
||||
required: true,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
78
src/pages/patrol/patrol_tasks/modals/PatrolOrderAssign.tsx
Normal file
78
src/pages/patrol/patrol_tasks/modals/PatrolOrderAssign.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
const JudgmentEnum = {
|
||||
Yes: { text: '有效工单', color: '#ff0000', value: 'Yes' },
|
||||
No: { text: '无效工单', color: '#00ff00', value: 'No' },
|
||||
};
|
||||
|
||||
type FormDataType = ApiTypes.WorkOrder.HouseWorkOrders.Assign & {
|
||||
attachments?: any[];
|
||||
judgment?: string;
|
||||
};
|
||||
|
||||
export default function WorkOrderAssign(
|
||||
props: MyBetaModalFormProps & { item: any },
|
||||
) {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.WorkOrder.HouseWorkOrders.Assign>
|
||||
{...MyModalFormProps.props}
|
||||
title={`指派工单`}
|
||||
wrapperCol={{ span: 19 }}
|
||||
width="500px"
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 5 }}
|
||||
trigger={
|
||||
<MyButtons.Default title={props.item.title || '指派'} type="primary" />
|
||||
}
|
||||
key={new Date().getTime()}
|
||||
form={form}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
onFinish={async (values: any) => {
|
||||
return Apis.WorkOrder.HouseWorkOrders.Assign({
|
||||
...values,
|
||||
id: props.item.house_work_orders_id,
|
||||
predict_complete_at: props.item.end_time,
|
||||
level: props.item.level,
|
||||
})
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success('指派工单成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false);
|
||||
}}
|
||||
columns={[
|
||||
Selects?.Employees({
|
||||
title: '选择处理人',
|
||||
key: 'assign_employees_id',
|
||||
colProps: { span: 24 },
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
required: true,
|
||||
}),
|
||||
{
|
||||
title: '备注',
|
||||
key: 'assign_remark',
|
||||
valueType: 'textarea',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
style: { width: '100%' },
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
182
src/pages/patrol/patrol_tasks/modals/PatrolOrderShow.tsx
Normal file
182
src/pages/patrol/patrol_tasks/modals/PatrolOrderShow.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
import { MyButtons, MyProTableProps } from '@/common';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import {
|
||||
PatrolTaskLocationsStatusEnum,
|
||||
PatrolTasksStatusEnum,
|
||||
} from '@/gen/Enums';
|
||||
import { ProDescriptions, ProTable } from '@ant-design/pro-components';
|
||||
import { Divider, Image, Modal, Space } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import PatrolOrderAssign from './PatrolOrderAssign';
|
||||
|
||||
interface PatrolOrderShowProps {
|
||||
item: any;
|
||||
title?: string;
|
||||
reload?: () => void;
|
||||
}
|
||||
|
||||
export default function PatrolOrderShow({
|
||||
item,
|
||||
title = '巡更详情',
|
||||
reload,
|
||||
}: PatrolOrderShowProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
const handleOpen = async () => {
|
||||
try {
|
||||
const res = await Apis.Patrol.PatrolTasks.Show({ id: item.id });
|
||||
setData(res.data);
|
||||
setOpen(true);
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyButtons.Default onClick={handleOpen} type="primary" title={title} />
|
||||
<Modal
|
||||
title={title}
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
footer={null}
|
||||
width={800}
|
||||
>
|
||||
{data && (
|
||||
<>
|
||||
<ProDescriptions
|
||||
column={2}
|
||||
dataSource={data}
|
||||
columns={[
|
||||
{
|
||||
title: '任务ID',
|
||||
dataIndex: 'id',
|
||||
},
|
||||
{
|
||||
title: '工单ID',
|
||||
dataIndex: 'house_work_orders_id',
|
||||
},
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: ['asset_project', 'name'],
|
||||
},
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: ['patrol_route', 'name'],
|
||||
},
|
||||
{
|
||||
title: '任务状态',
|
||||
dataIndex: 'status',
|
||||
valueEnum: PatrolTasksStatusEnum,
|
||||
},
|
||||
{
|
||||
title: '处理人',
|
||||
dataIndex: 'assign_employee_name',
|
||||
span: 2,
|
||||
render: (_, record) => {
|
||||
const assigneeInfo = record?.house_work_order
|
||||
?.assign_employee?.name
|
||||
? `${
|
||||
record.house_work_order.assign_employee.name || ''
|
||||
}-${
|
||||
record.house_work_order.assign_employee.phone || ''
|
||||
}`
|
||||
: '未分配';
|
||||
return (
|
||||
<Space>
|
||||
<span>{assigneeInfo}</span>
|
||||
<PatrolOrderAssign
|
||||
item={{ ...item, title: '重新分配' }}
|
||||
reload={handleOpen}
|
||||
title="重新分配"
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
dataIndex: 'start_time',
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
dataIndex: 'end_time',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
valueType: 'dateTime',
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updated_at',
|
||||
valueType: 'dateTime',
|
||||
},
|
||||
{
|
||||
title: '完成时间',
|
||||
dataIndex: 'complete_time',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
search={false}
|
||||
toolBarRender={false}
|
||||
pagination={false}
|
||||
dataSource={data?.patrol_task_locations}
|
||||
rowKey={(record, index) => record?.id_card || index}
|
||||
size="small"
|
||||
columns={[
|
||||
{
|
||||
title: '处理人',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '点位名称',
|
||||
dataIndex: ['patrol_location_json', 'name'],
|
||||
},
|
||||
{
|
||||
title: '任务状态',
|
||||
dataIndex: 'status',
|
||||
valueEnum: PatrolTaskLocationsStatusEnum,
|
||||
},
|
||||
{
|
||||
title: '完成时间',
|
||||
dataIndex: 'updated_at',
|
||||
render: (_, item) => {
|
||||
return item?.status ===
|
||||
PatrolTaskLocationsStatusEnum.Completed.value
|
||||
? item?.updated_at
|
||||
: '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '现场照片',
|
||||
render: (_, item) => {
|
||||
return (
|
||||
<Space>
|
||||
{item?.images?.[0] && (
|
||||
<Image
|
||||
height={30}
|
||||
style={{
|
||||
width: 'auto',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
src={item?.images[0]?.url}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
229
src/pages/system/sys_roles/components/AdminRole/index.tsx
Normal file
229
src/pages/system/sys_roles/components/AdminRole/index.tsx
Normal file
@ -0,0 +1,229 @@
|
||||
import { MyButtons, MyProTableProps, useCurrentPermissions } from '@/common';
|
||||
import { flattenToMultiLevelFormatWithRowSpanAdvancedNew } from '@/common/utils/flattenIterative';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { ProCard, ProTable } from '@ant-design/pro-components';
|
||||
import { Checkbox, message, Space, Tabs } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Create from '../../modals/Create';
|
||||
interface SelectedBuilding {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
export default function Index({ title = '角色' }) {
|
||||
const getCurrentPermissions = useCurrentPermissions();
|
||||
const [selectedBuilding, setSelectedBuilding] =
|
||||
useState<SelectedBuilding | null>(null);
|
||||
const [selectedPermissionsIds, setSelectedPermissionsIds] = useState<any[]>(
|
||||
[],
|
||||
);
|
||||
const [dataSource, setDataSource] = useState<any>([]);
|
||||
const [dataTabsSource, setDataTabsSource] = useState<any>([]);
|
||||
const [tabsKey, setTabsKey] = useState<any>('');
|
||||
const getSysPermissions = () => {
|
||||
Apis.Permission.Roles.PermissionTree().then((res) => {
|
||||
setDataSource(
|
||||
flattenToMultiLevelFormatWithRowSpanAdvancedNew(
|
||||
res?.data[0]?.children || [],
|
||||
),
|
||||
);
|
||||
console.log(res, 'res');
|
||||
});
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
if (selectedPermissionsIds?.length && selectedBuilding?.id) {
|
||||
Apis.Permission.Roles.SetPermissions({
|
||||
permissions_ids: selectedPermissionsIds,
|
||||
id: selectedBuilding?.id || 0,
|
||||
}).then(() => {
|
||||
message.success('保存成功');
|
||||
});
|
||||
} else {
|
||||
message.error('请选择角色和勾选权限!');
|
||||
}
|
||||
};
|
||||
|
||||
const getPermissions = (id: any) => {
|
||||
setTabsKey(id);
|
||||
// 更新选中的角色信息
|
||||
const selectedRole = dataTabsSource.find((item: any) => item.id === id);
|
||||
if (selectedRole) {
|
||||
setSelectedBuilding({
|
||||
id: selectedRole.id,
|
||||
name: selectedRole.name,
|
||||
});
|
||||
}
|
||||
Apis.Permission.Roles.GetPermissions({
|
||||
id: id ?? 0,
|
||||
}).then((res) => {
|
||||
setSelectedPermissionsIds(res?.data?.permissions_ids || []);
|
||||
});
|
||||
};
|
||||
|
||||
const getSysRoles = () => {
|
||||
Apis.Permission.Roles.List({ guard_name: 'Company' }).then((res) => {
|
||||
setDataTabsSource(res?.data || []);
|
||||
if (res?.data?.length) {
|
||||
const firstRole = res?.data[0];
|
||||
getPermissions(firstRole?.id || 0);
|
||||
// 初始化选中第一个角色
|
||||
setSelectedBuilding({
|
||||
id: firstRole?.id,
|
||||
name: firstRole?.name,
|
||||
});
|
||||
}
|
||||
console.log(res, 'res');
|
||||
});
|
||||
};
|
||||
|
||||
const onSelect = () => {
|
||||
//删除角色
|
||||
Apis.Permission.Roles.Delete({
|
||||
id: tabsKey,
|
||||
}).then(() => {
|
||||
getSysRoles();
|
||||
message.success('删除成功');
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSysRoles();
|
||||
getSysPermissions();
|
||||
}, []);
|
||||
|
||||
let toolBarRender = () => {
|
||||
return getCurrentPermissions({
|
||||
add: <Create key="Create" reload={() => getSysRoles()} title={title} />,
|
||||
delete: (
|
||||
<MyButtons.Default
|
||||
key="delete"
|
||||
size="middle"
|
||||
color="danger"
|
||||
variant="solid"
|
||||
isConfirm
|
||||
description="是否确定删除当前角色?"
|
||||
title="删除当前角色"
|
||||
onConfirm={() => onSelect()}
|
||||
// 如果当前选中角色是管理员,则禁用删除按钮
|
||||
disabled={selectedBuilding?.name === '管理员'}
|
||||
/>
|
||||
),
|
||||
save: (
|
||||
<MyButtons.Default
|
||||
key="save"
|
||||
type="primary"
|
||||
size="middle"
|
||||
title="保存权限"
|
||||
onClick={() => onSave()}
|
||||
/>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ProCard
|
||||
title="系统角色配置"
|
||||
extra={<Space>{toolBarRender()}</Space>}
|
||||
headerBordered
|
||||
>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '130px' }}>
|
||||
<Tabs
|
||||
tabPosition="left"
|
||||
items={dataTabsSource.map((item: any) => ({
|
||||
label: item?.name,
|
||||
key: item?.id,
|
||||
}))}
|
||||
activeKey={tabsKey}
|
||||
onChange={(key: any) => {
|
||||
getPermissions(key);
|
||||
console.log(key, 'key');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
<Checkbox.Group
|
||||
style={{ width: '100%' }}
|
||||
value={selectedPermissionsIds}
|
||||
onChange={(e) => {
|
||||
setSelectedPermissionsIds(e);
|
||||
console.log(e, 'e');
|
||||
}}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
search={false}
|
||||
pagination={false}
|
||||
// style={{ width: '100%' }}
|
||||
// tableExtraRender={() => (
|
||||
// <MyEnumRadioGroup
|
||||
// enums={getSysModuleEnum}
|
||||
// onChange={(e) => {
|
||||
// setGuardName(e as string);
|
||||
// }}
|
||||
// value={guardName}
|
||||
// />
|
||||
// )}
|
||||
style={{
|
||||
width: '100%',
|
||||
maxHeight: 'calc(100vh - 280px)', // 设置最大高度,可根据实际需求调整
|
||||
overflowY: 'auto', // 启用垂直滚动
|
||||
}}
|
||||
dataSource={dataSource}
|
||||
options={false}
|
||||
size="small"
|
||||
columns={[
|
||||
{
|
||||
title: '目录',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '120px',
|
||||
render: (_, item: any) => {
|
||||
return <Checkbox value={item?.id1}>{item?.name}</Checkbox>;
|
||||
},
|
||||
onCell: (res, index?: number) => {
|
||||
const rowSpan = res.row_spans?.rowSpan;
|
||||
const firstIndex = res.row_spans?.firstIndex;
|
||||
if (index === firstIndex && rowSpan > 0) {
|
||||
return { rowSpan };
|
||||
}
|
||||
return { rowSpan: 0 };
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '页面',
|
||||
dataIndex: 'name2',
|
||||
key: 'name2',
|
||||
width: '160px',
|
||||
render: (_, item: any) => {
|
||||
return item?.name2 ? (
|
||||
<Checkbox value={item?.id2}>{item?.name2}</Checkbox>
|
||||
) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '页签/按钮',
|
||||
width: '800px',
|
||||
render: (_, item: any) => {
|
||||
if (item?.buttonList?.length) {
|
||||
return item?.buttonList?.map(
|
||||
(res: any, index: number) => {
|
||||
return res?.name ? (
|
||||
<Checkbox value={res?.id} key={`index_${index}`}>
|
||||
{res?.name}
|
||||
</Checkbox>
|
||||
) : null;
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
</div>
|
||||
</ProCard>
|
||||
);
|
||||
}
|
||||
227
src/pages/system/sys_roles/components/EmployeeRole/index.tsx
Normal file
227
src/pages/system/sys_roles/components/EmployeeRole/index.tsx
Normal file
@ -0,0 +1,227 @@
|
||||
import { MyButtons, MyProTableProps, useCurrentPermissions } from '@/common';
|
||||
import { flattenToMultiLevelFormatWithRowSpanAdvancedNew } from '@/common/utils/flattenIterative';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { ProCard, ProTable } from '@ant-design/pro-components';
|
||||
import { Checkbox, message, Space, Tabs } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Create from './modals/Create';
|
||||
interface SelectedBuilding {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
export default function Index({ title = '角色' }) {
|
||||
const getCurrentPermissions = useCurrentPermissions();
|
||||
const [selectedBuilding, setSelectedBuilding] =
|
||||
useState<SelectedBuilding | null>(null);
|
||||
const [selectedPermissionsIds, setSelectedPermissionsIds] = useState<any[]>(
|
||||
[],
|
||||
);
|
||||
const [dataSource, setDataSource] = useState<any>([]);
|
||||
const [dataTabsSource, setDataTabsSource] = useState<any>([]);
|
||||
const [tabsKey, setTabsKey] = useState<any>('');
|
||||
const getSysPermissions = () => {
|
||||
Apis.Company.EmployeeRoles.PermissionTree().then((res) => {
|
||||
setDataSource(
|
||||
flattenToMultiLevelFormatWithRowSpanAdvancedNew(
|
||||
res?.data[0]?.children || [],
|
||||
),
|
||||
);
|
||||
console.log(res, 'res');
|
||||
});
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
if (selectedPermissionsIds?.length && selectedBuilding?.id) {
|
||||
Apis.Company.EmployeeRoles.SetPermissions({
|
||||
permissions_ids: selectedPermissionsIds,
|
||||
id: selectedBuilding?.id || 0,
|
||||
}).then(() => {
|
||||
message.success('保存成功');
|
||||
});
|
||||
} else {
|
||||
message.error('请选择角色和勾选权限!');
|
||||
}
|
||||
};
|
||||
|
||||
const getPermissions = (id: any) => {
|
||||
setTabsKey(id);
|
||||
// 更新选中的角色信息
|
||||
const selectedRole = dataTabsSource.find((item: any) => item.id === id);
|
||||
if (selectedRole) {
|
||||
setSelectedBuilding({
|
||||
id: selectedRole.id,
|
||||
name: selectedRole.name,
|
||||
});
|
||||
}
|
||||
Apis.Company.EmployeeRoles.GetPermissions({
|
||||
id: id ?? 0,
|
||||
}).then((res) => {
|
||||
setSelectedPermissionsIds(res?.data?.permissions_ids || []);
|
||||
});
|
||||
};
|
||||
|
||||
const getSysRoles = () => {
|
||||
Apis.Company.EmployeeRoles.List().then((res) => {
|
||||
setDataTabsSource(res?.data || []);
|
||||
if (res?.data?.length) {
|
||||
const firstRole = res?.data[0];
|
||||
getPermissions(firstRole?.id || 0);
|
||||
// 初始化选中第一个角色
|
||||
setSelectedBuilding({
|
||||
id: firstRole?.id,
|
||||
name: firstRole?.name,
|
||||
});
|
||||
}
|
||||
console.log(res, 'res');
|
||||
});
|
||||
};
|
||||
|
||||
const onSelect = () => {
|
||||
//删除角色
|
||||
Apis.Company.EmployeeRoles.Delete({
|
||||
id: tabsKey,
|
||||
}).then(() => {
|
||||
getSysRoles();
|
||||
message.success('删除成功');
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSysRoles();
|
||||
getSysPermissions();
|
||||
}, []);
|
||||
|
||||
let toolBarRender = () => {
|
||||
return getCurrentPermissions({
|
||||
add: <Create key="Create" reload={() => getSysRoles()} title={title} />,
|
||||
delete: (
|
||||
<MyButtons.Default
|
||||
key="delete"
|
||||
size="middle"
|
||||
color="danger"
|
||||
variant="solid"
|
||||
isConfirm
|
||||
description="是否确定删除当前角色?"
|
||||
title="删除当前角色"
|
||||
onConfirm={() => onSelect()}
|
||||
/>
|
||||
),
|
||||
save: (
|
||||
<MyButtons.Default
|
||||
key="save"
|
||||
type="primary"
|
||||
size="middle"
|
||||
title="保存权限"
|
||||
onClick={() => onSave()}
|
||||
/>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ProCard
|
||||
title="员工角色配置"
|
||||
headerBordered
|
||||
extra={<Space>{toolBarRender()}</Space>}
|
||||
>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '130px' }}>
|
||||
<Tabs
|
||||
tabPosition="left"
|
||||
items={dataTabsSource.map((item: any) => ({
|
||||
label: item?.name,
|
||||
key: item?.id,
|
||||
}))}
|
||||
activeKey={tabsKey}
|
||||
onChange={(key: any) => {
|
||||
getPermissions(key);
|
||||
console.log(key, 'key');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
<Checkbox.Group
|
||||
style={{ width: '100%' }}
|
||||
value={selectedPermissionsIds}
|
||||
onChange={(e) => {
|
||||
setSelectedPermissionsIds(e);
|
||||
console.log(e, 'e');
|
||||
}}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
search={false}
|
||||
pagination={false}
|
||||
// style={{ width: '100%' }}
|
||||
// tableExtraRender={() => (
|
||||
// <MyEnumRadioGroup
|
||||
// enums={getSysModuleEnum}
|
||||
// onChange={(e) => {
|
||||
// setGuardName(e as string);
|
||||
// }}
|
||||
// value={guardName}
|
||||
// />
|
||||
// )}
|
||||
style={{
|
||||
width: '100%',
|
||||
maxHeight: 'calc(100vh - 280px)', // 设置最大高度,可根据实际需求调整
|
||||
overflowY: 'auto', // 启用垂直滚动
|
||||
}}
|
||||
dataSource={dataSource}
|
||||
options={false}
|
||||
size="small"
|
||||
columns={[
|
||||
{
|
||||
title: '目录',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '120px',
|
||||
render: (_, item: any) => {
|
||||
return <Checkbox value={item?.id1}>{item?.name}</Checkbox>;
|
||||
},
|
||||
onCell: (res, index?: number) => {
|
||||
const rowSpan = res.row_spans?.rowSpan;
|
||||
const firstIndex = res.row_spans?.firstIndex;
|
||||
if (index === firstIndex && rowSpan > 0) {
|
||||
return { rowSpan };
|
||||
}
|
||||
return { rowSpan: 0 };
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '页面',
|
||||
dataIndex: 'name2',
|
||||
key: 'name2',
|
||||
width: '160px',
|
||||
render: (_, item: any) => {
|
||||
return item?.name2 ? (
|
||||
<Checkbox value={item?.id2}>{item?.name2}</Checkbox>
|
||||
) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '页签/按钮',
|
||||
width: '800px',
|
||||
render: (_, item: any) => {
|
||||
if (item?.buttonList?.length) {
|
||||
return item?.buttonList?.map(
|
||||
(res: any, index: number) => {
|
||||
return res?.name ? (
|
||||
<Checkbox value={res?.id} key={`index_${index}`}>
|
||||
{res?.name}
|
||||
</Checkbox>
|
||||
) : null;
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
</div>
|
||||
</ProCard>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
import {
|
||||
MyBetaModalFormProps,
|
||||
MyButtons,
|
||||
MyModalFormProps,
|
||||
rulesHelper,
|
||||
} from '@/common';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { Form, message } from 'antd';
|
||||
|
||||
export default function Create(props: MyBetaModalFormProps) {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
return (
|
||||
<BetaSchemaForm<ApiTypes.Company.EmployeeRoles.Store>
|
||||
{...MyModalFormProps.props}
|
||||
form={form}
|
||||
title={`添加${props.title}`}
|
||||
wrapperCol={{ span: 24 }}
|
||||
key={new Date().getTime()}
|
||||
width="420px"
|
||||
trigger={<MyButtons.Create title={`添加${props.title}`} />}
|
||||
onOpenChange={(open: any) => {
|
||||
if (open) {
|
||||
form.resetFields(); // 清空表单数据
|
||||
}
|
||||
}}
|
||||
onFinish={async (values) =>
|
||||
Apis.Company.EmployeeRoles.Store(values)
|
||||
.then(() => {
|
||||
props.reload?.();
|
||||
message.success(props.title + '成功');
|
||||
return true;
|
||||
})
|
||||
.catch(() => false)
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
key: 'name',
|
||||
title: '角色名称',
|
||||
formItemProps: { ...rulesHelper.text },
|
||||
},
|
||||
// MyFormItems.ColorPicker(),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,128 +1,20 @@
|
||||
import {
|
||||
MyButtons,
|
||||
MyPageContainer,
|
||||
MyProTableProps,
|
||||
useCurrentPermissions,
|
||||
} from '@/common';
|
||||
import { flattenToMultiLevelFormatWithRowSpanAdvancedNew } from '@/common/utils/flattenIterative';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import { ProCard, ProTable } from '@ant-design/pro-components';
|
||||
import { Checkbox, message, Space, Tabs } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Create from './modals/Create';
|
||||
interface SelectedBuilding {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
import { MyPageContainer } from '@/common';
|
||||
import { Tabs, TabsProps } from 'antd';
|
||||
import AdminRole from './components/AdminRole';
|
||||
import EmployeeRole from './components/EmployeeRole';
|
||||
export default function Index({ title = '角色' }) {
|
||||
const getCurrentPermissions = useCurrentPermissions();
|
||||
const [selectedBuilding, setSelectedBuilding] =
|
||||
useState<SelectedBuilding | null>(null);
|
||||
const [selectedPermissionsIds, setSelectedPermissionsIds] = useState<any[]>(
|
||||
[],
|
||||
);
|
||||
const [dataSource, setDataSource] = useState<any>([]);
|
||||
const [dataTabsSource, setDataTabsSource] = useState<any>([]);
|
||||
const [tabsKey, setTabsKey] = useState<any>('');
|
||||
const getSysPermissions = () => {
|
||||
Apis.Permission.Roles.PermissionTree().then((res) => {
|
||||
setDataSource(
|
||||
flattenToMultiLevelFormatWithRowSpanAdvancedNew(
|
||||
res?.data[0]?.children || [],
|
||||
),
|
||||
);
|
||||
console.log(res, 'res');
|
||||
});
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
if (selectedPermissionsIds?.length && selectedBuilding?.id) {
|
||||
Apis.Permission.Roles.SetPermissions({
|
||||
permissions_ids: selectedPermissionsIds,
|
||||
id: selectedBuilding?.id || 0,
|
||||
}).then(() => {
|
||||
message.success('保存成功');
|
||||
});
|
||||
} else {
|
||||
message.error('请选择角色和勾选权限!');
|
||||
}
|
||||
};
|
||||
|
||||
const getPermissions = (id: any) => {
|
||||
setTabsKey(id);
|
||||
// 更新选中的角色信息
|
||||
const selectedRole = dataTabsSource.find((item: any) => item.id === id);
|
||||
if (selectedRole) {
|
||||
setSelectedBuilding({
|
||||
id: selectedRole.id,
|
||||
name: selectedRole.name,
|
||||
});
|
||||
}
|
||||
Apis.Permission.Roles.GetPermissions({
|
||||
id: id ?? 0,
|
||||
}).then((res) => {
|
||||
setSelectedPermissionsIds(res?.data?.permissions_ids || []);
|
||||
});
|
||||
};
|
||||
|
||||
const getSysRoles = () => {
|
||||
Apis.Permission.Roles.List().then((res) => {
|
||||
setDataTabsSource(res?.data || []);
|
||||
if (res?.data?.length) {
|
||||
const firstRole = res?.data[0];
|
||||
getPermissions(firstRole?.id || 0);
|
||||
// 初始化选中第一个角色
|
||||
setSelectedBuilding({
|
||||
id: firstRole?.id,
|
||||
name: firstRole?.name,
|
||||
});
|
||||
}
|
||||
console.log(res, 'res');
|
||||
});
|
||||
};
|
||||
|
||||
const onSelect = () => {
|
||||
//删除角色
|
||||
Apis.Permission.Roles.Delete({
|
||||
id: tabsKey,
|
||||
}).then(() => {
|
||||
getSysRoles();
|
||||
message.success('删除成功');
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
getSysRoles();
|
||||
getSysPermissions();
|
||||
}, []);
|
||||
|
||||
let toolBarRender = () => {
|
||||
return getCurrentPermissions({
|
||||
add: <Create key="Create" reload={() => getSysRoles()} title={title} />,
|
||||
delete: (
|
||||
<MyButtons.Default
|
||||
key="delete"
|
||||
size="middle"
|
||||
color="danger"
|
||||
variant="solid"
|
||||
isConfirm
|
||||
description="是否确定删除当前角色?"
|
||||
title="删除当前角色"
|
||||
onConfirm={() => onSelect()}
|
||||
// 如果当前选中角色是管理员,则禁用删除按钮
|
||||
disabled={selectedBuilding?.name === '管理员'}
|
||||
/>
|
||||
),
|
||||
save: (
|
||||
<MyButtons.Default
|
||||
key="save"
|
||||
type="primary"
|
||||
size="middle"
|
||||
title="保存权限"
|
||||
onClick={() => onSave()}
|
||||
/>
|
||||
),
|
||||
});
|
||||
};
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: '后台角色',
|
||||
children: <AdminRole />,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '员工角色',
|
||||
children: <EmployeeRole />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<MyPageContainer
|
||||
@ -131,103 +23,7 @@ export default function Index({ title = '角色' }) {
|
||||
tabKey="system-roles"
|
||||
tabLabel={title}
|
||||
>
|
||||
<ProCard
|
||||
title="权限配置"
|
||||
style={{ width: '100%' }}
|
||||
headerBordered
|
||||
extra={<Space size="small">{toolBarRender()}</Space>}
|
||||
>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '130px' }}>
|
||||
<Tabs
|
||||
tabPosition="left"
|
||||
items={dataTabsSource.map((item: any) => ({
|
||||
label: item?.name,
|
||||
key: item?.id,
|
||||
}))}
|
||||
onChange={(key: any) => {
|
||||
getPermissions(key);
|
||||
console.log(key, 'key');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
<Checkbox.Group
|
||||
style={{ width: '100%' }}
|
||||
value={selectedPermissionsIds}
|
||||
onChange={(e) => {
|
||||
setSelectedPermissionsIds(e);
|
||||
console.log(e, 'e');
|
||||
}}
|
||||
>
|
||||
<ProTable
|
||||
{...MyProTableProps.props}
|
||||
search={false}
|
||||
pagination={false}
|
||||
// style={{ width: '100%' }}
|
||||
style={{
|
||||
width: '100%',
|
||||
maxHeight: 'calc(100vh - 280px)', // 设置最大高度,可根据实际需求调整
|
||||
overflowY: 'auto', // 启用垂直滚动
|
||||
}}
|
||||
dataSource={dataSource}
|
||||
options={false}
|
||||
size="small"
|
||||
columns={[
|
||||
{
|
||||
title: '目录',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '120px',
|
||||
render: (_, item: any) => {
|
||||
return (
|
||||
<Checkbox value={item?.id1}>{item?.name}</Checkbox>
|
||||
);
|
||||
},
|
||||
onCell: (res, index?: number) => {
|
||||
const rowSpan = res.row_spans?.rowSpan;
|
||||
const firstIndex = res.row_spans?.firstIndex;
|
||||
if (index === firstIndex && rowSpan > 0) {
|
||||
return { rowSpan };
|
||||
}
|
||||
return { rowSpan: 0 };
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '页面',
|
||||
dataIndex: 'name2',
|
||||
key: 'name2',
|
||||
width: '160px',
|
||||
render: (_, item: any) => {
|
||||
return item?.name2 ? (
|
||||
<Checkbox value={item?.id2}>{item?.name2}</Checkbox>
|
||||
) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '页签/按钮',
|
||||
width: '800px',
|
||||
render: (_, item: any) => {
|
||||
if (item?.buttonList?.length) {
|
||||
return item?.buttonList?.map(
|
||||
(res: any, index: number) => {
|
||||
return res?.name ? (
|
||||
<Checkbox value={res?.id} key={`index_${index}`}>
|
||||
{res?.name}
|
||||
</Checkbox>
|
||||
) : null;
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
</div>
|
||||
</ProCard>
|
||||
<Tabs defaultActiveKey="1" type="card" items={items} />
|
||||
</MyPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
245
src/pages/work_order/patrol_work/index.tsx
Normal file
245
src/pages/work_order/patrol_work/index.tsx
Normal file
@ -0,0 +1,245 @@
|
||||
import {
|
||||
MyButtons,
|
||||
MyColumns,
|
||||
MyProTableProps,
|
||||
useCurrentPermissions,
|
||||
} from '@/common';
|
||||
import { Selects } from '@/components/Select';
|
||||
import { Apis } from '@/gen/Apis';
|
||||
import {
|
||||
HouseWorkOrdersAssignStatusEnum,
|
||||
HouseWorkOrdersLevelEnum,
|
||||
HouseWorkOrdersLocationEnum,
|
||||
HouseWorkOrdersTypeEnum,
|
||||
} from '@/gen/Enums';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { Space } from 'antd';
|
||||
import WorkOrderAssign from '../list/modals/WorkOrderAssign';
|
||||
import WorkOrderCreate from '../list/modals/WorkOrderCreate';
|
||||
import WorkOrderShow from '../list/modals/WorkOrderShow';
|
||||
import WorkOrderUpdate from '../list/modals/WorkOrderUpdate';
|
||||
|
||||
export const RenovationWorkOrdersStatusEnum = {
|
||||
Pending: { text: '待处理', color: '#FFA500', value: 'Pending' },
|
||||
Processing: { text: '处理中', color: '#1E90FF', value: 'Processing' },
|
||||
Completed: { text: '已完成', color: '#28A745', value: 'Completed' },
|
||||
Closed: { text: '已关闭', color: '#6C757D', value: 'Closed' },
|
||||
};
|
||||
|
||||
export default function PatrolWorkIndex({ title = '巡更工单' }) {
|
||||
const getCurrentPermissions = useCurrentPermissions();
|
||||
|
||||
let tableRender = (item: any, action: any) => {
|
||||
return getCurrentPermissions(
|
||||
{
|
||||
show: (
|
||||
<WorkOrderShow item={item} title="详情" reload={action?.reload} />
|
||||
),
|
||||
assign: (
|
||||
<>
|
||||
{item.assign_status === 'Unassigned' &&
|
||||
item.type !== 'SecurityInspection' && (
|
||||
<WorkOrderAssign
|
||||
item={item}
|
||||
reload={action?.reload}
|
||||
title="指派"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
update: (
|
||||
<>
|
||||
{item.status === 'Pending' && (
|
||||
<WorkOrderUpdate
|
||||
item={{
|
||||
...item,
|
||||
// typeEnum: WorkTypeEnum,
|
||||
}}
|
||||
reload={action?.reload}
|
||||
title={title}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
delete: (
|
||||
<MyButtons.Delete
|
||||
onConfirm={() =>
|
||||
Apis.WorkOrder.HouseWorkOrders.SoftDelete({
|
||||
id: item.id,
|
||||
}).then(() => action?.reload())
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
'MyDecorationWorkorder',
|
||||
);
|
||||
};
|
||||
|
||||
const WorkTypeEnum: any = () => {
|
||||
let obj: any = JSON.parse(JSON.stringify(HouseWorkOrdersTypeEnum));
|
||||
delete obj.Repair;
|
||||
delete obj.Incident;
|
||||
delete obj.Complaint;
|
||||
delete obj.RenovationInspection;
|
||||
delete obj.RenovationAcceptance;
|
||||
delete obj.Emergency;
|
||||
delete obj.EquipmentMaintenance;
|
||||
delete obj.QualityCheck;
|
||||
return obj;
|
||||
};
|
||||
|
||||
return (
|
||||
<ProTable<Record<any, any>>
|
||||
{...MyProTableProps.props}
|
||||
headerTitle={title}
|
||||
request={async (params, sort) => {
|
||||
return MyProTableProps.request(
|
||||
{
|
||||
...params,
|
||||
type: [HouseWorkOrdersTypeEnum.SecurityInspection.value],
|
||||
},
|
||||
sort,
|
||||
Apis.WorkOrder.HouseWorkOrders.List,
|
||||
);
|
||||
}}
|
||||
toolBarRender={(action) => [
|
||||
<WorkOrderCreate
|
||||
key="Create"
|
||||
reload={action?.reload}
|
||||
title={title}
|
||||
item={
|
||||
{
|
||||
// typeEnum: WorkTypeEnum,
|
||||
}
|
||||
}
|
||||
/>,
|
||||
]}
|
||||
columns={[
|
||||
MyColumns.ID({ search: false }),
|
||||
Selects?.AssetProjects({
|
||||
title: '选择项目',
|
||||
key: 'asset_projects_id',
|
||||
hidden: true,
|
||||
}),
|
||||
MyColumns.EnumTag({
|
||||
title: '处理状态',
|
||||
dataIndex: 'status',
|
||||
valueEnum: RenovationWorkOrdersStatusEnum,
|
||||
}),
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'project_name',
|
||||
hidden: true,
|
||||
},
|
||||
MyColumns.EnumTag({
|
||||
title: '分配状态',
|
||||
dataIndex: 'assign_status',
|
||||
valueEnum: HouseWorkOrdersAssignStatusEnum,
|
||||
}),
|
||||
MyColumns.EnumTag({
|
||||
title: '工单类型',
|
||||
dataIndex: 'type',
|
||||
valueEnum: HouseWorkOrdersTypeEnum,
|
||||
search: false,
|
||||
}),
|
||||
MyColumns.EnumTag({
|
||||
title: '报修位置',
|
||||
dataIndex: 'location',
|
||||
valueEnum: HouseWorkOrdersLocationEnum,
|
||||
search: false,
|
||||
}),
|
||||
{
|
||||
title: '位置信息',
|
||||
dataIndex: ['asset_house', 'full_name'],
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Space>
|
||||
{record?.asset_house?.full_name
|
||||
? record?.asset_house?.full_name
|
||||
: record?.asset_project?.name}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
search: {
|
||||
transform: (value) => {
|
||||
return { house_name: value };
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '工单描述',
|
||||
dataIndex: 'content',
|
||||
search: false,
|
||||
width: 120, // 关键:固定列宽(若父容器过窄,可设 minWidth: 200 优先保证列宽)
|
||||
render: (text) => (
|
||||
<div
|
||||
style={{
|
||||
width: '100%', // 继承列宽
|
||||
// height: '60px', // 设置固定高度,约显示3行文本
|
||||
overflow: 'hidden', // 超出隐藏
|
||||
textOverflow: 'ellipsis', // 省略号
|
||||
display: '-webkit-box',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: 1, // 显示3行
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
MyColumns.EnumTag({
|
||||
title: '优先级',
|
||||
dataIndex: 'level',
|
||||
valueEnum: HouseWorkOrdersLevelEnum,
|
||||
search: false,
|
||||
}),
|
||||
|
||||
{
|
||||
title: '处理人',
|
||||
dataIndex: ['assign_employee', 'name'],
|
||||
search: false,
|
||||
render: (_, record) => {
|
||||
return `${record?.assign_employee?.name || ''}-${
|
||||
record?.assign_employee?.phone || ''
|
||||
}`;
|
||||
},
|
||||
},
|
||||
MyColumns.CreatedAt(),
|
||||
MyColumns.Option({
|
||||
render: (_, item: any, index, action) => (
|
||||
<Space key={index}>
|
||||
<WorkOrderShow item={item} title="详情" reload={action?.reload} />
|
||||
|
||||
{item.assign_status === 'Unassigned' &&
|
||||
item.type !== 'SecurityInspection' && (
|
||||
<WorkOrderAssign
|
||||
item={item}
|
||||
reload={action?.reload}
|
||||
title="指派"
|
||||
/>
|
||||
)}
|
||||
|
||||
{item.status === 'Pending' && (
|
||||
<WorkOrderUpdate
|
||||
item={{
|
||||
...item,
|
||||
// typeEnum: WorkTypeEnum,
|
||||
}}
|
||||
reload={action?.reload}
|
||||
title={title}
|
||||
/>
|
||||
)}
|
||||
<MyButtons.Delete
|
||||
onConfirm={() =>
|
||||
Apis.WorkOrder.HouseWorkOrders.SoftDelete({
|
||||
id: item.id,
|
||||
}).then(() => action?.reload())
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user