682 lines
30 KiB
TypeScript
682 lines
30 KiB
TypeScript
import LeftSelectTree from "@/components/leftSelectTree/leftSelectTree";
|
||
import { handleGetServerpartDDL } from "@/components/leftSelectTree/service";
|
||
import { ActionType, FormInstance, ProCard, ProForm, ProFormList, ProFormSelect, ProFormSwitch, ProFormText, ProFormTextArea, ProTable } from "@ant-design/pro-components";
|
||
import { Button, Col, message, Modal, Popconfirm, Row, Space, Image, Drawer } from "antd";
|
||
import moment from "moment";
|
||
import { useRef, useState } from "react";
|
||
import { connect } from "umi";
|
||
import { handleAddTemplates, handleDeleteTemplates, handleGetQuestionList, handleGetTemplatesList, handleUpdateTemplates, handleUploadFile } from "./service";
|
||
import { handleGetExamineTypeTreeList } from "../index/service";
|
||
import QRCode from 'qrcode';
|
||
import { base64ToFile } from "@/utils/publicMethods";
|
||
import RecordDetail from "../record/components/recordDetail";
|
||
|
||
|
||
const examineModal: React.FC<{ currentUser: any }> = (props) => {
|
||
const { currentUser } = props
|
||
|
||
const actionRef = useRef<ActionType>();
|
||
const formRef = useRef<FormInstance>();
|
||
const modalActionRef = useRef<ActionType>();
|
||
const modalFormRef = useRef<FormInstance>();
|
||
|
||
|
||
|
||
// 弹出框的表单实例
|
||
const modalRef = useRef<FormInstance>()
|
||
// 树相关的属性和方法
|
||
const [selectedId, setSelectedId] = useState<string>()
|
||
const [collapsible, setCollapsible] = useState<boolean>(false)
|
||
|
||
// 显示新增点位的悬浮框
|
||
const [showPlaceModal, setShowPlaceModal] = useState<boolean>(false)
|
||
// 显示详情
|
||
const [showDetail, handleShowDetail] = useState<boolean>(false)
|
||
// 当前点击选中的问题行
|
||
const [currentRow, setCurrentRow] = useState<any>()
|
||
// 服务区的枚举
|
||
const [serviceObj, setServiceObj] = useState<any>()
|
||
// 生成的二维码的初始
|
||
const [qrCodeUrl, setQrCodeUrl] = useState<string>()
|
||
// 选择问题的悬浮框
|
||
const [selectQuestionModal, setSelectQuestionModal] = useState<boolean>(false)
|
||
// 选择的问题列表 详情
|
||
const [selectedQuestionDetail, setSelectedQuestionDetail] = useState<any>()
|
||
// 选择的问题列表的id
|
||
const [selectedQuestionId, setSelectedQuestionId] = useState<any>()
|
||
|
||
// 显示的附件数据
|
||
const [showImgList, setShowImgList] = useState<string[]>([])
|
||
// 预览图片
|
||
const [imagePreviewVisible, setImagePreviewVisible] = useState<boolean>(false)
|
||
// 预览的索引
|
||
const [previewIndex, setPreviewIndex] = useState<number>(0)
|
||
const [columnsStateMap, setColumnsStateMap] = useState<any>({
|
||
updatedAt: { show: false },
|
||
createdAt: { show: false },
|
||
operator: { show: false },
|
||
})
|
||
|
||
const columns: any = [
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>服务区名称</div>,
|
||
dataIndex: "serverPartName",
|
||
hideInSearch: true,
|
||
width: 150,
|
||
ellipsis: true
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>点位名称</div>,
|
||
dataIndex: "title",
|
||
hideInSearch: true,
|
||
width: 200,
|
||
ellipsis: true
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>问题列表</div>,
|
||
dataIndex: "questions",
|
||
hideInSearch: true,
|
||
width: 400,
|
||
ellipsis: true,
|
||
render: (_, record) => {
|
||
let questionStr: string = ""
|
||
if (record?.questionnaireTemplateQuestions && record?.questionnaireTemplateQuestions.length > 0) {
|
||
record?.questionnaireTemplateQuestions.forEach((item: any, index: number) => {
|
||
let options: string = ''
|
||
if (item.question.options && item.question.options.length > 0) {
|
||
item.question.options.forEach((subItem: any, subIndex: number) => {
|
||
options += `${subIndex > 0 ? ',' : ''}选项${subIndex + 1}:${subItem.text}`
|
||
})
|
||
}
|
||
questionStr += `${index > 0 ? ',' : ''}问题${index + 1}:${item.question.title},${options}`
|
||
})
|
||
}
|
||
return questionStr || ''
|
||
// return record?.questions && record?.questions.length > 0 ? JSON.stringify(record?.questions) : ''
|
||
}
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>站点二维码</div>,
|
||
dataIndex: "qrUrl",
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
width: 150,
|
||
ellipsis: true,
|
||
render: (_, record) => {
|
||
return record?.qrUrl ? <Button type="primary" onClick={() => {
|
||
setShowImgList([record?.qrUrl])
|
||
setImagePreviewVisible(true)
|
||
}}>查看二维码</Button> : "-"
|
||
}
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>有效状态</div>,
|
||
dataIndex: "status",
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
width: 100,
|
||
ellipsis: true,
|
||
render: (_, record) => {
|
||
return record?.status ? '有效' : '无效'
|
||
}
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>备注说明</div>,
|
||
dataIndex: "description",
|
||
hideInSearch: true,
|
||
width: 200,
|
||
ellipsis: true
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>更新时间</div>,
|
||
dataIndex: "updatedAt",
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
width: 150,
|
||
ellipsis: true,
|
||
render: (_, record) => {
|
||
return record?.updatedAt ? moment(record?.updatedAt).format('YYYY-MM-DD HH:mm:ss') : ''
|
||
}
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>创建时间</div>,
|
||
dataIndex: "createdAt",
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
width: 150,
|
||
ellipsis: true,
|
||
render: (_, record) => {
|
||
return record?.createdAt ? moment(record?.createdAt).format('YYYY-MM-DD HH:mm:ss') : ''
|
||
}
|
||
},
|
||
{
|
||
title: <div style={{ textAlign: 'center' }}>操作人</div>,
|
||
dataIndex: "operator",
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
width: 100,
|
||
ellipsis: true,
|
||
},
|
||
{
|
||
title: '操作',
|
||
dataIndex: 'option',
|
||
align: 'center',
|
||
hideInSearch: true,
|
||
fixed: "right",
|
||
width: 120,
|
||
render: (_: any, record: any) => {
|
||
return <Space>
|
||
<a onClick={() => {
|
||
console.log('record', record);
|
||
setCurrentRow(record)
|
||
setShowPlaceModal(true)
|
||
}}>编辑</a>
|
||
<a onClick={() => {
|
||
setCurrentRow(record)
|
||
handleShowDetail(true)
|
||
}}>详情</a>
|
||
<Popconfirm
|
||
title={"确认删除?"}
|
||
onConfirm={async () => {
|
||
deleteQuestion(record?.id)
|
||
}}
|
||
>
|
||
<a>删除</a>
|
||
</Popconfirm>
|
||
|
||
</Space>
|
||
}
|
||
}
|
||
]
|
||
|
||
// 弹出框里面的
|
||
const modalColumns: any = [
|
||
{
|
||
title: "问题分类",
|
||
dataIndex: "id",
|
||
hideInTable: true,
|
||
valueType: 'treeSelect',
|
||
request: async () => {
|
||
const data = await handleGetExamineTypeTreeList()
|
||
console.log('data', data);
|
||
return data && data.length > 0 ? data.map(item => ({
|
||
title: item.name,
|
||
value: item.id,
|
||
children: item.children?.map((child: any) => ({
|
||
title: child.name,
|
||
value: child.id,
|
||
children: child.children || []
|
||
})) || []
|
||
})) : []
|
||
},
|
||
fieldProps: {
|
||
multiple: false,
|
||
treeDefaultExpandAll: true,
|
||
showSearch: true,
|
||
treeNodeFilterProp: 'title',
|
||
placeholder: '请选择问题分类'
|
||
}
|
||
},
|
||
{
|
||
title: "考核内容",
|
||
dataIndex: "title",
|
||
width: 200,
|
||
ellipsis: true,
|
||
hideInSearch: true
|
||
},
|
||
{
|
||
title: "考核选项",
|
||
dataIndex: "options",
|
||
width: 500,
|
||
ellipsis: true,
|
||
hideInSearch: true,
|
||
render: (_, record) => {
|
||
let str: string = ''
|
||
if (record.options && record?.options.length > 0) {
|
||
record.options.forEach((item: any, index: number) => {
|
||
str += `${index > 0 ? ',' : ''}选项${index + 1}:${item.text}`
|
||
})
|
||
}
|
||
return str || ''
|
||
// return record?.options && record?.options.length > 0 ? JSON.stringify(record?.options) : ""
|
||
}
|
||
}
|
||
]
|
||
|
||
// 删除模版
|
||
const deleteQuestion = async (id: number) => {
|
||
const data = await handleDeleteTemplates({ id: id })
|
||
if (data.code === 200) {
|
||
message.success(data.message)
|
||
actionRef.current?.reload()
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div style={{ backgroundColor: '#fff', display: 'flex' }}>
|
||
{/* <LeftSelectTree setSelectedId={setSelectedId} setCollapsible={setCollapsible} collapsible={collapsible} currentUser={currentUser} /> */}
|
||
|
||
<div style={{
|
||
// width: !collapsible ? 'calc(100% - 300px)' : 'calc(100% - 60px)',
|
||
width: "100%",
|
||
paddingTop: 0,
|
||
paddingBottom: 0,
|
||
paddingRight: 0
|
||
}}>
|
||
<ProTable
|
||
actionRef={actionRef}
|
||
formRef={formRef}
|
||
columns={columns}
|
||
bordered
|
||
expandable={{
|
||
expandRowByClick: true
|
||
}}
|
||
scroll={{ x: "100%", y: 'calc(100vh - 400px)' }}
|
||
headerTitle={<span style={{ color: "#1890ff", fontSize: 14, fontWeight: 600 }}>走动式点位管理</span>}
|
||
search={{ span: 6 }}
|
||
request={async () => {
|
||
const req: any = {
|
||
|
||
}
|
||
const data = await handleGetTemplatesList()
|
||
if (data && data.length > 0) {
|
||
return { data, success: true }
|
||
}
|
||
return { data: [], success: true }
|
||
}}
|
||
toolbar={{
|
||
actions: [
|
||
<Button type="primary" onClick={(e) => {
|
||
setShowPlaceModal(true)
|
||
}}>
|
||
添加点位
|
||
</Button>
|
||
]
|
||
}}
|
||
columnsState={{
|
||
value: columnsStateMap,
|
||
onChange: setColumnsStateMap,
|
||
}}
|
||
>
|
||
</ProTable>
|
||
|
||
{
|
||
showImgList && showImgList.length > 0 && <div style={{ display: 'none' }}>
|
||
|
||
<Image.PreviewGroup
|
||
|
||
preview={{
|
||
visible: imagePreviewVisible,
|
||
onVisibleChange: vis => {
|
||
setImagePreviewVisible(vis)
|
||
},
|
||
current: previewIndex
|
||
}}>
|
||
{
|
||
showImgList.map((n) =>
|
||
<Image src={n} key={n} />
|
||
)
|
||
}
|
||
</Image.PreviewGroup>
|
||
|
||
|
||
</div>
|
||
}
|
||
|
||
<Modal
|
||
width={1200}
|
||
title={`${currentRow?.id ? '编辑' : '创建'}点位`}
|
||
open={showPlaceModal}
|
||
destroyOnClose
|
||
onOk={() => {
|
||
modalRef.current?.validateFields().then(async (res) => {
|
||
console.log('res', res);
|
||
console.log('currentRow', currentRow);
|
||
let req = {}
|
||
let data = {}
|
||
if (currentRow?.id) {
|
||
let questions: any = []
|
||
if (selectedQuestionDetail && selectedQuestionDetail.length > 0) {
|
||
selectedQuestionDetail.forEach((item) => {
|
||
questions.push({
|
||
questionId: item.id,
|
||
isRequired: item.required,
|
||
sortOrder: item.sortOrder
|
||
})
|
||
})
|
||
} else {
|
||
// 判断是否有改动 一样 说明没改动 那么把老的拿进去就行
|
||
if (currentRow?.questionnaireTemplateQuestions.length === res.questions.length) {
|
||
if (currentRow?.questionnaireTemplateQuestions && currentRow?.questionnaireTemplateQuestions.length > 0) {
|
||
currentRow?.questionnaireTemplateQuestions.forEach((item) => {
|
||
questions.push({
|
||
questionId: item.question.id,
|
||
isRequired: item.question.required,
|
||
sortOrder: item.question.sortOrder
|
||
})
|
||
})
|
||
}
|
||
} else {
|
||
// 不一样 说明有删除 不然走的是第一个
|
||
if (res.questions && res.questions.length > 0) {
|
||
res.questions.forEach((item) => {
|
||
questions.push({
|
||
questionId: item.id,
|
||
isRequired: item.required,
|
||
sortOrder: item.sortOrder
|
||
})
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
req = {
|
||
...currentRow,
|
||
title: res.title,
|
||
serverPartName: serviceObj ? serviceObj[res.serverPartId] : "",
|
||
serverPartId: res.serverPartId,
|
||
questions: questions && questions.length > 0 ? questions : "",
|
||
operator: currentUser?.operator,
|
||
status: res.status,
|
||
description: res.description
|
||
}
|
||
data = await handleUpdateTemplates(req)
|
||
} else {
|
||
let questions: any = []
|
||
if (selectedQuestionDetail && selectedQuestionDetail.length > 0) {
|
||
selectedQuestionDetail.forEach((item) => {
|
||
questions.push({
|
||
questionId: item.id,
|
||
isRequired: item.required,
|
||
sortOrder: item.sortOrder
|
||
})
|
||
})
|
||
}
|
||
|
||
req = {
|
||
title: res.title,
|
||
placeName: "",
|
||
placeId: 0,
|
||
serverPartName: serviceObj ? serviceObj[res.serverPartId] : "",
|
||
serverPartId: res.serverPartId,
|
||
// qrUrl: "",
|
||
questions: questions && questions.length > 0 ? questions : "",
|
||
operator: currentUser?.operator,
|
||
status: res.status,
|
||
description: res.description
|
||
}
|
||
data = await handleAddTemplates(req)
|
||
console.log('datadsadsa1', data.data);
|
||
const myQRCodeDataUrl = await QRCode.toDataURL(`pages/walkAroundManager/index?id=${data.data.id}`);
|
||
const file = base64ToFile(myQRCodeDataUrl, `wenjuan${data.data.id}.png`);
|
||
console.log('file', file);
|
||
const formData = new FormData();
|
||
formData.append("file", file, `wenjuan${data.data.id}.png`); // 确保文件名也传递
|
||
|
||
const fileData = await handleUploadFile(formData)
|
||
console.log('fileData', fileData);
|
||
let imgUrl: string = `https://es.robot-z.cn/${fileData.data.path}`
|
||
await handleUpdateTemplates({
|
||
...data.data,
|
||
qrUrl: imgUrl
|
||
})
|
||
setQrCodeUrl(imgUrl)
|
||
// setQrCodeUrl(myQRCodeDataUrl)
|
||
}
|
||
console.log('datadsadsa', data);
|
||
if (data.code === 200) {
|
||
modalRef.current?.resetFields()
|
||
message.success(data.message)
|
||
setShowPlaceModal(false)
|
||
actionRef.current?.reload()
|
||
setCurrentRow(undefined)
|
||
} else {
|
||
message.error(data.message)
|
||
}
|
||
|
||
})
|
||
}}
|
||
onCancel={() => {
|
||
modalRef.current?.resetFields()
|
||
setShowPlaceModal(false)
|
||
setCurrentRow(undefined)
|
||
setQrCodeUrl(undefined)
|
||
}}
|
||
>
|
||
<ProForm formRef={modalRef} submitter={false} request={() => {
|
||
console.log('currentRow', currentRow);
|
||
|
||
let questionsList: any = []
|
||
let keyList: any = []
|
||
if (currentRow?.questionnaireTemplateQuestions && currentRow?.questionnaireTemplateQuestions.length > 0) {
|
||
console.log('1');
|
||
|
||
currentRow?.questionnaireTemplateQuestions.forEach((item) => {
|
||
console.log('2');
|
||
if (item.question) {
|
||
let obj = JSON.parse(JSON.stringify(item.question))
|
||
|
||
let str: string = ''
|
||
if (obj.options && obj?.options.length > 0) {
|
||
obj.options.forEach((subItem: any, index: number) => {
|
||
str += `${index > 0 ? ',' : ''}选项${index + 1}:${subItem.text}`
|
||
})
|
||
}
|
||
|
||
obj.text = obj.title
|
||
obj.mark = JSON.stringify(obj.options)
|
||
obj.showText = str
|
||
keyList.push(obj.id)
|
||
questionsList.push(obj)
|
||
}
|
||
})
|
||
}
|
||
|
||
// setSelectedQuestionDetail
|
||
setSelectedQuestionId(keyList)
|
||
return {
|
||
...currentRow,
|
||
serverPartId: currentRow.serverPartId.toString(),
|
||
questions: questionsList
|
||
}
|
||
}}>
|
||
<Row gutter={8}>
|
||
<Col span={8}>
|
||
<ProFormSelect
|
||
label={"服务区名"}
|
||
name={"serverPartId"}
|
||
request={async () => {
|
||
const req = {
|
||
ProvinceCode: currentUser?.provinceCode,
|
||
StatisticsType: 1000
|
||
}
|
||
const data = await handleGetServerpartDDL(req)
|
||
let obj: any = {}
|
||
if (data && data.length > 0) {
|
||
data.forEach((item) => {
|
||
obj[item.value] = item.label
|
||
})
|
||
}
|
||
setServiceObj(obj)
|
||
return data
|
||
}}
|
||
fieldProps={{
|
||
showSearch: true,
|
||
}}
|
||
rules={[{
|
||
required: true,
|
||
message: "请选择服务区!"
|
||
}]}
|
||
/>
|
||
</Col>
|
||
|
||
<Col span={8}>
|
||
<ProFormText
|
||
label={"点位名称"}
|
||
name={"title"}
|
||
rules={[{
|
||
required: true,
|
||
message: "请输入点位名称!"
|
||
}]}
|
||
/>
|
||
</Col>
|
||
<Col span={8}>
|
||
<ProFormSwitch
|
||
label={"有效状态"}
|
||
name={"status"}
|
||
initialValue={currentRow?.id ? currentRow.status : true}
|
||
/>
|
||
</Col>
|
||
<Col span={20}>
|
||
<ProFormList
|
||
name="questions"
|
||
label="考核列表"
|
||
initialValue={[]}
|
||
creatorButtonProps={{
|
||
position: 'bottom',
|
||
creatorButtonText: '添加选项',
|
||
style: { display: 'none' },
|
||
onClick: (e) => {
|
||
console.log('e', e);
|
||
}
|
||
}}
|
||
copyIconProps={false}
|
||
style={{ width: '100%' }}
|
||
itemContainerStyle={{ width: '100%' }}
|
||
itemRender={({ listDom, action }, { record, index }) => (
|
||
<div style={{ width: '100%', display: 'flex', alignItems: 'flex-start', marginBottom: '10px' }}>
|
||
<div style={{ flex: 1, width: '100%' }}>{listDom}</div>
|
||
<div style={{ marginLeft: '8px', marginTop: '30px' }}>{action}</div>
|
||
</div>
|
||
)}
|
||
>
|
||
<Row gutter={6} style={{ width: '100%', margin: 0 }}>
|
||
<Col span={8}>
|
||
<ProFormTextArea
|
||
name="text"
|
||
label="考核内容"
|
||
fieldProps={{
|
||
style: { width: '100%' }
|
||
}}
|
||
/>
|
||
</Col>
|
||
<Col span={16}>
|
||
<ProFormTextArea
|
||
name="showText"
|
||
label="考核标准"
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
</ProFormList>
|
||
</Col>
|
||
<Col span={4}>
|
||
<Button type={"primary"} onClick={() => {
|
||
setSelectQuestionModal(true)
|
||
}}>添加考核</Button>
|
||
</Col>
|
||
<Col span={24}>
|
||
<ProFormTextArea
|
||
label={"备注说明"}
|
||
name={"description"}
|
||
/>
|
||
</Col>
|
||
|
||
<Col span={24}>
|
||
<ProFormTextArea
|
||
label={"二维码URL"}
|
||
name={"qrUrl"}
|
||
/>
|
||
</Col>
|
||
<Col span={24}>
|
||
{
|
||
qrCodeUrl || currentRow?.qrUrl ?
|
||
<img style={{ width: "150px", height: "150px" }} src={qrCodeUrl || currentRow?.qrUrl} /> : ""
|
||
}
|
||
</Col>
|
||
|
||
|
||
</Row>
|
||
</ProForm>
|
||
</Modal>
|
||
|
||
<Modal
|
||
width={1400}
|
||
title={`点位`}
|
||
open={selectQuestionModal}
|
||
destroyOnClose
|
||
onOk={() => {
|
||
console.log('selectedQuestionDetail', selectedQuestionDetail);
|
||
// 显示的问题列表
|
||
let showQuestion: any = []
|
||
if (selectedQuestionDetail && selectedQuestionDetail.length > 0) {
|
||
selectedQuestionDetail.forEach((item) => {
|
||
let str: string = ''
|
||
if (item.options && item?.options.length > 0) {
|
||
item.options.forEach((item: any, index: number) => {
|
||
str += `${index > 0 ? ',' : ''}选项${index + 1}:${item.text}`
|
||
})
|
||
}
|
||
showQuestion.push({ text: item.title, showText: str, mark: item.options ? JSON.stringify(item.options) : "" })
|
||
})
|
||
}
|
||
modalRef.current?.setFieldsValue({
|
||
questions: showQuestion
|
||
})
|
||
setSelectQuestionModal(false)
|
||
}}
|
||
onCancel={() => {
|
||
setSelectQuestionModal(false)
|
||
}}
|
||
>
|
||
<ProTable
|
||
actionRef={modalActionRef}
|
||
formRef={modalFormRef}
|
||
columns={modalColumns}
|
||
rowKey={"id"}
|
||
scroll={{ y: 'calc(100vh - 500px)' }}
|
||
request={async (params) => {
|
||
console.log('查询参数:', params);
|
||
const req: any = {
|
||
categoryId: params.id
|
||
}
|
||
const data = await handleGetQuestionList(req)
|
||
if (data && data.length > 0) {
|
||
return { data, success: true }
|
||
}
|
||
return { data: [], success: true }
|
||
|
||
}}
|
||
rowSelection={{
|
||
type: "checkbox",
|
||
defaultSelectedRowKeys: selectedQuestionId,
|
||
onChange: (selectedRowKeys, selectedRows) => {
|
||
setSelectedQuestionDetail(selectedRows)
|
||
setSelectedQuestionId(selectedRowKeys)
|
||
}
|
||
}}
|
||
/>
|
||
</Modal>
|
||
</div>
|
||
|
||
|
||
<Drawer
|
||
title={false}
|
||
closeIcon={false}
|
||
onClose={() => {
|
||
handleShowDetail(false)
|
||
}}
|
||
open={showDetail}
|
||
destroyOnClose
|
||
width={'60%'}
|
||
>
|
||
<RecordDetail parentRow={currentRow} show={showDetail} detailType={'modal'} />
|
||
</Drawer>
|
||
</div >
|
||
)
|
||
}
|
||
|
||
export default connect(({ user }: ConnectState) => ({
|
||
currentUser: user.data
|
||
}))(examineModal);
|
||
|