commit 1476e3276ece54f381bad5b906ac36783a56f8ba Author: LiuJiaNan <15703339975@163.com> Date: Sat Aug 22 09:30:25 2026 +0800 init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..271822f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b840685 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# production +/dist +/demo + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.idea +yarn.lock \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a3da031 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# 微应用模板说明文档 + +## 在线文档 + +https://www.yuque.com/buhangjiecheshen-ymbtb/qc0093/gxdun1dphetcurko + + +## 安装依赖 +项目依赖可通过 **yarn** 或 **npm** 进行安装: + +```bash +# 使用 yarn +yarn + +# 或使用 npm +npm i +``` + +## 开发服务&打包应用 + +```bash +# 启动开发服务 +yarn serve: +# 或 +npm run serve: + +# 开发环境打包 +yarn build: +# 或 +npm run build: +``` + +## 路由配置&路由访问&自动化路由 +所有页面必须放在`src/pages/container`目录下,启动访问页面请在浏览器地址栏输入`//container/<你的路由页面文件名称>` +解释: +1. 所有页面组件命名为`index.js`或`index.jsx`,必须放在一个首字母大写的文件中。 +2. `container`为固定路径访问格式 +3. ``为应用的唯一标识符,也是应用路由的`basename`,在底座中用于区分其他应用。可在根目录 `jjb.config.js` 文件的 `appIdentifier` 节点中进行修改。 +4. 自动化路由将根据`pages/container`中的路由页面文件自动生成路由树。 +5. `id`匹配路由,文件夹命名`_id` + +## 应用接口环境配置 +应用接口环境相关配置在根目录 `jjb.config.js` 文件的 `environment` 节点中进行定义。 + +## 应用开发服务配置 +应用开发服务相关配置在根目录 `jjb.config.js` 文件的 `server` 节点中进行定义。 + +## Babel 配置 +应用的 `Babel` 配置在根目录 `jjb.babel.js` 文件中进行管理。 + +## 目录说明 + +1. `src/api/` 配置各个 store 模块的接口数据。 +2. `src/components/` 全局公共组件。 +3. `src/enumerate/` 全局各种枚举配置。 +4. `src/pages/` 页面文件目录。 +5. `src/main.js` 应用的入口文件。 + +## 核心依赖 +1. `@cqsjjb/jjb-common-decorator` + 1. 公共装饰器库,内部包含: + 1. 按钮权限处理 + 2. antd/Table 控制 + 3. 文本重命名处理 + 4. 具体使用方式可参考各个模块的 `d.ts`。 +2. `@cqsjjb/jjb-common-lib` + 1. 公共工具库,具体 API 使用请查看 `d.ts` +3. `@cqsjjb/jjb-dva-runtime` + 1. 核心运行时,基于 `dvajs` 实现。 + 1. 应用核心依赖模块 + 2. 应用的自动化路由 + 3. `store` 模块接口数据处理 + 4. 均基于此依赖实现,具体使用方式请查看 `d.ts`。 +4. `@cqsjjb/jjb-react-admin-component` + 1. 公共组件库,具体组件使用方式请查看 `d.ts`。 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..fab559e --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,49 @@ +import antfu from "@antfu/eslint-config"; + +export default antfu({ + formatters: { + html: false, + css: true, + }, + test: false, + typescript: true, + react: true, + vue: false, + markdown: false, + stylistic: { + semi: true, + quotes: "double", + }, + overrides: { + react: { + "react/no-comment-textnodes": "off", + "react-hooks-extra/no-unnecessary-use-prefix": "off", + "react-hooks-extra/prefer-use-state-lazy-initialization": "off", + "react-hooks/exhaustive-deps": "off", + "react/no-implicit-key": "off", + }, + javascript: { + "no-console": process.env.NODE_ENV === "production" ? "error" : "warn", + "no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn", + "no-alert": process.env.NODE_ENV === "production" ? "error" : "warn", + "no-restricted-syntax": [ + "error", + { + selector: "VariableDeclarator[id.name='pd']", + message: "不允许使用 pd,请改用有语义化的变量名", + }, + { + selector: "ObjectExpression > Property[key.name='pd']", + message: "不允许使用 pd,请改用有语义化的变量名", + }, + ], + "no-unused-vars": ["error", { varsIgnorePattern: "^React$" }], + }, + }, + rules: { + "antfu/top-level-function": "off", + "node/prefer-global/process": "off", + "dot-notation": "off", + "linebreak-style": ["off", "windows"], + }, +}); diff --git a/jjb.babel.js b/jjb.babel.js new file mode 100644 index 0000000..e58cbef --- /dev/null +++ b/jjb.babel.js @@ -0,0 +1,23 @@ +module.exports = { + compact: false, + // 插件 + plugins: [ + [ + "@babel/plugin-proposal-decorators", + { + legacy: true, + }, + ], + ], + // 预设 + presets: [ + ["@babel/preset-env", { + targets: { + browsers: ["ie >= 10"], + }, + }], + ["@babel/preset-react", { + runtime: "automatic", + }], + ], +}; diff --git a/jjb.config.js b/jjb.config.js new file mode 100644 index 0000000..25e492e --- /dev/null +++ b/jjb.config.js @@ -0,0 +1,74 @@ +module.exports = { + // 应用后端git地址,部署上线需要 + javaGit: "", + // 应用后端仓库名称,部署上线需要 + javaGitName: "", + // 环境配置 + environment: { + development: { + // 应用后端分支名称,部署上线需要 + javaGitBranch: "", + // 接口服务地址 + API_HOST: "https://gbs-gateway.qhdsafety.com", + }, + production: { + // 应用后端分支名称,部署上线需要 + javaGitBranch: "", + // 接口服务地址 + API_HOST: "", + }, + }, + // 应用唯一标识符 + appIdentifier: "proDispatch", + // 应用上下文注入全局变量 + contextInject: { + // 应用Key + appKey: "", + }, + // public/index.html注入全局变量 + windowInject: { + // 应用标题 + title: "微应用模板", + // 注入css链接集合 + links: [], + element: { + root: { + // 挂载DOM元素ID + id: "root", + }, + }, + // 注入js链接集合 + scripts: [], + }, + // 开发服务 + server: { + // 监听端口号 + port: "8088", + // 服务地址 + host: "127.0.0.1", + // 是否自动打开浏览器 + open: true, + }, + // 框架 + framework: { + // ant-design + antd: { + // 全局antd-class-name前缀 + "ant-prefix": "micro-temp", + // 全局字体 + "fontFamily": "PingFangSC-Regular", + // 全局主题色 + "colorPrimary": "#1677ff", + // 全局圆角 + "borderRadius": 2, + }, + }, + // webpack + webpackConfig: { + // 单页面插件 + htmlWebpackPluginOption: { + // 自动注入编译后的文件到public/index.html中 + inject: true, + }, + }, +}; diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..ed50d55 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "paths": { + "~/*": ["*"] + }, + "jsx": "react" + }, + "include": ["src"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8a3c4bf --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "micro-app", + "version": "2.0.0", + "description": "建教帮微应用模板", + "author": "JJB", + "license": "MIT", + "main": "index.js", + "scripts": { + "serve": "node node_modules/@cqsjjb/scripts/webpack.dev.server.js", + "build": "node node_modules/@cqsjjb/scripts/webpack.build.js", + "push": "jjb-cmd push java production", + "clean-cache": "rimraf node_modules/.cache/webpack", + "serve:development": "cross-env NODE_ENV=development npm run serve", + "serve:production": "cross-env NODE_ENV=production npm run serve", + "build:development": "cross-env NODE_ENV=development npm run build", + "build:production": "cross-env NODE_ENV=production npm run build", + "code-optimization": "node node_modules/@cqsjjb/scripts/code-optimization.js", + "lint": "eslint --ext .js,.jsx,.tsx --fix src" + }, + "dependencies": { + "@ahooksjs/use-url-state": "^3.5.1", + "@ant-design/icons": "^5.6.1", + "@ant-design/pro-components": "^2.8.10", + "@cqsjjb/jjb-common-decorator": "latest", + "@cqsjjb/jjb-common-lib": "latest", + "@cqsjjb/jjb-dva-runtime": "latest", + "@cqsjjb/jjb-react-admin-component": "latest", + "ahooks": "^3.9.5", + "antd": "^5.27.6", + "dayjs": "^1.11.7", + "lodash-es": "^4.17.21", + "mockjs": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "throttle-debounce": "^5.0.2", + "zy-react-library": "^1.3.31" + }, + "devDependencies": { + "@antfu/eslint-config": "^5.4.1", + "@babel/plugin-proposal-decorators": "^7.19.3", + "@cqsjjb/scripts": "2.0.0-alpha-1", + "@eslint-react/eslint-plugin": "^2.2.2", + "cross-env": "^7.0.3", + "eslint": "^9.37.0", + "eslint-plugin-format": "^1.0.2", + "eslint-plugin-react-hooks": "^7.0.0", + "eslint-plugin-react-refresh": "^0.4.23", + "typescript": "^5.9.3" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..04e3b0e --- /dev/null +++ b/public/index.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + <% for (const item of $links) { %> + + <% } %> + -- + + + <% for (const item of $scripts) { %> + + <% } %> + + + + + +<% const { root } = $element; %> +
+ + diff --git a/router.md b/router.md new file mode 100644 index 0000000..1a5b9f6 --- /dev/null +++ b/router.md @@ -0,0 +1,24 @@ +# 生产派工管理路由规划 + +## 派工工单管理 + +### 人员派工 + +- 生产派工 + `/proDispatch/container/personnelDispatch/productionDispatch` +- 清扫派工 + `/proDispatch/container/personnelDispatch/cleaningDispatch` + +### 车辆派工 + +- 生产派工 + `/proDispatch/container/vehicleDispatch/productionDispatch` +- 清扫派工 + `/proDispatch/container/vehicleDispatch/cleaningDispatch` + +## 设备管理 + +- 内部设备管理 + `/proDispatch/container/deviceManagement/internalDevice` +- 相关方设备管理 + `/proDispatch/container/deviceManagement/partnerDevice` diff --git a/src/api/deviceManagement/index.js b/src/api/deviceManagement/index.js new file mode 100644 index 0000000..e0c746d --- /dev/null +++ b/src/api/deviceManagement/index.js @@ -0,0 +1,22 @@ +import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; + +// 当前接口地址仅用于原型阶段的本地请求拦截,收到正式接口文档后需按文档替换。 +export const internalDeviceList = declareRequest( + "internalDeviceLoading", + "Post > @/production-dispatch/device/internal/list", +); + +export const internalDeviceSync = declareRequest( + "internalDeviceSyncLoading", + "Post > @/production-dispatch/device/internal/sync", +); + +export const partnerDeviceList = declareRequest( + "partnerDeviceLoading", + "Post > @/production-dispatch/device/partner/list", +); + +export const partnerDeviceSync = declareRequest( + "partnerDeviceSyncLoading", + "Post > @/production-dispatch/device/partner/sync", +); diff --git a/src/api/global/index.js b/src/api/global/index.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/api/global/index.js @@ -0,0 +1 @@ +export {}; diff --git a/src/api/personnelDispatch/index.js b/src/api/personnelDispatch/index.js new file mode 100644 index 0000000..f2309b0 --- /dev/null +++ b/src/api/personnelDispatch/index.js @@ -0,0 +1,32 @@ +import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; + +// 当前接口地址仅用于原型阶段的本地请求拦截,收到正式接口文档后需按文档替换。 +export const productionDispatchList = declareRequest( + "productionDispatchLoading", + "Post > @/production-dispatch/personnel/production/list", +); + +export const productionDispatchApproval = declareRequest( + "productionDispatchApprovalLoading", + "Post > @/production-dispatch/personnel/production/approval", +); + +export const productionDispatchSync = declareRequest( + "productionDispatchSyncLoading", + "Post > @/production-dispatch/personnel/production/sync", +); + +export const cleaningDispatchList = declareRequest( + "cleaningDispatchLoading", + "Post > @/production-dispatch/personnel/cleaning/list", +); + +export const cleaningDispatchApproval = declareRequest( + "cleaningDispatchApprovalLoading", + "Post > @/production-dispatch/personnel/cleaning/approval", +); + +export const cleaningDispatchSync = declareRequest( + "cleaningDispatchSyncLoading", + "Post > @/production-dispatch/personnel/cleaning/sync", +); diff --git a/src/api/vehicleDispatch/index.js b/src/api/vehicleDispatch/index.js new file mode 100644 index 0000000..95bd312 --- /dev/null +++ b/src/api/vehicleDispatch/index.js @@ -0,0 +1,32 @@ +import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; + +// 当前接口地址仅用于原型阶段的本地请求拦截,收到正式接口文档后需按文档替换。 +export const productionDispatchList = declareRequest( + "productionDispatchLoading", + "Post > @/production-dispatch/vehicle/production/list", +); + +export const productionDispatchApproval = declareRequest( + "productionDispatchApprovalLoading", + "Post > @/production-dispatch/vehicle/production/approval", +); + +export const productionDispatchSync = declareRequest( + "productionDispatchSyncLoading", + "Post > @/production-dispatch/vehicle/production/sync", +); + +export const cleaningDispatchList = declareRequest( + "cleaningDispatchLoading", + "Post > @/production-dispatch/vehicle/cleaning/list", +); + +export const cleaningDispatchApproval = declareRequest( + "cleaningDispatchApprovalLoading", + "Post > @/production-dispatch/vehicle/cleaning/approval", +); + +export const cleaningDispatchSync = declareRequest( + "cleaningDispatchSyncLoading", + "Post > @/production-dispatch/vehicle/cleaning/sync", +); diff --git a/src/components/DeviceDetailModal/index.js b/src/components/DeviceDetailModal/index.js new file mode 100644 index 0000000..fdc0db1 --- /dev/null +++ b/src/components/DeviceDetailModal/index.js @@ -0,0 +1,57 @@ +import { Button, Descriptions, Divider, Modal } from "antd"; + +export default function DeviceDetailModal(props) { + const { onClose, record, unitLabel } = props; + + return ( + 关闭} + maskClosable={false} + onCancel={onClose} + > + 设备基础信息 + + 定位绑定信息 + + 维护边界 + + + ); +} diff --git a/src/components/DeviceManagementList/index.js b/src/components/DeviceManagementList/index.js new file mode 100644 index 0000000..b663a7c --- /dev/null +++ b/src/components/DeviceManagementList/index.js @@ -0,0 +1,131 @@ +import { Button, Tag } from "antd"; +import { useState } from "react"; +import Page from "zy-react-library/components/Page"; +import Search from "zy-react-library/components/Search"; +import Table from "zy-react-library/components/Table"; +import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender"; +import useTable from "zy-react-library/hooks/useTable"; + +import DeviceDetailModal from "~/components/DeviceDetailModal"; +import SummaryMetrics from "~/components/SummaryMetrics"; +import SyncExportActions from "~/components/SyncExportActions"; + +const selectItems = values => [...new Set(values)].map(value => ({ bianma: value, name: value })); + +export default function DeviceManagementList(props) { + const { listRequest, requestSync, scope, syncLoading } = props; + const [form] = Search.useForm(); + const [currentRecord, setCurrentRecord] = useState({}); + const [detailModalOpen, setDetailModalOpen] = useState(false); + const { tableProps, getData } = useTable(listRequest, { form }); + const partner = scope === "partner"; + const currentList = tableProps.dataSource || []; + const locatedCount = currentList.filter(item => item.gpsNo && item.gpsNo !== "-").length; + const normalCount = currentList.filter(item => item.status === "正常").length; + const unitLabel = partner ? "相关方单位" : "所属单位"; + const unitItems = partner ? ["相关方清扫一队", "相关方清扫二队"] : ["流机公司", "清扫二队"]; + const statusItems = partner ? ["正常", "维修", "停用"] : ["正常"]; + + return ( + + + + + + ( + form.getFieldsValue()} + /> + )} + columns={[ + { title: "设备编号", dataIndex: "deviceNo", render: (_, record) => {record.deviceNo} }, + { title: "设备名称", dataIndex: "deviceName" }, + { title: "设备类型", dataIndex: "deviceType" }, + { title: unitLabel, dataIndex: "unit" }, + { title: "绑定对象", dataIndex: "bindingObject" }, + { + title: "设备状态", + dataIndex: "status", + render: (_, record) => {record.status}, + }, + { title: "更新时间", dataIndex: "updateTime" }, + { + title: "操作", + fixed: "right", + width: 80, + render: (_, record) => ( + + ), + }, + ]} + showIndexColumn={false} + {...tableProps} + /> + + {detailModalOpen && ( + { + setDetailModalOpen(false); + setCurrentRecord({}); + }} + /> + )} + + ); +} diff --git a/src/components/DispatchDetailModal/index.js b/src/components/DispatchDetailModal/index.js new file mode 100644 index 0000000..3146b0b --- /dev/null +++ b/src/components/DispatchDetailModal/index.js @@ -0,0 +1,310 @@ +import { Button, Descriptions, Divider, message, Modal, Steps, Tag } from "antd"; +import FormBuilder from "zy-react-library/components/FormBuilder"; +import Table from "zy-react-library/components/Table"; +import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender"; + +import DispatchMap from "~/components/DispatchMap"; + +import styles from "./index.module.less"; + +const productionWorkflowNodes = [ + "公务公司审核", + "港务公司计划员审核", + "港务公司业务部长审核", + "流机计划员审核", + "流机业务部长审核", + "流机计划员分发", + "流机调度员下达", +]; +const cleaningWorkflowNodes = ["任务同步", "双控审核", "作业前确认", "派工下达", "作业后确认", "归档"]; + +function getCurrentWorkflowIndex(record, workflowNodes, cleaning) { + if (/已下达|已完成|已通过/.test(record.approvalStatus) && !/审核中|待审/.test(record.approvalStatus)) { + return workflowNodes.length - 1; + } + const currentIndex = workflowNodes.findIndex(node => record.currentRole.includes(node) || node.includes(record.currentRole)); + if (currentIndex >= 0) { + return currentIndex; + } + if (cleaning && /现场值守人/.test(record.currentRole)) { + return 2; + } + return record.approvalStatus === "待审" ? 0 : 1; +} + +function getApprovalResultColor(value) { + if (/异常|驳回/.test(value)) { + return "red"; + } + if (/处理中|待审/.test(value)) { + return "gold"; + } + return "green"; +} + +function getModalTitle(record, cleaning, vehicle) { + if (cleaning && vehicle) { + return `${record.id} 车辆清扫派工详情`; + } + if (cleaning) { + return `${record.id} 清扫派工详情`; + } + if (vehicle) { + return `${record.planNo} 车辆派工详情`; + } + return `${record.planNo} 派工详情`; +} + +function getWorkflowDescription(index, currentIndex) { + if (index < currentIndex) { + return "已处理"; + } + if (index === currentIndex) { + return "当前节点"; + } + return "待流转"; +} + +export default function DispatchDetailModal(props) { + const { + approvalLoading, + approvalMode, + businessType, + objectType, + onClose, + onSuccess, + record, + requestApproval, + } = props; + const [form] = FormBuilder.useForm(); + const cleaning = businessType === "cleaning"; + const vehicle = objectType === "vehicle"; + const approvalObject = cleaning ? record.cleaningPeople : record.personnel; + const workflowNodes = cleaning ? cleaningWorkflowNodes : productionWorkflowNodes; + const currentWorkflowIndex = getCurrentWorkflowIndex(record, workflowNodes, cleaning); + const canApprove = approvalMode + && record.approvalOwner === "双控系统审批" + && ["待审", "审核中"].includes(record.approvalStatus) + && !/未配置|未匹配/.test(`${record.currentRole} ${approvalObject}`); + + const handleApproval = async (values) => { + const { success } = await requestApproval({ id: record.id, ...values }); + if (success) { + message.success(values.approvalResult === "PASS" ? "审批已通过" : "审批已驳回"); + onSuccess(); + } + }; + + return ( + 关闭} + confirmLoading={approvalLoading} + maskClosable={false} + okText="提交审批" + onOk={canApprove ? form.submit : undefined} + onCancel={onClose} + > + {canApprove && ( + <> + 审批处理 + values.approvalResult !== "REJECT", + required: values => values.approvalResult === "REJECT", + rules: [{ whitespace: true, message: "请输入驳回原因" }], + }, + ]} + onFinish={handleApproval} + /> + + )} + 位置与轨迹 + + + 基础信息 + + {cleaning && ( + <> + 清扫派工作业信息 + + 清扫执行信息 + + + )} + {!cleaning && ( + <> + 生产派工作业信息 + + + )} + {!cleaning && vehicle && ( + <> + 车辆/设备信息 + + + )} + {!cleaning && !vehicle && ( + <> + 人员信息 + + + )} + 审批与执行链路 +
+ ({ + title: node, + description: getWorkflowDescription(index, currentWorkflowIndex), + }))} + labelPlacement="vertical" + responsive={false} + size="small" + /> +
+ 处理记录 +
{record.result}, + }, + { title: "意见", dataIndex: "opinion" }, + ]} + dataSource={(record.approvalRecords || []).map(([time, node, result, opinion], index) => ({ + id: `${record.id}-${index}`, + time, + node, + result, + opinion, + }))} + disabledResizer + options={false} + pagination={false} + showIndexColumn={false} + /> + + ); +} diff --git a/src/components/DispatchDetailModal/index.module.less b/src/components/DispatchDetailModal/index.module.less new file mode 100644 index 0000000..81f6a42 --- /dev/null +++ b/src/components/DispatchDetailModal/index.module.less @@ -0,0 +1,9 @@ +.workflowSteps { + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 8px; + + :global(.micro-temp-steps) { + min-width: 900px; + } +} diff --git a/src/components/DispatchList/CleaningDispatchList/index.js b/src/components/DispatchList/CleaningDispatchList/index.js new file mode 100644 index 0000000..23ba53a --- /dev/null +++ b/src/components/DispatchList/CleaningDispatchList/index.js @@ -0,0 +1,211 @@ +import { Button, Space, Tag } from "antd"; +import { useState } from "react"; +import Page from "zy-react-library/components/Page"; +import Search from "zy-react-library/components/Search"; +import Table from "zy-react-library/components/Table"; +import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender"; +import useTable from "zy-react-library/hooks/useTable"; + +import DispatchDetailModal from "~/components/DispatchDetailModal"; +import SummaryMetrics from "~/components/SummaryMetrics"; +import SyncExportActions from "~/components/SyncExportActions"; + +const statusColor = { + 待审: "gold", + 审核中: "orange", + 已通过: "green", + 已驳回: "red", + 已下达: "green", +}; + +const orderStatusColor = { + 待下达: "gold", + 作业中: "blue", + 已完成: "green", +}; + +const selectItems = values => [...new Set(values)].map(value => ({ bianma: value, name: value })); + +export default function CleaningDispatchList(props) { + const { + approvalLoading, + listRequest, + objectType, + requestApproval, + requestSync, + syncLoading, + } = props; + const [form] = Search.useForm(); + const [currentRecord, setCurrentRecord] = useState({}); + const [detailModalOpen, setDetailModalOpen] = useState(false); + const [approvalMode, setApprovalMode] = useState(false); + const { tableProps, getData } = useTable(listRequest, { form }); + const personnel = objectType === "personnel"; + const currentList = tableProps.dataSource || []; + const pendingCount = currentList.filter(item => ["待审", "审核中"].includes(item.approvalStatus)).length; + const dualApprovalCount = currentList.filter(item => item.approvalOwner === "双控系统审批").length; + const activeCount = currentList.filter(item => ["待下达", "作业中"].includes(item.orderStatus)).length; + + const closeDetailModal = () => { + setApprovalMode(false); + setDetailModalOpen(false); + setCurrentRecord({}); + }; + + const openDetailModal = (record, approve = false) => { + setApprovalMode(approve); + setCurrentRecord(record); + setDetailModalOpen(true); + }; + + return ( + + + + + +
( + form.getFieldsValue()} + /> + )} + columns={[ + { title: "工单编号", dataIndex: "id", render: (_, record) => {record.id} }, + { title: "作业日期", dataIndex: "workDate" }, + { title: "工单来源", dataIndex: "workSource" }, + { title: "公司名称", dataIndex: "companyName" }, + { title: "作业公司", dataIndex: "workCompany" }, + { title: "作业项目", dataIndex: "workProject" }, + { title: "作业过程", dataIndex: "workProcess" }, + { title: "作业地点", dataIndex: "workPlace" }, + { title: "流机现场巡视员", dataIndex: "inspector" }, + { title: "清扫人员", dataIndex: "cleaningPeople" }, + { title: "标识名称", dataIndex: "markerName" }, + { title: "设备名称", dataIndex: "deviceName" }, + { title: "工单说明", dataIndex: "description" }, + { + title: "工单状态", + dataIndex: "orderStatus", + render: (_, record) => ( + {record.orderStatus} + ), + }, + { title: "结算状态", dataIndex: "settlementStatus" }, + { + title: "作业是否超时", + dataIndex: "timeout", + render: (_, record) => {record.timeout}, + }, + { title: "评价", dataIndex: "evaluation" }, + { + title: "审批归属", + dataIndex: "approvalOwner", + render: (_, record) => ( + {record.approvalOwner} + ), + }, + { title: "当前审批角色", dataIndex: "currentRole" }, + { + title: "审批状态", + dataIndex: "approvalStatus", + render: (_, record) => {record.approvalStatus}, + }, + { + title: "操作", + fixed: "right", + width: 150, + render: (_, record) => { + const canApprove = record.approvalOwner === "双控系统审批" + && ["待审", "审核中"].includes(record.approvalStatus) + && !/未配置|未匹配/.test(`${record.currentRole} ${record.cleaningPeople}`); + + return ( + + + {canApprove && } + + ); + }, + }, + ]} + {...tableProps} + /> + + {detailModalOpen && ( + { + closeDetailModal(); + getData(); + }} + /> + )} + + ); +} diff --git a/src/components/DispatchList/ProductionDispatchList/index.js b/src/components/DispatchList/ProductionDispatchList/index.js new file mode 100644 index 0000000..5260ba6 --- /dev/null +++ b/src/components/DispatchList/ProductionDispatchList/index.js @@ -0,0 +1,220 @@ +import { Button, Space, Tag } from "antd"; +import { useState } from "react"; +import Page from "zy-react-library/components/Page"; +import Search from "zy-react-library/components/Search"; +import Table from "zy-react-library/components/Table"; +import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender"; +import useTable from "zy-react-library/hooks/useTable"; + +import DispatchDetailModal from "~/components/DispatchDetailModal"; +import styles from "~/components/ManagementList/index.module.less"; +import SummaryMetrics from "~/components/SummaryMetrics"; +import SyncExportActions from "~/components/SyncExportActions"; + +const statusColor = { + 待审: "gold", + 审核中: "orange", + 已通过: "green", + 已驳回: "red", + 已下达: "green", +}; + +const selectItems = values => [...new Set(values)].map(value => ({ bianma: value, name: value })); + +export default function ProductionDispatchList(props) { + const { + approvalLoading, + listRequest, + objectType, + requestApproval, + requestSync, + syncLoading, + } = props; + const [form] = Search.useForm(); + const [currentRecord, setCurrentRecord] = useState({}); + const [detailModalOpen, setDetailModalOpen] = useState(false); + const [approvalMode, setApprovalMode] = useState(false); + const { tableProps, getData } = useTable(listRequest, { form }); + const personnel = objectType === "personnel"; + const carUnitItems = personnel + ? ["杂货公司东港作业部", "东港业务部", "西港业务部"] + : ["西港业务部", "东港业务部"]; + const currentRoleItems = personnel + ? ["港务公司计划员审核", "审批角色未配置", "已归档"] + : ["流机调度员下达", "港务公司业务部长审核", "已归档"]; + const currentList = tableProps.dataSource || []; + const pendingCount = currentList.filter(item => ["待审", "审核中"].includes(item.approvalStatus)).length; + const dualApprovalCount = currentList.filter(item => item.approvalOwner === "双控系统审批").length; + const activeCount = currentList.filter(item => ["待下达", "作业中"].includes(item.orderStatus)).length; + + const closeDetailModal = () => { + setApprovalMode(false); + setDetailModalOpen(false); + setCurrentRecord({}); + }; + + const openDetailModal = (record, approve = false) => { + setApprovalMode(approve); + setCurrentRecord(record); + setDetailModalOpen(true); + }; + + return ( + + + + + +
( + form.getFieldsValue()} + /> + )} + columns={[ + { + title: "计划号", + width: 200, + dataIndex: "planNo", + render: (_, record) => ( +
+ {record.planNo} + {record.sourceNo} +
+ ), + }, + { title: "当前审批角色", dataIndex: "currentRole" }, + { title: "用车单位", dataIndex: "carUnit" }, + { title: "申请用车部门", dataIndex: "applyDept" }, + { title: "现场负责部门", dataIndex: "responsibleDept" }, + { title: "现场负责人", dataIndex: "responsiblePerson" }, + { title: "预到位时间", dataIndex: "expectedArrivalTime" }, + { title: "白/夜班", dataIndex: "shift" }, + { title: "作业地点", dataIndex: "workPlace" }, + { title: "到位地点", dataIndex: "arrivalPlace" }, + { title: "作业内容", dataIndex: "workContent" }, + { title: "作业过程", dataIndex: "workProcess" }, + { title: "作业货名", dataIndex: "cargoName" }, + { title: "船名航次", dataIndex: "voyage" }, + { title: "人员姓名", dataIndex: "personnel", hidden: !personnel }, + { title: "人员ID", dataIndex: "personId", hidden: !personnel }, + { title: "申请设备", dataIndex: "requestedEquipment", hidden: personnel }, + { title: "兑现设备", dataIndex: "fulfilledEquipment", hidden: personnel }, + { title: "车牌号", dataIndex: "licenseNo", hidden: personnel }, + { title: "车辆类型", dataIndex: "vehicleType", hidden: personnel }, + { title: "司机", dataIndex: "driver", hidden: personnel }, + { title: "申请人", dataIndex: "applicant" }, + { title: "申请时间", dataIndex: "applyTime" }, + { + title: "审批归属", + dataIndex: "approvalOwner", + render: (_, record) => ( + {record.approvalOwner} + ), + }, + { + title: "审批状态", + dataIndex: "approvalStatus", + render: (_, record) => {record.approvalStatus}, + }, + { + title: "操作", + fixed: "right", + width: 150, + render: (_, record) => { + const canApprove = record.approvalOwner === "双控系统审批" + && ["待审", "审核中"].includes(record.approvalStatus) + && !/未配置|未匹配/.test(`${record.currentRole} ${record.personnel}`); + + return ( + + + {canApprove && } + + ); + }, + }, + ]} + {...tableProps} + /> + + {detailModalOpen && ( + { + closeDetailModal(); + getData(); + }} + /> + )} + + ); +} diff --git a/src/components/DispatchList/index.js b/src/components/DispatchList/index.js new file mode 100644 index 0000000..40fae05 --- /dev/null +++ b/src/components/DispatchList/index.js @@ -0,0 +1,9 @@ +import CleaningDispatchList from "./CleaningDispatchList"; +import ProductionDispatchList from "./ProductionDispatchList"; + +export default function DispatchList(props) { + if (props.businessType === "production") { + return ; + } + return ; +} diff --git a/src/components/DispatchMap/index.js b/src/components/DispatchMap/index.js new file mode 100644 index 0000000..6d3e170 --- /dev/null +++ b/src/components/DispatchMap/index.js @@ -0,0 +1,109 @@ +import { message, Spin } from "antd"; +import { useEffect, useState } from "react"; +import CesiumMap from "zy-react-library/components/Map/CesiumMap"; +import { dynamicLoadCss, dynamicLoadJs } from "zy-react-library/utils"; +import styles from "./index.module.less"; + +import "./index.less"; + +const CESIUM_SCRIPT_URL = "https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Cesium.js"; +const CESIUM_STYLE_URL = "https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Widgets/widgets.css"; +const EMPTY_TRACK_POINTS = []; +let cesiumResourcePromise; + +async function loadCesiumResources() { + if (window.Cesium) { + return; + } + if (!cesiumResourcePromise) { + cesiumResourcePromise = (async () => { + if (window?.base?.loadDynamicResource) { + await window.base.loadDynamicResource({ + url: CESIUM_SCRIPT_URL, + type: "script", + attr: { type: "text/javascript" }, + }); + await window.base.loadDynamicResource({ + url: CESIUM_STYLE_URL, + type: "link", + attr: { rel: "stylesheet", type: "text/css" }, + }); + return; + } + await dynamicLoadJs(CESIUM_SCRIPT_URL); + await dynamicLoadCss(CESIUM_STYLE_URL); + })(); + } + await cesiumResourcePromise; +} + +export default function DispatchMap(props) { + const { latitude, longitude, trackPoints = EMPTY_TRACK_POINTS } = props; + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + let viewer; + + const initMap = async () => { + try { + await loadCesiumResources(); + if (cancelled) { + return; + } + + const map = new CesiumMap(); + viewer = map.init().viewer; + const centerLongitude = Number(longitude || window.mapLongitude); + const centerLatitude = Number(latitude || window.mapLatitude); + map.flyTo({ longitude: centerLongitude, latitude: centerLatitude, height: 30000 }); + + const hasPosition = longitude !== undefined && longitude !== null && longitude !== "" + && latitude !== undefined && latitude !== null && latitude !== ""; + if (hasPosition) { + map.addMarkPoint({ longitude: Number(longitude), latitude: Number(latitude) }); + } + + const positions = trackPoints + .filter(point => Array.isArray(point) && point.length >= 2) + .map(point => [Number(point[0]), Number(point[1])]) + .filter(point => point.every(Number.isFinite)) + .map(point => window.Cesium.Cartesian3.fromDegrees(Number(point[0]), Number(point[1]))); + if (positions.length > 1) { + viewer.entities.add({ + polyline: { + positions, + width: 4, + clampToGround: true, + material: window.Cesium.Color.fromCssColorString("#1677ff"), + }, + }); + } + } + catch { + message.error("地图加载失败"); + } + finally { + if (!cancelled) { + setLoading(false); + } + } + }; + + initMap(); + return () => { + cancelled = true; + if (viewer && !viewer.isDestroyed()) { + viewer.destroy(); + } + }; + }, [latitude, longitude, trackPoints]); + + return ( +
+ +
+ +
+ ); +} diff --git a/src/components/DispatchMap/index.less b/src/components/DispatchMap/index.less new file mode 100644 index 0000000..1c00d89 --- /dev/null +++ b/src/components/DispatchMap/index.less @@ -0,0 +1,5 @@ +.cesium-viewer-toolbar, +.cesium-viewer-fullscreenContainer, +.cesium-infoBox-visible { + display: none !important; +} diff --git a/src/components/DispatchMap/index.module.less b/src/components/DispatchMap/index.module.less new file mode 100644 index 0000000..dc4a0a6 --- /dev/null +++ b/src/components/DispatchMap/index.module.less @@ -0,0 +1,13 @@ +.mapWrapper { + width: 100%; + margin-bottom: 12px; + overflow: hidden; + border: 1px solid #f0f0f0; + border-radius: 2px; +} + +.map { + position: relative; + width: 100%; + height: 240px; +} diff --git a/src/components/ManagementList/index.module.less b/src/components/ManagementList/index.module.less new file mode 100644 index 0000000..e099bc2 --- /dev/null +++ b/src/components/ManagementList/index.module.less @@ -0,0 +1,9 @@ +.planNo { + display: grid; + gap: 3px; + + span { + color: #8c8c8c; + font-size: 12px; + } +} diff --git a/src/components/SummaryMetrics/index.js b/src/components/SummaryMetrics/index.js new file mode 100644 index 0000000..4e5e474 --- /dev/null +++ b/src/components/SummaryMetrics/index.js @@ -0,0 +1,17 @@ +import styles from "./index.module.less"; + +export default function SummaryMetrics(props) { + const { items } = props; + + return ( +
+ {items.map(item => ( +
+ {item.label} + {item.value} + {item.note} +
+ ))} +
+ ); +} diff --git a/src/components/SummaryMetrics/index.module.less b/src/components/SummaryMetrics/index.module.less new file mode 100644 index 0000000..e5fa0a6 --- /dev/null +++ b/src/components/SummaryMetrics/index.module.less @@ -0,0 +1,49 @@ +.metrics { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin-bottom: 16px; +} + +.metric { + display: grid; + gap: 8px; + min-width: 0; + padding: 16px; + border: 1px solid #e5e7eb; + border-top: 3px solid #64748b; + border-radius: 6px; + background: #fff; + + span, + small { + color: #667085; + } + + span { + font-size: 13px; + font-weight: 600; + } + + strong { + color: #1d2939; + font-size: 26px; + line-height: 1; + } + + small { + line-height: 1.5; + } +} + +.blue { + border-top-color: #1677ff; +} + +.green { + border-top-color: #389e0d; +} + +.orange { + border-top-color: #d97706; +} diff --git a/src/components/SyncExportActions/index.js b/src/components/SyncExportActions/index.js new file mode 100644 index 0000000..3073470 --- /dev/null +++ b/src/components/SyncExportActions/index.js @@ -0,0 +1,43 @@ +import { SyncOutlined } from "@ant-design/icons"; +import { Button, message, Space } from "antd"; +import ExportIcon from "zy-react-library/components/Icon/ExportIcon"; +import useDownloadBlob from "zy-react-library/hooks/useDownloadBlob"; + +export default function SyncExportActions(props) { + const { + exportName, + exportUrl, + getExportParams, + onSyncSuccess, + requestSync, + syncLoading, + syncMessage, + syncText, + } = props; + const { loading: exportLoading, downloadBlob } = useDownloadBlob(); + + const handleSync = async () => { + const { success } = await requestSync(); + if (success) { + message.success(syncMessage); + onSyncSuccess(); + } + }; + + return ( + + + + + ); +} diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/components/index.js @@ -0,0 +1 @@ +export {}; diff --git a/src/enumerate/constant/index.js b/src/enumerate/constant/index.js new file mode 100644 index 0000000..f077757 --- /dev/null +++ b/src/enumerate/constant/index.js @@ -0,0 +1,5 @@ +/** + * 全局常量定义 + */ + +export {}; diff --git a/src/enumerate/context/index.js b/src/enumerate/context/index.js new file mode 100644 index 0000000..2b11e05 --- /dev/null +++ b/src/enumerate/context/index.js @@ -0,0 +1,8 @@ +/** + * 全局上下文定义 + */ + +import React from "react"; + +// 获取antd全局静态方法 +export const InjectContext = React.createContext({}); diff --git a/src/enumerate/namespace/index.js b/src/enumerate/namespace/index.js new file mode 100644 index 0000000..ad5f553 --- /dev/null +++ b/src/enumerate/namespace/index.js @@ -0,0 +1,10 @@ +/** + * 全局数据状态管理模块定义 + */ + +import { defineNamespace } from "@cqsjjb/jjb-dva-runtime"; + +export const NS_GLOBAL = defineNamespace("global"); +export const NS_DEVICE_MANAGEMENT = defineNamespace("deviceManagement"); +export const NS_PERSONNEL_DISPATCH = defineNamespace("personnelDispatch"); +export const NS_VEHICLE_DISPATCH = defineNamespace("vehicleDispatch"); diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..80d2830 --- /dev/null +++ b/src/main.js @@ -0,0 +1,56 @@ +import { setJJBCommonAntdMessage } from "@cqsjjb/jjb-common-lib"; +import { setup } from "@cqsjjb/jjb-dva-runtime"; +import { message } from "antd"; +import dayjs from "dayjs"; +import { getFileUrlFromServer } from "zy-react-library/utils"; +import "dayjs/locale/zh-cn"; + +require("antd/dist/reset.css"); +require("zy-react-library/css/common.less"); + +if (process.env.NODE_ENV === "development") { + require("~/mock"); +} + +dayjs.locale("zh-cn"); +setJJBCommonAntdMessage(message); + +const app = setup(); +getFileUrlFromServer(); +window.mapLongitude = "119.69457721306945"; +window.mapLatitude = "39.940504336846665"; +window.mapBaiDuKey = "OElqFYoKiAH8KFtph8ftLKF5NlNrbCUr"; + +// 非底座环境运行 +if (!window.__POWERED_BY_QIANKUN__) { + // 云组件默认依赖 + window.__coreLib = {}; + window.__coreLib.React = require("react"); + window.__coreLib.ReactDOM = require("react-dom"); + window.__coreLib.jjbCommonLib = require("@cqsjjb/jjb-common-lib"); +} + +/** + * @description 挂载 + * @param props {{ setGlobalState: ({ rendered: boolean }) => void }} + * @returns {Promise<*>} '' + */ +export const mount = async (props) => { + // 云组件默认依赖 + window.__coreLib.React = require("react"); + window.__coreLib.ReactDOM = require("react-dom"); + window.__coreLib.jjbCommonLib = require("@cqsjjb/jjb-common-lib"); + app.mount(props); +}; + +/** + * @description 卸载 + * @param props {object} + * @returns {Promise<*>} '' + */ +export const unmount = async props => app.unmount(props); +/** + * @description 启动 + * @param props + */ +export const bootstrap = async props => app.bootstrap(props); diff --git a/src/mock/deviceManagement/internalDevice/index.js b/src/mock/deviceManagement/internalDevice/index.js new file mode 100644 index 0000000..546a233 --- /dev/null +++ b/src/mock/deviceManagement/internalDevice/index.js @@ -0,0 +1,101 @@ +import Mock from "mockjs"; + +// 当前没有内部设备接口文档,以下数据仅用于还原交互原型,接入真实接口后删除本拦截文件。 +const internalDeviceList = [ + { + id: "SB-NB-001", + scope: "内部", + deviceNo: "SB-NB-001", + deviceName: "定位终端PT-3107", + deviceType: "定位终端", + unit: "流机公司", + bindingObject: "冀C-3107", + status: "正常", + updateTime: "2026-07-24 10:12", + gpsNo: "GPS-PT-3107", + relatedVehicle: "装载机L-07", + location: "东港3号泊位", + sourceSystem: "流机公司智能调度平台", + }, + { + id: "SB-NB-014", + scope: "内部", + deviceNo: "SB-NB-014", + deviceName: "装载机L-07", + deviceType: "车辆", + unit: "流机公司", + bindingObject: "司机李明", + status: "正常", + updateTime: "2026-07-24 10:10", + gpsNo: "GPS-L-07", + relatedVehicle: "冀C-3107", + location: "东港3号泊位", + sourceSystem: "流机公司智能调度平台", + }, + { + id: "SB-NB-022", + scope: "内部", + deviceNo: "SB-NB-022", + deviceName: "清扫车QS-22", + deviceType: "车辆", + unit: "清扫二队", + bindingObject: "东港堆场南路", + status: "正常", + updateTime: "2026-07-24 09:58", + gpsNo: "GPS-QS-22", + relatedVehicle: "冀C-QS22", + location: "东港堆场南路", + sourceSystem: "流机公司智能调度平台", + }, + { + id: "SB-NB-031", + scope: "内部", + deviceNo: "SB-NB-031", + deviceName: "牵引车T-09", + deviceType: "车辆", + unit: "流机公司", + bindingObject: "郭涛", + status: "正常", + updateTime: "2026-07-24 12:03", + gpsNo: "GPS-T-09", + relatedVehicle: "冀C-6309", + location: "东港5号堆场", + sourceSystem: "流机公司智能调度平台", + }, +]; + +function getRequestBody(options) { + try { + return JSON.parse(options.body || "{}"); + } + catch { + return {}; + } +} + +Mock.mock(/\/production-dispatch\/device\/internal\/list$/, "post", (options) => { + const body = getRequestBody(options); + const keyword = body.keyword?.trim(); + const filteredList = internalDeviceList.filter((record) => { + if (keyword && ![ + record.deviceNo, + record.deviceName, + record.bindingObject, + ].some(value => value.includes(keyword))) { + return false; + } + return ["deviceType", "unit", "status"] + .every(key => !body[key] || record[key] === body[key]); + }); + const pageIndex = Number(body.pageIndex || 1); + const pageSize = Number(body.pageSize || 20); + const start = (pageIndex - 1) * pageSize; + + return { + success: true, + totalCount: filteredList.length, + data: filteredList.slice(start, start + pageSize), + }; +}); + +Mock.mock(/\/production-dispatch\/device\/internal\/sync$/, "post", () => ({ success: true })); diff --git a/src/mock/deviceManagement/partnerDevice/index.js b/src/mock/deviceManagement/partnerDevice/index.js new file mode 100644 index 0000000..669cba4 --- /dev/null +++ b/src/mock/deviceManagement/partnerDevice/index.js @@ -0,0 +1,86 @@ +import Mock from "mockjs"; + +// 当前没有相关方设备接口文档,以下数据仅用于还原交互原型,接入真实接口后删除本拦截文件。 +const partnerDeviceList = [ + { + id: "SB-XG-011", + scope: "相关方", + deviceNo: "SB-XG-011", + deviceName: "相关方定位卡R-008", + deviceType: "定位终端", + unit: "相关方清扫一队", + bindingObject: "张敏", + status: "正常", + updateTime: "2026-07-24 09:42", + gpsNo: "GPS-R-008", + relatedVehicle: "-", + location: "隔离封闭区2号门", + sourceSystem: "流机公司智能调度平台", + }, + { + id: "SB-XG-019", + scope: "相关方", + deviceNo: "SB-XG-019", + deviceName: "吸污车QS-08", + deviceType: "车辆", + unit: "相关方清扫一队", + bindingObject: "冀C-QS08", + status: "维修", + updateTime: "2026-07-24 09:25", + gpsNo: "GPS-QS-08", + relatedVehicle: "冀C-QS08", + location: "隔离封闭区2号门排水沟", + sourceSystem: "流机公司智能调度平台", + }, + { + id: "SB-XG-027", + scope: "相关方", + deviceNo: "SB-XG-027", + deviceName: "洒水车SW-03", + deviceType: "车辆", + unit: "相关方清扫二队", + bindingObject: "冀C-SW03", + status: "停用", + updateTime: "2026-07-24 08:18", + gpsNo: "GPS-SW-03", + relatedVehicle: "冀C-SW03", + location: "西港维修点", + sourceSystem: "流机公司智能调度平台", + }, +]; + +function getRequestBody(options) { + try { + return JSON.parse(options.body || "{}"); + } + catch { + return {}; + } +} + +Mock.mock(/\/production-dispatch\/device\/partner\/list$/, "post", (options) => { + const body = getRequestBody(options); + const keyword = body.keyword?.trim(); + const filteredList = partnerDeviceList.filter((record) => { + if (keyword && ![ + record.deviceNo, + record.deviceName, + record.bindingObject, + ].some(value => value.includes(keyword))) { + return false; + } + return ["deviceType", "unit", "status"] + .every(key => !body[key] || record[key] === body[key]); + }); + const pageIndex = Number(body.pageIndex || 1); + const pageSize = Number(body.pageSize || 20); + const start = (pageIndex - 1) * pageSize; + + return { + success: true, + totalCount: filteredList.length, + data: filteredList.slice(start, start + pageSize), + }; +}); + +Mock.mock(/\/production-dispatch\/device\/partner\/sync$/, "post", () => ({ success: true })); diff --git a/src/mock/index.js b/src/mock/index.js new file mode 100644 index 0000000..d4bc5c8 --- /dev/null +++ b/src/mock/index.js @@ -0,0 +1,20 @@ +import "./deviceManagement/internalDevice"; +import "./deviceManagement/partnerDevice"; +import "./personnelDispatch/productionDispatch"; +import "./personnelDispatch/cleaningDispatch"; +import "./vehicleDispatch/productionDispatch"; +import "./vehicleDispatch/cleaningDispatch"; + +// MockJS 1.1.0 不会把 open 后设置的 responseType 传给未命中的原生 XHR,导致 Cesium 无法解码地图瓦片。 +Object.defineProperty(window.XMLHttpRequest.prototype, "responseType", { + configurable: true, + get() { + return this.custom?.xhr?.responseType || this.custom?.responseType || ""; + }, + set(value) { + this.custom.responseType = value; + if (this.custom.xhr) { + this.custom.xhr.responseType = value; + } + }, +}); diff --git a/src/mock/personnelDispatch/cleaningDispatch/index.js b/src/mock/personnelDispatch/cleaningDispatch/index.js new file mode 100644 index 0000000..cb18b39 --- /dev/null +++ b/src/mock/personnelDispatch/cleaningDispatch/index.js @@ -0,0 +1,154 @@ +import Mock from "mockjs"; + +// 当前没有人员清扫派工接口文档,以下数据仅用于还原交互原型,接入真实接口后删除本拦截文件。 +const cleaningDispatchList = [ + { + id: "QSGD-260724-006", + sourceNo: "QSGD-260724-006", + sourceSystem: "流机公司智能调度平台", + businessType: "清扫派工", + objectType: "人员", + dispatchMode: "临时派工", + workDate: "2026-07-24", + workSource: "临时清扫", + companyName: "秦港股份", + workCompany: "相关方清扫一队", + workProject: "清扫", + workProcess: "吸污", + workPlace: "隔离封闭区2号门周边", + inspector: "孙晓", + cleaningPeople: "张敏、李兵、何强、王磊", + markerName: "QS-08", + deviceName: "吸污车", + description: "临时清扫与吸污作业,作业前后需港务公司现场值守人确认", + orderStatus: "待下达", + settlementStatus: "未结算", + timeout: "否", + evaluation: "-", + currentRole: "公务公司审核", + approvalOwner: "双控系统审批", + approvalStatus: "待审", + applicant: "港务值守员", + applyTime: "2026-07-24 09:05", + position: "隔离封闭区2号门", + locationTime: "未开始定位", + onlineStatus: "离线", + trackStatus: "未生成", + progress: "20%", + accessState: "领取任务并持定位卡后可入区", + videoState: "吸污车监控待推送", + approvalRecords: [["2026-07-24 09:07", "派工单同步", "已接收", "等待公务公司审核"]], + }, + { + id: "QSGD-260724-012", + sourceNo: "QSGD-260724-012", + sourceSystem: "流机公司智能调度平台", + businessType: "清扫派工", + objectType: "人员", + dispatchMode: "计划派工", + workDate: "2026-07-24", + workSource: "清扫计划", + companyName: "秦港股份", + workCompany: "清扫二队", + workProject: "道路清扫", + workProcess: "机扫巡检", + workPlace: "东港堆场南路", + inspector: "郭涵", + cleaningPeople: "王磊、刘雪", + markerName: "QS-22", + deviceName: "清扫车", + description: "道路机扫及现场巡视", + orderStatus: "已完成", + settlementStatus: "已结算", + timeout: "否", + evaluation: "满意", + currentRole: "已归档", + approvalOwner: "派工系统审批", + approvalStatus: "已通过", + applicant: "清扫计划员", + applyTime: "2026-07-24 06:55", + position: "东港堆场南路", + locationTime: "2026-07-24 09:58:00", + onlineStatus: "在线", + trackStatus: "轨迹可回放", + progress: "100%", + accessState: "任务已结束,人员不可再次入区", + videoState: "视频可回放", + approvalRecords: [["2026-07-24 07:05", "流机调度员下达", "已下达", "派工系统审批完成后同步"]], + }, +]; + +function getRequestBody(options) { + try { + return JSON.parse(options.body || "{}"); + } + catch { + return {}; + } +} + +Mock.mock(/\/production-dispatch\/personnel\/cleaning\/list$/, "post", (options) => { + const body = getRequestBody(options); + const keyword = body.keyword?.trim(); + const filteredList = cleaningDispatchList.filter((record) => { + if (keyword && ![ + record.id, + record.workPlace, + record.cleaningPeople, + record.deviceName, + ].some(value => value.includes(keyword))) { + return false; + } + return ["workSource", "workCompany", "orderStatus", "settlementStatus", "timeout"] + .every(key => !body[key] || record[key] === body[key]); + }); + const pageIndex = Number(body.pageIndex || 1); + const pageSize = Number(body.pageSize || 20); + const start = (pageIndex - 1) * pageSize; + + return { + success: true, + totalCount: filteredList.length, + data: filteredList.slice(start, start + pageSize), + }; +}); + +Mock.mock(/\/production-dispatch\/personnel\/cleaning\/sync$/, "post", () => ({ success: true })); + +Mock.mock(/\/production-dispatch\/personnel\/cleaning\/approval$/, "post", (options) => { + const body = getRequestBody(options); + const record = cleaningDispatchList.find(item => item.id === body.id); + const canApprove = record + && record.approvalOwner === "双控系统审批" + && ["待审", "审核中"].includes(record.approvalStatus) + && !/未配置|未匹配/.test(`${record.currentRole} ${record.cleaningPeople}`); + + if (!canApprove) { + return { success: false, errMessage: "当前清扫派工单不可审批" }; + } + if (!["PASS", "REJECT"].includes(body.approvalResult)) { + return { success: false, errMessage: "请选择审批结果" }; + } + if (body.approvalResult === "REJECT" && !body.rejectReason?.trim()) { + return { success: false, errMessage: "请输入驳回原因" }; + } + + const approved = body.approvalResult === "PASS"; + record.approvalStatus = approved ? "已通过" : "已驳回"; + record.rejectReason = approved ? "" : body.rejectReason.trim(); + const approvalRecord = [ + new Date().toLocaleString("sv-SE", { hour12: false }).slice(0, 16), + record.currentRole, + approved ? "通过" : "驳回", + approved ? "审批通过" : record.rejectReason, + ]; + const processingRecordIndex = record.approvalRecords.findIndex(item => item[2] === "处理中"); + if (processingRecordIndex >= 0) { + record.approvalRecords[processingRecordIndex] = approvalRecord; + } + else { + record.approvalRecords.push(approvalRecord); + } + + return { success: true, data: record }; +}); diff --git a/src/mock/personnelDispatch/productionDispatch/index.js b/src/mock/personnelDispatch/productionDispatch/index.js new file mode 100644 index 0000000..1c6b1b1 --- /dev/null +++ b/src/mock/personnelDispatch/productionDispatch/index.js @@ -0,0 +1,223 @@ +import Mock from "mockjs"; + +// 当前没有人员生产派工接口文档,以下数据仅用于还原交互原型,接入真实接口后删除本拦截文件。 +const productionDispatchList = [ + { + id: "QG-SC-RY-20260724-001", + sourceNo: "PGD-SC-260724-0815", + sourceSystem: "流机公司智能调度平台", + businessType: "生产派工", + objectType: "人员", + dispatchMode: "自动派工", + planNo: "JH-DG-0724-015", + currentRole: "港务公司计划员审核", + carUnit: "杂货公司东港作业部", + applyDept: "东港生产调度室", + responsibleDept: "东港现场保障组", + responsiblePerson: "王海", + expectedArrivalTime: "2026-07-24 10:00", + shift: "白班", + workPlace: "东港3号泊位", + arrivalPlace: "3号泊位东侧集结点", + workContent: "装卸保障与现场辅助作业", + workProcess: "到位确认、作业执行、反馈记录", + cargoName: "矿石", + voyage: "QHD-0724-A", + personnel: "李明、赵强、刘洋、韩涛", + personId: "DK-RY-10021、DK-RY-10046、DK-RY-10108、DK-RY-10177", + licenseNo: "冀C-3107、冀C-3112", + requestedEquipment: "装载机2台、对讲机6部", + fulfilledEquipment: "装载机L-07、L-12", + applicant: "刘静", + applyTime: "2026-07-24 08:15", + approvalOwner: "双控系统审批", + approvalStatus: "审核中", + orderStatus: "待下达", + position: "东港3号泊位东侧集结点", + longitude: 119.694577, + latitude: 39.940504, + trackPoints: [ + [119.6908, 39.9386], + [119.6924, 39.9394], + [119.694577, 39.940504], + ], + locationTime: "2026-07-24 09:42:16", + onlineStatus: "在线", + trackStatus: "轨迹已生成", + accessState: "派工有效,可进入隔离封闭区域", + videoState: "移动监控已推送", + progress: "35%", + approvalRecords: [ + ["2026-07-24 08:20", "公务公司审核", "通过", "用工信息完整"], + ["2026-07-24 09:10", "港务公司计划员审核", "处理中", "等待当前节点确认"], + ], + }, + { + id: "QG-SC-RY-20260724-013", + sourceNo: "PGD-SC-260724-1028", + sourceSystem: "流机公司智能调度平台", + businessType: "生产派工", + objectType: "人员", + dispatchMode: "临时派工", + planNo: "JH-DG-0724-031", + currentRole: "审批角色未配置", + carUnit: "东港业务部", + applyDept: "东港计划室", + responsibleDept: "现场维修配合组", + responsiblePerson: "邓凯", + expectedArrivalTime: "2026-07-24 13:00", + shift: "白班", + workPlace: "东港维修区", + arrivalPlace: "维修区南门", + workContent: "设备检修配合作业", + workProcess: "停机确认、维修配合、验收反馈", + cargoName: "-", + voyage: "-", + personnel: "派工人员未匹配", + personId: "-", + licenseNo: "流机07号装载机", + requestedEquipment: "维修工具包A3", + fulfilledEquipment: "-", + applicant: "陈立", + applyTime: "2026-07-24 10:28", + approvalOwner: "双控系统审批", + approvalStatus: "待审", + orderStatus: "待下达", + position: "东港维修区", + longitude: 119.6819, + latitude: 39.9358, + trackPoints: [], + locationTime: "未开始定位", + onlineStatus: "离线", + trackStatus: "未生成", + accessState: "未完成审批,暂不可入区", + videoState: "未推送", + progress: "8%", + approvalRecords: [["2026-07-24 10:30", "同步校验", "异常", "人员ID未匹配,当前审批角色未配置"]], + }, + { + id: "QG-SC-RY-20260724-021", + sourceNo: "PGD-SC-260724-1136", + sourceSystem: "流机公司智能调度平台", + businessType: "生产派工", + objectType: "人员", + dispatchMode: "自动派工", + planNo: "JH-XG-0724-041", + currentRole: "已归档", + carUnit: "西港业务部", + applyDept: "西港生产计划室", + responsibleDept: "西港现场保障组", + responsiblePerson: "周强", + expectedArrivalTime: "2026-07-24 14:30", + shift: "白班", + workPlace: "西港主干路", + arrivalPlace: "西港2号门", + workContent: "短倒转运保障", + workProcess: "人员到位、连续作业、结束反馈", + cargoName: "煤炭", + voyage: "XG-0724-C", + personnel: "陈勇、孙凯、马宁", + personId: "DK-RY-10203、DK-RY-10211、DK-RY-10245", + licenseNo: "冀C-5021、冀C-5022、冀C-5025", + requestedEquipment: "对讲机3部", + fulfilledEquipment: "对讲机3部", + applicant: "周强", + applyTime: "2026-07-24 11:36", + approvalOwner: "派工系统审批", + approvalStatus: "已下达", + orderStatus: "作业中", + position: "西港主干路北段", + longitude: 119.6426, + latitude: 39.9284, + trackPoints: [ + [119.6378, 39.9256], + [119.6401, 39.9271], + [119.6426, 39.9284], + ], + locationTime: "2026-07-24 14:58:41", + onlineStatus: "在线", + trackStatus: "轨迹记录中", + accessState: "派工有效,可进入隔离封闭区域", + videoState: "无移动监控", + progress: "68%", + approvalRecords: [["2026-07-24 12:04", "流机调度员下达", "已下达", "派工系统审批完成后同步"]], + }, +]; + +function getRequestBody(options) { + try { + return JSON.parse(options.body || "{}"); + } + catch { + return {}; + } +} + +Mock.setup({ timeout: "300-600" }); + +Mock.mock(/\/production-dispatch\/personnel\/production\/list$/, "post", (options) => { + const body = getRequestBody(options); + const keyword = body.keyword?.trim(); + const filteredList = productionDispatchList.filter((record) => { + if (keyword && ![ + record.planNo, + record.workPlace, + record.personnel, + record.licenseNo, + record.applicant, + ].some(value => value.includes(keyword))) { + return false; + } + return ["carUnit", "shift", "approvalStatus", "currentRole"] + .every(key => !body[key] || record[key] === body[key]); + }); + const pageIndex = Number(body.pageIndex || 1); + const pageSize = Number(body.pageSize || 20); + const start = (pageIndex - 1) * pageSize; + + return { + success: true, + totalCount: filteredList.length, + data: filteredList.slice(start, start + pageSize), + }; +}); + +Mock.mock(/\/production-dispatch\/personnel\/production\/sync$/, "post", () => ({ success: true })); + +Mock.mock(/\/production-dispatch\/personnel\/production\/approval$/, "post", (options) => { + const body = getRequestBody(options); + const record = productionDispatchList.find(item => item.id === body.id); + const canApprove = record + && record.approvalOwner === "双控系统审批" + && ["待审", "审核中"].includes(record.approvalStatus) + && !/未配置|未匹配/.test(`${record.currentRole} ${record.personnel}`); + + if (!canApprove) { + return { success: false, errMessage: "当前派工单不可审批" }; + } + if (!["PASS", "REJECT"].includes(body.approvalResult)) { + return { success: false, errMessage: "请选择审批结果" }; + } + if (body.approvalResult === "REJECT" && !body.rejectReason?.trim()) { + return { success: false, errMessage: "请输入驳回原因" }; + } + + const approved = body.approvalResult === "PASS"; + record.approvalStatus = approved ? "已通过" : "已驳回"; + record.rejectReason = approved ? "" : body.rejectReason.trim(); + const approvalRecord = [ + new Date().toLocaleString("sv-SE", { hour12: false }).slice(0, 16), + record.currentRole, + approved ? "通过" : "驳回", + approved ? "审批通过" : record.rejectReason, + ]; + const processingRecordIndex = record.approvalRecords.findIndex(item => item[2] === "处理中"); + if (processingRecordIndex >= 0) { + record.approvalRecords[processingRecordIndex] = approvalRecord; + } + else { + record.approvalRecords.push(approvalRecord); + } + + return { success: true, data: record }; +}); diff --git a/src/mock/vehicleDispatch/cleaningDispatch/index.js b/src/mock/vehicleDispatch/cleaningDispatch/index.js new file mode 100644 index 0000000..e810890 --- /dev/null +++ b/src/mock/vehicleDispatch/cleaningDispatch/index.js @@ -0,0 +1,157 @@ +import Mock from "mockjs"; + +// 当前没有车辆清扫派工接口文档,以下数据仅用于还原交互原型,接入真实接口后删除本拦截文件。 +const cleaningDispatchList = [ + { + id: "QSGD-260724-018", + sourceNo: "QSGD-260724-018", + sourceSystem: "流机公司智能调度平台", + businessType: "清扫派工", + objectType: "车辆", + dispatchMode: "计划派工", + workDate: "2026-07-24", + workSource: "清扫计划", + companyName: "秦港股份", + workCompany: "清扫二队", + workProject: "道路清扫", + workProcess: "机扫", + workPlace: "东港堆场南路至5号门", + inspector: "郭涵", + cleaningPeople: "王磊、刘雪", + markerName: "冀C-QS22", + deviceName: "清扫车QS-22", + description: "按计划完成道路清扫", + orderStatus: "作业中", + settlementStatus: "未结算", + timeout: "否", + evaluation: "-", + currentRole: "流机调度员下达", + approvalOwner: "派工系统审批", + approvalStatus: "已下达", + applicant: "清扫计划员", + applyTime: "2026-07-24 07:20", + position: "东港堆场南路", + locationTime: "2026-07-24 10:16:29", + onlineStatus: "在线", + trackStatus: "轨迹记录中", + progress: "74%", + accessState: "派工有效,可进入隔离封闭区域", + videoState: "移动监控已推送", + approvalRecords: [["2026-07-24 07:36", "流机调度员下达", "已下达", "派工系统审批完成后同步"]], + }, + { + id: "QSGD-260724-031", + sourceNo: "QSGD-260724-031", + sourceSystem: "流机公司智能调度平台", + businessType: "清扫派工", + objectType: "车辆", + dispatchMode: "临时派工", + workDate: "2026-07-24", + workSource: "临时清扫", + companyName: "秦港股份", + workCompany: "相关方清扫一队", + workProject: "吸污", + workProcess: "吸污", + workPlace: "隔离封闭区2号门排水沟", + inspector: "孙晓", + cleaningPeople: "张敏、李兵", + markerName: "冀C-QS08", + deviceName: "吸污车QS-08", + description: "排水沟吸污,作业后需现场确认", + orderStatus: "待下达", + settlementStatus: "未结算", + timeout: "否", + evaluation: "-", + currentRole: "港务公司现场值守人确认", + approvalOwner: "双控系统审批", + approvalStatus: "审核中", + applicant: "港务值守员", + applyTime: "2026-07-24 10:05", + position: "隔离封闭区2号门排水沟", + locationTime: "2026-07-24 10:26:18", + onlineStatus: "在线", + trackStatus: "轨迹已生成", + progress: "28%", + accessState: "等待作业前现场确认", + videoState: "移动监控已推送", + approvalRecords: [ + ["2026-07-24 10:07", "公务公司审核", "通过", "临时清扫申请有效"], + ["2026-07-24 10:22", "港务公司现场值守人确认", "处理中", "等待作业前确认"], + ], + }, +]; + +function getRequestBody(options) { + try { + return JSON.parse(options.body || "{}"); + } + catch { + return {}; + } +} + +Mock.mock(/\/production-dispatch\/vehicle\/cleaning\/list$/, "post", (options) => { + const body = getRequestBody(options); + const keyword = body.keyword?.trim(); + const filteredList = cleaningDispatchList.filter((record) => { + if (keyword && ![ + record.id, + record.workPlace, + record.cleaningPeople, + record.deviceName, + ].some(value => value.includes(keyword))) { + return false; + } + return ["workSource", "workCompany", "orderStatus", "settlementStatus", "timeout"] + .every(key => !body[key] || record[key] === body[key]); + }); + const pageIndex = Number(body.pageIndex || 1); + const pageSize = Number(body.pageSize || 20); + const start = (pageIndex - 1) * pageSize; + + return { + success: true, + totalCount: filteredList.length, + data: filteredList.slice(start, start + pageSize), + }; +}); + +Mock.mock(/\/production-dispatch\/vehicle\/cleaning\/sync$/, "post", () => ({ success: true })); + +Mock.mock(/\/production-dispatch\/vehicle\/cleaning\/approval$/, "post", (options) => { + const body = getRequestBody(options); + const record = cleaningDispatchList.find(item => item.id === body.id); + const canApprove = record + && record.approvalOwner === "双控系统审批" + && ["待审", "审核中"].includes(record.approvalStatus) + && !/未配置|未匹配/.test(`${record.currentRole} ${record.cleaningPeople}`); + + if (!canApprove) { + return { success: false, errMessage: "当前车辆清扫派工单不可审批" }; + } + if (!["PASS", "REJECT"].includes(body.approvalResult)) { + return { success: false, errMessage: "请选择审批结果" }; + } + if (body.approvalResult === "REJECT" && !body.rejectReason?.trim()) { + return { success: false, errMessage: "请输入驳回原因" }; + } + + const approved = body.approvalResult === "PASS"; + record.approvalStatus = approved ? "已通过" : "已驳回"; + record.rejectReason = approved ? "" : body.rejectReason.trim(); + const approvalRecord = [ + new Date().toLocaleString("sv-SE", { hour12: false }).slice(0, 16), + record.currentRole, + approved ? "通过" : "驳回", + approved ? "审批通过" : record.rejectReason, + ]; + const processingRecordIndex = record.approvalRecords.findIndex(item => item[2] === "处理中"); + if (processingRecordIndex >= 0) { + record.approvalRecords[processingRecordIndex] = approvalRecord; + } + else { + record.approvalRecords.push(approvalRecord); + } + + return { success: true, data: record }; +}); diff --git a/src/mock/vehicleDispatch/productionDispatch/index.js b/src/mock/vehicleDispatch/productionDispatch/index.js new file mode 100644 index 0000000..30d17e2 --- /dev/null +++ b/src/mock/vehicleDispatch/productionDispatch/index.js @@ -0,0 +1,211 @@ +import Mock from "mockjs"; + +// 当前没有车辆生产派工接口文档,以下数据仅用于还原交互原型,接入真实接口后删除本拦截文件。 +const productionDispatchList = [ + { + id: "QG-SC-CL-20260724-002", + sourceNo: "PGD-SC-260724-0832", + planNo: "JH-XG-0724-022", + sourceSystem: "流机公司智能调度平台", + businessType: "生产派工", + objectType: "车辆", + dispatchMode: "自动派工", + currentRole: "流机调度员下达", + carUnit: "西港业务部", + applyDept: "西港生产计划室", + responsibleDept: "西港现场保障组", + responsiblePerson: "李海军", + expectedArrivalTime: "2026-07-24 09:30", + shift: "白班", + workPlace: "西港主干路", + arrivalPlace: "西港2号门", + workContent: "短倒转运保障", + workProcess: "车辆到位、连续作业、结束反馈", + cargoName: "煤炭", + voyage: "XG-0724-C", + requestedEquipment: "自卸车3台", + fulfilledEquipment: "自卸车X-21、X-22、X-25", + personnel: "陈勇、孙凯、马宁", + personId: "DK-RY-10203、DK-RY-10211、DK-RY-10245", + licenseNo: "冀C-5021、冀C-5022、冀C-5025", + vehicleType: "自卸车", + driver: "陈勇、孙凯、马宁", + applicant: "周强", + applyTime: "2026-07-24 08:32", + approvalOwner: "派工系统审批", + approvalStatus: "已下达", + orderStatus: "作业中", + position: "西港主干路北段", + locationTime: "2026-07-24 10:24:08", + onlineStatus: "在线", + trackStatus: "轨迹记录中", + progress: "68%", + accessState: "派工有效,可进入隔离封闭区域", + videoState: "移动监控已推送", + approvalRecords: [["2026-07-24 08:54", "流机调度员下达", "已下达", "派工系统审批完成后同步"]], + }, + { + id: "QG-SC-CL-20260724-017", + sourceNo: "PGD-SC-260724-1048", + planNo: "JH-DG-0724-038", + sourceSystem: "流机公司智能调度平台", + businessType: "生产派工", + objectType: "车辆", + dispatchMode: "临时派工", + currentRole: "港务公司业务部长审核", + carUnit: "东港业务部", + applyDept: "东港生产调度室", + responsibleDept: "东港现场保障组", + responsiblePerson: "韩冰", + expectedArrivalTime: "2026-07-24 12:30", + shift: "白班", + workPlace: "东港5号堆场", + arrivalPlace: "5号堆场西门", + workContent: "倒运车辆临时支援", + workProcess: "派车确认、安全交底、作业反馈", + cargoName: "钢材", + voyage: "DG-0724-H", + requestedEquipment: "牵引车2台、平板车2台", + fulfilledEquipment: "牵引车T-09、T-12", + personnel: "郭涛、吴建", + personId: "DK-RY-10305、DK-RY-10361", + licenseNo: "冀C-6309、冀C-6312", + vehicleType: "牵引车", + driver: "郭涛、吴建", + applicant: "赵一鸣", + applyTime: "2026-07-24 10:48", + approvalOwner: "双控系统审批", + approvalStatus: "审核中", + orderStatus: "待下达", + position: "东港5号堆场西门", + locationTime: "2026-07-24 12:06:22", + onlineStatus: "在线", + trackStatus: "轨迹已生成", + progress: "41%", + accessState: "审批中,等待下达后放行", + videoState: "移动监控待推送", + approvalRecords: [ + ["2026-07-24 10:55", "公务公司审核", "通过", "车辆资质有效"], + ["2026-07-24 11:40", "港务公司计划员审核", "通过", "作业窗口符合计划"], + ["2026-07-24 12:02", "港务公司业务部长审核", "处理中", "等待当前节点确认"], + ], + }, + { + id: "QG-SC-CL-20260724-029", + sourceNo: "PGD-SC-260724-1510", + planNo: "JH-XG-0724-068", + sourceSystem: "流机公司智能调度平台", + businessType: "生产派工", + objectType: "车辆", + dispatchMode: "自动派工", + currentRole: "已归档", + carUnit: "西港业务部", + applyDept: "西港生产计划室", + responsibleDept: "西港现场保障组", + responsiblePerson: "曹俊", + expectedArrivalTime: "2026-07-24 16:00", + shift: "夜班", + workPlace: "西港煤场南路", + arrivalPlace: "煤场南路检查口", + workContent: "夜班倒运保障", + workProcess: "车辆入区、转运、离区确认", + cargoName: "煤炭", + voyage: "XG-0724-N", + requestedEquipment: "自卸车4台", + fulfilledEquipment: "自卸车X-31、X-32、X-33、X-36", + personnel: "沈旭、李杨、贾鹏、刘帅", + personId: "DK-RY-10511、DK-RY-10512、DK-RY-10515、DK-RY-10519", + licenseNo: "冀C-5031、冀C-5032、冀C-5033、冀C-5036", + vehicleType: "自卸车", + driver: "沈旭、李杨、贾鹏、刘帅", + applicant: "马琳", + applyTime: "2026-07-24 15:10", + approvalOwner: "派工系统审批", + approvalStatus: "已通过", + orderStatus: "待下达", + position: "西港煤场南路检查口", + locationTime: "未开始定位", + onlineStatus: "离线", + trackStatus: "未生成", + progress: "0%", + accessState: "待派工系统下达", + videoState: "未推送", + approvalRecords: [["2026-07-24 15:22", "流机计划员审核", "已通过", "派工系统审批完成后同步"]], + }, +]; + +function getRequestBody(options) { + try { + return JSON.parse(options.body || "{}"); + } + catch { + return {}; + } +} + +Mock.mock(/\/production-dispatch\/vehicle\/production\/list$/, "post", (options) => { + const body = getRequestBody(options); + const keyword = body.keyword?.trim(); + const filteredList = productionDispatchList.filter((record) => { + if (keyword && ![ + record.planNo, + record.workPlace, + record.personnel, + record.licenseNo, + record.applicant, + ].some(value => value.includes(keyword))) { + return false; + } + return ["carUnit", "shift", "approvalStatus", "currentRole"] + .every(key => !body[key] || record[key] === body[key]); + }); + const pageIndex = Number(body.pageIndex || 1); + const pageSize = Number(body.pageSize || 20); + const start = (pageIndex - 1) * pageSize; + + return { + success: true, + totalCount: filteredList.length, + data: filteredList.slice(start, start + pageSize), + }; +}); + +Mock.mock(/\/production-dispatch\/vehicle\/production\/sync$/, "post", () => ({ success: true })); + +Mock.mock(/\/production-dispatch\/vehicle\/production\/approval$/, "post", (options) => { + const body = getRequestBody(options); + const record = productionDispatchList.find(item => item.id === body.id); + const canApprove = record + && record.approvalOwner === "双控系统审批" + && ["待审", "审核中"].includes(record.approvalStatus) + && !/未配置|未匹配/.test(`${record.currentRole} ${record.personnel}`); + + if (!canApprove) { + return { success: false, errMessage: "当前车辆派工单不可审批" }; + } + if (!["PASS", "REJECT"].includes(body.approvalResult)) { + return { success: false, errMessage: "请选择审批结果" }; + } + if (body.approvalResult === "REJECT" && !body.rejectReason?.trim()) { + return { success: false, errMessage: "请输入驳回原因" }; + } + + const approved = body.approvalResult === "PASS"; + record.approvalStatus = approved ? "已通过" : "已驳回"; + record.rejectReason = approved ? "" : body.rejectReason.trim(); + const approvalRecord = [ + new Date().toLocaleString("sv-SE", { hour12: false }).slice(0, 16), + record.currentRole, + approved ? "通过" : "驳回", + approved ? "审批通过" : record.rejectReason, + ]; + const processingRecordIndex = record.approvalRecords.findIndex(item => item[2] === "处理中"); + if (processingRecordIndex >= 0) { + record.approvalRecords[processingRecordIndex] = approvalRecord; + } + else { + record.approvalRecords.push(approvalRecord); + } + + return { success: true, data: record }; +}); diff --git a/src/pages/Container/DeviceManagement/InternalDevice/index.js b/src/pages/Container/DeviceManagement/InternalDevice/index.js new file mode 100644 index 0000000..6bc0bb0 --- /dev/null +++ b/src/pages/Container/DeviceManagement/InternalDevice/index.js @@ -0,0 +1,17 @@ +import { Connect } from "@cqsjjb/jjb-dva-runtime"; + +import DeviceManagementList from "~/components/DeviceManagementList"; +import { NS_DEVICE_MANAGEMENT } from "~/enumerate/namespace"; + +function InternalDevice(props) { + return ( + + ); +} + +export default Connect([NS_DEVICE_MANAGEMENT], true)(InternalDevice); diff --git a/src/pages/Container/DeviceManagement/PartnerDevice/index.js b/src/pages/Container/DeviceManagement/PartnerDevice/index.js new file mode 100644 index 0000000..1042dab --- /dev/null +++ b/src/pages/Container/DeviceManagement/PartnerDevice/index.js @@ -0,0 +1,17 @@ +import { Connect } from "@cqsjjb/jjb-dva-runtime"; + +import DeviceManagementList from "~/components/DeviceManagementList"; +import { NS_DEVICE_MANAGEMENT } from "~/enumerate/namespace"; + +function PartnerDevice(props) { + return ( + + ); +} + +export default Connect([NS_DEVICE_MANAGEMENT], true)(PartnerDevice); diff --git a/src/pages/Container/DeviceManagement/index.js b/src/pages/Container/DeviceManagement/index.js new file mode 100644 index 0000000..48fc37e --- /dev/null +++ b/src/pages/Container/DeviceManagement/index.js @@ -0,0 +1,3 @@ +export default function DeviceManagement(props) { + return props.children; +} diff --git a/src/pages/Container/Entry/index.js b/src/pages/Container/Entry/index.js new file mode 100644 index 0000000..c51ecfd --- /dev/null +++ b/src/pages/Container/Entry/index.js @@ -0,0 +1,31 @@ +import { ImportCore } from "@cqsjjb/jjb-common-decorator/module"; +import React from "react"; + +export default class Entry extends React.Component { + state = { + Component: undefined, + }; + + componentDidMount() { + if (process.env.app.appKey) { + ImportCore({ + name: "$", + from: "https://cdn.cqjjb.cn/jcloud/use/plugin/b31c9840a57f11ef91cf7f3cabbb7484/latest", + }).then((res) => { + if (res.status) { + this.setState({ Component: res.module?.default }); + } + }); + } + } + + render() { + const { Component } = this.state; + return (Component && process.env.app.appKey) && ( + + ); + } +} diff --git a/src/pages/Container/PersonnelDispatch/CleaningDispatch/index.js b/src/pages/Container/PersonnelDispatch/CleaningDispatch/index.js new file mode 100644 index 0000000..d9fc5ec --- /dev/null +++ b/src/pages/Container/PersonnelDispatch/CleaningDispatch/index.js @@ -0,0 +1,20 @@ +import { Connect } from "@cqsjjb/jjb-dva-runtime"; + +import DispatchList from "~/components/DispatchList"; +import { NS_PERSONNEL_DISPATCH } from "~/enumerate/namespace"; + +function CleaningDispatch(props) { + return ( + + ); +} + +export default Connect([NS_PERSONNEL_DISPATCH], true)(CleaningDispatch); diff --git a/src/pages/Container/PersonnelDispatch/ProductionDispatch/index.js b/src/pages/Container/PersonnelDispatch/ProductionDispatch/index.js new file mode 100644 index 0000000..880d6e7 --- /dev/null +++ b/src/pages/Container/PersonnelDispatch/ProductionDispatch/index.js @@ -0,0 +1,20 @@ +import { Connect } from "@cqsjjb/jjb-dva-runtime"; + +import DispatchList from "~/components/DispatchList"; +import { NS_PERSONNEL_DISPATCH } from "~/enumerate/namespace"; + +function ProductionDispatch(props) { + return ( + + ); +} + +export default Connect([NS_PERSONNEL_DISPATCH], true)(ProductionDispatch); diff --git a/src/pages/Container/PersonnelDispatch/index.js b/src/pages/Container/PersonnelDispatch/index.js new file mode 100644 index 0000000..a8eff3b --- /dev/null +++ b/src/pages/Container/PersonnelDispatch/index.js @@ -0,0 +1,3 @@ +export default function PersonnelDispatch(props) { + return props.children; +} diff --git a/src/pages/Container/VehicleDispatch/CleaningDispatch/index.js b/src/pages/Container/VehicleDispatch/CleaningDispatch/index.js new file mode 100644 index 0000000..49c99ca --- /dev/null +++ b/src/pages/Container/VehicleDispatch/CleaningDispatch/index.js @@ -0,0 +1,20 @@ +import { Connect } from "@cqsjjb/jjb-dva-runtime"; + +import DispatchList from "~/components/DispatchList"; +import { NS_VEHICLE_DISPATCH } from "~/enumerate/namespace"; + +function CleaningDispatch(props) { + return ( + + ); +} + +export default Connect([NS_VEHICLE_DISPATCH], true)(CleaningDispatch); diff --git a/src/pages/Container/VehicleDispatch/ProductionDispatch/index.js b/src/pages/Container/VehicleDispatch/ProductionDispatch/index.js new file mode 100644 index 0000000..f342496 --- /dev/null +++ b/src/pages/Container/VehicleDispatch/ProductionDispatch/index.js @@ -0,0 +1,20 @@ +import { Connect } from "@cqsjjb/jjb-dva-runtime"; + +import DispatchList from "~/components/DispatchList"; +import { NS_VEHICLE_DISPATCH } from "~/enumerate/namespace"; + +function ProductionDispatch(props) { + return ( + + ); +} + +export default Connect([NS_VEHICLE_DISPATCH], true)(ProductionDispatch); diff --git a/src/pages/Container/VehicleDispatch/index.js b/src/pages/Container/VehicleDispatch/index.js new file mode 100644 index 0000000..32645ee --- /dev/null +++ b/src/pages/Container/VehicleDispatch/index.js @@ -0,0 +1,3 @@ +export default function VehicleDispatch(props) { + return props.children; +} diff --git a/src/pages/Container/index.js b/src/pages/Container/index.js new file mode 100644 index 0000000..6a812c4 --- /dev/null +++ b/src/pages/Container/index.js @@ -0,0 +1,100 @@ +import { ImportCore } from "@cqsjjb/jjb-common-decorator/module"; +import { theme as antdTheme, App, ConfigProvider } from "antd"; +import language from "antd/locale/zh_CN"; +import React from "react"; +import { InjectContext } from "~/enumerate/context"; + +export default class Container extends React.Component { + state = window?.base?.themeConfig || { + algorithm: window.process.env.app.antd.algorithm, + borderRadius: window.process.env.app.antd.borderRadius, + colorPrimary: window.process.env.app.antd.colorPrimary, + }; + + get token() { + const { + colorPrimary, + borderRadius, + } = this.state; + return { + fontFamily: window.process.env.app.antd.fontFamily, + colorPrimary, + borderRadius, + }; + } + + get algorithm() { + return antdTheme[this.state.algorithm]; + } + + componentDidMount() { + if (window.__IN_BASE__) { + // eslint-disable-next-line react-web-api/no-leaked-event-listener + window.base.addEventListener("EVENT_THEME_CONTROL", (e) => { + const config = e.data; + this.setState({ [config.field]: config.value }); + }); + } + } + + render() { + return ( + + + + + + ); + } +} + +function AppMiddle(props) { + return ( + + {process.env.NODE_ENV === "development" + ? props.children + : ( + + {props.children} + + )} + + ); +} + +class Interceptor extends React.Component { + state = { + Component: undefined, + }; + + componentDidMount() { + if (process.env.app.appKey) { + ImportCore({ + name: "$", + from: "https://cdn.cqjjb.cn/jcloud/use/plugin/b31c9840a57f11ef91cf7f3cabbb7484/latest", + }).then(async (res) => { + if (res.status) { + this.setState({ Component: res.module?.PageCover }); + } + }); + } + } + + render() { + const { Component } = this.state; + return (Component && process.env.app.appKey && process.env.NODE_ENV === "development") + ? ( + + {this.props.children} + + ) + : this.props.children; + } +} diff --git a/src/pages/index.js b/src/pages/index.js new file mode 100644 index 0000000..64c36bb --- /dev/null +++ b/src/pages/index.js @@ -0,0 +1,8 @@ +export default function () { + return ( +

+ 底座微应用模板,技术文档: + https://www.yuque.com/buhangjiecheshen-ymbtb/qc0093/gxdun1dphetcurko +

+ ); +} diff --git a/webstorm.config.js b/webstorm.config.js new file mode 100644 index 0000000..ca262cd --- /dev/null +++ b/webstorm.config.js @@ -0,0 +1,16 @@ +"use strict"; +const path = require("node:path"); + +function resolve(dir) { + return path.join(__dirname, ".", dir); +} + +module.exports = { + context: path.resolve(__dirname, "./"), + resolve: { + extensions: [".js"], + alias: { + "~": resolve("src/"), + }, + }, +};