From 1476e3276ece54f381bad5b906ac36783a56f8ba Mon Sep 17 00:00:00 2001 From: LiuJiaNan <15703339975@163.com> Date: Sat, 22 Aug 2026 09:30:25 +0800 Subject: [PATCH] init --- .editorconfig | 13 + .gitignore | 14 + README.md | 75 +++++ eslint.config.js | 49 +++ jjb.babel.js | 23 ++ jjb.config.js | 74 +++++ jsconfig.json | 10 + package.json | 50 +++ public/index.html | 55 ++++ router.md | 24 ++ src/api/deviceManagement/index.js | 22 ++ src/api/global/index.js | 1 + src/api/personnelDispatch/index.js | 32 ++ src/api/vehicleDispatch/index.js | 32 ++ src/components/DeviceDetailModal/index.js | 57 ++++ src/components/DeviceManagementList/index.js | 131 ++++++++ src/components/DispatchDetailModal/index.js | 310 ++++++++++++++++++ .../DispatchDetailModal/index.module.less | 9 + .../CleaningDispatchList/index.js | 211 ++++++++++++ .../ProductionDispatchList/index.js | 220 +++++++++++++ src/components/DispatchList/index.js | 9 + src/components/DispatchMap/index.js | 109 ++++++ src/components/DispatchMap/index.less | 5 + src/components/DispatchMap/index.module.less | 13 + .../ManagementList/index.module.less | 9 + src/components/SummaryMetrics/index.js | 17 + .../SummaryMetrics/index.module.less | 49 +++ src/components/SyncExportActions/index.js | 43 +++ src/components/index.js | 1 + src/enumerate/constant/index.js | 5 + src/enumerate/context/index.js | 8 + src/enumerate/namespace/index.js | 10 + src/main.js | 56 ++++ .../deviceManagement/internalDevice/index.js | 101 ++++++ .../deviceManagement/partnerDevice/index.js | 86 +++++ src/mock/index.js | 20 ++ .../cleaningDispatch/index.js | 154 +++++++++ .../productionDispatch/index.js | 223 +++++++++++++ .../vehicleDispatch/cleaningDispatch/index.js | 157 +++++++++ .../productionDispatch/index.js | 211 ++++++++++++ .../DeviceManagement/InternalDevice/index.js | 17 + .../DeviceManagement/PartnerDevice/index.js | 17 + src/pages/Container/DeviceManagement/index.js | 3 + src/pages/Container/Entry/index.js | 31 ++ .../CleaningDispatch/index.js | 20 ++ .../ProductionDispatch/index.js | 20 ++ .../Container/PersonnelDispatch/index.js | 3 + .../VehicleDispatch/CleaningDispatch/index.js | 20 ++ .../ProductionDispatch/index.js | 20 ++ src/pages/Container/VehicleDispatch/index.js | 3 + src/pages/Container/index.js | 100 ++++++ src/pages/index.js | 8 + webstorm.config.js | 16 + 53 files changed, 2976 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 README.md create mode 100644 eslint.config.js create mode 100644 jjb.babel.js create mode 100644 jjb.config.js create mode 100644 jsconfig.json create mode 100644 package.json create mode 100644 public/index.html create mode 100644 router.md create mode 100644 src/api/deviceManagement/index.js create mode 100644 src/api/global/index.js create mode 100644 src/api/personnelDispatch/index.js create mode 100644 src/api/vehicleDispatch/index.js create mode 100644 src/components/DeviceDetailModal/index.js create mode 100644 src/components/DeviceManagementList/index.js create mode 100644 src/components/DispatchDetailModal/index.js create mode 100644 src/components/DispatchDetailModal/index.module.less create mode 100644 src/components/DispatchList/CleaningDispatchList/index.js create mode 100644 src/components/DispatchList/ProductionDispatchList/index.js create mode 100644 src/components/DispatchList/index.js create mode 100644 src/components/DispatchMap/index.js create mode 100644 src/components/DispatchMap/index.less create mode 100644 src/components/DispatchMap/index.module.less create mode 100644 src/components/ManagementList/index.module.less create mode 100644 src/components/SummaryMetrics/index.js create mode 100644 src/components/SummaryMetrics/index.module.less create mode 100644 src/components/SyncExportActions/index.js create mode 100644 src/components/index.js create mode 100644 src/enumerate/constant/index.js create mode 100644 src/enumerate/context/index.js create mode 100644 src/enumerate/namespace/index.js create mode 100644 src/main.js create mode 100644 src/mock/deviceManagement/internalDevice/index.js create mode 100644 src/mock/deviceManagement/partnerDevice/index.js create mode 100644 src/mock/index.js create mode 100644 src/mock/personnelDispatch/cleaningDispatch/index.js create mode 100644 src/mock/personnelDispatch/productionDispatch/index.js create mode 100644 src/mock/vehicleDispatch/cleaningDispatch/index.js create mode 100644 src/mock/vehicleDispatch/productionDispatch/index.js create mode 100644 src/pages/Container/DeviceManagement/InternalDevice/index.js create mode 100644 src/pages/Container/DeviceManagement/PartnerDevice/index.js create mode 100644 src/pages/Container/DeviceManagement/index.js create mode 100644 src/pages/Container/Entry/index.js create mode 100644 src/pages/Container/PersonnelDispatch/CleaningDispatch/index.js create mode 100644 src/pages/Container/PersonnelDispatch/ProductionDispatch/index.js create mode 100644 src/pages/Container/PersonnelDispatch/index.js create mode 100644 src/pages/Container/VehicleDispatch/CleaningDispatch/index.js create mode 100644 src/pages/Container/VehicleDispatch/ProductionDispatch/index.js create mode 100644 src/pages/Container/VehicleDispatch/index.js create mode 100644 src/pages/Container/index.js create mode 100644 src/pages/index.js create mode 100644 webstorm.config.js 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/"), + }, + }, +};