accidentDialog.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. <template>
  2. <u-popup
  3. :show="visible"
  4. mode="bottom"
  5. :round="10"
  6. :closeOnClickOverlay="false"
  7. @close="cancel"
  8. class="accident-popup"
  9. >
  10. <view class="popup-content">
  11. <view class="popup-header"
  12. ><text class="popup-title">{{ dialogTitle }}</text
  13. ><view class="close-btn" @click="cancel">×</view></view
  14. >
  15. <scroll-view class="popup-body" scroll-y>
  16. <view class="card-a">
  17. <!-- 事故事件信息 -->
  18. <view class="card-section">
  19. <view class="section-title">📋 事故事件信息</view>
  20. <u--form
  21. labelPosition="left"
  22. labelWidth="180rpx"
  23. :model="form"
  24. :rules="rules"
  25. ref="formRef"
  26. >
  27. <u-form-item
  28. label="事故事件名称"
  29. prop="acdntName"
  30. borderBottom
  31. required
  32. >
  33. <u--input
  34. v-model="form.acdntName"
  35. placeholder="请输入"
  36. :disabled="isView"
  37. border="none"
  38. inputAlign="right"
  39. />
  40. </u-form-item>
  41. <u-form-item
  42. label="发生时间"
  43. prop="occurrenceTime"
  44. borderBottom
  45. required
  46. >
  47. <view
  48. class="picker-value"
  49. @click="!isView && showDateTimePicker('occurrenceTime')"
  50. >{{ form.occurrenceTime || "请选择" }}</view
  51. >
  52. <u-icon slot="right" name="arrow-right" />
  53. </u-form-item>
  54. <u-form-item
  55. label="事故发生地点"
  56. prop="acdntPlace"
  57. borderBottom
  58. required
  59. >
  60. <u--input
  61. v-model="form.acdntPlace"
  62. placeholder="请输入"
  63. :disabled="isView"
  64. border="none"
  65. inputAlign="right"
  66. />
  67. </u-form-item>
  68. <u-form-item label="来源类型" borderBottom>
  69. <view
  70. class="picker-value"
  71. @click="!isView && showSourceTypePicker()"
  72. >{{ getSourceTypeLabel(form.sourceType) || "请选择" }}</view
  73. >
  74. <u-icon slot="right" name="arrow-right" />
  75. </u-form-item>
  76. <u-form-item label="来源单据名称" borderBottom>
  77. <u--input
  78. v-model="form.sourceName"
  79. placeholder="请选择"
  80. readonly
  81. :disabled="isView"
  82. border="none"
  83. inputAlign="right"
  84. @click="addSource"
  85. />
  86. </u-form-item>
  87. <u-form-item
  88. label="事故事件经过"
  89. prop="acdntPass"
  90. borderBottom
  91. required
  92. >
  93. <u--textarea
  94. v-model="form.acdntPass"
  95. placeholder="请输入"
  96. :disabled="isView"
  97. border="none"
  98. height="120rpx"
  99. />
  100. </u-form-item>
  101. <u-form-item label="上报人" prop="reportPersonName" borderBottom>
  102. <u--input
  103. v-model="form.reportPersonName"
  104. placeholder="请选择"
  105. readonly
  106. :disabled="isView"
  107. border="none"
  108. inputAlign="report"
  109. @click="userOpen('duty')"
  110. />
  111. </u-form-item>
  112. <u-form-item label="上报部门" borderBottom>
  113. <u--input
  114. v-model="form.reportDeptName"
  115. disabled
  116. border="none"
  117. inputAlign="right"
  118. />
  119. </u-form-item>
  120. <u-form-item
  121. label="上报时间"
  122. prop="reportTime"
  123. borderBottom
  124. required
  125. >
  126. <view
  127. class="picker-value"
  128. @click="!isView && showDateTimePicker('reportTime')"
  129. >{{ form.reportTime || "请选择" }}</view
  130. >
  131. <u-icon slot="right" name="arrow-right" />
  132. </u-form-item>
  133. </u--form>
  134. </view>
  135. <!-- 调查处理信息 -->
  136. <view class="card-section" v-if="!isView || form.acdntLevelId">
  137. <view class="section-title">🔍 调查处理信息</view>
  138. <u--form
  139. labelPosition="left"
  140. labelWidth="180rpx"
  141. :model="form"
  142. ref="form2Ref"
  143. >
  144. <u-form-item
  145. label="事故级别"
  146. prop="acdntLevelId"
  147. borderBottom
  148. required
  149. >
  150. <DictSelection
  151. dictName="事故级别"
  152. clearable
  153. :disabled="isView"
  154. v-model="formData.acdntLevelId"
  155. />
  156. </u-form-item>
  157. <u-form-item
  158. label="事故事件类型"
  159. prop="acdntTypeId"
  160. borderBottom
  161. required
  162. >
  163. <DictSelection
  164. dictName="事故事件类型"
  165. clearable
  166. :disabled="isView"
  167. v-model="formData.acdntTypeId"
  168. />
  169. </u-form-item>
  170. <u-form-item label="事故原因" prop="acdntCauselText" borderBottom>
  171. <u--textarea
  172. v-model="form.acdntCauselText"
  173. placeholder="请输入"
  174. :disabled="isView"
  175. border="none"
  176. height="120rpx"
  177. />
  178. </u-form-item>
  179. <u-form-item
  180. label="事故责任人"
  181. prop="dutyPersonName"
  182. borderBottom
  183. >
  184. <u--input
  185. v-model="form.dutyPersonName"
  186. placeholder="请选择"
  187. readonly
  188. :disabled="isView"
  189. border="none"
  190. inputAlign="right"
  191. @click="userOpen('duty')"
  192. />
  193. </u-form-item>
  194. <u-form-item label="责任单位" borderBottom>
  195. <u--input
  196. v-model="form.dutyUnitName"
  197. disabled
  198. border="none"
  199. inputAlign="right"
  200. />
  201. </u-form-item>
  202. <u-form-item label="间接经济损失(万元)" borderBottom>
  203. <u--input
  204. v-model="form.indirectEconomicLoss"
  205. type="number"
  206. placeholder="请输入"
  207. :disabled="isView"
  208. border="none"
  209. inputAlign="right"
  210. />
  211. </u-form-item>
  212. <u-form-item label="直接经济损失(万元)" borderBottom>
  213. <u--input
  214. v-model="form.directEconomicLoss"
  215. type="number"
  216. placeholder="请输入"
  217. :disabled="isView"
  218. border="none"
  219. inputAlign="right"
  220. />
  221. </u-form-item>
  222. </u--form>
  223. </view>
  224. <!-- 伤亡人员列表 -->
  225. <view class="card-section">
  226. <view class="section-title"
  227. >👥 伤亡人员列表
  228. <text class="add-btn" v-if="!isView" @click="userOpen('casualty')"
  229. >+ 添加</text
  230. ></view
  231. >
  232. <view
  233. v-for="(p, idx) in form.ehsAccidentCasualtiesVOList"
  234. :key="idx"
  235. class="person-item"
  236. >
  237. <view class="person-info"
  238. ><text class="name">{{ p.userName }}</text
  239. ><text class="dept">{{ p.userDeptName }}</text></view
  240. >
  241. <view class="person-extra">
  242. <picker
  243. mode="selector"
  244. :range="injuryDegreeOptions"
  245. range-key="label"
  246. :value="injuryDegreeIndex(p.injuryDegree)"
  247. @change="(e) => onInjuryChange(e, idx)"
  248. :disabled="isView"
  249. >
  250. <view class="tag">{{
  251. getInjuryLabel(p.injuryDegree) || "受伤程度"
  252. }}</view>
  253. </picker>
  254. <input
  255. class="days-input"
  256. v-model="p.lostWorkDays"
  257. type="number"
  258. placeholder="损失工作日"
  259. :disabled="isView"
  260. />
  261. <text class="delete" v-if="!isView" @click="removeCasualty(idx)"
  262. >✕</text
  263. >
  264. </view>
  265. </view>
  266. <view
  267. v-if="form.ehsAccidentCasualtiesVOList.length === 0"
  268. class="empty-tip"
  269. >暂无伤亡人员</view
  270. >
  271. </view>
  272. </view>
  273. </scroll-view>
  274. <view class="popup-footer">
  275. <u-button type="default" @click="cancel">取消</u-button>
  276. <u-button v-if="!isView" type="primary" @click="save" :loading="loading"
  277. >保存</u-button
  278. >
  279. </view>
  280. </view>
  281. <!-- 选择器:字典、日期、人员等 -->
  282. <u-action-sheet
  283. :show="showDictPopup"
  284. :actions="dictActions"
  285. @close="showDictPopup = false"
  286. @select="onDictSelect"
  287. />
  288. <u-datetime-picker
  289. :show="showDateTimePopup"
  290. v-model="datetimeValue"
  291. mode="datetime"
  292. @confirm="onDateTimeConfirm"
  293. @cancel="showDateTimePopup = false"
  294. />
  295. <selectUserDialog ref="userSelectRef" @confirm="changePersonel" />
  296. <drillOrderSelect
  297. ref="drillOrderSelectRef"
  298. @confirm="onDrillOrderConfirm"
  299. />
  300. <u-toast ref="uToast" />
  301. </u-popup>
  302. </template>
  303. <script>
  304. import drillOrderSelect from "./drillOrderSelect.vue";
  305. import selectUserDialog from "@/components/selectUserDialog.vue";
  306. import { save, getById } from "@/api/accidentReport/index.js";
  307. import dictMixins from "@/mixins/dictMixins";
  308. const defForm = {
  309. acdntName: "",
  310. occurrenceTime: "",
  311. acdntPlace: "",
  312. acdntPass: "",
  313. sourceType: "",
  314. sourceName: "",
  315. sourceId: "",
  316. reportPersonName: "",
  317. reportPersonUserId: "",
  318. reportDeptName: "",
  319. reportDeptId: "",
  320. reportTime: "",
  321. acdntLevelId: "",
  322. acdntTypeId: "",
  323. acdntCauselText: "",
  324. dutyUnitId: "",
  325. dutyUnitName: "",
  326. dutyPersonName: "",
  327. dutyPersonId: "",
  328. indirectEconomicLoss: undefined,
  329. directEconomicLoss: undefined,
  330. ehsAccidentCasualtiesVOList: [],
  331. };
  332. export default {
  333. components: { selectUserDialog, drillOrderSelect },
  334. mixins: [dictMixins],
  335. data() {
  336. return {
  337. visible: false,
  338. loading: false,
  339. dialogType: "add",
  340. form: JSON.parse(JSON.stringify(defForm)),
  341. injuryDegreeOptions: [
  342. { value: "1", label: "轻微伤" },
  343. { value: "2", label: "轻伤二级" },
  344. { value: "3", label: "轻微一级" },
  345. { value: "4", label: "重伤二级" },
  346. { value: "5", label: "重伤一级" },
  347. ],
  348. showDictPopup: false,
  349. dictActions: [],
  350. dictField: "",
  351. showDateTimePopup: false,
  352. datetimeValue: 0,
  353. dateField: "",
  354. selectMode: "",
  355. rules: {
  356. acdntName: { required: true, message: "请输入事故事件名称" },
  357. occurrenceTime: { required: true, message: "请选择发生时间" },
  358. acdntPlace: { required: true, message: "请输入事故发生地点" },
  359. acdntPass: { required: true, message: "请输入事故事件经过" },
  360. reportTime: { required: true, message: "请选择上报时间" },
  361. acdntLevelId: { required: true, message: "请选择事故级别" },
  362. acdntTypeId: { required: true, message: "请选择事故事件类型" },
  363. },
  364. };
  365. },
  366. computed: {
  367. dialogTitle() {
  368. return (
  369. { add: "新增事故上报", edit: "编辑事故上报", view: "查看事故上报" }[
  370. this.dialogType
  371. ] || "事故上报"
  372. );
  373. },
  374. isView() {
  375. return this.dialogType === "view";
  376. },
  377. },
  378. methods: {
  379. async open(row, type = "add") {
  380. this.visible = true;
  381. this.dialogType = type;
  382. if (row && row.id) {
  383. const data = await getById(row.id);
  384. this.form = { ...JSON.parse(JSON.stringify(defForm)), ...data };
  385. if (!this.form.ehsAccidentCasualtiesVOList)
  386. this.form.ehsAccidentCasualtiesVOList = [];
  387. } else {
  388. this.form = JSON.parse(JSON.stringify(defForm));
  389. const userInfo = uni.getStorageSync("userInfo") || {};
  390. this.form.reportPersonName = userInfo.name || "";
  391. this.form.reportPersonUserId = userInfo.userId || "";
  392. this.form.reportDeptName = userInfo.groupName || "";
  393. this.form.reportDeptId = userInfo.groupId || "";
  394. }
  395. this.$nextTick(() => {
  396. if (this.$refs.formRef) this.$refs.formRef.setRules(this.rules);
  397. });
  398. },
  399. cancel() {
  400. this.visible = false;
  401. this.form = JSON.parse(JSON.stringify(defForm));
  402. },
  403. // 日期选择
  404. showDateTimePicker(field) {
  405. if (this.isView) return;
  406. this.dateField = field;
  407. this.datetimeValue = this.form[field]
  408. ? new Date(this.form[field]).getTime()
  409. : Date.now();
  410. this.showDateTimePopup = true;
  411. },
  412. onDateTimeConfirm(e) {
  413. this.showDateTimePopup = false;
  414. this.form[this.dateField] = this.$u.timeFormat(
  415. e.value,
  416. "yyyy-mm-dd hh:MM:ss",
  417. );
  418. },
  419. onDictSelect(e) {
  420. this.showDictPopup = false;
  421. this.form[this.dictField] = e.value;
  422. if (this.$refs.formRef) this.$refs.formRef.validateField(this.dictField);
  423. },
  424. // 来源类型
  425. getSourceTypeLabel(val) {
  426. const map = { 1: "应急演练工单" };
  427. return map[val] || "";
  428. },
  429. showSourceTypePicker() {
  430. if (this.isView) return;
  431. this.dictField = "sourceType";
  432. this.dictActions = [{ name: "应急演练工单", value: "1" }];
  433. this.showDictPopup = true;
  434. },
  435. // 来源单据
  436. addSource() {
  437. if (this.isView) return;
  438. if (!this.form.sourceType) {
  439. this.$refs.uToast.show({
  440. type: "warning",
  441. message: "请先选择来源类型",
  442. });
  443. return;
  444. }
  445. if (this.form.sourceType == "1") this.$refs.drillOrderSelectRef.open();
  446. },
  447. onDrillOrderConfirm(data) {
  448. this.form.sourceName = data.name || "";
  449. this.form.sourceId = data.id || "";
  450. this.form.acdntPlace = data.planLocation || "";
  451. },
  452. // 打开人员选择
  453. userOpen(key) {
  454. if (this.isView || this.loading) return;
  455. this.userKey = key;
  456. // 上报人、责任人:单选
  457. if (key === "report" || key === "duty") {
  458. this.$refs.userSelectRef.open(2);
  459. }
  460. // 伤亡人员:多选,传入已选列表禁用
  461. if (key === "casualty") {
  462. const disabledList = this.form.ehsAccidentCasualtiesVOList.map(
  463. (item) => ({
  464. id: item.userId,
  465. }),
  466. );
  467. this.$refs.userSelectRef.open(1);
  468. }
  469. },
  470. // 统一回调
  471. changePersonel(data) {
  472. if (!data || data.length === 0) return;
  473. if (this.userKey === "report") {
  474. const person = data[0];
  475. this.form.reportPersonName = person.name || "";
  476. this.form.reportPersonUserId = person.id || "";
  477. this.form.reportDeptName = person.groupName || "";
  478. this.form.reportDeptId = person.groupId || "";
  479. } else if (this.userKey === "duty") {
  480. const person = data[0];
  481. this.form.dutyPersonName = person.name || "";
  482. this.form.dutyPersonId = person.id || "";
  483. this.form.dutyUnitName = person.groupName || "";
  484. this.form.dutyUnitId = person.groupId || "";
  485. } else if (this.userKey === "casualty") {
  486. data.forEach((person) => {
  487. this.form.ehsAccidentCasualtiesVOList.push({
  488. userId: person.id || "",
  489. userName: person.name || "",
  490. userDeptId: person.groupId || "",
  491. userDeptName: person.groupName || "",
  492. injuryDegree: "",
  493. lostWorkDays: undefined,
  494. accidentId: this.form.id || "",
  495. });
  496. });
  497. }
  498. },
  499. // 伤亡人员受伤程度
  500. injuryDegreeIndex(val) {
  501. return this.injuryDegreeOptions.findIndex((i) => i.value == val);
  502. },
  503. getInjuryLabel(val) {
  504. const item = this.injuryDegreeOptions.find((i) => i.value == val);
  505. return item ? item.label : "";
  506. },
  507. onInjuryChange(e, idx) {
  508. const val = this.injuryDegreeOptions[e.detail.value]?.value;
  509. this.$set(
  510. this.form.ehsAccidentCasualtiesVOList[idx],
  511. "injuryDegree",
  512. val,
  513. );
  514. },
  515. removeCasualty(idx) {
  516. this.form.ehsAccidentCasualtiesVOList.splice(idx, 1);
  517. },
  518. // 保存
  519. async save() {
  520. try {
  521. await this.$refs.formRef.validate();
  522. // 填充分类名称
  523. this.form.acdntLevelName = this.getDictValue(
  524. "事故级别",
  525. this.form.acdntLevelId,
  526. );
  527. this.form.acdntTypeName = this.getDictValue(
  528. "事故事件类型",
  529. this.form.acdntTypeId,
  530. );
  531. this.loading = true;
  532. await save(this.form);
  533. this.$refs.uToast.show({ type: "success", message: "保存成功" });
  534. this.cancel();
  535. this.$emit("reload");
  536. } catch (e) {
  537. if (e && e.message)
  538. this.$refs.uToast.show({ type: "error", message: e.message });
  539. } finally {
  540. this.loading = false;
  541. }
  542. },
  543. },
  544. };
  545. </script>
  546. <style lang="scss" scoped>
  547. .accident-popup {
  548. /deep/ .u-popup__content {
  549. border-radius: 32rpx 32rpx 0 0;
  550. overflow: hidden;
  551. }
  552. }
  553. .popup-content {
  554. height: 80vh;
  555. display: flex;
  556. flex-direction: column;
  557. background: #eff2f7;
  558. }
  559. .popup-header {
  560. display: flex;
  561. justify-content: space-between;
  562. align-items: center;
  563. padding: 30rpx 32rpx;
  564. background: #fff;
  565. border-bottom: 2rpx solid #eef2f6;
  566. flex-shrink: 0;
  567. .popup-title {
  568. font-size: 36rpx;
  569. font-weight: bold;
  570. color: #1f2b3c;
  571. }
  572. .close-btn {
  573. width: 60rpx;
  574. height: 60rpx;
  575. display: flex;
  576. align-items: center;
  577. justify-content: center;
  578. font-size: 52rpx;
  579. color: #8e9aae;
  580. }
  581. }
  582. .popup-body {
  583. flex: 1;
  584. overflow-y: auto;
  585. padding: 24rpx;
  586. }
  587. .card-a {
  588. background: #fff;
  589. border-radius: 48rpx;
  590. box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.05);
  591. overflow: hidden;
  592. }
  593. .card-section {
  594. padding: 30rpx 32rpx;
  595. &:not(:last-child) {
  596. border-bottom: 2rpx solid #f0f2f5;
  597. }
  598. }
  599. .section-title {
  600. font-size: 30rpx;
  601. font-weight: 700;
  602. color: #1f2a44;
  603. margin-bottom: 24rpx;
  604. padding-left: 16rpx;
  605. border-left: 6rpx solid #4caf50;
  606. display: flex;
  607. justify-content: space-between;
  608. align-items: center;
  609. .add-btn {
  610. font-size: 26rpx;
  611. color: #2979ff;
  612. font-weight: normal;
  613. padding: 4rpx 16rpx;
  614. background: #e8f0fe;
  615. border-radius: 30rpx;
  616. }
  617. }
  618. /deep/ .u-form-item {
  619. padding: 12rpx 0;
  620. .u-form-item__body__left__label {
  621. font-size: 26rpx !important;
  622. color: #6c7a91 !important;
  623. }
  624. .u-input__content__field-wrapper__field {
  625. font-size: 26rpx !important;
  626. text-align: right;
  627. }
  628. }
  629. .picker-value {
  630. font-size: 26rpx;
  631. color: #1e2a3a;
  632. text-align: right;
  633. flex: 1;
  634. min-height: 44rpx;
  635. padding: 4rpx 0;
  636. }
  637. .person-item {
  638. background: #f5f7fb;
  639. border-radius: 16rpx;
  640. padding: 16rpx;
  641. margin-bottom: 12rpx;
  642. .person-info {
  643. display: flex;
  644. gap: 20rpx;
  645. .name {
  646. font-weight: 600;
  647. font-size: 28rpx;
  648. }
  649. .dept {
  650. color: #8e9aae;
  651. font-size: 24rpx;
  652. }
  653. }
  654. .person-extra {
  655. display: flex;
  656. align-items: center;
  657. gap: 12rpx;
  658. margin-top: 8rpx;
  659. .tag {
  660. background: #e8edf4;
  661. padding: 4rpx 16rpx;
  662. border-radius: 30rpx;
  663. font-size: 24rpx;
  664. color: #2979ff;
  665. }
  666. .days-input {
  667. flex: 1;
  668. background: #fff;
  669. border-radius: 30rpx;
  670. padding: 0 16rpx;
  671. height: 50rpx;
  672. font-size: 24rpx;
  673. }
  674. .delete {
  675. color: #f56c6c;
  676. font-size: 28rpx;
  677. padding: 0 8rpx;
  678. }
  679. }
  680. }
  681. .empty-tip {
  682. text-align: center;
  683. font-size: 26rpx;
  684. color: #aaa;
  685. padding: 30rpx 0;
  686. }
  687. .popup-footer {
  688. display: flex;
  689. padding: 16rpx 24rpx;
  690. background: #fff;
  691. border-top: 2rpx solid #eef2f6;
  692. gap: 16rpx;
  693. flex-shrink: 0;
  694. /deep/ .u-button {
  695. flex: 1;
  696. border-radius: 48rpx;
  697. height: 72rpx;
  698. }
  699. }
  700. </style>