hejianhao
2026-01-20 fb7de884c037e138e068e3fcdccdd8e7390c70ea
feat(事故管理): 实现事故类型、账号和消息的完整功能

- 新增事故类型管理页面及表单
- 重构账号管理功能,支持关联委托方/项目
- 实现事故消息的增删改查及富文本编辑
- 添加城市级联选择组件
- 优化表单数据处理和错误提示
- 更新API接口调用逻辑
1个文件已添加
8个文件已修改
909 ■■■■■ 已修改文件
estate-frontend-v2024/src/api/screenSetting.js 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/common/constants.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/common/crud-view-base.jsx 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/screenSetting/accidentMessageSetting.jsx 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/screenSetting/accidentTypeSetting.jsx 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/screenSetting/accountSetting.jsx 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/screenSetting/forms/edit-accidentMessage-form.jsx 301 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/screenSetting/forms/edit-accidentType-form.jsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/views/screenSetting/forms/edit-account-form.jsx 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
estate-frontend-v2024/src/api/screenSetting.js
New file
@@ -0,0 +1,126 @@
import request from '@/utils/request'
export function getAccountList(data, params) {
  return request({
    url: `/admin/screen-account/list`,
    method: 'post',
    data,
    params
  })
}
export function addAccount(data) {
  return request({
    url: '/admin/screen-account/add',
    method: 'post',
    data
  })
}
export function editAccount(data) {
  return request({
    url: '/admin/screen-account/edit',
    method: 'post',
    data
  })
}
export function deleteAccount(id) {
  return request({
    url: `/admin/screen-account/delete/${id}`,
    method: 'delete'
  })
}
export function resetPassword(data) {
  return request({
    url: '/admin/screen-account/resetPassword',
    method: 'post',
    data
  })
}
export function enableAccount(id) {
  return request({
    url: `/admin/screen-account/enable/${id}`,
    method: 'post'
  })
}
export function getClientOptions() {
  return request({
    url: '/admin/entity/clients',
    method: 'get'
  })
}
export function getAccidentClassificationList(data, params) {
  return request({
    url: '/admin/accident-classification/list',
    method: 'post',
    data,
    params
  })
}
export function addAccidentClassification(data) {
  return request({
    url: '/admin/accident-classification/add',
    method: 'post',
    data
  })
}
export function editAccidentClassification(data) {
  return request({
    url: '/admin/accident-classification/edit',
    method: 'post',
    data
  })
}
export function deleteAccidentClassification(id) {
  return request({
    url: `/admin/accident-classification/delete/${id}`,
    method: 'delete'
  })
}
export function getAccidentBriefList(data, params) {
  return request({
    url: '/admin/accident-brief/list',
    method: 'post',
    data,
    params
  })
}
export function addAccidentBrief(data) {
  return request({
    url: '/admin/accident-brief/add',
    method: 'post',
    data
  })
}
export function editAccidentBrief(data) {
  return request({
    url: '/admin/accident-brief/edit',
    method: 'post',
    data
  })
}
export function deleteAccidentBrief(id) {
  return request({
    url: `/admin/accident-brief/delete/${id}`,
    method: 'delete'
  })
}
export function getAccidentBriefDetail(id) {
  return request({
    url: `/admin/accident-brief/detail/${id}`,
    method: 'get'
  })
}
estate-frontend-v2024/src/views/common/constants.js
@@ -3,7 +3,7 @@
// 定义全局变量
// export const BASE_URL = 'http://47.93.28.249:8181'
export const BASE_URL = 'http://221.182.45.100:8080';
export const BASE_URL = 'http://221.182.45.100:8180';
export const SCREEN_URL = 'http://221.182.45.100:8087/#/ ';
// export const BASE_URL = 'http://localhost:8080';
estate-frontend-v2024/src/views/common/crud-view-base.jsx
@@ -68,8 +68,8 @@
            .then(res => {
                if (res.data.status === 'OK') {
                    this.setState({
                        entities: res.data.data.list,
                        totalElements: res.data.data.totalElements
                        entities: res.data.data.list || res.data.data.records,
                        totalElements: res.data.data.totalElements || res.data.data.total
                    })
                } else {
                    message.error("获取数据失败" + res.data.errorMsg)
@@ -288,12 +288,15 @@
    // 处理删除实体
    handleDeleteEntity = (rows) => {
        // 如果传入的是单个对象,将其放入数组中
        if (!Array.isArray(rows)) {
            rows = [rows];
        }
        const ids = rows.map(row => row.id);
        console.log(ids);
        this.deleteEntities({
            ids: ids
        }).then(res => {
@@ -317,11 +320,13 @@
            if (err) {
                return;
            }
            console.log(this.editFormRef.state)
            this.setState({ editModalLoading: true, });
            let formState = this.editFormRef.state
            let action = formState.isEdit ? '编辑' : '添加';
            this.editEntity(formState.editableData, formState.isEdit).then((response) => {
                console.log(response);
                if (response.data.status === 'OK') {
                    form.resetFields();
                    message.success(action + "成功!")
@@ -333,10 +338,10 @@
                    this.reloadEntities()
                } else {
                    this.setState({ editModalLoading: false });
                    message.success(response.data.errorMsg || action + "失败,请重试")
                    message.error(response.data.errorMsg || action + "失败,请重试")
                }
            }).catch(e => {
                message.success(action + "失败,请重试! " + e.message)
                message.error(action + "失败,请重试! " + e.message)
            })
        });
    };
estate-frontend-v2024/src/views/screenSetting/accidentMessageSetting.jsx
@@ -1,89 +1,77 @@
import React from "react";
import { Card, Divider, Icon, Input, Select, Table } from "antd";
import { deleteDevice, editDevice, getDevicesList } from "@/api/devices";
import EditAccountMessageForm from "./forms/edit-accidentMessage-form";
import {
    getAccidentBriefList,
    addAccidentBrief,
    editAccidentBrief,
    deleteAccidentBrief
} from "@/api/screenSetting";
import EditAccidentMessageForm from "./forms/edit-accidentMessage-form";
import "./style/index.less";
import CrudViewBase from "../common/crud-view-base";
const { Column } = Table;
const { Option } = Select;
const inputLongStyle = {
  padding: "1px 10px",
  width: "250px",
};
class AccidentMessageSetting extends CrudViewBase {
  state = {
    ...this.state,
    entities: [],
  };
  initListBody() {
    return {
      name: "",
      keyword: "",
      deviceType: "",
      title: "",
    };
  }
  // 重写处理listBody变化的方法,当deviceType变化时加载表单
  handleListBodyChange = (key, value) => {
    this.setState(
      (prevState) => {
        const newListBody = { ...prevState.listBody, [key]: value };
        return { listBody: newListBody };
      },
      () => {
        this.reloadEntities();
      }
    );
  };
  getEntityList(pageInfo, listBody) {
    return getDevicesList(pageInfo, listBody);
    const params = {
        title: listBody.title
    };
    return getAccidentBriefList(pageInfo, params);
  }
  editEntity(data, isEdit) {
    return editDevice(data, isEdit);
    // 数据清洗与转换
    // 1. 处理级联选择结果
    if (data.cascaderValue && data.cascaderValue.length > 0) {
        data.province = data.cascaderValue[0] || '';
        data.city = data.cascaderValue[1] || '';
        data.area = data.cascaderValue[2] || '';
    }
    if (this.editFormRef && this.editFormRef.state.editableData) {
        data.img = this.editFormRef.state.editableData.img.name;
    }
    // 3. 删除多余字段
    const allowedFields = [
        'title', 'classifyId', 'area', 'city', 'province',
        'casualties', 'time', 'content', 'detail', 'img', 'state'
    ];
    // 如果是编辑,保留 id
    if (isEdit) {
        data.id = this.state.currentRowData.id;
        allowedFields.push('id');
    }
    const payload = {};
    allowedFields.forEach(field => {
        if (data[field] !== undefined) {
            payload[field] = data[field];
        }
    });
    if (isEdit) {
      return editAccidentBrief(payload);
    } else {
      return addAccidentBrief(payload);
    }
  }
  deleteEntities(ids) {
    return deleteDevice(ids);
    return deleteAccidentBrief(ids.ids[0]);
  }
  // 处理重置密码实体确认
  handleResetPasswordEntityOk = (_) => {
    const { form } = this.resetPasswordFormRef.props;
    form.validateFields((err) => {
      if (err) {
        return;
      }
      this.setState({ resetPasswordModalLoading: true });
      let formState = this.resetPasswordFormRef.state;
      console.log(formState.editableData);
      // this.editEntity(formState.editableData, formState.isEdit)
      //   .then((response) => {
      //     if (response.data.status === "OK") {
      //       form.resetFields();
      //       message.success(action + "成功!");
      //       this.setState({
      //         resetPasswordModalVisible: false,
      //         resetPasswordModalLoading: false,
      //       });
      //       this.onResetPasswordSuccess();
      //       this.reloadEntities();
      //     } else {
      //       this.setState({ resetPasswordModalLoading: false });
      //       message.success(response.data.errorMsg || action + "失败,请重试");
      //     }
      //   })
      //   .catch((e) => {
      //     message.success(action + "失败,请重试! " + e.message);
      //   });
    });
  };
  render() {
    const { entities } = this.state;
@@ -92,27 +80,14 @@
        <div>
          <Input
            style={inputLongStyle}
            placeholder="请输入姓名"
            placeholder="请输入标题"
            value={this.state.listBody ? this.state.listBody.title : ""}
            onChange={(e) => {
              this.handleListBodyChange("keyword", e.target.value);
              this.handleListBodyChange("title", e.target.value);
            }}
            prefix={<Icon style={{ marginLeft: "4px" }} type="search" />}
            allowClear
          />
          &nbsp;
          <Select
            style={{
              width: 250,
            }}
            placeholder="账号级别"
            onChange={(value) => {
              this.handleListBodyChange("deviceType", value);
            }}
          >
            <Option value={1}>委托方</Option>
            <Option value={2}>项目</Option>
          </Select>
          &nbsp; &nbsp;
        </div>
        &nbsp; &nbsp;
        <div className="right">{this.renderAddButton("device:add")}</div>
@@ -130,65 +105,66 @@
          >
            <Column
              title="序号"
              width={125}
              width={80}
              dataIndex="id"
              key="id"
              align="center"
            />
            <Column
              title="标题"
              width={125}
              dataIndex="name"
              key="name"
              width={150}
              dataIndex="title"
              key="title"
              align="center"
            />
            <Column
              title="事故类型"
              width={125}
              dataIndex="account"
              key="account"
              width={120}
              dataIndex="classifyId" // 暂时显示ID,或者需要后端返回名称
              key="classifyId"
              align="center"
            />
            <Column
              title="发生地点"
              width={125}
              dataIndex="accountType"
              key="accountType"
              width={200}
              key="location"
              align="center"
              render={(text, row) => (
                  <span>{`${row.province || ''}${row.city || ''}${row.area || ''}`}</span>
              )}
            />
            <Column
              title="伤亡情况"
              width={125}
              width={150}
              dataIndex="casualties"
              key="casualties"
              align="center"
            />
            <Column
              title="发生时间"
              dataIndex="date"
              key="date"
              dataIndex="time"
              key="time"
              align="center"
              width={150}
            />
            <Column
              title="状态"
              key="state"
              align="center"
              width={100}
              render={(text, row) => (
                <span style={{ color: row.state ? "yellow" : "inherit" }}>
                  {row.state ? "启用" : "禁用"}
                <span style={{ color: row.state === 1 ? "green" : "red" }}>
                  {row.state === 1 ? "启用" : "禁用"}
                </span>
              )}
            />
            <Column
              title="操作"
              key="action"
              width={260}
              fixed="right"
              width={200}
              align="center"
              render={(text, row) => (
                <span>
                  {this.renderEnableDisableButton(row, "device:edit")}
                  <Divider type="vertical" />
                  {this.renderEditButton(row, "device:edit")}
                  <Divider type="vertical" />
                  {this.renderDeleteButton(row, "device:delete")}
@@ -199,7 +175,7 @@
          <br />
          {this.renderPagination()}
        </Card>
        <EditAccountMessageForm
        <EditAccidentMessageForm
          currentRowData={this.state.currentRowData}
          wrappedComponentRef={(formRef) => (this.editFormRef = formRef)}
          visible={this.state.editModalVisible}
estate-frontend-v2024/src/views/screenSetting/accidentTypeSetting.jsx
@@ -1,6 +1,11 @@
import React from "react";
import { Card, Divider, Icon, Input, Table } from "antd";
import { deleteDevice, editDevice, getDevicesList } from "@/api/devices";
import {
  getAccidentClassificationList,
  addAccidentClassification,
  editAccidentClassification,
  deleteAccidentClassification
} from "@/api/screenSetting";
import EditAccidentTypeForm from "./forms/edit-accidentType-form";
import "./style/index.less";
import CrudViewBase from "../common/crud-view-base";
@@ -12,42 +17,31 @@
};
class AccidentTypeSetting extends CrudViewBase {
  state = {
    ...this.state,
    entities: [],
  };
  initListBody() {
    return {
      keyword: "",
      deviceType: "",
      name: "",
    };
  }
  // 重写处理listBody变化的方法,当deviceType变化时加载表单
  handleListBodyChange = (key, value) => {
    this.setState(
      (prevState) => {
        const newListBody = { ...prevState.listBody, [key]: value };
        return { listBody: newListBody };
      },
      () => {
        this.reloadEntities();
      }
    );
  };
  getEntityList(pageInfo, listBody) {
    return getDevicesList(pageInfo, listBody);
    // 接口要求:body传分页(page, size),query传name
    // listBody 包含 name
    const params = {
        name: listBody.name
    };
    return getAccidentClassificationList(pageInfo, params);
  }
  editEntity(data, isEdit) {
    return editDevice(data, isEdit);
    if (isEdit) {
      return editAccidentClassification(data);
    } else {
      return addAccidentClassification(data);
    }
  }
  deleteEntities(ids) {
    return deleteDevice(ids);
    return deleteAccidentClassification(ids.ids[0]);
  }
  render() {
@@ -58,8 +52,9 @@
          <Input
            style={inputLongStyle}
            placeholder="请输入分类"
            value={this.state.listBody ? this.state.listBody.name : ""}
            onChange={(e) => {
              this.handleListBodyChange("keyword", e.target.value);
              this.handleListBodyChange("name", e.target.value);
            }}
            prefix={<Icon style={{ marginLeft: "4px" }} type="search" />}
            allowClear
@@ -84,6 +79,7 @@
              dataIndex="id"
              key="id"
              align="center"
              width={80}
            />
            <Column
              title="事故类型"
@@ -94,13 +90,13 @@
            <Column
              title="操作"
              key="action"
              width={360}
              width={200}
              align="center"
              render={(text, row) => (
                <span>
                  {this.renderEditButton(row, "device:edit")}
                  {this.renderEditButton(row, "device:add")}
                  <Divider type="vertical" />
                  {this.renderDeleteButton(row, "device:delete")}
                  {this.renderDeleteButton(row, "device:add")}
                </span>
              )}
            />
estate-frontend-v2024/src/views/screenSetting/accountSetting.jsx
@@ -1,11 +1,13 @@
import React from "react";
import { Card, Divider, Icon, Input, Select, Table } from "antd";
import { Card, Divider, Icon, Input, Select, Table, message, Modal } from "antd";
import {
  deleteDevice,
  editDevice,
  getDevicesList,
  loadDeviceForm,
} from "@/api/devices";
  deleteAccount,
  editAccount,
  addAccount,
  getAccountList,
  resetPassword,
  enableAccount
} from "@/api/screenSetting";
import EditAccountForm from "./forms/edit-account-form";
import ResetPasswordForm from "./forms/reset-password-form";
@@ -20,75 +22,98 @@
};
class AccountSetting extends CrudViewBase {
  state = {
    ...this.state,
    entities: [],
  };
  // initListBody 实现基类抽象方法
  initListBody() {
    return {
      name: "",
      keyword: "",
      deviceType: "",
      type: undefined,
    };
  }
  // 重写处理listBody变化的方法,当deviceType变化时加载表单
  handleListBodyChange = (key, value) => {
    this.setState(
      (prevState) => {
        const newListBody = { ...prevState.listBody, [key]: value };
        return { listBody: newListBody };
      },
      () => {
        this.reloadEntities();
      }
    );
  };
  // getEntityList 实现基类抽象方法
  getEntityList(pageInfo, listBody) {
    return getDevicesList(pageInfo, listBody);
    return getAccountList(pageInfo, listBody);
  }
  // editEntity 实现基类抽象方法
  editEntity(data, isEdit) {
    return editDevice(data, isEdit);
    const submitData = { ...data };
    if (Array.isArray(submitData.clientProjectIds)) {
      submitData.clientProjectIds = submitData.clientProjectIds.join(',');
    }
    submitData.state = submitData.state ? 1 : 2;
    if (isEdit) {
      return editAccount(submitData);
    } else {
      return addAccount(submitData);
    }
  }
  deleteEntities(ids) {
    return deleteDevice(ids);
    return deleteAccount(ids.ids[0])
  }
  // 处理重置密码实体确认
  // 自定义方法:启用/禁用
  handleEnableDisableEntity = (row) => {
    // 增加一个二次确认
    Modal.confirm({
      title: "确认操作",
      content: `确定${row.state === 1 ? "禁用" : "启用"}账号 ${row.name}吗?`,
      okText: "确认",
      onOk: () => {
        enableAccount(row.id).then(res => {
          if (res && res.data && (res.data.status === 'OK' || res.data.code === 200)) {
            message.success("操作成功");
            this.reloadEntities();
          } else {
            message.error((res && res.data && res.data.errorMsg) || "操作失败");
          }
        }).catch(e => {
          message.error("操作失败: " + e.message);
        })
      }
    });
  }
  // 自定义方法:重置密码确认
  handleResetPasswordEntityOk = (_) => {
    const { form } = this.resetPasswordFormRef.props;
    form.validateFields((err) => {
    form.validateFields((err, values) => {
      if (err) {
        return;
      }
      this.setState({ resetPasswordModalLoading: true });
      let formState = this.resetPasswordFormRef.state;
      console.log(formState.editableData);
      // this.editEntity(formState.editableData, formState.isEdit)
      //   .then((response) => {
      //     if (response.data.status === "OK") {
      //       form.resetFields();
      //       message.success(action + "成功!");
      //       this.setState({
      //         resetPasswordModalVisible: false,
      //         resetPasswordModalLoading: false,
      //       });
      //       this.onResetPasswordSuccess();
      //       this.reloadEntities();
      //     } else {
      //       this.setState({ resetPasswordModalLoading: false });
      //       message.success(response.data.errorMsg || action + "失败,请重试");
      //     }
      //   })
      //   .catch((e) => {
      //     message.success(action + "失败,请重试! " + e.message);
      //   });
      const payload = {
        id: this.state.currentRowData.id,
        password: values.password
      };
      resetPassword(payload)
        .then((response) => {
          if (response && response.data && (response.data.status === 'OK' || response.data.code === 200)) {
            form.resetFields();
            message.success("重置密码成功!");
            this.setState({
              resetPasswordModalVisible: false,
              resetPasswordModalLoading: false,
            });
          } else {
            this.setState({ resetPasswordModalLoading: false });
            message.error((response && response.data && response.data.errorMsg) || "重置密码失败");
          }
        })
        .catch((e) => {
          this.setState({ resetPasswordModalLoading: false });
          message.error("重置密码失败: " + e.message);
        });
    });
  };
@@ -101,7 +126,7 @@
            style={inputLongStyle}
            placeholder="请输入姓名"
            onChange={(e) => {
              this.handleListBodyChange("keyword", e.target.value);
              this.handleListBodyChange("name", e.target.value);
            }}
            prefix={<Icon style={{ marginLeft: "4px" }} type="search" />}
            allowClear
@@ -109,7 +134,7 @@
          &nbsp;
          <Input
            style={inputLongStyle}
            placeholder="请输入委托方/项目名称"
            placeholder="请输入关键字"
            onChange={(e) => {
              this.handleListBodyChange("keyword", e.target.value);
            }}
@@ -123,8 +148,9 @@
            }}
            placeholder="账号级别"
            onChange={(value) => {
              this.handleListBodyChange("deviceType", value);
              this.handleListBodyChange("type", value);
            }}
            allowClear
          >
            <Option value={1}>委托方</Option>
            <Option value={2}>项目</Option>
@@ -147,7 +173,7 @@
          >
            <Column
              title="序号"
              width={125}
              width={80}
              dataIndex="id"
              key="id"
              align="center"
@@ -169,21 +195,26 @@
            <Column
              title="账号级别"
              width={125}
              dataIndex="accountType"
              key="accountType"
              dataIndex="type"
              key="type"
              align="center"
              render={(text, row) => (
                <span>{row.accountType === 1 ? "委托方" : "项目"}</span>
                <span>{row.type === 1 ? "委托方" : "项目"}</span>
              )}
            />
            <Column title="关联委托方/项目" key="type" align="center" />
            <Column
              title="关联委托方/项目"
              key="clientOrProject"
              dataIndex="clientOrProject"
              align="center"
            />
            <Column
              title="状态"
              key="state"
              align="center"
              render={(text, row) => (
                <span style={{ color: row.state ? "yellow" : "inherit" }}>
                  {row.state ? "启用" : "禁用"}
                <span style={{ color: row.state === 1 ? "green" : "red" }}>
                  {row.state === 1 ? "启用" : "禁用"}
                </span>
              )}
            />
@@ -195,9 +226,9 @@
              align="center"
              render={(text, row) => (
                <span>
                  {this.renderEnableDisableButton(row, "device:edit")}
                  {this.renderEnableDisableButton(row)}
                  <Divider type="vertical" />
                  {this.renderResetPasswordButton(row, "device:delete")}
                  {this.renderResetPasswordButton(row, "device:edit")}
                  <Divider type="vertical" />
                  {this.renderEditButton(row, "device:edit")}
                  <Divider type="vertical" />
@@ -232,4 +263,4 @@
  }
}
export default AccountSetting;
export default AccountSetting;
estate-frontend-v2024/src/views/screenSetting/forms/edit-accidentMessage-form.jsx
@@ -9,16 +9,19 @@
  Switch,
  Upload,
  Icon,
  message
} from "antd";
import "../style/index.less";
import moment from "moment/moment";
import moment from "moment";
import { checkFileSize } from "../../common/common_func";
import EditFormBase from "../../common/edit-form-base";
import { inputLongStyle } from "../../common/constants";
import { EditorState, convertToRaw } from "draft-js";
import { EditorState, convertToRaw, ContentState } from "draft-js";
import { Editor } from "react-draft-wysiwyg";
import draftToHtml from "draftjs-to-html";
import htmlToDraft from "html-to-draftjs";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import { getAccidentClassificationList } from "@/api/screenSetting";
const { Option } = Select;
@@ -31,22 +34,169 @@
  });
}
class EditAccountMessageForm extends EditFormBase {
class EditAccidentMessageForm extends EditFormBase {
  state = {
    ...this.state,
    classificationOptions: [],
    cityOptions: [],
  };
  initView() {
    this.loadClassificationOptions();
    this.loadCityOptions();
  }
  loadClassificationOptions = () => {
    getAccidentClassificationList({ page: 0, size: 9999 }, {}).then(res => {
      if (res && res.data && res.data.data && res.data.data.records) {
        this.setState({ classificationOptions: res.data.data.records });
      }
    });
  };
  loadCityOptions = async () => {
    // 初始加载全国省份
    const provinces = await this.getDistrictData();
    this.setState({ cityOptions: provinces });
  };
  getDistrictData = (keyword = "", level = 0) => {
    // subdistrict=1 只获取下一级
    const url = `https://restapi.amap.com/v3/config/district?keywords=${keyword}&subdistrict=1&key=aeec2a4b436322753dd2458fde89506b`;
    return fetch(url)
      .then(response => response.json())
      .then(data => {
        if (data.status === "1" && data.districts && data.districts.length > 0) {
          // 如果是空关键词查询全国,districts[0] 是中国,取其 children
          // 如果是具体关键词(如山东省),districts[0] 是山东省,取其 children
          const root = data.districts[0];
          const list = root.districts || [];
          return list.map(node => ({
            value: node.name,
            label: node.name,
            isLeaf: node.level === "district", // 区县级为叶子节点
            level: node.level
          }));
        }
        return [];
      })
      .catch(e => {
        console.error("加载城市数据失败", e);
        return [];
      });
  };
  onLoadData = async (selectedOptions) => {
    const targetOption = selectedOptions[selectedOptions.length - 1];
    targetOption.loading = true;
    const children = await this.getDistrictData(targetOption.label);
    targetOption.loading = false;
    if (children && children.length > 0) {
      targetOption.children = children;
    } else {
      targetOption.isLeaf = true;
    }
    this.setState({
      cityOptions: [...this.state.cityOptions],
    });
  };
  initEditableData() {
    return {
      title: "",
      type: 1,
      casualties: "",
      description: "",
      date: null,
      detail: "",
      status: 1,
      classifyId: undefined,
      area: "",
      city: "",
      province: "",
      casualties: "",
      time: null,
      content: "",
      detail: "",
      img: "",
      state: 1,
      cascaderValue: [],
      file: [],
      editorState: EditorState.createEmpty(),
      previewVisible: false,
      previewImage: "",
      editorState: EditorState.createEmpty(),
    };
  }
  async ensureCascaderData(province, city) {
    let provinces = this.state.cityOptions;
    // 如果省份数据还没加载,先加载
    if (!provinces || provinces.length === 0) {
        provinces = await this.getDistrictData();
    }
    // 找到对应省份节点
    const provinceNode = provinces.find(p => p.value === province);
    if (provinceNode) {
        // 如果该省份没有子节点(市),加载市
        if (!provinceNode.children || provinceNode.children.length === 0) {
             const cities = await this.getDistrictData(province);
             provinceNode.children = cities;
        }
        // 如果有市,继续加载区
        if (city) {
            const cityNode = provinceNode.children.find(c => c.value === city);
            if (cityNode) {
                if (!cityNode.children || cityNode.children.length === 0) {
                    const areas = await this.getDistrictData(city);
                    cityNode.children = areas;
                }
            }
        }
    }
    this.setState({ cityOptions: [...provinces] });
  }
  viewWillAppear() {
     const { detail, img, province, city, area } = this.state.editableData;
     let editorState = EditorState.createEmpty();
     if (detail) {
         const contentBlock = htmlToDraft(detail);
         if (contentBlock) {
             const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
             editorState = EditorState.createWithContent(contentState);
         }
     }
     let file = [];
     if (img) {
         file = [{
             uid: '-1',
             name: 'image.png',
             status: 'done',
             url: img
         }];
     }
     let cascaderValue = [];
     if (province) cascaderValue.push(province);
     if (city) cascaderValue.push(city);
     if (area) cascaderValue.push(area);
     // 预加载级联数据以支持回显
     if (province) {
         this.ensureCascaderData(province, city);
     }
     this.setState({
         editableData: {
             ...this.state.editableData,
             editorState,
             file,
             cascaderValue
         }
     });
  }
  onEditorStateChange = (editorState) => {
@@ -82,27 +232,43 @@
    });
  };
  handleCascaderChange = (value) => {
      this.setFieldValue("cascaderValue", value);
      this.setFieldValue("province", value[0] || "");
      this.setFieldValue("city", value[1] || "");
      this.setFieldValue("area", value[2] || "");
  }
  handleUploadChange = ({ file, fileList }) => {
    this.setFieldValue("file", fileList);
    // 如果没有文件了,清空 img
    if (fileList.length === 0) {
        this.setFieldValue("img", "");
    }
  }
  render() {
    const { visible, onCancel, onOk, form } = this.props;
    const { getFieldDecorator } = form;
    const {
      title,
      type,
      classifyId,
      casualties,
      description,
      date,
      content,
      time,
      detail,
      status,
      city,
      state,
      file,
      editorState,
      previewVisible,
      previewImage,
      cascaderValue
    } = this.state.editableData;
    const uploadButton = (
      <div>
        <Icon type="plus" />
        <div className="ant-upload-text">上传</div>
      </div>
    );
@@ -127,73 +293,68 @@
                onChange: this.handleFieldChange("title"),
              })(<Input style={inputLongStyle} placeholder="请输入标题" />)}
            </Form.Item>
            <Form.Item label="类别">
              {getFieldDecorator("type", {
              {getFieldDecorator("classifyId", {
                rules: [{ required: true, message: "请选择类别!" }],
                initialValue: type,
                onChange: this.handleFieldChange("type"),
                initialValue: classifyId,
                onChange: this.handleFieldChange("classifyId"),
              })(
                <Select style={inputLongStyle} placeholder="请选择关联委托方">
                  <Option value={1}>委托方1</Option>
                  <Option value={2}>委托方2</Option>
                <Select style={inputLongStyle} placeholder="请选择类别">
                  {this.state.classificationOptions.map(option => (
                      <Option key={option.id} value={option.id}>{option.name}</Option>
                  ))}
                </Select>,
              )}
            </Form.Item>
            <Form.Item label="发生城市">
              {getFieldDecorator("city", {
                initialValue: city,
                onChange: this.handleFieldChange("city"),
              {getFieldDecorator("cascaderValue", {
                initialValue: cascaderValue,
                onChange: this.handleCascaderChange,
                rules: [{ required: true, message: "请选择发生城市!" }],
              })(
                <Cascader
                  style={{ width: "250px" }}
                  options={[
                    {
                      value: "1",
                      label: "北京",
                    },
                    {
                      value: "2",
                      label: "上海",
                    },
                  ]}
                  expandTrigger="hover"
                />,
                  options={this.state.cityOptions}
                  loadData={this.onLoadData}
                  changeOnSelect
                  placeholder="请选择发生城市"
                  style={inputLongStyle}
                />
              )}
            </Form.Item>
            <Form.Item label="造成伤亡情况">
            <Form.Item label="伤亡情况">
              {getFieldDecorator("casualties", {
                rules: [{ required: true, message: "请选择启用状态!" }],
                rules: [{ required: true, message: "请输入伤亡情况!" }],
                initialValue: casualties,
                onChange: this.handleFieldChange("casualties"),
              })(
                <Input
                  style={inputLongStyle}
                  placeholder="请输入造成伤亡情况"
                  placeholder="请输入伤亡情况"
                />,
              )}
            </Form.Item>
            <Form.Item label="封面图">
            <Form.Item label="图片">
              {getFieldDecorator("file", {
                valuePropName: "file",
                // getValueFromEvent: singleFile,
                initialValue: file,
                rules: [{ required: true, message: "请上传封面图" }],
                rules: [{ required: true, message: "请上传图片!" }],
              })(
                <div>
                  <Upload
                    disabled={this.uploading}
                    name={"file"}
                    name="file"
                    maxCount={1}
                    listType="picture-card"
                    multiple={false}
                    customRequest={this.uploadAttachment.bind(null, "file")}
                    onRemove={() => {
                      this.setFieldValue("file", []);
                    }}
                    showUploadList={true}
                    customRequest={this.uploadAttachment.bind(this, "img")}
                    onChange={this.handleUploadChange}
                    onPreview={this.handlePreview}
                    beforeUpload={checkFileSize}
                    accept="image/*"
                    fileList={file}
                  >
                    {(!file || file.length === 0) ? uploadButton : null}
                  </Upload>
@@ -211,28 +372,39 @@
                </div>,
              )}
            </Form.Item>
            <Form.Item label="情况简述">
              {getFieldDecorator("description", {
              {getFieldDecorator("content", {
                rules: [{ required: true, message: "请输入情况简述!" }],
                initialValue: description,
                onChange: this.handleFieldChange("description"),
                initialValue: content,
                onChange: this.handleFieldChange("content"),
              })(<Input style={inputLongStyle} placeholder="请输入情况简述" />)}
            </Form.Item>
            <Form.Item label="发生时间">
              {getFieldDecorator("date", {
                rules: [{ required: true, message: "请输入日期" }],
                initialValue: date ? moment(date) : null,
                onChange: this.handleDateFieldChange("date"),
              {getFieldDecorator("time", {
                rules: [{ required: true, message: "请输入发生时间" }],
                initialValue: time ? moment(time) : null,
                onChange: this.handleDateFieldChange("time"),
              })(
                <DatePicker style={{ width: "250px" }} format={"YYYY-MM-DD"} />,
                <DatePicker showTime style={{ width: "250px" }} format={"YYYY-MM-DD HH:mm:ss"} />,
              )}
            </Form.Item>
            <Form.Item label="启用状态">
                {getFieldDecorator("state", {
                rules: [{ required: true, message: "请选择启用状态!" }],
                initialValue: state,
                onChange: (value) => this.setFieldValue("state", value ? 1 : 2), // Switch返回bool,后端要1/2
                })(<Switch checked={state === 1} />)}
            </Form.Item>
          </div>
          <Form.Item label="详情">
            {getFieldDecorator("detail", {
              rules: [{ required: true, message: "请输入详情" }],
              initialValue: detail,
              onChange: this.handleFieldChange("detail"),
              // onChange 由 onEditorStateChange 处理
            })(
              <div style={{ marginTop: 10 }}>
                <Editor
@@ -246,19 +418,12 @@
              </div>,
            )}
          </Form.Item>
          <Form.Item label="启用状态">
            {getFieldDecorator("status", {
              rules: [{ required: true, message: "请选择启用状态!" }],
              initialValue: status,
              onChange: this.handleFieldChange("status"),
            })(<Switch checked={status === 1} />)}
          </Form.Item>
        </Form>
      </Modal>
    );
  }
}
export default Form.create({ name: "EditAccountMessageForm" })(
  EditAccountMessageForm,
export default Form.create({ name: "EditAccidentMessageForm" })(
  EditAccidentMessageForm,
);
estate-frontend-v2024/src/views/screenSetting/forms/edit-accidentType-form.jsx
@@ -4,7 +4,7 @@
import EditFormBase from "../../common/edit-form-base";
import { inputLongStyle } from "../../common/constants";
class EditDeviceForm extends EditFormBase {
class EditAccidentTypeForm extends EditFormBase {
  initEditableData() {
    return {
      name: "",
@@ -37,4 +37,4 @@
  }
}
export default Form.create({ name: "EditDeviceForm" })(EditDeviceForm);
export default Form.create({ name: "EditAccidentTypeForm" })(EditAccidentTypeForm);
estate-frontend-v2024/src/views/screenSetting/forms/edit-account-form.jsx
@@ -2,17 +2,44 @@
import { Form, Input, Modal, Select, Switch, Radio } from "antd";
import "../style/index.less";
import EditFormBase from "../../common/edit-form-base";
import {  inputLongStyle } from "../../common/constants";
import { inputLongStyle } from "../../common/constants";
import { getClientOptions } from "@/api/screenSetting";
const { Option } = Select;
class EditDeviceForm extends EditFormBase {
class EditAccountForm extends EditFormBase {
  state = {
    ...this.state,
    clientOptions: []
  };
  initView() {
    this.loadClientOptions();
  }
  loadClientOptions = () => {
    getClientOptions().then(response => {
      if (response && response.data) {
        let options = [];
        if (response.data.data && Array.isArray(response.data.data)) {
          options = response.data.data;
        } else if (Array.isArray(response.data)) {
          options = response.data;
        }
        this.setState({ clientOptions: options });
      }
    }).catch(e => {
      console.error("加载委托方列表失败", e);
      this.setState({ clientOptions: [] });
    });
  };
  initEditableData() {
    return {
      name: "",
      type: 1,
      status: 1,
      accountType: 1,
      state: 1,
      clientProjectIds: [],
      account: "",
      password: "",
    };
@@ -21,7 +48,17 @@
  render() {
    const { visible, onCancel, onOk, form } = this.props;
    const { getFieldDecorator } = form;
    const { name, status, account, password, type, accountType } = this.state.editableData;
    let { name, state, account, password, type, clientProjectIds } = this.state.editableData;
    // Handle string to array conversion for Select mode="multiple"
    // If it's a string (from backend), split it. If it's already array (from user interaction), keep it.
    let selectedClientIds = [];
    if (typeof clientProjectIds === 'string' && clientProjectIds) {
      selectedClientIds = clientProjectIds.split(',');
    } else if (Array.isArray(clientProjectIds)) {
      selectedClientIds = clientProjectIds.map(String); // Ensure strings for Select
    }
    return (
      <Modal
        title={this.state.isEdit ? "编辑账号" : "添加账号"}
@@ -50,24 +87,25 @@
              </Radio.Group>
            )}
          </Form.Item>
          <Form.Item label="关联委托方">
            {getFieldDecorator("accountType", {
              rules: [{ required: true, message: "请选择关联委托方!" }],
              initialValue: accountType,
              onChange: this.handleFieldChange("accountType"),
          <Form.Item label="关联委托方/项目">
            {getFieldDecorator("clientProjectIds", {
              rules: [{ required: true, message: "请选择关联委托方/项目!" }],
              initialValue: selectedClientIds,
              onChange: this.handleFieldChange("clientProjectIds"),
            })(
              <Select style={inputLongStyle} mode="multiple" placeholder="请选择关联委托方">
                <Option value={1}>委托方1</Option>
                <Option value={2}>委托方2</Option>
              <Select style={inputLongStyle} mode="multiple" placeholder="请选择关联委托方/项目">
                {this.state.clientOptions.map(option => (
                  <Option key={option.code} value={option.code.toString()}>{option.name}</Option>
                ))}
              </Select>
            )}
          </Form.Item>
          <Form.Item label="启用状态">
            {getFieldDecorator("status", {
              rules: [{ required: true, message: "请选择启用状态!" }],
              initialValue: status,
              onChange: this.handleFieldChange("status"),
            })(<Switch checked={status === 1} />)}
            {getFieldDecorator("state", {
              initialValue: state === 1,
              valuePropName: 'checked',
              onChange: (checked) => this.handleFieldChange("state")(checked ? 1 : 2),
            })(<Switch checkedChildren="启用" unCheckedChildren="禁用" />)}
          </Form.Item>
          {!this.state.isEdit && (
            <Form.Item label="登录账号">
@@ -93,4 +131,4 @@
  }
}
export default Form.create({ name: "EditDeviceForm" })(EditDeviceForm);
export default Form.create({ name: "EditAccountForm" })(EditAccountForm);