This commit is contained in:
ylj20011123 2026-02-09 14:33:36 +08:00
parent 3357d03316
commit 9925f330bd
10 changed files with 2258 additions and 190 deletions

View File

@ -87,6 +87,11 @@ export default [
path: '/ComplaintForwardingProcess/index',
name: '投诉转发流程',
component: "@/pages/ComplaintForwardingProcess/index",
},
{
path: '/BusinessConfiguration/index',
name: '业务配置',
component: "@/pages/BusinessConfiguration/index",
}
]

View File

@ -317,6 +317,12 @@ const UserModel: UserModelType = {
name: '投诉转发流程',
component: "@/pages/ComplaintForwardingProcess/index",
},
{
path: '/BusinessConfiguration/index',
redirect: '',
name: '业务配置',
component: "@/pages/BusinessConfiguration/index",
},
]
}
@ -402,6 +408,7 @@ const UserModel: UserModelType = {
"/realEstate/index",
"/ComplaintApproval/index",
"/ComplaintForwardingProcess/index",
"/BusinessConfiguration/index",
// '/examine/index',
// '/examine/modal',
// '/examine/question',

View File

@ -0,0 +1,205 @@
import { ActionType, ProCard, ProTable } from "@ant-design/pro-components"
import { useEffect, useImperativeHandle, useRef, useState } from "react"
import { getOnwer, getUserTypeTree, handleGetUserList } from "@/pages/serverpartAssets/service";
import { Tree } from "antd";
import './style.less'
const ChosePlayers = ({ actionRef, onRef, defaultPerson }: { actionRef?: any, onRef: any, defaultPerson: any }) => {
const tableRef = useRef<ActionType>();
// 是否显示账号分类查询树
// 左侧业务类型数据
const [treeView, setTreeView] = useState<any>()
// 选择的部门
const [selectedId, setSelectedId] = useState<any>()
// 选择的行
const [selectedRowKeys, setSelectedRowKeys] = useState<any>()
// 选择的行的详情
const [selectedRowDetail, setSelectedRowDetial] = useState<any>()
// 表格内容
const columns: any = [
{
title: '查询账号',
dataIndex: 'searchKey',
hideInDescriptions: true,
hideInTable: true,
fieldProps: {
placeholder: '请输入账号/用户名称/业主单位/手机号/经营单位'
}
},
{
title: '账号部门',
dataIndex: 'USERTYPE_ID',
hideInSearch: true,
hideInDescriptions: true,
valueType: "treeSelect",
fieldProps: {
options: treeView,
},
},
{
title: '账号',
dataIndex: 'USER_PASSPORT',
sorter: (a, b) => {
return (a.USER_PASSPORT || '').localeCompare((b.USER_PASSPORT || ''))
},
hideInSearch: true,
},
{
title: '用户名称',
dataIndex: 'USER_NAME',
sorter: (a, b) => {
return (a.USER_NAME || '').localeCompare((b.USER_NAME || ''))
},
hideInDescriptions: true,
hideInSearch: true,
},
{
title: '手机号',
dataIndex: 'USER_MOBILEPHONE',
hideInSearch: true
},
{
title: '状态',
dataIndex: 'USER_STATUS',
hideInSearch: true,
filters: true,
onFilter: true,
valueType: 'select',
initialValue: 1,
valueEnum: {
all: { text: '全部', status: 'Default' },
0: { text: '无效', status: 'Default' },
1: { text: '有效', status: 'Processing' },
},
},
{
title: '业主单位',
dataIndex: 'PROVINCE_UNIT',
hideInSearch: true,
ellipsis: true,
sorter: (a, b) => {
return (a.PROVINCE_UNIT || '').localeCompare((b.PROVINCE_UNIT || ''))
},
valueType: "select",
request: async () => {
let data = await getOnwer({ ProvinceCode: "530000", DataType: 1 })
if (data && data.length > 0) {
data.forEach((item: any) => {
item.value = item.value.toString()
})
}
return data
}
},
{
title: '备注',
dataIndex: 'USER_DESC',
hideInSearch: true,
render: (_, record) => {
return record?.USER_DESC
}
}
]
useEffect(() => {
getUserTypeTree({ UserTypePattern: 1000, ShowStatus: true }).then((res: any) => {
console.log('fdsklfja', res);
setTreeView(res[0].children)
})
}, [])
useEffect(() => {
console.log('defaultPersondefaultPerson', defaultPerson);
if (defaultPerson && defaultPerson.length > 0) {
setSelectedRowDetial(defaultPerson)
let keyList: any = []
defaultPerson.forEach((item: any) => {
keyList.push(item.USER_ID)
})
setSelectedRowKeys(keyList)
}
}, [defaultPerson])
useImperativeHandle(onRef, () => ({
selectedRowKeys,
selectedRowDetail
}))
return (
<div className="ChosePlayersBox" style={{ backgroundColor: '#fff', display: 'flex', width: '100%', height: '700px' }}>
<ProCard
style={{ width: "300px" }}
className="ChosePlayersLeft"
bodyStyle={{ padding: 0, paddingTop: 20, paddingLeft: 20, width: "300px" }}
colSpan={"300px"}
headerBordered
>
{treeView && treeView.length > 0 ? <Tree
checkable
treeData={[{
label: '云南省',
value: 0,
key: '0-0',
children: treeView
}]}
fieldNames={{
title: "label",
key: "key"
}}
blockNode
defaultExpandedKeys={['0-0']}
onCheck={(checkedKeys: React.Key[] | any, info) => {
const selectedIds = info.checkedNodes.filter((n: any) => n?.type === 1)
let res: any = selectedIds.map(n => n?.value)?.toString() || ''
console.log('reqs', res);
setSelectedId(res)
}}
/> : ''}
</ProCard>
<div style={{
width: 'calc(100% - 300px)',
paddingTop: 0,
paddingBottom: 0,
paddingRight: 0
}}>
<ProTable
rowKey="USER_ID"
actionRef={tableRef}
scroll={{ x: "100%", y: '430px' }}
options={false}
request={async (params, sorter) => {
if (!selectedId) return []
const req: any = {
SearchParameter: {
UserTypeIds: selectedId,
USER_STATUS: 1
},
PageIndex: 1,
PageSize: 999999,
keyWord: params.searchKey ? { key: "USER_PASSPORT,USER_NAME,USER_MOBILEPHONE,PROVINCE_UNIT,BUSINESSMAN_NAME", value: params.searchKey } : null,
}
const data = await handleGetUserList(req)
console.log('dsajkdjas;da', data);
if (data && data.length > 0) {
return { data, success: true }
}
return { data: [], success: true }
}}
columns={columns}
rowSelection={{
selectedRowKeys: selectedRowKeys,
onChange: (e: any, detail: any) => {
setSelectedRowDetial(detail)
setSelectedRowKeys(e)
},
preserveSelectedRowKeys: true,
}}
/>
</div>
</div>
)
}
export default ChosePlayers;

View File

@ -0,0 +1,7 @@
.ChosePlayersBox {
.ant-pro-card {
.ant-pro-card-body {
overflow-y: scroll;
}
}
}

View File

@ -0,0 +1,489 @@
import { FileSearchOutlined, MenuFoldOutlined, PlusOutlined } from "@ant-design/icons"
import { ActionType, ProCard, ProForm, ProFormDigit, ProFormSelect, ProFormText, ProFormTreeSelect, ProTable } from "@ant-design/pro-components"
import { Button, FormInstance, message, Modal, Popconfirm, Space, Tree } from "antd"
import { useEffect, useRef, useState } from "react"
import { getFieldEnumTree, handleDeleteAPPROVALROUTE, handleGetAPPROVALROUTEList, handleGetUserList, handleSynchroAPPROVALROUTE } from "../serverpartAssets/service"
import moment from "moment"
import ChosePlayers from "./components/ChosePlayers"
const BusinessConfiguration = () => {
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const modalFormRef = useRef<FormInstance>();
const chosePlayerRef = useRef<any>();
// 折叠左侧业务类型属性选择框
const [collapsible, setCollapsible] = useState<boolean>(false)
// 左侧业务类型数据
const [treeView, setTreeView] = useState<any>()
// 左侧业务类型数据对象
const [serviceTypeObj, setServiceTypeObj] = useState<any>()
// 树相关的属性和方法
const [selectedId, setSelectedId] = useState<string>()
// 存储当前数据的搜索条件
const [searchParams, setSearchParams] = useState<any>()
// 显示新增环节的悬浮框
const [modalVisible, setModalVisible] = useState<boolean>(false)
// 选中的当前行数据
const [currentRow, setCurrentRow] = useState<any>()
// 悬浮框的确认按钮的loading
const [confirmLoading, setConfirmLoading] = useState<boolean>(false)
// 参与人列表
const [playerList, setPlayerList] = useState<any>()
// 当前已经存在了的参与人
const [defaultPerson, setDefaultPerson] = useState<any>()
// 显示参与人的悬浮框
const [showChosePlayer, setShowChosePlayer] = useState<boolean>(false)
const columns: any = [
{
title: '环节名称',
width: 150,
dataIndex: 'APPROVALROUTE_NAME',
align: 'center',
hideInSearch: true
},
{
title: '业务状态',
width: 120,
dataIndex: 'APPROVALROUTE_STATE',
align: 'center',
sorter: (a, b) => a.APPROVALROUTE_STATE - b.APPROVALROUTE_STATE,
defaultSortOrder: 'ascend',
},
{
title: '业务类型',
width: 120,
dataIndex: 'OPERATION_TYPE',
valueType: 'select',
align: 'center',
hideInSearch: true,
valueEnum: serviceTypeObj
},
{
title: '下一业务状态',
width: 120,
dataIndex: 'NEXT_STATE',
align: 'center',
// valueType: 'select',
hideInSearch: true,
// valueEnum:{
// 2000:'商户确认审核',
// 2010:'服务区经理审核',
// 2020:'片区中心审核',
// 2030:'经发部审核',
// 2040:'财务部审核',
// }
},
{
title: '参与人',
dataIndex: 'APPROVALSTAFF_NAME',
align: 'center',
width: 200,
ellipsis: true,
hideInSearch: true,
},
{
title: '生成时间',
width: 150,
dataIndex: 'RECORD_DATE',
align: 'center',
hideInSearch: true,
render: (_, record) => {
return record?.RECORD_DATE ? moment(record?.RECORD_DATE).format('YYYY-MM-DD') : ''
}
},
{
title: '业主单位',
width: 120,
align: 'center',
dataIndex: 'PROVINCE_CODE',
hideInSearch: true,
},
{
dataIndex: 'option',
title: '操作',
width: 120,
valueType: 'option',
align: 'center',
fixed: 'right',
hideInSearch: true,
render: (_, record) => {
return (
<Space>
<a onClick={() => {
setCurrentRow(record)
setModalVisible(true)
}}></a>
<Popconfirm
title="确认删除?"
onConfirm={async () => {
const req: any = {
APPROVALROUTEId: record?.APPROVALROUTE_ID
}
const data = await handleDeleteAPPROVALROUTE(req)
if (data.Result_Code === 100) {
message.success('删除成功')
actionRef.current?.reload()
} else {
message.error(data.Result_Desc)
}
}}
>
<a></a>
</Popconfirm>
</Space>
)
}
}
]
// 解构左侧类型
const extractValueLabel = (list: any) => {
const result: any = {};
function traverse(nodes: any) {
if (!Array.isArray(nodes)) return;
nodes.forEach(item => {
if (item.value !== undefined && item.label !== undefined) {
result[item.value] = item.label;
}
if (Array.isArray(item.children)) {
traverse(item.children);
}
});
}
traverse(list);
return result;
}
useEffect(() => {
getFieldEnumTree({ FieldExplainField: 'PROCESS_TYPE', sessionName: 'PROCESS_TYPE' }).then((res: any) => {
console.log('resresresres', res);
let obj = extractValueLabel(res)
setServiceTypeObj(obj)
setTreeView(res)
})
}, []);
return (
<div style={{ height: 'calc(100vh - 100px)', background: "#fff", display: 'flex' }}>
<div>
<ProCard
style={{ width: !collapsible ? "300px" : "60px" }}
className="pageTable-leftnav"
bodyStyle={{ padding: 0, paddingTop: 20, paddingLeft: 20, width: !collapsible ? "300px" : "60px" }}
extra={<MenuFoldOutlined onClick={() => {
setCollapsible(!collapsible)
}} />}
colSpan={!collapsible ? "300px" : "60px"}
title={!collapsible ? "请选择业务类型" : ""}
headerBordered
collapsed={collapsible}
>
{treeView && treeView.length > 0 ? <Tree
checkable
treeData={[{
label: '全部',
value: 0,
key: '0-0',
children: treeView
}]}
fieldNames={{
title: "label",
key: "key"
}}
blockNode
defaultExpandedKeys={['0-0']}
onCheck={(checkedKeys: React.Key[] | any, info) => {
const selectedIds = info.checkedNodes.filter(n => n?.type === 2)
setSelectedId(selectedIds.map(n => n?.value)?.toString() || '')
}}
// switcherIcon={<PlusOutlined />}
/> : ''}
</ProCard>
</div>
<div style={{
width: !collapsible ? 'calc(100% - 300px)' : 'calc(100% - 60px)',
paddingTop: 0,
paddingBottom: 0,
paddingRight: 0
}}>
<ProTable
actionRef={actionRef}
formRef={formRef}
columns={columns}
bordered
expandable={{
expandRowByClick: true
}}
headerTitle={'业务配置'}
search={{ span: 6 }}
request={async (params) => {
if (!selectedId) {
return
}
const req: any = {
SearchParameter: {
PROVINCE_CODE: '530000',
OPERATION_TYPES: selectedId || '',
APPROVALROUTE_VALID: 1
},
PageIndex: 1,
PageSize: 999999
}
setSearchParams(params)
const data = await handleGetAPPROVALROUTEList(req)
console.log('fdsjfhsdjkfhsa', data);
if (data && data.length > 0) {
return { data, success: true }
}
return { data: [], success: true }
}}
toolbar={{
actions: [
<Button
key="new"
icon={<PlusOutlined />}
type="primary"
onClick={(e) => {
setModalVisible(true)
}}
>
</Button>
]
}}
/>
</div>
<Modal
title={currentRow ? "编辑环节" : "新增环节"}
width={600}
destroyOnClose
open={modalVisible}
confirmLoading={confirmLoading}
onOk={(e) => {
setModalVisible(false)
modalFormRef.current?.validateFields().then((res: any) => {
modalFormRef.current?.submit()
})
}}
onCancel={(e) => {
setModalVisible(false)
modalFormRef.current?.resetFields();
setDefaultPerson([])
setPlayerList([])
setCurrentRow(undefined)
}}
>
<ProForm
formRef={modalFormRef}
submitter={false}
initialValues={currentRow}
request={async () => {
console.log('currentRowcurrentRow', currentRow);
if (currentRow) {
if (currentRow?.APPROVALSTAFF_ID) {
const req: any = {
SearchParameter: {
USER_IDS: currentRow?.APPROVALSTAFF_ID
},
PageIndex: 1,
PageSize: 999999
}
const data = await handleGetUserList(req)
console.log('datadatadata', data);
setDefaultPerson(data)
if (data && data.length > 0) {
const list: any = []
data.forEach((item: any) => {
list.push({ label: item.USER_NAME, value: item.USER_ID.toString() })
})
setPlayerList(list)
}
}
return {
...currentRow,
APPROVALSTAFF_ID: currentRow?.APPROVALSTAFF_ID ? currentRow?.APPROVALSTAFF_ID.split(',') : null
}
} else {
return {}
}
}}
onFinish={async (res) => {
console.log('readas', res);
let req: any = {}
if (currentRow) {
req = {
APPROVALROUTE_ID: currentRow?.APPROVALROUTE_ID,
APPROVALROUTE_NAME: res?.APPROVALROUTE_NAME || '',
APPROVALROUTE_STATE: res?.APPROVALROUTE_STATE || '',
OPERATION_TYPE: res?.OPERATION_TYPE || '',
APPROVALSTAFF_ID: res?.APPROVALSTAFF_ID ? res?.APPROVALSTAFF_ID.toString() : null,
APPROVALROUTE_VALID: 1,
PROVINCE_CODE: '530000',
NEXT_STATE: res?.NEXT_STATE
}
} else {
req = {
APPROVALROUTE_NAME: res?.APPROVALROUTE_NAME || '',
APPROVALROUTE_STATE: res?.APPROVALROUTE_STATE || '',
OPERATION_TYPE: res?.OPERATION_TYPE || '',
APPROVALSTAFF_ID: res?.APPROVALSTAFF_ID ? res?.APPROVALSTAFF_ID.toString() : null,
APPROVALROUTE_VALID: 1,
PROVINCE_CODE: '530000',
NEXT_STATE: res?.NEXT_STATE
}
}
setConfirmLoading(true)
const data = await handleSynchroAPPROVALROUTE(req)
setConfirmLoading(false)
if (data.Result_Code === 100) {
message.success('新增成功')
modalFormRef.current?.resetFields();
setCurrentRow(undefined)
actionRef.current?.reload()
setModalVisible(false)
setDefaultPerson([])
setPlayerList([])
return true
}
message.error(data.Result_Desc)
}}
>
<ProFormTreeSelect
label={"业务类型"}
name={"OPERATION_TYPE"}
request={async (params) => {
let list: any = JSON.parse(JSON.stringify(treeView))
if (list && list.length > 0) {
list.forEach((item: any) => {
item.disabled = true
})
}
return list
}}
fieldProps={{
treeDefaultExpandAll: true
}}
rules={[
{
required: true,
message: '请选择业务类型',
},
]}
/>
<ProFormText
label={'环节名称'}
name={'APPROVALROUTE_NAME'}
rules={[
{
required: true,
message: '请输入环节名称',
},
]}
/>
<ProFormDigit
label={"业务状态"}
name={"APPROVALROUTE_STATE"}
rules={[
{
required: true,
message: '请选择业务状态',
},
]}
/>
<ProFormDigit
label={"下一个业务状态"}
name={"NEXT_STATE"}
rules={[
{
required: true,
message: '请选择下一个业务状态',
},
]}
tooltip={'若下一状态为审结则是9000'}
/>
<ProFormSelect
label={'参与人'}
name={'APPROVALSTAFF_ID'}
width={'lg'}
fieldProps={{
options: playerList || [],
mode: 'multiple'
}}
addonAfter={
<Button type="primary" icon={<FileSearchOutlined />}
onClick={() => {
const res = modalFormRef.current?.getFieldValue('APPROVALSTAFF_ID')
console.log('resres', res);
// if ([res] && [res].length > 0) {
// const list: any = []
// [res].forEach((item: any) => {
// list.push(Number(item))
// })
// setDefaultPerson(list)
// } else {
// setDefaultPerson([])
// setPlayerList([])
// }
setShowChosePlayer(true)
}}>
</Button>}
/>
</ProForm>
</Modal>
<Modal
title={'选择参与人'}
width={'80%'}
open={showChosePlayer}
destroyOnClose
bodyStyle={{
height: '700px',
overflowY: 'auto'
}}
onOk={(e) => {
setShowChosePlayer(false)
let detailList: any = chosePlayerRef.current.selectedRowDetail
let list: any = []
if (detailList && detailList.length > 0) {
detailList.forEach((item: any) => {
list.push({ label: item.USER_NAME, value: item.USER_ID })
})
}
setPlayerList(list)
modalFormRef.current?.setFieldsValue({ APPROVALSTAFF_ID: chosePlayerRef.current.selectedRowKeys })
}}
onCancel={(e) => {
setShowChosePlayer(false)
}}
>
<ChosePlayers onRef={chosePlayerRef} actionRef={actionRef} defaultPerson={defaultPerson} />
</Modal>
</div>
)
}
export default BusinessConfiguration;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
export interface ResizeHalfOptions {
quality?: number;
}
export const resizeImageHalf = (
file: File,
options: ResizeHalfOptions = {}
): Promise<File> => {
const {
quality = 0.8
} = options;
return new Promise((resolve, reject) => {
if (!file || !file.type.includes('image')) {
reject(new Error('无效的图片文件'));
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
resizeToHalfDimensions(img, file, quality, resolve, reject);
};
img.onerror = () => reject(new Error('图片加载失败'));
img.src = e.target?.result as string;
};
reader.onerror = () => reject(new Error('文件读取失败'));
reader.readAsDataURL(file);
});
};
/**
* -
*/
function resizeToHalfDimensions(
img: HTMLImageElement,
originalFile: File,
quality: number,
resolve: (file: File) => void,
reject: (error: Error) => void
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
reject(new Error('浏览器不支持Canvas'));
return;
}
// 计算减半后的尺寸
const halfWidth = Math.floor(img.width / 2);
const halfHeight = Math.floor(img.height / 2);
console.log(`原图尺寸: ${img.width}x${img.height}`);
console.log(`减半后尺寸: ${halfWidth}x${halfHeight}`);
// 设置canvas尺寸为原图的一半
canvas.width = halfWidth;
canvas.height = halfHeight;
// 在canvas上绘制缩小后的图片
// 使用平滑缩放算法以获得更好的图像质量
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, halfWidth, halfHeight);
// 将canvas转换为Blob并创建新的File对象
canvas.toBlob(
(blob) => {
if (!blob) {
reject(new Error('图片处理失败'));
return;
}
// 创建新的文件名添加_resize_half后缀
const fileNameParts = originalFile.name.split('.');
const extension = fileNameParts.pop();
const fileNameWithoutExtension = fileNameParts.join('.');
const newFileName = `${fileNameWithoutExtension}_half.${extension}`;
// 创建压缩后的文件对象
const resizedFile = new File([blob], newFileName, {
type: originalFile.type,
lastModified: Date.now()
});
console.log(`✅ 图片尺寸减半完成: ${img.width}x${img.height}${halfWidth}x${halfHeight}`);
console.log(`文件大小: ${(originalFile.size / 1024).toFixed(1)}KB → ${(resizedFile.size / 1024).toFixed(1)}KB`);
resolve(resizedFile);
},
originalFile.type,
quality
);
}

View File

@ -0,0 +1,304 @@
import { Button } from "antd";
import ReactDOM from "react-dom";
import "./index.less";
const ComplaintForwardingProcess = () => {
// 附件1 结构(已根据您的要求优化)
const Attachment1 = () => (
<div className="printContainer attachment1">
<div className="printTitle">1</div>
<div style={{ textAlign: 'left', marginBottom: '5px' }}></div>
<table className="printTable">
<tbody>
<tr>
<td className="labelCell"></td>
<td className="contentCell"></td>
<td className="labelCell"></td>
<td className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td className="contentCell"></td>
<td className="labelCell"></td>
<td className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td className="contentCell"></td>
<td className="labelCell"></td>
<td className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="requirement-cell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="feedback-cell">
<div className="feedback-content-wrapper">
<div className="feedback-text"></div>
<div className="feedback-footer">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</div>
</div>
</td>
</tr>
</tbody>
</table>
<div style={{ textAlign: 'left', marginTop: '5px', marginBottom: '5px' }}>驿CYYGY0001</div>
</div>
);
// 附件2 结构恢复独立不受附件1样式影响
const Attachment2 = () => (
<div className="printContainer attachment2">
<div className="printTitle">12328</div>
<div style={{ textAlign: 'left', marginBottom: '5px' }}></div>
<table className="printTable">
<tbody>
<tr>
<td className="labelCell"></td>
<td className="contentCell"></td>
<td className="labelCell"></td>
<td className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td className="contentCell"></td>
<td className="labelCell"></td>
<td className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td className="contentCell"></td>
<td className="labelCell"></td>
<td className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="requirement-cell">
<div className="feedback-content-wrapper">
<div className="feedback-text"></div>
</div>
</td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="requirement-cell">
<div className="feedback-content-wrapper">
<div className="feedback-text"></div>
<div className="feedback-footer">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</div>
</div>
</td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="feedback-cell">
<div className="feedback-content-wrapper">
<div className="feedback-text"></div>
<div className="feedback-footer">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
);
// 附件3 结构(已加入高度优化)
const Attachment3 = () => (
<div className="printContainer attachment3">
<div className="printTitle"></div>
<div style={{ textAlign: 'right', marginBottom: '5px' }}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
<table className="printTable">
<tbody>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td className="contentCell"></td>
<td className="labelCell"></td>
<td className="contentCell"></td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="requirement-cell">
<div className="feedback-content-wrapper">
<div className="feedback-text"></div>
</div>
</td>
</tr>
<tr>
<td className="labelCell"></td>
<td colSpan={3} className="feedback-cell">
<div className="feedback-content-wrapper">
<div className="feedback-text"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
);
// 真正的打印函数,使用 Iframe 隔离
const handlePrintByIframe = (AttachmentComponent: React.ComponentType) => {
// 1. 创建隐藏的 iframe
const iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.right = '0';
iframe.style.bottom = '0';
iframe.style.width = '0';
iframe.style.height = '0';
iframe.style.border = '0';
document.body.appendChild(iframe);
const iframeDoc = iframe.contentWindow?.document;
if (!iframeDoc) return;
// 2. 注入样式和容器
iframeDoc.open();
iframeDoc.write(`
<html>
<head>
<title></title>
<style>
@page { size: A4; margin: 15mm; }
body { margin: 0; padding: 0; font-family: SimSun, STSong, serif; }
.printContainer {
width: 100%;
box-sizing: border-box;
}
.printTitle { text-align: center; font-size: 24px; font-weight: bold; margin-bottom: 25px; margin-top: 10px; }
.printTable {
width: 100%;
border-collapse: collapse;
border: 2px solid #000;
table-layout: fixed;
}
.printTable td { border: 1px solid #000; padding: 10px 12px; font-size: 15px; word-break: break-all; }
.labelCell { background-color: #f2f2f2 !important; width: 110px; font-weight: bold; text-align: center; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.contentCell { text-align: left; }
/* --- 附件1 专用样式 --- */
.attachment1 {
min-height: 260mm;
display: flex;
flex-direction: column;
}
.attachment1 .printTable { height: auto; }
.attachment1 .requirement-cell { height: 100px; vertical-align: top; }
.attachment1 .feedback-cell { vertical-align: top; padding: 0 !important; }
.attachment1 .feedback-content-wrapper {
display: flex; flex-direction: column; min-height: 550px; padding: 12px; box-sizing: border-box;
}
.attachment1 .feedback-text { flex: 1; }
.attachment1 .feedback-footer { text-align: right; margin-top: 20px; }
/* --- 附件2 专用样式 --- */
.attachment2 {
min-height: 260mm;
display: flex;
flex-direction: column;
}
.attachment2 .printTable { height: auto; }
/* 来电内容和转办要求:默认四行 (约 130px) */
.attachment2 .requirement-cell { height: 130px; vertical-align: top; padding: 0 !important; }
.attachment2 .feedback-cell { vertical-align: top; padding: 0 !important; }
/* 公用的内容包装器:用于签名贴底 */
.attachment2 .feedback-content-wrapper {
display: flex;
flex-direction: column;
height: 100%;
padding: 12px;
box-sizing: border-box;
}
/* 关键修复:只有真正的反馈区域才需要 min-height 来撑开全页 */
.attachment2 .feedback-cell .feedback-content-wrapper {
min-height: 400px;
}
.attachment2 .feedback-text { flex: 1; }
.attachment2 .feedback-footer { text-align: right; margin-top: 10px; }
/* --- 附件3 专用样式 --- */
.attachment3 {
min-height: 260mm;
display: flex;
flex-direction: column;
}
.attachment3 .printTable { height: auto; }
/* 舆情概况:默认两行 (约 70px) */
.attachment3 .requirement-cell { height: 70px; vertical-align: top; padding: 0 !important; }
.attachment3 .feedback-cell { vertical-align: top; padding: 0 !important; }
.attachment3 .feedback-content-wrapper {
display: flex;
flex-direction: column;
height: 100%;
padding: 12px;
box-sizing: border-box;
}
.attachment3 .feedback-cell .feedback-content-wrapper {
min-height: 500px;
}
.attachment3 .feedback-text { flex: 1; }
</style>
</head>
<body>
<div id="print-root"></div>
</body>
</html>
`);
iframeDoc.close();
// 3. 将 React 组件渲染到 iframe 中
const printRoot = iframeDoc.getElementById('print-root');
if (printRoot) {
ReactDOM.render(<AttachmentComponent />, printRoot);
}
// 4. 调用打印
setTimeout(() => {
iframe.contentWindow?.focus();
iframe.contentWindow?.print();
// 5. 打印完成后清理
setTimeout(() => {
document.body.removeChild(iframe);
}, 1000);
}, 300);
};
return (
<div style={{ padding: '24px' }}>
<div style={{ display: 'flex', gap: '16px', marginBottom: '24px' }}>
<Button type="primary" onClick={() => handlePrintByIframe(Attachment1)}>1</Button>
<Button type="primary" onClick={() => handlePrintByIframe(Attachment2)}>2</Button>
<Button type="primary" onClick={() => handlePrintByIframe(Attachment3)}>3</Button>
</div>
<div style={{ color: '#999', fontSize: '14px' }}>
使 Iframe ****
2
</div>
</div>
);
};
export default ComplaintForwardingProcess;

View File

@ -16,6 +16,7 @@ const authority: PageAuthority = {
'/realEstate/index': ['/realEstate/index'],
'/ComplaintApproval/index': ['/ComplaintApproval/index'],
'/ComplaintForwardingProcess/index': ['/ComplaintForwardingProcess/index'],
'/BusinessConfiguration/index': ['/BusinessConfiguration/index'],
};

View File

@ -595,3 +595,152 @@ export async function handleGetServerpartList(params?: any) {
}
// 查询业务环节配置的列表
export async function handleGetAPPROVALROUTEList(params?: any) {
const data = await requestSamember(`/BusinessProcess/GetAPPROVALROUTEList`, {
method: 'POST',
data: params,
});
if (data.Result_Code !== 100) {
return []
}
return data.Result_Data.List;
}
// 获取账号分类树
export async function getUserTypeTree(params?: any) {
const data = await requestSamember('/FrameWork/GetUserTypeTree', {
method: 'GET',
params
})
if (data.Result_Code !== 100) {
return []
}
const treeTable = wrapTreeNode(data.Result_Data.List);
return [...treeTable];
}
// 获取账号
export async function handleGetUserList(params?: any) {
const data = await requestSamember('/Platform/GetUserList', {
method: 'POST',
data: params
})
if (data.Result_Code !== 100) {
return data
}
return data.Result_Data.List
}
export async function getOnwer(params?: any) {
const data = await requestSamember('/BaseInfo/BindingOwnerUnitDDL', {
method: 'GET',
params
})
if (data.Result_Code !== 100) {
return data
}
return data.Result_Data.List
}
// 配置审批环节
export async function handleSynchroAPPROVALROUTE(params?: any) {
const data = await requestSamember('/BusinessProcess/SynchroAPPROVALROUTE', {
method: 'POST',
data: params
})
if (data.Result_Code !== 100) {
return data
}
return data
}
// 删除配置审批环节
export async function handleDeleteAPPROVALROUTE(params?: any) {
const data = await requestSamember(`/BusinessProcess/DeleteAPPROVALROUTE`, {
method: 'GET',
params
});
if (data.Result_Code !== 100) {
return data
}
return data;
}
// 获取用户建议表列表
export async function handleGetSUGGESTIONList(params?: any) {
const data = await requestSamember('/SiteManage/GetSUGGESTIONList', {
method: 'POST',
data: params
})
if (data.Result_Code !== 100) {
return data
}
return data.Result_Data.List
}
// 同步用户建议表
export async function handleSynchroSUGGESTION(params?: any) {
const data = await requestSamember('/SiteManage/SynchroSUGGESTION', {
method: 'POST',
data: params
})
if (data.Result_Code !== 100) {
return data
}
return data
}
// 删除用户建议表
export async function handleDeleteSUGGESTION(params?: any) {
const data = await requestSamember(`/SiteManage/DeleteSUGGESTION`, {
method: 'GET',
params
});
if (data.Result_Code !== 100) {
return data
}
return data;
}
// 同步审批意见
export async function handleSynchroAPPLYAPPROVE(params?: any) {
const data = await requestSamember(`/BusinessProcess/SynchroAPPLYAPPROVE`, {
method: 'POST',
data: params
});
if (data.Result_Code !== 100) {
return data
}
return data;
}
// 审批意见的流程过程
export async function handleGetAPPLYAPPROVEList(params?: any) {
const data = await requestSamember(`/BusinessProcess/GetAPPLYAPPROVEList`, {
method: 'POST',
data: params
});
if (data.Result_Code !== 100) {
return data
}
return data.Result_Data.List;
}