245 lines
7.6 KiB
TypeScript
245 lines
7.6 KiB
TypeScript
|
|
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>
|
|||
|
|
),
|
|||
|
|
}),
|
|||
|
|
]}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|