fix:更新员工端权限

This commit is contained in:
Your Name 2026-02-24 17:48:14 +08:00
parent d898d299ed
commit 78ab79c80b
47 changed files with 6393 additions and 784 deletions

View File

@ -49,3 +49,7 @@ export function GetFromNow(time: string) {
//获取时间距离现在的时间
return time ? dayjs(time).fromNow() : '';
}
export function getTodayDate(): string {
return dayjs().format('YYYY-MM-DD');
}

View File

@ -0,0 +1,2 @@
export const mapKey = '4ce26ecef55ae1ec47910a72a098efc0';
// export const mapKey = '35fe6f4443fddf86cdcca3b97fd8c872';

View 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>
);
}

View File

@ -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
View File

@ -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; // 角色名称

View File

@ -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 });

File diff suppressed because it is too large Load Diff

View 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>
);
}

View 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 },
// },
]}
/>
);
}

View 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 },
// },
]}
/>
);
}

View 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>
);
}

View 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 },
}),
]}
/>
);
}

View File

@ -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',
},
]}
/>
);
}

View File

@ -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',
},
]}
/>
);
}

View 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>
);
}

View File

@ -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 },
}),
]}
/>
);
}

View File

@ -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',
},
]}
/>
);
}

View File

@ -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',
},
]}
/>
);
}

View 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>
);
}

View 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>
);
}

View 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}
/>
);
},
},
];
},
},
]}
/>
);
}

View 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 },
},
]}
/>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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;
}

View File

@ -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,
},

View File

@ -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: '备注',

View File

@ -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: '密码',

View 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>
);
}

View 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>
),
}),
]}
/>
);
}

View 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 },
},
]}
/>
);
}

View 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 },
},
]}
/>
);
}

View 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>
),
}),
]}
/>
);
}

View 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 },
},
]}
/>
);
}

View 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 },
},
]}
/>
);
}

View 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>
),
}),
]}
/>
);
}

View 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,
}),
]}
/>
);
}

View 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%' },
},
},
]}
/>
);
}

View 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>
</>
);
}

View 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>
);
}

View 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>
);
}

View File

@ -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(),
]}
/>
);
}

View File

@ -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>
);
}

View 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>
),
}),
]}
/>
);
}