riskV3.vue 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230
  1. <template>
  2. <el-dialog
  3. v-loading="loading"
  4. :title="title"
  5. :visible.sync="dialogVisible"
  6. :close-on-click-modal="false"
  7. :close-on-press-escape="false"
  8. :show-close="false"
  9. append-to-body
  10. fullscreen
  11. class="dialog paper-detail-dialog"
  12. top="0"
  13. >
  14. <div slot="title" class="dialog-title">
  15. <span class="dialogtitle">{{ title }}</span>
  16. <div>
  17. <ibps-toolbar :actions="toolbars" @action-event="handleActionEvent" />
  18. </div>
  19. </div>
  20. <div class="container">
  21. <div class="left" :style="{ width: initWidth }">
  22. <el-descriptions
  23. title=""
  24. direction="vertical"
  25. :column="5"
  26. border
  27. size="mini"
  28. >
  29. <el-descriptions-item label="年度" class="aaaa">
  30. <span class="required-star">*</span>
  31. <el-date-picker
  32. v-model="infoFxssbData.nian_du_"
  33. type="year"
  34. placeholder="选择年"
  35. :disabled="readonly"
  36. value-format="yyyy"
  37. size="mini"
  38. />
  39. </el-descriptions-item>
  40. <el-descriptions-item label="编制部门">
  41. <div style="display: flex">
  42. <span class="required-star">*</span>
  43. <ibps-user-selector
  44. v-model="infoFxssbData.bian_zhi_bu_men_"
  45. type="position"
  46. readonly-text="text"
  47. :disabled="readonly"
  48. :multiple="false"
  49. size="mini"
  50. style="width: 100%"
  51. :filter="filter"
  52. filtrate
  53. />
  54. </div>
  55. </el-descriptions-item>
  56. <el-descriptions-item label="编制人">
  57. <ibps-user-selector
  58. type="user"
  59. :value="infoFxssbData.bian_zhi_ren_"
  60. readonly-text="text"
  61. :disabled="true"
  62. :multiple="true"
  63. size="mini"
  64. />
  65. </el-descriptions-item>
  66. <el-descriptions-item label="编制时间">
  67. <span class="required-star">*</span>
  68. <el-date-picker
  69. v-model="infoFxssbData.bian_zhi_shi_jian"
  70. type="datetime"
  71. placeholder="选择日期时间"
  72. :disabled="readonly"
  73. value-format="yyyy-MM-dd HH:mm:ss"
  74. size="mini"
  75. />
  76. </el-descriptions-item>
  77. <el-descriptions-item label="评估编号">
  78. {{ infoFxssbData.ji_hua_bian_hao_ }}
  79. </el-descriptions-item>
  80. <el-descriptions-item label="组长">
  81. <ibps-user-selector
  82. type="user"
  83. :value="infoFxssbData.zu_chang_id_"
  84. readonly-text="text"
  85. :disabled="true"
  86. :multiple="true"
  87. size="mini"
  88. :filter="filter"
  89. filtrate
  90. />
  91. </el-descriptions-item>
  92. <el-descriptions-item label="评估开始时间">
  93. <span class="required-star">*</span>
  94. <el-date-picker
  95. v-model="infoFxssbData.kai_shi_ri_qi_"
  96. type="date"
  97. placeholder="选择日期"
  98. :disabled="readonly"
  99. value-format="yyyy-MM-dd"
  100. size="mini"
  101. />
  102. </el-descriptions-item>
  103. <el-descriptions-item label="评估结束时间">
  104. <span class="required-star">*</span>
  105. <el-date-picker
  106. v-model="infoFxssbData.jie_shu_ri_qi_"
  107. type="date"
  108. placeholder="选择日期"
  109. :disabled="readonly"
  110. value-format="yyyy-MM-dd"
  111. size="mini"
  112. />
  113. </el-descriptions-item>
  114. <el-descriptions-item label="报告完成时间">
  115. <span class="required-star">*</span>
  116. <el-date-picker
  117. v-model="infoFxssbData.bao_gao_shi_jian_"
  118. type="date"
  119. placeholder="选择日期"
  120. :disabled="readonly"
  121. value-format="yyyy-MM-dd"
  122. size="mini"
  123. />
  124. </el-descriptions-item>
  125. <el-descriptions-item label="风险类型">
  126. <div>
  127. <el-radio
  128. v-model="infoFxssbData.feng_xian_lei_xin"
  129. label="质量"
  130. size="mini"
  131. :disabled="isEdit"
  132. >质量</el-radio
  133. >
  134. <el-radio
  135. v-model="infoFxssbData.feng_xian_lei_xin"
  136. label="安全"
  137. size="mini"
  138. :disabled="isEdit"
  139. >安全</el-radio
  140. >
  141. </div>
  142. </el-descriptions-item>
  143. <el-descriptions-item label="时机" :span="1">
  144. <el-select
  145. v-model="infoFxssbData.shi_ji_"
  146. :disabled="readonly"
  147. placeholder="请选择"
  148. size="mini"
  149. >
  150. <el-option
  151. v-for="item in [
  152. '定期评估',
  153. '新增活动',
  154. '不良事件',
  155. '程序变化',
  156. '业务变化'
  157. ]"
  158. :key="item"
  159. :label="item"
  160. :value="item"
  161. />
  162. </el-select>
  163. </el-descriptions-item>
  164. <el-descriptions-item label="范围" :span="onlyoneWay ? 2 : 1">
  165. <el-input
  166. v-model="infoFxssbData.fan_wei_"
  167. :disabled="readonly"
  168. size="mini"
  169. />
  170. </el-descriptions-item>
  171. <el-descriptions-item label="方法" :span="onlyoneWay ? 2 : 1">
  172. <el-select
  173. v-model="infoFxssbData.fang_fa_"
  174. size="mini"
  175. style="width: 100%"
  176. :disabled="readonly"
  177. filterable
  178. allow-create
  179. clearable
  180. placeholder="请选择方法"
  181. >
  182. <el-option
  183. v-for="item in fangFaOptions"
  184. :key="item.value"
  185. :label="item.label"
  186. :value="item.value"
  187. />
  188. </el-select>
  189. </el-descriptions-item>
  190. <el-descriptions-item
  191. v-if="!onlyoneWay"
  192. label="风险系数计算方式"
  193. :span="2"
  194. >
  195. <span class="required-star">*</span>
  196. <el-radio
  197. v-for="(v, k) in culWays"
  198. :key="k"
  199. v-model="infoFxssbData.ji_suan_fang_shi_"
  200. :label="k"
  201. size="mini"
  202. :disabled="isEdit"
  203. >{{ v }}
  204. </el-radio>
  205. </el-descriptions-item>
  206. <el-descriptions-item label="目的" :span="5">
  207. <el-input
  208. v-model="infoFxssbData.mu_di_"
  209. :disabled="readonly"
  210. size="mini"
  211. />
  212. </el-descriptions-item>
  213. <el-descriptions-item label="依据文件" :span="5">
  214. <!-- 模版弹窗 -->
  215. <ibps-custom-dialog
  216. v-if="!readonly"
  217. v-model="infoFxssbData.yi_ju_wen_jian_id"
  218. template-key="nsyjwjxz"
  219. multiple
  220. type="dialog"
  221. class="custom-dialog"
  222. placeholder="请选择依据文件"
  223. size="mini"
  224. icon="el-icon-search"
  225. />
  226. <pre style="margin: 0">{{ infoFxssbData.yi_ju_wen_jian_ }}</pre>
  227. </el-descriptions-item>
  228. <el-descriptions-item label="首次会议时间" :span="1">
  229. <el-date-picker
  230. v-model="infoFxssbData.hui_yi_shi_jian_"
  231. type="datetime"
  232. placeholder="选择日期时间"
  233. default-time="12:00:00"
  234. :disabled="readonly"
  235. value-format="yyyy-MM-dd HH:mm"
  236. size="mini"
  237. />
  238. </el-descriptions-item>
  239. <el-descriptions-item label="参会人员" :span="4">
  240. <div style="display: flex">
  241. <ibps-user-selector
  242. v-model="infoFxssbData.can_hui_ren_yuan_"
  243. style="width: 100%"
  244. type="user"
  245. readonly-text="text"
  246. :disabled="readonly"
  247. :multiple="true"
  248. size="mini"
  249. :filter="filter"
  250. filtrate
  251. />
  252. </div>
  253. </el-descriptions-item>
  254. <el-descriptions-item label="会议附件" :span="5">
  255. <ibps-attachment
  256. v-model="infoFxssbData.hui_yi_fu_jian_"
  257. :download="true"
  258. multiple
  259. accept="*"
  260. :readonly="readonly"
  261. size="mini"
  262. />
  263. </el-descriptions-item>
  264. <el-descriptions-item label="事务说明 " :span="5">
  265. <el-input
  266. v-model="infoFxssbData.shi_wu_shuo_ming_"
  267. :disabled="readonly"
  268. size="mini"
  269. /></el-descriptions-item>
  270. <el-descriptions-item label="评估人员 " :span="5">
  271. <div style="display: flex">
  272. <span class="required-star">*</span>
  273. <ibps-user-selector
  274. v-model="infoFxssbData.ping_gu_ren_yuan_"
  275. type="user"
  276. style="width: 100%"
  277. readonly-text="text"
  278. :disabled="readonly"
  279. :multiple="true"
  280. size="mini"
  281. :filter="filter"
  282. filtrate
  283. />
  284. </div>
  285. </el-descriptions-item>
  286. </el-descriptions>
  287. <div style="margin-top: 20px">
  288. <el-alert
  289. title="根据评估人员选定的风险识别项给每个措施制定人推送风险改进流程"
  290. type="success"
  291. :closable="false"
  292. />
  293. <RiskPeopleTable
  294. ref="RiskPeopleTableRef"
  295. :params="params"
  296. :people-ids="infoFxssbData.ping_gu_ren_yuan_"
  297. :people-dept-info="infoFxssbData.ping_gu_bu_men_"
  298. :cul-ways="culWays"
  299. :bian-zhi-ren="infoFxssbData.bian_zhi_ren_"
  300. @goBack="goRefresh"
  301. @update-people-dept-info="handleUpdatePeopleDeptInfo"
  302. />
  303. </div>
  304. </div>
  305. </div>
  306. <RiskDetail ref="RiskDetailRef" :cul-ways="culWays" @close="goRefresh" />
  307. <el-image-viewer
  308. v-if="showViewer"
  309. :on-close="closeViewer"
  310. :url-list="[FlowPic]"
  311. />
  312. </el-dialog>
  313. </template>
  314. <script>
  315. import { getSetting } from '@/utils/query'
  316. import dayjs from 'dayjs'
  317. import RiskPeopleTable from './riskPeopleTableV3.vue'
  318. import ibpsUserSelector from '@/business/platform/org/selector'
  319. import RiskDetail from './riskDetail.vue'
  320. import IbpsAttachment from '@/business/platform/file/attachment/selector'
  321. import DataFormrender from '@/business/platform/data/templaterender/form'
  322. import DataTemplateFormrenderDialog from '@/business/platform/data/templaterender/form/dialog.vue'
  323. import ElImageViewer from 'element-ui/packages/image/src/image-viewer'
  324. import FlowPic from '@/assets/images/risk/file-read-18951.png'
  325. export default {
  326. components: {
  327. ElImageViewer,
  328. RiskDetail,
  329. RiskPeopleTable,
  330. ibpsUserSelector,
  331. IbpsAttachment,
  332. DataFormrender,
  333. DataTemplateFormrenderDialog,
  334. IbpsCustomDialog: () =>
  335. import('@/business/platform/data/templaterender/custom-dialog')
  336. },
  337. props: {
  338. params: {
  339. type: Object,
  340. default: function () {
  341. return {}
  342. }
  343. }
  344. },
  345. data() {
  346. const { userId, position, level } = this.$store.getters
  347. return {
  348. FlowPic: FlowPic,
  349. showViewer: false,
  350. filter: [
  351. {
  352. descVal: '1',
  353. includeSub: true,
  354. old: 'position',
  355. partyId: this.$store.getters.userInfo.employee.positions,
  356. partyName: '',
  357. scriptContent: '',
  358. type: 'user',
  359. userType: 'position'
  360. }
  361. ],
  362. isFirst: true,
  363. userId: userId,
  364. position: position,
  365. level: level.second || level.first,
  366. pkValue: '',
  367. readonly: true,
  368. DialogVisible: false,
  369. loading: false,
  370. dialogVisible: true,
  371. title: '风险评估与措施',
  372. toolbars: [
  373. { key: 'openFlowPic', label: '流程图', icon: 'ibps-icon-image' },
  374. {
  375. key: 'refresh',
  376. label: '刷新',
  377. hidden: () => {
  378. return !this.isZuZhang || !this.isEdit || this.isFinished
  379. }
  380. },
  381. {
  382. key: 'save',
  383. label: '保存',
  384. hidden: () => {
  385. return !this.isZuZhang || this.isFinished
  386. }
  387. },
  388. // { key: 'sendMsg', label: '提醒评估人', icon: 'el-icon-bell', hidden: () => { return !this.isZuZhang || !this.isEdit || this.isFinished } },
  389. // { key: 'peizhifengxian', label: '更新风险库', type: 'info', icon: 'el-icon-setting', hidden: () => { return !this.isZuZhang || !this.isEdit } },
  390. {
  391. key: 'submit',
  392. label: '提交',
  393. icon: 'el-icon-finished',
  394. hidden: () => {
  395. return !this.isZuZhang || !this.isEdit || this.isFinished
  396. }
  397. },
  398. {
  399. key: 'cancel',
  400. label: '退出',
  401. type: 'danger',
  402. icon: 'ibps-icon-close'
  403. }
  404. ],
  405. infoFxssbData: {
  406. nian_du_: '',
  407. bian_zhi_bu_men_: '',
  408. bian_zhi_ren_: '',
  409. bian_zhi_shi_jian: '',
  410. zu_chang_: '',
  411. zu_chang_id_: '',
  412. kai_shi_ri_qi_: '',
  413. jie_shu_ri_qi_: '',
  414. bao_gao_shi_jian_: '',
  415. feng_xian_lei_xin: '',
  416. shi_ji_: '',
  417. fan_wei_: '',
  418. fang_fa_: '',
  419. mu_di_: '',
  420. yi_ju_wen_jian_id: '',
  421. yi_ju_wen_jian_: '',
  422. hui_yi_shi_jian_: '',
  423. can_hui_ren_yuan_: '',
  424. hui_yi_fu_jian_: '',
  425. shi_wu_shuo_ming_: '',
  426. ping_gu_ren_yuan_: '',
  427. ji_hua_bian_hao_: '',
  428. ji_suan_fang_shi_: '',
  429. ping_gu_bu_men_: [] // 新增字段,存储 [{uid: 'xxx', did: 'yyy'}, ...]
  430. },
  431. initWidth: '1280px',
  432. isEdit: false,
  433. isPingGuRen: false,
  434. isZuZhang: true,
  435. isFinished: false,
  436. preParams: {},
  437. Ids: [],
  438. culWays: { 1: '风险矩阵法', 2: 'FMEA法' },
  439. fangFaOptions: [
  440. { value: '定量风险评估法', label: '定量风险评估法' },
  441. { value: '风险矩阵', label: '风险矩阵' },
  442. {
  443. value: '头脑风暴法(集体讨论法)',
  444. label: '头脑风暴法(集体讨论法)'
  445. },
  446. { value: '德尔菲法(专家调查法)', label: '德尔菲法(专家调查法)' },
  447. { value: '情景分析', label: '情景分析' },
  448. { value: '检查表', label: '检查表' },
  449. { value: '故障树分析', label: '故障树分析' },
  450. { value: '蒙特卡罗模拟分析法', label: '蒙特卡罗模拟分析法' },
  451. { value: '贝叶斯统计及贝叶斯网络', label: '贝叶斯统计及贝叶斯网络' }
  452. ]
  453. }
  454. },
  455. computed: {
  456. onlyoneWay() {
  457. const keys = Object.keys(this.culWays)
  458. return keys.length === 1
  459. }
  460. },
  461. watch: {
  462. 'infoFxssbData.yi_ju_wen_jian_id': {
  463. handler(val) {
  464. this.$common
  465. .request('query', {
  466. key: 'getRiskFileInfo',
  467. params: [val]
  468. })
  469. .then((response) => {
  470. const conts = response.variables.data
  471. let fileName = ''
  472. let num = 1
  473. conts.forEach((item, index) => {
  474. fileName += `${num}、${item.name_}\n`
  475. num = num + 1
  476. })
  477. this.infoFxssbData.yi_ju_wen_jian_ = fileName
  478. })
  479. },
  480. immediate: true
  481. },
  482. 'infoFxssbData.zu_chang_id_': {
  483. handler(val) {
  484. if (val) {
  485. const userList = this.$store.getters.userList
  486. const bianzhiUserid = userList.find((i) => i.userId === val)
  487. if (bianzhiUserid) {
  488. this.infoFxssbData.zu_chang_ = bianzhiUserid.userName
  489. }
  490. }
  491. }
  492. },
  493. 'infoFxssbData.feng_xian_lei_xin': {
  494. handler(val) {
  495. if (val) {
  496. if (this.isFirst) {
  497. this.isFirst = false
  498. } else {
  499. this.infoFxssbData.fan_wei_ = `本实验室与${val}相关所有活动`
  500. this.infoFxssbData.mu_di_ = `通过对实验室(${val}活动)进行风险评估,识别实验室在(${val}活动)方面存在的潜在风险,评估风险影响程度,制定风险管理策略,减少不利后果发生的概率和影响程度。`
  501. this.infoFxssbData.shi_wu_shuo_ming_ = `年度:${this.infoFxssbData.nian_du_};组长:${this.infoFxssbData.zu_chang_};评估开始日期:${this.infoFxssbData.kai_shi_ri_qi_};风险类型:${val}`
  502. }
  503. }
  504. }
  505. },
  506. // 监听评估人员变化,同步更新部门映射(保留已有部门)
  507. 'infoFxssbData.ping_gu_ren_yuan_': {
  508. handler(newVal, oldVal) {
  509. if (!this.isEdit && newVal !== oldVal) {
  510. this.syncPeopleDeptInfo()
  511. }
  512. }
  513. }
  514. },
  515. async mounted() {
  516. const culWays = await getSetting('risk', 'culWays')
  517. if (this.$utils.isNotEmpty(culWays)) {
  518. this.culWays = culWays
  519. }
  520. this.init()
  521. },
  522. beforeDestroy() {
  523. if (this.msg) {
  524. this.msg.close()
  525. }
  526. },
  527. methods: {
  528. // 同步评估人员与部门映射(用于新增时)
  529. syncPeopleDeptInfo() {
  530. const userIds = this.infoFxssbData.ping_gu_ren_yuan_
  531. ? this.infoFxssbData.ping_gu_ren_yuan_.split(',').filter(id => id)
  532. : []
  533. const currentMap = this.infoFxssbData.ping_gu_bu_men_ || []
  534. const newMap = userIds.map(uid => {
  535. const existing = currentMap.find(item => item.uid === uid)
  536. if (existing) {
  537. return existing
  538. } else {
  539. return { uid, did: this.getPersonPosition(uid) || '' }
  540. }
  541. })
  542. this.infoFxssbData.ping_gu_bu_men_ = newMap
  543. },
  544. // 子组件部门更新事件
  545. handleUpdatePeopleDeptInfo(newDeptInfo) {
  546. this.infoFxssbData.ping_gu_bu_men_ = newDeptInfo
  547. },
  548. // 在保存前主动从子组件同步最新的部门映射,确保数据一致
  549. syncDeptInfoFromChild() {
  550. if (this.$refs.RiskPeopleTableRef) {
  551. const latestDeptInfo = this.$refs.RiskPeopleTableRef.getCurrentDeptInfo()
  552. if (latestDeptInfo) {
  553. this.infoFxssbData.ping_gu_bu_men_ = latestDeptInfo
  554. }
  555. }
  556. },
  557. // 获取最新数据
  558. async getNewData() {
  559. const {
  560. variables: { data }
  561. } = await this.$common.request('query', {
  562. key: 'getFxpgjlb2ById',
  563. params: [this.params.id_]
  564. })
  565. return data
  566. },
  567. // 判断是否已完成
  568. async getIsFinish() {
  569. const data = await this.getNewData()
  570. if (data.length > 0 && data[0].shi_fou_guo_shen_ === '已完成') {
  571. throw new Error('已结束,不可操作!')
  572. }
  573. },
  574. // 快照URL
  575. getReportParams(path, selection, data) {
  576. const { level } = this.$store.getters
  577. const str = `org_=${level.first}&second_=${level.second}&id_=`
  578. const arr = path.split('&')
  579. if (arr.length === 2) {
  580. const fieldArr = arr[1].split('=')
  581. return str + `${data[fieldArr[1]]}`
  582. } else {
  583. // 如果是没有传参,还是原报表路径
  584. return str + `${selection}`
  585. }
  586. },
  587. // 提交
  588. async goSubmit() {
  589. try {
  590. this.$confirm('提交后不可修改,是否确认保存并提交?', '提示', {
  591. confirmButtonText: '继续',
  592. cancelButtonText: '取消',
  593. type: 'warning'
  594. })
  595. .then(async () => {
  596. // 提交前自动保存,并先同步部门映射
  597. await this.goSave()
  598. // 判断每个评估人是否已完成识别项
  599. const pinGuRenNum = this.Ids.length
  600. const {
  601. variables: { data }
  602. } = await this.$common.request('query', {
  603. key: 'getFxsbpgb2ByPid',
  604. params: [this.params.id_]
  605. })
  606. const submitNum = new Set(data.map((item) => item.bian_zhi_ren_))
  607. .size
  608. if (
  609. submitNum === pinGuRenNum &&
  610. data.every((item) => item.shi_fou_guo_shen_ === '已完成')
  611. ) {
  612. // 1.修改状态为已完成
  613. const updateParamsRecord = {
  614. tableName: 't_fxpgjlb2',
  615. updList: [
  616. {
  617. where: {
  618. id_: this.params.id_
  619. },
  620. param: {
  621. shi_fou_guo_shen_: '已完成'
  622. }
  623. }
  624. ]
  625. }
  626. await this.$common.request('update', updateParamsRecord)
  627. // 2.推送给措施制定人下一个流程
  628. const addParams = {
  629. tableName: 't_fxkzcsb2',
  630. paramWhere: data
  631. .map((item) => {
  632. if (
  633. item.feng_xian_ying_du !== '风险接受' &&
  634. item.zhi_ding_ren_
  635. ) {
  636. return {
  637. di_dian_: this.level,
  638. shi_fou_guo_shen_: '待制定',
  639. bian_zhi_ren_: item.zhi_ding_ren_,
  640. bian_zhi_bu_men_: this.getPersonPosition(
  641. item.zhi_ding_ren_.split(',')[0]
  642. ),
  643. bian_zhi_shi_jian: dayjs().format(
  644. 'YYYY-MM-DD HH:mm:ss'
  645. ),
  646. gai_jin_bian_hao_: '',
  647. yao_su_tiao_kuan_: item.yao_su_tiao_kuan_,
  648. gong_zuo_huan_jie: item.gong_zuo_huan_jie,
  649. // gong_zuo_miao_shu: item.gong_zuo_miao_shu,
  650. feng_xian_miao_sh: item.feng_xian_miao_sh,
  651. zu_chang_: this.infoFxssbData.zu_chang_,
  652. zu_chang_id_: this.infoFxssbData.zu_chang_id_,
  653. shi_wu_shuo_ming_: this.infoFxssbData.shi_wu_shuo_ming_,
  654. yuan_zhi_shu_: item.feng_xian_zhi_shu,
  655. kong_zhi_fang_fa_: item.xian_xing_kong_zh,
  656. feng_xian_lei_xin: this.infoFxssbData.feng_xian_lei_xin,
  657. qian_fu_jian_: this.infoFxssbData.yi_ju_wen_jian_id,
  658. ji_hua_bian_hao_: this.infoFxssbData.ji_hua_bian_hao_,
  659. ni_cai_qu_kong_zh: item.ni_cai_qu_cuo_shi,
  660. qian_zai_yuan_yin: item.qian_zai_yuan_yin,
  661. ji_suan_fang_shi_: this.infoFxssbData.ji_suan_fang_shi_,
  662. yuan_feng_xian_de: item.feng_xian_deng_ji,
  663. yuan_feng_xian_yi: item.feng_xian_ying_du,
  664. parent_id_: item.id_ || ''
  665. }
  666. }
  667. })
  668. .filter((i) => i !== undefined),
  669. formKey: 'fxcscsbV2',
  670. defKey: 'Process_1li9h0n'
  671. }
  672. this.loading = true
  673. for (let i = 0; i < addParams.paramWhere.length; i++) {
  674. const item = addParams.paramWhere[i]
  675. item.gai_jin_bian_hao_ = await this.getNextAlias('gjjllsh')
  676. }
  677. if (addParams.paramWhere.length) {
  678. // 预防推送流程数据过多,做切片处理
  679. await this.processInBatches(addParams.paramWhere)
  680. .then(() => {})
  681. .catch(() => {
  682. this.loading = false
  683. })
  684. console.log('改进流程推送成功')
  685. } else {
  686. console.log('无需推送')
  687. }
  688. // 3.推送给组长 评估报告流程
  689. const addParams2 = {
  690. tableName: 't_fxkzbg',
  691. paramWhere: [
  692. {
  693. di_dian_: this.level,
  694. shi_fou_guo_shen_: '待编制',
  695. bian_zhi_ren_: this.infoFxssbData.zu_chang_id_,
  696. bian_zhi_bu_men_: this.getPersonPosition(
  697. this.infoFxssbData.zu_chang_id_
  698. ),
  699. bian_zhi_shi_jian: dayjs().format('YYYY-MM-DD HH:mm'),
  700. zu_chang_: this.infoFxssbData.zu_chang_,
  701. zu_chang_id_: this.infoFxssbData.zu_chang_id_,
  702. feng_xian_lei_xin: this.infoFxssbData.feng_xian_lei_xin,
  703. kai_shi_ri_qi_: this.infoFxssbData.kai_shi_ri_qi_,
  704. jie_shu_ri_qi_: this.infoFxssbData.jie_shu_ri_qi_,
  705. wan_cheng_ri_qi_: this.infoFxssbData.bao_gao_shi_jian_,
  706. shi_wu_shuo_ming_: this.infoFxssbData.shi_wu_shuo_ming_,
  707. ji_hua_bian_hao_: this.infoFxssbData.ji_hua_bian_hao_,
  708. hui_yi_wen_jian_: this.infoFxssbData.hui_yi_fu_jian_
  709. }
  710. ],
  711. formKey: 'fxbg',
  712. defKey: 'Process_0mrlsj7'
  713. }
  714. console.log('报告流程推送成功')
  715. await this.$common.request('add', addParams2)
  716. this.$message.success('提交成功')
  717. // 提醒用户推送信息
  718. this.$alert(
  719. `需要改进项${addParams.paramWhere.length}条,已向其中的每位措施制定人推送风险改进流程,同时向组长 ${this.infoFxssbData.zu_chang_} 推送风险报告流程!`,
  720. '提交成功',
  721. {
  722. confirmButtonText: '确定',
  723. callback: (action) => {
  724. this.closeDialog(true)
  725. }
  726. }
  727. )
  728. } else {
  729. this.$message.warning(
  730. '存在未完成风险识别项的评估人员,无法提交!'
  731. )
  732. return
  733. }
  734. })
  735. .catch(() => {})
  736. } catch (error) {
  737. this.$message.warning(error.message)
  738. }
  739. },
  740. async processInBatches(array, batchSize = 20) {
  741. for (let i = 0; i < array.length; i += batchSize) {
  742. const batch = array.slice(i, i + batchSize)
  743. try {
  744. const addParams = {
  745. tableName: 't_fxkzcsb2',
  746. paramWhere: batch,
  747. formKey: 'fxcscsbV2',
  748. defKey: 'Process_1li9h0n'
  749. }
  750. await this.$common.request('add', addParams)
  751. } catch (error) {
  752. console.error('处理批次时出错:', error)
  753. // 可以添加重试逻辑或错误处理
  754. }
  755. }
  756. console.log('所有数据处理完成')
  757. },
  758. // id 转 姓名
  759. switchIdtoUserName(id) {
  760. const { userList } = this.$store.getters
  761. const user = userList.find((item) => item.userId === id)
  762. return user ? user.userName : ''
  763. },
  764. goPeizhifengxian() {
  765. const buttons = document.querySelector('button[name="peizhifengxian"]')
  766. if (buttons) {
  767. buttons.click()
  768. }
  769. },
  770. handleActionEvent({ key }) {
  771. switch (key) {
  772. case 'cancel':
  773. this.closeDialog(true)
  774. break
  775. case 'record':
  776. this.goRecord()
  777. break
  778. case 'save':
  779. this.goSave('close')
  780. break
  781. case 'submit':
  782. this.goSubmit()
  783. break
  784. case 'refresh':
  785. this.goRefresh()
  786. break
  787. case 'sendMsg':
  788. this.goSendMsg()
  789. break
  790. case 'peizhifengxian':
  791. this.goPeizhifengxian()
  792. break
  793. case 'openFlowPic':
  794. this.showViewer = true
  795. break
  796. default:
  797. break
  798. }
  799. },
  800. // 关闭大图预览
  801. closeViewer() {
  802. this.showViewer = false
  803. },
  804. // 推送消息给评估人
  805. async goSendMsg() {
  806. const {
  807. variables: { data: data2 }
  808. } = await this.$common.request('query', {
  809. key: 'getFxpgjlb2ById',
  810. params: [this.params.id_]
  811. })
  812. if (data2.length > 0 && data2[0].shi_fou_guo_shen_ === '已完成') {
  813. return this.$message('已结束,不可推送消息!')
  814. }
  815. if (!data2[0].ping_gu_ren_yuan_) {
  816. return this.$message.warning('请先指定评估人!')
  817. }
  818. this.$confirm('此操作将通知所有评估人,是否继续?', '提示', {
  819. confirmButtonText: '确定',
  820. cancelButtonText: '取消',
  821. type: 'warning'
  822. })
  823. .then(() => {
  824. const allRequests = []
  825. this.Ids.forEach((item) => {
  826. allRequests.push(
  827. this.$common.sendMsg({
  828. subject: '风险识别项待填写提醒',
  829. content: `您有一份风险识别项待填写,请前往-风险控制-风险评估与措施页面填写,计划编号:${this.infoFxssbData.ji_hua_bian_hao_},组长:${this.infoFxssbData.zu_chang_}。如已提交请忽略。`,
  830. receiverId: item,
  831. canreplay: '0',
  832. skipTypeMsg: JSON.stringify({
  833. skipType: 3,
  834. pathInfo: '/tygl/fxkzV2/fxpgycslb' // 路由
  835. })
  836. })
  837. )
  838. })
  839. Promise.all(allRequests)
  840. .then(() => {
  841. this.$message({
  842. type: 'success',
  843. message: '推送成功!'
  844. })
  845. })
  846. .catch(() => {
  847. this.$message({
  848. type: 'warning',
  849. message: '推送失败!'
  850. })
  851. })
  852. })
  853. .catch(() => {})
  854. },
  855. // 获取人员部门
  856. getPersonPosition(id) {
  857. const userList = this.$store.getters.userList
  858. const bianzhiUserid = userList.find((i) => i.userId === id)
  859. if (bianzhiUserid) {
  860. return bianzhiUserid.positionId.split(',')[0]
  861. }
  862. },
  863. checkRequired() {
  864. if (!this.infoFxssbData.nian_du_) {
  865. throw new Error('请填写年度!')
  866. }
  867. if (!this.infoFxssbData.kai_shi_ri_qi_) {
  868. throw new Error('请填写评估开始日期!')
  869. }
  870. if (!this.infoFxssbData.jie_shu_ri_qi_) {
  871. throw new Error('请填写评估结束日期!')
  872. }
  873. if (!this.infoFxssbData.bao_gao_shi_jian_) {
  874. throw new Error('请填写报告完成时间!')
  875. }
  876. // if (!this.infoFxssbData.hui_yi_shi_jian_) {
  877. // throw new Error('请填写首次会议时间!')
  878. // }
  879. // if (!this.infoFxssbData.can_hui_ren_yuan_) {
  880. // throw new Error('请填写参会人员!')
  881. // }
  882. if (!this.infoFxssbData.ping_gu_ren_yuan_) {
  883. throw new Error('请填写评估人员!')
  884. }
  885. if (!this.infoFxssbData.bian_zhi_shi_jian) {
  886. throw new Error('请选择编制时间!')
  887. }
  888. if (!this.infoFxssbData.bian_zhi_bu_men_) {
  889. throw new Error('请选择编制部门!')
  890. }
  891. if (!this.infoFxssbData.ji_suan_fang_shi_) {
  892. throw new Error('请选择风险系数计算方式!')
  893. }
  894. },
  895. async goAdd() {
  896. try {
  897. // 将部门映射数组转为 JSON 字符串
  898. const toSave = { ...this.infoFxssbData }
  899. if (toSave.ping_gu_bu_men_) {
  900. toSave.ping_gu_bu_men_ = JSON.stringify(toSave.ping_gu_bu_men_)
  901. }
  902. const addParamsRecord = {
  903. tableName: 't_fxpgjlb2',
  904. paramWhere: [toSave]
  905. }
  906. const {
  907. variables: { cont }
  908. } = await this.$common.request('add', addParamsRecord)
  909. if (cont.length) {
  910. this.$message.success('添加成功')
  911. this.closeDialog(true)
  912. }
  913. } catch (error) {
  914. console.log(error)
  915. this.$message.warning('添加失败')
  916. }
  917. },
  918. async goEdit(flag) {
  919. try {
  920. // 先检查状态
  921. await this.getIsFinish()
  922. // 更新主表,部门映射转为 JSON 字符串
  923. const toUpdate = { ...this.infoFxssbData }
  924. if (toUpdate.ping_gu_bu_men_) {
  925. toUpdate.ping_gu_bu_men_ = JSON.stringify(toUpdate.ping_gu_bu_men_)
  926. }
  927. const updateParamsRecord = {
  928. tableName: 't_fxpgjlb2',
  929. updList: [
  930. {
  931. where: {
  932. id_: this.params.id_
  933. },
  934. param: toUpdate
  935. }
  936. ]
  937. }
  938. updateParamsRecord.updList.forEach((item) => {
  939. Object.keys(item.param).forEach((key) => {
  940. if (key.endsWith('_label_value')) {
  941. delete item.param[key]
  942. }
  943. })
  944. })
  945. console.log('updateParamsRecord', updateParamsRecord)
  946. await this.$common.request('update', updateParamsRecord)
  947. // 当前人员数组
  948. const curIds = this.infoFxssbData.ping_gu_ren_yuan_.split(',')
  949. // 计算需要增加项
  950. const addedIds = curIds.filter((item) => !this.Ids.includes(item))
  951. // 计算需要更新项
  952. const updatedIds = curIds.filter((item) => this.Ids.includes(item))
  953. // 计算需要删除项
  954. const deletedIds = this.Ids.filter((id) => !curIds.includes(id))
  955. console.log(addedIds, updatedIds, deletedIds)
  956. // 删除
  957. if (deletedIds.length > 0) {
  958. const ids = deletedIds.map((id) => id).join(',')
  959. const {
  960. variables: { data: data3 }
  961. } = await this.$common.request('query', {
  962. key: 'getFxsbpgb2ByUid',
  963. params: [this.params.id_, ids]
  964. })
  965. if (data3.length > 0) {
  966. const params = {
  967. tableName: 't_fxsbpgb2',
  968. paramWhere: {
  969. id_: data3.map((item) => item.id_).join(',')
  970. }
  971. }
  972. console.log(params)
  973. await this.$common.request('delete', params)
  974. console.log('删除成功')
  975. }
  976. }
  977. if (flag === 'close') {
  978. this.closeDialog(true)
  979. this.$message.success('修改成功')
  980. } else {
  981. await this.goRefresh() // 修改之后刷新
  982. }
  983. } catch (error) {
  984. this.$message.warning(error.message)
  985. throw new Error(error.message)
  986. }
  987. },
  988. async goSave(flag) {
  989. try {
  990. // 保存前同步最新的部门映射
  991. this.syncDeptInfoFromChild()
  992. this.checkRequired()
  993. if (this.isEdit) {
  994. await this.goEdit(flag)
  995. } else {
  996. this.$confirm(
  997. `风险类型${
  998. this.onlyoneWay ? '' : '和风险系数计算公式'
  999. }保存后不可再修改,是否继续?`,
  1000. '提示',
  1001. {
  1002. confirmButtonText: '继续',
  1003. cancelButtonText: '取消',
  1004. type: 'warning'
  1005. }
  1006. )
  1007. .then(async () => {
  1008. await this.goAdd()
  1009. })
  1010. .catch(() => {})
  1011. }
  1012. } catch (error) {
  1013. this.$message.warning(error.message)
  1014. throw new Error(error.message)
  1015. }
  1016. },
  1017. // 刷新
  1018. async goRefresh() {
  1019. this.loading = true
  1020. if (this.msg) {
  1021. this.msg.close()
  1022. }
  1023. await this.init()
  1024. if (this.$refs.RiskPeopleTableRef) {
  1025. await this.$refs.RiskPeopleTableRef.refresh() // 改为调用 refresh 方法
  1026. }
  1027. this.loading = false
  1028. },
  1029. // 关闭当前窗口
  1030. closeDialog(needRefresh) {
  1031. this.dialogVisible = false
  1032. if (needRefresh) {
  1033. this.$emit('close')
  1034. }
  1035. if (this.msg) {
  1036. this.msg.close()
  1037. }
  1038. },
  1039. // 弹出提醒
  1040. async showAlert() {
  1041. if (!this.isPingGuRen || this.isFinished) return
  1042. const {
  1043. variables: { data }
  1044. } = await this.$common.request('query', {
  1045. key: 'getFxsbpgb2ByUid',
  1046. params: [this.params.id_, this.userId]
  1047. })
  1048. let status = '填写'
  1049. let type = 'warning'
  1050. if (data.length > 0) {
  1051. if (data.every((item) => item.shi_fou_guo_shen_ === '已完成')) {
  1052. return
  1053. } else {
  1054. status = '提交'
  1055. type = 'success'
  1056. }
  1057. }
  1058. this.msg = this.$notify({
  1059. offset: 50,
  1060. type: type,
  1061. title: `您有一份风险识别评估表待${status}!`,
  1062. message: `点击前去${status}`,
  1063. showClose: false,
  1064. duration: 0,
  1065. onClick: () => {
  1066. this.$refs.RiskDetailRef.open(this.params)
  1067. }
  1068. })
  1069. },
  1070. getNextAlias(alias) {
  1071. return new Promise((resolve, reject) => {
  1072. this.$common
  1073. .getNextIdByAlias({
  1074. alias
  1075. })
  1076. .then((response) => {
  1077. resolve(response.data)
  1078. })
  1079. .catch((error) => {
  1080. reject(error)
  1081. })
  1082. })
  1083. },
  1084. async init() {
  1085. this.isEdit = !!(this.params && this.params.id_)
  1086. if (this.isEdit) {
  1087. // 深拷贝 params,避免直接修改
  1088. this.infoFxssbData = JSON.parse(JSON.stringify(this.params))
  1089. // 从数据库获取最新的部门映射,避免外部传入旧数据
  1090. const latestData = await this.getNewData()
  1091. if (latestData.length && latestData[0].ping_gu_bu_men_) {
  1092. // 用最新的部门映射覆盖
  1093. this.infoFxssbData.ping_gu_bu_men_ = latestData[0].ping_gu_bu_men_
  1094. }
  1095. // 解析部门映射 JSON
  1096. if (this.infoFxssbData.ping_gu_bu_men_ && typeof this.infoFxssbData.ping_gu_bu_men_ === 'string') {
  1097. try {
  1098. this.infoFxssbData.ping_gu_bu_men_ = JSON.parse(this.infoFxssbData.ping_gu_bu_men_)
  1099. } catch (e) {
  1100. this.infoFxssbData.ping_gu_bu_men_ = []
  1101. }
  1102. } else if (!this.infoFxssbData.ping_gu_bu_men_) {
  1103. this.infoFxssbData.ping_gu_bu_men_ = []
  1104. }
  1105. console.log('infoFxssbData', this.infoFxssbData)
  1106. this.preParams = JSON.parse(JSON.stringify(this.params))
  1107. this.isPingGuRen = this.params.ping_gu_ren_yuan_.indexOf(this.userId) >= 0
  1108. this.isZuZhang = this.userId === this.infoFxssbData.zu_chang_id_
  1109. this.isFinished = this.params && this.params.shi_fou_guo_shen_ === '已完成'
  1110. this.readonly = !!(!this.isZuZhang || this.isFinished)
  1111. if (this.params.ping_gu_ren_yuan_) {
  1112. this.Ids = this.params.ping_gu_ren_yuan_.split(',')
  1113. } else {
  1114. this.Ids = []
  1115. }
  1116. await this.showAlert()
  1117. // 强制子组件使用最新的部门映射重新加载(关键修复)
  1118. await this.$nextTick()
  1119. if (this.$refs.RiskPeopleTableRef) {
  1120. await this.$refs.RiskPeopleTableRef.refresh()
  1121. }
  1122. } else {
  1123. const { positions } = this.$store.getters.userInfo
  1124. // 默认编制部门是主部门,没有主部门默认第一个部门
  1125. const mainPost =
  1126. positions.find((item) => item.isMainPost === 'Y')?.id ||
  1127. positions[0].id
  1128. this.readonly = false
  1129. this.infoFxssbData.di_dian_ = this.level
  1130. this.infoFxssbData.ji_hua_bian_hao_ = await this.getNextAlias('fxjhlsh')
  1131. this.infoFxssbData.nian_du_ = dayjs().format('YYYY')
  1132. this.infoFxssbData.bian_zhi_bu_men_ = mainPost
  1133. this.infoFxssbData.bian_zhi_ren_ = this.userId
  1134. this.infoFxssbData.bian_zhi_shi_jian = dayjs().format(
  1135. 'YYYY-MM-DD HH:mm:ss'
  1136. )
  1137. this.infoFxssbData.zu_chang_id_ = this.userId
  1138. this.infoFxssbData.kai_shi_ri_qi_ = dayjs().format('YYYY-MM-DD')
  1139. this.infoFxssbData.jie_shu_ri_qi_ = dayjs().format('YYYY-MM-DD')
  1140. this.infoFxssbData.feng_xian_lei_xin = '质量'
  1141. this.infoFxssbData.shi_ji_ = '定期评估'
  1142. this.infoFxssbData.fan_wei_ = '本实验室与质量相关所有活动'
  1143. this.infoFxssbData.fang_fa_ = '定量风险评估法'
  1144. this.infoFxssbData.mu_di_ =
  1145. '通过对实验室(质量活动)进行风险评估,识别实验室在(质量活动)方面存在的潜在风险,评估风险影响程度,制定风险管理策略,减少不利后果发生的概率和影响程度。'
  1146. this.infoFxssbData.hui_yi_shi_jian_ = dayjs().format('YYYY-MM-DD HH:mm')
  1147. this.$nextTick(() => {
  1148. this.infoFxssbData.shi_wu_shuo_ming_ = `年度:${this.infoFxssbData.nian_du_};组长:${this.infoFxssbData.zu_chang_};评估开始日期:${this.infoFxssbData.kai_shi_ri_qi_};风险类型:${this.infoFxssbData.feng_xian_lei_xin}`
  1149. })
  1150. this.infoFxssbData.ji_suan_fang_shi_ = Object.keys(this.culWays)[0]
  1151. // 新增模式默认部门映射为空数组,等评估人员选择后再同步
  1152. this.infoFxssbData.ping_gu_bu_men_ = []
  1153. }
  1154. }
  1155. }
  1156. }
  1157. </script>
  1158. <style lang="scss" scoped>
  1159. .paper-detail-dialog {
  1160. ::v-deep {
  1161. .el-dialog__header {
  1162. text-align: center;
  1163. }
  1164. }
  1165. .container {
  1166. display: flex;
  1167. width: 100%;
  1168. justify-content: center;
  1169. .left {
  1170. height: calc(100vh - 100px);
  1171. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  1172. padding: 10px;
  1173. overflow-y: auto;
  1174. }
  1175. .right {
  1176. height: calc(100vh - 100px);
  1177. flex: 1;
  1178. padding: 10px;
  1179. .text {
  1180. line-height: 1.5;
  1181. }
  1182. overflow-y: auto;
  1183. }
  1184. }
  1185. }
  1186. .required-star {
  1187. color: red;
  1188. margin-right: 4px;
  1189. }
  1190. .dialog-title {
  1191. display: flex;
  1192. align-items: center;
  1193. justify-content: center;
  1194. div {
  1195. position: absolute;
  1196. right: 8vw;
  1197. }
  1198. .dialogtitle {
  1199. font-size: 22px;
  1200. font-family: SimHei;
  1201. font-weight: bold;
  1202. color: #222;
  1203. }
  1204. }
  1205. .el-image-viewer__wrapper {
  1206. z-index: 9999 !important;
  1207. }
  1208. </style>