eventComponent.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <template>
  2. <div>
  3. <el-table
  4. :data="tableData"
  5. style="width: 100%"
  6. size="mini"
  7. border>
  8. <el-table-column
  9. prop="eventTime"
  10. align="center"
  11. label="事件发生时间">
  12. </el-table-column>
  13. <el-table-column
  14. prop="eventHospital"
  15. align="center"
  16. label="事件发生的医院及科室">
  17. </el-table-column>
  18. <el-table-column
  19. prop="eventDesc"
  20. align="center"
  21. label="事件详细描述">
  22. </el-table-column>
  23. <el-table-column
  24. prop="eventImage"
  25. align="center"
  26. label="图片上传">
  27. <template slot-scope="scope">
  28. <div>
  29. <div v-for="img in scope.row.eventImage" style="color: #1890ff; cursor: pointer;" :key="img" @click="handleItem(img)">
  30. {{ img.name }}
  31. </div>
  32. </div>
  33. </template>
  34. </el-table-column>
  35. <el-table-column
  36. prop="eventOpinion"
  37. align="center"
  38. label="其他意见">
  39. </el-table-column>
  40. <el-table-column
  41. label="操作"
  42. align="center"
  43. width="100"
  44. v-if="!view">
  45. <template slot-scope="scope">
  46. <el-button @click="editRow(scope.row, scope.$index)" type="text" size="mini">编辑</el-button>
  47. <el-button @click="deleteRow(scope.$index)" type="text" size="mini">删除</el-button>
  48. </template>
  49. </el-table-column>
  50. </el-table>
  51. <div v-if="!view" @click="addRow" style="text-align: center;">
  52. <el-button type="text" icon="el-icon-plus">添加</el-button>
  53. </div>
  54. <ele-modal
  55. title="明细"
  56. custom-class="ele-dialog-form long-dialog-form"
  57. :visible.sync="visible"
  58. :before-close="handleClose"
  59. :close-on-click-modal="false"
  60. top="5vh"
  61. :close-on-press-escape="false"
  62. append-to-body
  63. width="70%"
  64. :maxable="true"
  65. >
  66. <el-card shadow="never">
  67. <el-form ref="form" :model="form">
  68. <el-form-item
  69. label="事件发生时间"
  70. label-width="110px"
  71. prop="eventTime"
  72. :rules="{
  73. required: true,
  74. message: '选择事件发生时间',
  75. trigger: 'change'
  76. }"
  77. >
  78. <el-date-picker
  79. style="width: 100%"
  80. v-model="form.eventTime"
  81. value-format="yyyy-MM-dd HH:mm:ss"
  82. type="datetime"
  83. placeholder="选择日期"
  84. >
  85. </el-date-picker>
  86. </el-form-item>
  87. <el-form-item
  88. label="事件发生的医院及科室"
  89. label-width="110px"
  90. prop="eventHospital"
  91. :rules="{
  92. required: true,
  93. message: '输入事件发生的医院及科室',
  94. trigger: 'change'
  95. }"
  96. >
  97. <el-input
  98. v-model="form.eventHospital"
  99. type="textarea"
  100. placeholder="请输入"
  101. :autosize="{ minRows: 2, maxRows: 4}"
  102. ></el-input>
  103. </el-form-item>
  104. <el-form-item
  105. label="事件详细描述"
  106. label-width="110px"
  107. prop="eventDesc"
  108. >
  109. <el-input
  110. v-model="form.eventDesc"
  111. type="textarea"
  112. placeholder="含患者信息及使用目的、问题点及出现问题的场景描述"
  113. :autosize="{ minRows: 2, maxRows: 4}"
  114. ></el-input>
  115. </el-form-item>
  116. <el-form-item
  117. label="图片上传"
  118. label-width="110px"
  119. prop="eventImage"
  120. :rules="{
  121. required: true,
  122. message: '上传图片',
  123. trigger: 'change'
  124. }"
  125. >
  126. <el-upload
  127. class="upload-demo"
  128. action="#"
  129. list-type="picture-card"
  130. :http-request="handlRequest"
  131. :before-remove="beforeRemove"
  132. :on-remove="handleRemove"
  133. :on-preview="handleItem"
  134. multiple
  135. :before-upload="beforeUpload"
  136. :file-list="form.eventImage"
  137. :show-file-list="true"
  138. accept=".jpg, .jpeg, .png, .gif"
  139. >
  140. <i class="el-icon-plus"></i>
  141. </el-upload>
  142. </el-form-item>
  143. <el-form-item
  144. label="其他意见"
  145. label-width="110px"
  146. prop="eventOpinion"
  147. >
  148. <el-input
  149. v-model="form.eventOpinion"
  150. type="textarea"
  151. placeholder="请输入"
  152. :autosize="{ minRows: 2, maxRows: 4}"
  153. ></el-input>
  154. </el-form-item>
  155. </el-form>
  156. </el-card>
  157. <div slot="footer">
  158. <el-button type="primary" size="small" @click="save">保存</el-button>
  159. <el-button size="small" @click="handleClose">关闭</el-button>
  160. </div>
  161. </ele-modal>
  162. <ele-modal
  163. title="预览"
  164. custom-class="ele-dialog-form long-dialog-form"
  165. :visible.sync="previewVisible"
  166. :close-on-click-modal="false"
  167. top="5vh"
  168. :close-on-press-escape="false"
  169. append-to-body
  170. width="60%"
  171. :maxable="true"
  172. >
  173. <el-card shadow="never" style="text-align: center;">
  174. <img :src="currentImg.url" alt="" style="height: 300px;">
  175. </el-card>
  176. </ele-modal>
  177. </div>
  178. </template>
  179. <script>
  180. import upload from '@/components/uploadImg';
  181. import { uploadFile } from '@/api/system/file/index.js';
  182. import {
  183. removeFile,
  184. getFile
  185. } from '@/api/system/file/index.js';
  186. export default {
  187. components: {
  188. upload
  189. },
  190. props: {
  191. info: {
  192. type: Object,
  193. default: () => {}
  194. },
  195. generateForm: {
  196. type: Object,
  197. default: () => {}
  198. },
  199. id: {
  200. type: String,
  201. default: ''
  202. },
  203. view: {
  204. type: Boolean,
  205. default: false
  206. },
  207. },
  208. watch: {
  209. info: {
  210. handler(newVal, oldVal) {
  211. console.log('info', newVal);
  212. this.tableData = newVal[this.id] || [];
  213. },
  214. deep: true,
  215. immediate: true
  216. },
  217. },
  218. data() {
  219. const formItem = {
  220. eventType: '',
  221. eventHospital: '',
  222. eventDesc: '',
  223. eventImage: [],
  224. eventOpinion: '',
  225. }
  226. return {
  227. form: {
  228. eventImage: []
  229. },
  230. formItem,
  231. tableData: [],
  232. visible: false,
  233. currentItem: {},
  234. currentIndex: null,
  235. type: 'add',
  236. limit: 9,
  237. size: 20,
  238. module: 'processSubmit',
  239. currentImg: {},
  240. previewVisible: false
  241. }
  242. },
  243. mounted() {
  244. // console.log('info~~~', this.info);
  245. // this.initForm();
  246. },
  247. methods: {
  248. // 自定义上传方法,处理文件上传逻辑
  249. handlRequest ({ file }) {
  250. return this.beforeUpload(file);
  251. },
  252. // 上传前校验并处理上传
  253. beforeUpload (file) {
  254. // 1. 文件大小校验
  255. if (file.size / 1024 / 1024 > this.size) {
  256. this.$message.error(`大小不能超过 ${this.size}MB`);
  257. return Promise.reject(new Error('文件大小超出限制'));
  258. }
  259. // 2. 文件数量校验
  260. if (this.limit > 0 && this.form.eventImage.length >= this.limit) {
  261. this.$message.error(`最多上传 ${this.limit}个文件`);
  262. return Promise.reject(new Error('文件数量超出限制'));
  263. }
  264. // 3. 文件类型校验(虽然accept属性已限制,但这里再做一层校验确保安全性)
  265. const isImage = /\.(jpg|jpeg|png|gif)$/i.test(file.name);
  266. if (!isImage) {
  267. this.$message.error('请上传图片格式的文件');
  268. return Promise.reject(new Error('文件类型不是图片'));
  269. }
  270. // 4. 执行文件上传
  271. return uploadFile({
  272. module: this.module,
  273. multiPartFile: file
  274. }).then((res) => {
  275. if (res.data) {
  276. // 将上传成功的文件添加到表单中
  277. const uploadedFile = {
  278. ...file,
  279. url: res.data.storePath,
  280. ...res.data,
  281. status: 'success' // 标记为成功状态
  282. };
  283. // 检查文件是否已存在,避免重复添加
  284. const existingIndex = this.form.eventImage.findIndex(item => item.uid === file.uid);
  285. if (existingIndex === -1) {
  286. this.form.eventImage.push(uploadedFile);
  287. } else {
  288. this.form.eventImage.splice(existingIndex, 1, uploadedFile);
  289. }
  290. return res.data;
  291. }
  292. return Promise.reject(new Error('上传失败,未返回数据'));
  293. }).catch(error => {
  294. this.$message.error('文件上传失败');
  295. return Promise.reject(error);
  296. });
  297. },
  298. beforeRemove (file) {
  299. if (file.id) {
  300. // 原明细仍可能被取消编辑或被流程引用,编辑时仅移除草稿中的引用。
  301. if (this.type === 'edit' && (this.currentItem.eventImage || []).some(item => item.id === file.id)) {
  302. return true;
  303. }
  304. return removeFile({
  305. fileId: file.id
  306. }).then(() => {
  307. return true;
  308. });
  309. } else {
  310. return true;
  311. }
  312. },
  313. handleRemove (file, fileList) {
  314. this.form.eventImage = fileList;
  315. },
  316. handleItem(file){
  317. console.log(file,'5555')
  318. // getFile({ objectName: file.storePath }, file.name);
  319. this.currentImg = file;
  320. this.previewVisible = true;
  321. },
  322. async updateRowInfo() {
  323. let data = await this.generateForm.getData(false);
  324. this.generateForm.setData({
  325. [this.id]: this.tableData
  326. });
  327. console.log('updateRowInfo', this.tableData, data);
  328. },
  329. validateForm() {
  330. //开始表单校验
  331. return new Promise((resolve, reject) => {
  332. this.$refs.form.validate((valid) => {
  333. if (!valid) {
  334. reject(false);
  335. } else {
  336. resolve(true);
  337. }
  338. });
  339. });
  340. },
  341. addRow() {
  342. this.type = 'add';
  343. this.form = JSON.parse(JSON.stringify(this.formItem));
  344. this.visible = true;
  345. },
  346. handleClose() {
  347. this.visible = false;
  348. },
  349. editRow(row, index) {
  350. this.type = 'edit';
  351. console.log('编辑行', row, index);
  352. this.currentItem = row;
  353. this.currentIndex = index;
  354. this.visible = true;
  355. this.form = JSON.parse(JSON.stringify(row));
  356. },
  357. deleteRow(index) {
  358. this.tableData.splice(index, 1);
  359. this.updateRowInfo();
  360. },
  361. async save() {
  362. try {
  363. // 1. 先进行表单基本验证
  364. await this.validateForm();
  365. // 2. 额外验证图片上传是否有效
  366. // if (!this.form.eventImage || this.form.eventImage.length === 0) {
  367. // this.$message.error('请上传图片');
  368. // return;
  369. // }
  370. // // 3. 检查是否有上传中的图片或上传失败的图片
  371. // const hasInvalidFile = this.form.eventImage.some(file =>
  372. // file.status === 'uploading' || file.status === 'fail'
  373. // );
  374. // if (hasInvalidFile) {
  375. // this.$message.error('存在图片正在上传中或上传失败,请等待上传完成或重新上传');
  376. // return;
  377. // }
  378. // console.log('表单验证通过,图片上传有效');
  379. // 4. 深拷贝form对象,避免引用问题
  380. const newForm = JSON.parse(JSON.stringify(this.form));
  381. // 5. 过滤出有效的图片数据(只保留成功上传的图片)
  382. newForm.eventImage = newForm.eventImage.filter(file =>
  383. file.status === 'success' && file.url
  384. );
  385. console.log('newForm', newForm);
  386. // 6. 更新表格数据
  387. if(this.type === 'add') {
  388. this.tableData.push(newForm);
  389. } else {
  390. this.$set(this.tableData, this.currentIndex, newForm);
  391. }
  392. console.log('this.tableData', this.tableData);
  393. // 7. 更新父组件数据
  394. this.updateRowInfo();
  395. // 8. 保存成功后关闭弹窗并重置表单
  396. this.handleClose();
  397. // 重置form
  398. // this.form = {
  399. // eventType: '',
  400. // eventHospital: '',
  401. // eventDesc: '',
  402. // eventImage: [],
  403. // eventOpinion: ''
  404. // };
  405. } catch (error) {
  406. console.log('表单验证失败', error);
  407. // this.$message.error('表单验证失败,请检查输入内容');
  408. }
  409. }
  410. }
  411. }
  412. </script>
  413. <style scoped>
  414. .header {
  415. display: flex;
  416. align-items: center;
  417. justify-content: space-between;
  418. width: 100%;
  419. }
  420. .blank_business_component {
  421. display: flex;
  422. align-items: center;
  423. margin-bottom: 10px;
  424. }
  425. .label {
  426. width: 80px;
  427. }
  428. .value {
  429. flex: 1;
  430. width: calc(100% - 80px);
  431. }
  432. :deep(.el-form-box .el-table .el-table__row .el-table__cell .cell) {
  433. padding-left: 10px;
  434. padding-right: 10px;
  435. }
  436. </style>