华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1532 lines
57 KiB

  1. <template>
  2. <div class="flex flex1">
  3. <div class="flex1 flex">
  4. <el-input v-if="type === 'input'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
  5. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" @blur="onBlur"
  6. :placeholder="getPlaceholder()" v-model="inputValue" @input="onInputChange" @change="onInputChange" />
  7. <el-input v-else-if="type === 'textarea'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
  8. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" type="textarea" show-word-limit
  9. resize="none" @blur="onBlur" :rows="item.rows || 3" :placeholder="getPlaceholder()" v-model="inputValue"
  10. @input="onInputChange" @change="onInputChange" />
  11. <DecimalInput v-else-if="type === 'inputNumber'" @blur="onCommonHandleSaveRecord"
  12. :maxlength="item.maxlength || 10" class="flex1" :disabled="getDisabled()"
  13. :controls="item.controls || false" :min="item.min || 0" :prepend="item.prepend"
  14. :decimalDigits="getDecimalDigits()" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  15. :placeholder="getPlaceholder()" v-model="inputValue" @input="onInputChange" @change="onInputChange" />
  16. <el-select v-else-if="type === 'select'" class="flex1" :multiple="item.multiple"
  17. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  18. :disabled="getDisabled()" :placeholder="getPlaceholder()" @remove-tag="onRemoveTag"
  19. @visible-change="onSelectBlur" @change="onInputChange">
  20. <el-option v-for="op in item.options" :key="op.value" :label="op.label" :value="op.value">
  21. </el-option>
  22. </el-select>
  23. <el-checkbox v-else-if="type === 'checkbox'" class="flex1" :multiple="item.multiple"
  24. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  25. :disabled="getDisabled()" :placeholder="getPlaceholder()" @change="onItemCheckboxChange">
  26. </el-checkbox>
  27. <div v-else-if="type === 'checkboxList'" class="flex1 checkbox-list-container"
  28. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')">
  29. <el-checkbox-group v-model="inputValue" @change="onInputChange">
  30. <div v-for="option in item.options" :key="option.value" class="checkbox-item">
  31. <el-checkbox :label="option.value" :disabled="getDisabled()">
  32. {{ option.label }}
  33. </el-checkbox>
  34. <div v-if="option.otherCode && inputValue.includes(option.value)">
  35. <el-input v-model="otherValues[option.otherCode]"
  36. :placeholder="option.otherPlaceholder || '请输入'"
  37. :class="{ 'error-border': isOtherInputError(option.otherCode) }"
  38. @blur="onBlur"
  39. @input="onOtherInputChange(option.otherCode, $event)" />
  40. </div>
  41. </div>
  42. </el-checkbox-group>
  43. </div>
  44. <el-date-picker v-else-if="type === 'dateTime'" type="datetime" class="flex1"
  45. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  46. :disabled="getDisabled()" format="yyyy/MM/dd HH:mm:ss" :placeholder="getPlaceholder()"
  47. @change="(val) => onDateChange(val, 'yyyy/MM/DD HH:mm:ss')">
  48. </el-date-picker>
  49. <el-date-picker v-else-if="type === 'datePicker'" class="flex1"
  50. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  51. :disabled="getDisabled()" format="yyyy/MM/dd" :placeholder="getPlaceholder()"
  52. @change="(val) => onDateChange(val, 'yyyy/MM/DD')">
  53. </el-date-picker>
  54. <el-button v-else-if="type === 'button'" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  55. :disabled="getDisabled()" type="primary" @click="handleClickButton(item)">
  56. {{ item.buttonName }}
  57. </el-button>
  58. <div class="clickable"
  59. :class="getFillTypeStyle() + (getDisabled() ? ' disabled' : '') + (orangeBg ? ' orange-bg' : '')"
  60. v-else-if="item.type === 'clickable'" @click="handleClickable(item, $event)">
  61. <span v-if="inputValue">{{ inputValue }}</span>
  62. <span v-else class="default-placeholder-text">{{ getPlaceholder() }}</span>
  63. </div>
  64. <div class="clickable"
  65. :class="getFillTypeStyle() + (getDisabled() ? ' disabled' : '') + (orangeBg ? ' orange-bg' : '')"
  66. v-else-if="regentType.includes(item.type)" @click="onCommonHandleRegent(item, item.type)">
  67. <span v-if="inputValue">{{ inputValue }}</span>
  68. <span v-else class="default-placeholder-text">{{ getPlaceholder() }}</span>
  69. </div>
  70. <template v-else-if="type === 'attachment'">
  71. <el-upload ref="uploadRef" class="upload-demo" :action="uploadFileUrl" :on-preview="handlePreview"
  72. :headers="headers" :before-remove="beforeRemove" :on-remove="handleRemove" multiple :limit="10"
  73. :on-success="handleSuccess" :on-change="handleChange" :on-exceed="handleExceed"
  74. :file-list="fileList" :auto-upload="false">
  75. <el-button :disabled="getDisabled()" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  76. size="small" type="primary">点击上传</el-button>
  77. <span v-if="error" class="atta-tips">请上传附件</span>
  78. <div slot="tip" class="el-upload__tip">支持扩展名.rar .zip .doc .docx .pdf .jpg文件大小不超过2MB</div>
  79. </el-upload>
  80. </template>
  81. </div>
  82. <div class="handle-row" v-if="isShowHandle()">
  83. <el-checkbox v-model="checkboxValue" v-if="getIsShowCheckboxIcon()" :disabled="getCheckboxDisabled()"
  84. class="mr-5" @change="onCheckboxChange"></el-checkbox>
  85. <div class="handle-icon" v-if="getIsShowQuestionIcon()" @click="onClickQuestion"
  86. @mouseenter="(e) => onMouseEnter('replyRecord', e)" @mouseleave="onMouseLeave">
  87. <Question :class="getQuestionColor()" />
  88. </div>
  89. <img v-if="getIsShowCopyIcon()" @click="onCopy" src="@/assets/images/copy-icon.svg" class="handle-icon"
  90. alt="" />
  91. <img v-if="getIsShowRecordIcon()" @mouseenter="(e) => onMouseEnter('fieldChanged', e)"
  92. @mouseleave="onMouseLeave" src="@/assets/images/record-icon.svg" class="handle-icon" alt="" />
  93. </div>
  94. <!-- 修改记录模态框 -->
  95. <div v-if="showModal && modificationRecords.length > 0" ref="modalRef"
  96. :class="['modification-modal', { 'show': showModal }]" @mouseenter="onModalEnter"
  97. @mouseleave="onModalLeave">
  98. <div class="modal-content">
  99. <div class="records-list">
  100. <div v-for="(record, index) in modificationRecords" :key="index" class="record-item">
  101. <!-- 字段修改记录 -->
  102. <div class="record-row" v-if="currentRecordType === 'fieldChanged'">
  103. <div>
  104. <span>{{ index + 1 }}.</span>
  105. <span> {{ getUserName(record) }} </span>
  106. <span>{{ record.time }} </span>
  107. <span>{{ modificationRecords.length - 1 == index ? "提交" : "修改" }}</span>
  108. </div>
  109. <div v-if="modificationRecords.length - 1 !== index">
  110. <div>原值{{ record.oldValue }}</div>
  111. <div>修改值{{ record.value }}</div>
  112. <div v-if="record.reason">备注{{ record.reason }}</div>
  113. </div>
  114. </div>
  115. <!-- 回复记录 -->
  116. <div class="record-row" v-if="currentRecordType === 'replyRecord'">
  117. <div>
  118. <span> {{ getUserName(record) }} </span>
  119. <span>{{ record.time }} </span>
  120. </div>
  121. <div>
  122. <div v-if="record.content">复核意见{{ record.content }}</div>
  123. <div v-if="record.reply">回复意见{{ record.reply }}</div>
  124. </div>
  125. </div>
  126. <hr v-if="index < modificationRecords.length - 1">
  127. </div>
  128. </div>
  129. </div>
  130. </div>
  131. <el-dialog :close-on-click-modal="false" append-to-body :title="templateFillType == 'actFill' ? '回复意见' : '复核意见'"
  132. :visible.sync="visible" width="30%">
  133. <el-input v-model="replyContent" type="textarea" show-word-limit resize="none" rows="8" placeholder="输入内容"
  134. maxlength="500" />
  135. <span slot="footer" class="dialog-footer">
  136. <el-button @click="visible = false"> </el-button>
  137. <el-button type="primary" @click="onReplyConfirm"> </el-button>
  138. </span>
  139. </el-dialog>
  140. </div>
  141. </template>
  142. <script>
  143. import Question from "./icons/Question.vue";
  144. import DecimalInput from "./DecimalInput.vue";
  145. import { EventBus } from "@/utils/eventBus";
  146. import moment from "moment";
  147. import { deepClone } from "@/utils/index";
  148. import { getuuid } from "@/utils/index.js";
  149. import { getToken } from "@/utils/auth"
  150. export default {
  151. inject: ['templateData', 'templateFillType', "getZdxgjl", "getFhyjjl", "updateZdxgjl", "replaceFhyjjl", "updateFhyjjl", "getFieldCheckObj", "updateFieldCheckObj"],
  152. components: {
  153. Question,
  154. DecimalInput,
  155. },
  156. props: {
  157. type: {//form类型 input/select等
  158. type: String,
  159. default: "input"
  160. },
  161. item: {
  162. type: Object,
  163. default: () => {
  164. return {
  165. placeholder: "",
  166. maxlength: 30,
  167. label: "",
  168. disabled: false,
  169. }
  170. }
  171. },
  172. // v-model 值
  173. value: {
  174. type: [String, Number, Array, Boolean, Object],
  175. default: ''
  176. },
  177. // 错误状态
  178. error: {
  179. type: Boolean,
  180. default: false
  181. },
  182. // 橙色背景状态
  183. orangeBg: {
  184. type: Boolean,
  185. default: false
  186. },
  187. fieldKey: {
  188. type: String,
  189. default: ""
  190. },
  191. fieldItemLabel: {
  192. type: String,
  193. default: "",
  194. },
  195. //是否记录修改
  196. isFieldsRecord: {
  197. type: Boolean,
  198. default: true,
  199. },
  200. sourceFrom: {
  201. type: String,
  202. default: ""
  203. },
  204. },
  205. data() {
  206. let initialValue = this.value;
  207. let initialOtherValues = {};
  208. // 如果是checkboxList类型且value是对象格式
  209. if (this.type === 'checkboxList' && this.value && typeof this.value === 'object') {
  210. initialValue = this.value.checkboxValues || [];
  211. initialOtherValues = this.value.otherValues || {};
  212. } else if (this.type === 'checkboxList' && !Array.isArray(this.value)) {
  213. initialValue = [];
  214. }
  215. return {
  216. inputValue: initialValue,
  217. oldValue: initialValue, // 记录上一次的值
  218. otherValues: initialOtherValues, // 存储checkboxList中otherCode对应的输入值
  219. oldOtherValues: {...initialOtherValues}, // 记录上一次的otherValues
  220. showModal: false, // 控制模态框显示
  221. modificationRecords: [], // 存储修改记录
  222. modalTimer: null, // 用于延迟隐藏模态框
  223. isHoveringModal: false, // 是否悬停在模态框上
  224. isHoveringMain: false, // 是否悬停在主元素上(这个实际上不需要,因为我们有事件处理)
  225. currentRecordType: '', // 当前悬停的记录类型(replyRecord 或 modifyRecord)
  226. replyContent: '', // 回复内容
  227. visible: false,//是否显示弹窗
  228. checkboxValue: this.getChecked(),//是否选中
  229. uuid: getuuid(), // 唯一标识符,用于EventBus事件匹配
  230. regentType: ['sj', 'gsp', 'mix', 'xj', 'xb', 'gyzj', 'mjy', 'yq', 'jcb'], //试剂/仪器/供试品等类型
  231. selectRegentInfo: {},//选择的试剂/仪器/供试品等信息
  232. fileList: [],//上传的文件列表
  233. uploadFileUrl: process.env.VUE_APP_BASE_API + "/file/upload",
  234. headers: {
  235. Authorization: "Bearer " + getToken(),
  236. },
  237. pendingUploadFile: null, // 用于存储待上传的文件
  238. pendingRemoveFile: null, // 用于存储待删除的文件
  239. }
  240. },
  241. watch: {
  242. value(newVal) {
  243. if (this.type === 'checkboxList' && newVal && typeof newVal === 'object') {
  244. this.inputValue = newVal.checkboxValues || [];
  245. this.otherValues = newVal.otherValues || {};
  246. } else {
  247. this.inputValue = this.type === 'checkboxList' && !Array.isArray(newVal) ? [] : newVal;
  248. }
  249. }
  250. },
  251. filters: {
  252. },
  253. mounted() {
  254. if (this.item.type === 'attachment') {
  255. try {
  256. this.fileList = JSON.parse(this.value);
  257. } catch (e) {
  258. this.fileList = [];
  259. }
  260. }
  261. EventBus.$on('onExternalFieldUpdate', this.handleExternalFieldUpdate);
  262. EventBus.$on('onEditSignCancel', this.handleEditSignCancel);
  263. EventBus.$on('onEditSignCallback', this.handleEditSignCallback);
  264. //试剂
  265. EventBus.$on("onReagentSubmit", this.onReagentSubmit)
  266. //仪器
  267. EventBus.$on("onInstrumentSubmit", this.onMixReagentSubmit)
  268. //供试品/试剂/给药制剂等
  269. EventBus.$on("onMixReagentSubmit", this.onMixReagentSubmit)
  270. },
  271. unmounted() {
  272. EventBus.$off('onExternalFieldUpdate', this.handleExternalFieldUpdate);
  273. EventBus.$off('onEditSignCancel', this.handleEditSignCancel);
  274. EventBus.$off('onEditSignCallback', this.handleEditSignCallback);
  275. EventBus.$off("onReagentSubmit", this.onReagentSubmit)
  276. EventBus.$off("onInstrumentSubmit", this.onMixReagentSubmit)
  277. EventBus.$off("onMixReagentSubmit", this.onMixReagentSubmit)
  278. },
  279. methods: {
  280. getDecimalDigits(){
  281. const {precision} = this.item;
  282. if(!isNaN(precision)){
  283. return precision
  284. }
  285. return 6
  286. },
  287. onInstrumentSubmit(data) {
  288. if (data.uuid !== this.uuid) return;
  289. this.selectRegentInfo = data;
  290. },
  291. // 文件状态改变时的钩子,添加文件、上传成功、上传失败时都会被调用
  292. handleChange(file, fileList) {
  293. // 如果是新添加的文件(status为ready),进行验证
  294. if (file.status === 'ready') {
  295. const isAllowedType = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf',
  296. 'application/x-rar-compressed', 'application/zip',
  297. 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'].includes(file.raw.type)
  298. const isLt2M = file.size / 1024 / 1024 < 2
  299. if (!isAllowedType) {
  300. this.$message.error(`文件 ${file.name} 格式不支持!只能上传 JPG/PNG/GIF/PDF/RAR/ZIP/DOC/DOCX 格式的文件`)
  301. // 从fileList中移除该文件
  302. const index = fileList.indexOf(file);
  303. if (index > -1) {
  304. fileList.splice(index, 1);
  305. }
  306. this.fileList = [...fileList];
  307. return
  308. }
  309. if (!isLt2M) {
  310. this.$message.error(`文件 ${file.name} 大小超过2MB!`)
  311. // 从fileList中移除该文件
  312. const index = fileList.indexOf(file);
  313. if (index > -1) {
  314. fileList.splice(index, 1);
  315. }
  316. this.fileList = [...fileList];
  317. return
  318. }
  319. // 如果已经有文件在列表中(不包括当前新添加的),需要验证电子签名
  320. const existingFiles = fileList.filter(f => f !== file && f.status === 'success');
  321. if (existingFiles.length > 0) {
  322. // 保存待上传的文件信息
  323. this.pendingUploadFile = file;
  324. // 从fileList中暂时移除新文件,等待签名验证
  325. const index = fileList.indexOf(file);
  326. if (index > -1) {
  327. fileList.splice(index, 1);
  328. }
  329. this.fileList = [...fileList];
  330. // 触发电子签名弹窗
  331. EventBus.$emit('showEditSignDialog', { uuid: this.uuid });
  332. return;
  333. }
  334. // 没有现有文件或验证通过,手动提交上传
  335. this.$nextTick(() => {
  336. // 找到对应的upload组件并提交
  337. const uploadComponent = this.$refs.uploadRef;
  338. if (uploadComponent) {
  339. uploadComponent.submit();
  340. }
  341. });
  342. }
  343. // 更新fileList
  344. this.fileList = fileList;
  345. },
  346. handleSuccess(res, file, fileList) {
  347. if (res.code == 200) {
  348. this.fileList = fileList;
  349. // 更新inputValue为文件路径列表,方便后续保存
  350. this.inputValue = JSON.stringify(this.getFileList(fileList));
  351. this.$emit("change", this.inputValue)
  352. this.onCommonHandleSaveRecord();
  353. this.$message.success('文件上传成功');
  354. } else {
  355. this.$message.error(res.message || '文件上传失败');
  356. // 上传失败,从列表中移除
  357. const index = fileList.indexOf(file);
  358. if (index > -1) {
  359. fileList.splice(index, 1);
  360. this.fileList = [...fileList];
  361. }
  362. }
  363. },
  364. getFileList(fileList) {
  365. const list = [];
  366. fileList.forEach(item => {
  367. const o = { name: item.name };
  368. if (item.url) {//回填的数据
  369. o.url = item.url
  370. } else {//新上传的
  371. o.url = item.response.data.url
  372. }
  373. list.push(o)
  374. })
  375. return list;
  376. },
  377. // 删除前验证电子签名
  378. beforeRemove(file) {
  379. // 所有删除操作都需要验证电子签名
  380. // 保存待删除的文件信息
  381. this.pendingRemoveFile = { file, fileList: this.fileList };
  382. // 触发电子签名弹窗
  383. EventBus.$emit('showEditSignDialog', { uuid: this.uuid });
  384. // 返回false阻止默认删除行为,等待签名验证通过后再删除
  385. return false;
  386. },
  387. handleRemove(file, fileList) {
  388. // on-remove事件在before-remove返回false时不会触发
  389. // 这个方法在签名验证通过后通过executeRemove调用
  390. // 这里不需要额外处理,因为executeRemove已经处理了删除逻辑
  391. },
  392. // 执行实际的文件删除操作
  393. executeRemove(file, fileList) {
  394. // 从el-upload的内部uploadFiles中移除文件
  395. const uploadComponent = this.$refs.uploadRef;
  396. if (uploadComponent && uploadComponent.uploadFiles) {
  397. const uploadFileIndex = uploadComponent.uploadFiles.indexOf(file);
  398. if (uploadFileIndex > -1) {
  399. uploadComponent.uploadFiles.splice(uploadFileIndex, 1);
  400. }
  401. }
  402. // 从fileList中移除文件
  403. const index = fileList.indexOf(file);
  404. if (index > -1) {
  405. fileList.splice(index, 1);
  406. }
  407. // 更新fileList
  408. this.fileList = [...fileList];
  409. // 更新inputValue为剩余文件路径列表
  410. this.inputValue = JSON.stringify(this.getFileList(fileList));
  411. this.$emit("change", this.inputValue);
  412. // 触发保存记录
  413. this.onCommonHandleSaveRecord();
  414. this.$message.success(`文件 ${file.name} 已移除`);
  415. },
  416. handlePreview(file) {
  417. console.log(file)
  418. const url = file.url || file.response?.data?.url;
  419. if (url) {
  420. window.open(process.env.VUE_APP_FILE_DOMAIN + url, '_blank');
  421. }
  422. },
  423. handleExceed(files, fileList) {
  424. this.$message.warning(`当前限制选择 10 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
  425. },
  426. //试剂弹窗提交
  427. onMixReagentSubmit(data) {
  428. if (data.uuid !== this.uuid) return;
  429. // 创建一个验证控制器,用于收集各级验证结果
  430. const validationController = {
  431. isPrevented: false,
  432. errorMsg: '',
  433. prevent(msg) {
  434. this.isPrevented = true;
  435. if (msg) this.errorMsg = msg;
  436. }
  437. };
  438. // 触发自定义验证事件,让父组件判断是否满足需求
  439. // 父组件可以通过validationController.prevent(msg)来阻止提交
  440. this.$emit('beforeReagentSubmit', data, validationController);
  441. // 检查是否被阻止
  442. if (validationController.isPrevented) {
  443. // 验证不通过,显示错误信息
  444. if (validationController.errorMsg) {
  445. this.$message.error(validationController.errorMsg);
  446. }
  447. return;
  448. }
  449. // 验证通过,继续执行
  450. this.executeReagentSubmit(data);
  451. },
  452. // 执行试剂提交的共同逻辑
  453. executeReagentSubmit(data) {
  454. this.inputValue = data.selectedId;
  455. this.selectRegentInfo = data;
  456. EventBus.$emit("hideSelectMixReagentDialog");
  457. this.onCommonHandleSaveRecord(this.inputValue);
  458. },
  459. //统一处理试剂/供试品等弹窗
  460. onCommonHandleRegent(item, type) {
  461. if (this.templateFillType !== 'actFill') {
  462. return
  463. }
  464. let params = {
  465. studyFormId: this.templateData.id,
  466. uuid: this.uuid,
  467. sourceFrom: this.sourceFrom,
  468. }
  469. let eventName = "showSelectMixReagentDialog";
  470. if (type === "yq") {
  471. eventName = "showSelectInstrumentDialog";
  472. } else {
  473. const sjType = {
  474. sj: "1",//试剂
  475. gsp: "7",//供试品
  476. mix: "1",//试剂/供试品/试剂
  477. gyzj: "3",//给药制剂
  478. mjy: "5",//麻精药
  479. xj: "9",//细菌
  480. xb: "11",//细胞
  481. jcb: "13",//检测板
  482. }
  483. params = {
  484. ...params,
  485. type: sjType[type]
  486. };
  487. if (type === "mix") {
  488. params.mixType = true;
  489. }
  490. }
  491. EventBus.$emit(eventName, params)
  492. // this.$emit('regent', item,type);
  493. },
  494. // 点击按钮
  495. handleClickButton(item) {
  496. this.$emit('clickButton', item);
  497. },
  498. onDateChange(val, format) {
  499. this.inputValue = moment(val).format(format);
  500. this.onCommonHandleSaveRecord(this.inputValue);
  501. },
  502. getUserName(record) {
  503. const locale = this.$i18n.locale;
  504. if (locale === 'zh_CN') {
  505. return record.userNameCn;
  506. }
  507. return record.userNameEn;
  508. },
  509. // 处理电子签名取消事件
  510. handleEditSignCancel(data) {
  511. if (data.uuid === this.uuid) {
  512. // 如果有待上传的文件,清空它
  513. if (this.pendingUploadFile) {
  514. this.pendingUploadFile = null;
  515. this.$message.info('已取消文件上传');
  516. } else if (this.pendingRemoveFile) {
  517. // 如果有待删除的文件,清空它
  518. this.pendingRemoveFile = null;
  519. this.$message.info('已取消文件删除');
  520. } else {
  521. this.resetRecord();
  522. }
  523. }
  524. },
  525. // 处理电子签名确认回调事件
  526. handleEditSignCallback(data) {
  527. if (data.uuid === this.uuid) {
  528. this.onEditSignSave(data.data);
  529. }
  530. },
  531. onEditSignSave(data) {
  532. // 检查是否有待上传的文件
  533. if (this.pendingUploadFile) {
  534. // 签名验证通过,将文件添加到列表并上传
  535. const file = this.pendingUploadFile;
  536. // 将文件添加到 fileList
  537. this.fileList.push(file);
  538. // 清空待上传文件标记
  539. this.pendingUploadFile = null;
  540. // 手动触发上传
  541. this.$nextTick(() => {
  542. const uploadComponent = this.$refs.uploadRef;
  543. if (uploadComponent) {
  544. uploadComponent.submit();
  545. }
  546. });
  547. } else if (this.pendingRemoveFile) {
  548. // 签名验证通过,执行文件删除
  549. const { file, fileList } = this.pendingRemoveFile;
  550. // 清空待删除文件标记
  551. this.pendingRemoveFile = null;
  552. // 执行删除操作
  553. this.executeRemove(file, fileList);
  554. } else {
  555. // 没有待上传/删除文件,执行正常的记录更新
  556. this.handleUpdateRecord(data);
  557. }
  558. },
  559. getChecked() {
  560. return !!this.getFieldCheckObj()[this.fieldKey]?.checked;
  561. },
  562. getFillTypeStyle(type) {
  563. const { fillType } = this.item;
  564. const typeObj = {
  565. actFill: "orange-border",//实际填写的边框颜色
  566. green: "green-border",
  567. preFill: "blue-border",//预填写的边框颜色
  568. }
  569. // 如果有错误状态,返回红色边框样式,覆盖原有的边框颜色
  570. if (this.error && this.type !== "attachment") {
  571. return "error-border";
  572. }
  573. return typeObj[fillType] || ""
  574. },
  575. //确认回复
  576. onReplyConfirm() {
  577. if (!this.replyContent) {
  578. this.$message({
  579. message: '请输入内容',
  580. type: 'error'
  581. });
  582. return;
  583. }
  584. const baseInfo = this.getCommonRecordInfo();
  585. const record = {
  586. ...baseInfo,
  587. title: this.templateFillType == 'actFill' ? "回复意见" : "复核意见",
  588. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  589. }
  590. if (this.templateFillType == 'actFill') {
  591. record.reply = this.replyContent;
  592. const deepList = deepClone(this.getFhyjjl());//实际填报应该是修改指定的字段
  593. const item = deepList.find(o => o.key == record.key);
  594. if (item) {
  595. item.reply = this.replyContent;
  596. }
  597. this.replaceFhyjjl(deepList);//实际填报应该是修改指定的字段
  598. } else {
  599. const records = this.getReplyRecords();
  600. record.content = this.replyContent;
  601. if (records.length > 0) {
  602. const o = records[0];
  603. if (o.reply && o.content) {//如果填报人员已回复,那么就产生一条新的记录。
  604. this.updateFhyjjl(record);//qc直接插入数据源
  605. } else {//如果填报人员未填报,只更新当条记录的复核内容
  606. const deepList = deepClone(this.getFhyjjl());
  607. const item = deepList.find(it => it.key == record.key);
  608. if (item) {
  609. item.content = this.replyContent;
  610. }
  611. this.replaceFhyjjl(deepList);
  612. }
  613. } else {
  614. this.updateFhyjjl(record);//qc直接插入数据源
  615. }
  616. }
  617. const params = {
  618. //reply:回复,content:复核
  619. type: this.templateFillType == 'actFill' ? "reply" : "content",
  620. newRecord: [record],
  621. resourceList: this.getFhyjjl(),
  622. }
  623. // 触发回复记录事件
  624. EventBus.$emit('onModifyRecord', params);
  625. // 清空回复内容
  626. this.replyContent = '';
  627. // 隐藏弹窗
  628. this.visible = false;
  629. },
  630. //获取question图标颜色
  631. getQuestionColor() {
  632. const records = this.getReplyRecords();
  633. if (records.length > 0) {
  634. const o = records[0];
  635. if (o.reply && o.content) {//有回复意见和复核意见
  636. return "green"
  637. } else if (o.content && !o.reply) {//只有复核意见
  638. return "orange"
  639. } else {
  640. return "gray"
  641. }
  642. } else {//没有回复记录
  643. return "gray"
  644. }
  645. },
  646. // 复选框变化处理
  647. onCheckboxChange(val) {
  648. //有提出意见就不能勾选
  649. if (this.templateFillType == 'qc' && this.getQuestionColor() === "orange") {
  650. this.checkboxValue = false;
  651. this.$message({
  652. message: '该表单还有质疑项未处理,无法勾选',
  653. type: 'error'
  654. });
  655. return;
  656. }
  657. this.checkboxValue = val;
  658. // 触发修改记录事件
  659. EventBus.$emit('onModifyRecord', {
  660. type: "checkbox",
  661. fieldCheckObj: JSON.stringify({ ...this.getFieldCheckObj(), [this.fieldKey]: { checked: val } }),//复选框状态对象
  662. });
  663. this.updateFieldCheckObj({ [this.fieldKey]: { checked: val } });
  664. // this.$emit('input', val);
  665. // this.$emit('change', val);
  666. },
  667. onRemoveTag(e) {
  668. this.onCommonHandleSaveRecord(this.inputValue);
  669. },
  670. onItemCheckboxChange() {
  671. this.onCommonHandleSaveRecord(this.inputValue);
  672. },
  673. // 下拉框失去焦点处理
  674. onSelectBlur(visible) {
  675. if (!visible) {
  676. this.onCommonHandleSaveRecord(this.inputValue);
  677. }
  678. },
  679. // 统一处理输入变化
  680. onInputChange(val) {
  681. let value = val !== undefined ? val : this.inputValue;
  682. // 如果是checkboxList类型,需要同时发送checkboxValues和otherValues
  683. if (this.type === 'checkboxList') {
  684. // 检查是否有被取消选中的checkbox
  685. if (this.oldValue && Array.isArray(this.oldValue)) {
  686. const uncheckedValues = this.oldValue.filter(oldVal => !this.inputValue.includes(oldVal));
  687. // 清除被取消选中的checkbox对应的otherValues
  688. if (uncheckedValues.length > 0) {
  689. this.item.options.forEach(option => {
  690. if (uncheckedValues.includes(option.value) && option.otherCode) {
  691. this.$delete(this.otherValues, option.otherCode);
  692. }
  693. });
  694. }
  695. }
  696. value = {
  697. checkboxValues: this.inputValue,
  698. otherValues: this.otherValues
  699. };
  700. if(val){
  701. this.onCommonHandleSaveRecord();
  702. }
  703. }
  704. this.$emit('input', value);
  705. this.$emit('change', value);
  706. // 根据输入值判断是否显示错误状态
  707. const isEmpty = this.isValueEmpty(value);
  708. if (this.error && !isEmpty) {
  709. this.$emit('update:error', false);
  710. } else if (!this.error && isEmpty) {
  711. this.$emit('update:error', true);
  712. }
  713. },
  714. // 处理checkboxList中otherCode对应的输入框变化
  715. onOtherInputChange(code, value) {
  716. this.otherValues[code] = value;
  717. this.onInputChange();
  718. },
  719. // 统一处理失去焦点事件
  720. onBlur(e) {
  721. this.onCommonHandleSaveRecord(e.target.value);
  722. },
  723. // 点击question图标
  724. onClickQuestion() {
  725. const { templateFillType } = this;
  726. if (templateFillType == 'actFill' || templateFillType == 'qc') {
  727. if (templateFillType == 'qc') {
  728. const field = this.getFieldCheckObj()[this.fieldKey];
  729. if (field && field.checked) {
  730. this.$message({
  731. message: '该字段已勾选复核框,请先取消勾选后再进行提交疑问',
  732. type: 'error'
  733. });
  734. return;
  735. }
  736. }
  737. const records = this.getReplyRecords();
  738. let content = "";
  739. if (records.length > 0) {
  740. const o = records[0];
  741. if (!o.reply && templateFillType == 'qc') {//如果填报人员没有回复,qc点击的时候需要回填上次填报的信息
  742. content = o.content;
  743. } else if (templateFillType == 'actFill') {//如果qc没有复核,填报点击的时候需要回填上次填报的信息
  744. content = o.reply;
  745. }
  746. }
  747. this.replyContent = content;
  748. this.visible = true;
  749. }
  750. },
  751. async onCommonHandleSaveRecord(val) {
  752. const isEmpty = this.isValueEmpty(this.inputValue);
  753. if (this.error && !isEmpty) {
  754. this.$emit('update:error', false);
  755. } else if (!this.error && isEmpty) {
  756. this.$emit('update:error', true);
  757. }
  758. // 创建验证控制器,让父组件可以在保存前进行验证
  759. const validationController = {
  760. isPrevented: false,
  761. errorMsg: '',
  762. prevent(msg) {
  763. this.isPrevented = true;
  764. if (msg) this.errorMsg = msg;
  765. }
  766. };
  767. // 触发beforeSaveRecord事件,让父组件进行验证
  768. this.$emit('beforeSaveRecord', {
  769. value: this.inputValue,
  770. oldValue: this.oldValue,
  771. fieldKey: this.fieldKey
  772. }, validationController);
  773. // 检查是否被阻止
  774. if (validationController.isPrevented) {
  775. // 验证不通过,显示错误信息
  776. if (validationController.errorMsg) {
  777. this.$message.error(validationController.errorMsg);
  778. }
  779. // 回退到旧值
  780. this.inputValue = this.oldValue;
  781. this.$emit('input', this.inputValue);
  782. return;
  783. }
  784. if (!this.isFieldsRecord) {//是否需要记录修改记录
  785. this.$emit("blur", this.inputValue);
  786. this.$emit('input', this.inputValue);
  787. this.$emit("change", this.inputValue);
  788. return;
  789. }
  790. // 值发生了变化,需要弹出密码输入框
  791. const isSame = this.isEqual(this.oldValue, this.inputValue);
  792. let isOtherValuesSame = true;
  793. // 如果是checkboxList类型,需要同时比较otherValues
  794. if (this.type === 'checkboxList' && this.otherValues) {
  795. isOtherValuesSame = this.isEqual(this.oldOtherValues, this.otherValues);
  796. }
  797. console.log(this.oldValue, this.inputValue, isSame, isOtherValuesSame,this.otherValues,this.oldOtherValues,"是否需要记录修改记录")
  798. if (isSame && isOtherValuesSame) {
  799. return;
  800. }
  801. if (!this.isValueEmpty(this.oldValue) && !(isSame && isOtherValuesSame) && this.templateFillType === "actFill") {
  802. // 通过EventBus触发电子签名弹窗
  803. EventBus.$emit('showEditSignDialog', { uuid: this.uuid });
  804. } else {//如果是第一次填写,不需要密码验证
  805. this.handleUpdateRecord()
  806. }
  807. },
  808. //如果用户取消,那么回退到上一次的值
  809. resetRecord() {
  810. // 用户点击取消,还原数据
  811. let oldValue = this.oldValue;
  812. if (this.type === 'checkboxList') {
  813. oldValue = {
  814. checkboxValues: oldValue.checkboxValues || oldValue,
  815. otherValues: this.oldOtherValues
  816. };
  817. }
  818. console.log(this.oldValue, oldValue,"ooo")
  819. this.inputValue = this.oldValue;
  820. this.$emit('input', oldValue); // 触发 v-model 更新
  821. // this.$emit("blur", this.oldValue);
  822. this.$emit("change", oldValue, "cancel");
  823. if (this.item.type === "clickable") {
  824. this.$emit("resetRecord");
  825. }
  826. },
  827. //处理更新记录
  828. handleUpdateRecord(data) {
  829. const baseInfo = this.getCommonRecordInfo();
  830. if (!this.oldValue && !this.inputValue) return;
  831. const record = {
  832. ...baseInfo,
  833. oldValue: this.oldValue,
  834. value: this.inputValue,
  835. title: this.oldValue ? "修改" : "提交",
  836. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  837. }
  838. if (data) {
  839. record.reason = data.remark
  840. }
  841. const params = {
  842. type: "fieldChanged",
  843. newRecord: [record],
  844. resourceList: this.getZdxgjl(),
  845. }
  846. // 更新oldValue和oldOtherValues
  847. if (this.type === 'checkboxList') {
  848. this.oldValue = [...this.inputValue];
  849. this.oldOtherValues = { ...this.otherValues };
  850. }
  851. let value = this.inputValue;
  852. if (this.type === 'checkboxList') {
  853. value = {
  854. checkboxValues: this.inputValue,
  855. otherValues: this.otherValues
  856. };
  857. }
  858. //用户输入密码并点击确定,保存修改
  859. this.oldValue = value; // 更新旧值
  860. this.$emit("blur", value);
  861. this.$emit('input', value);
  862. this.$emit("change", value, data ? "save" : "");
  863. if (this.item.type === "clickable") {//clickable的丢给父级去处理
  864. return;
  865. }
  866. if (this.templateFillType === "actFill") {//只有实际填报的时候才记录修改记录
  867. this.updateZdxgjl(record);
  868. }
  869. this.$nextTick(() => {
  870. EventBus.$emit('onModifyRecord', params,)
  871. if (this.regentType.includes(this.item.type)) {
  872. this.$emit("onRegentSubmit", this.selectRegentInfo);
  873. }
  874. })
  875. },
  876. //判断两个值是否相等
  877. isEqual(oldValue, nowValue) {
  878. if (oldValue === null || nowValue === null) {
  879. return oldValue === nowValue;
  880. }
  881. if (typeof oldValue === 'object' && typeof nowValue === 'object') {
  882. return JSON.stringify(oldValue) === JSON.stringify(nowValue);
  883. }
  884. return oldValue === nowValue;
  885. },
  886. //获取公共记录信息
  887. getCommonRecordInfo() {
  888. const { nickName, name } = this.$store.getters;
  889. //locale:zh-CN 中文 en-US 英文
  890. const { label, parentLabel } = this.item;
  891. let fieldLabelCn = this.$i18n.t(label, "zh_CN"), fieldLabelEn = this.$i18n.t(label, "en_US");
  892. if (label === "template.common.other") {
  893. fieldLabelCn = this.$i18n.t(parentLabel, "zh_CN") + this.$i18n.t("template.common.otherInfo", "zh_CN");
  894. fieldLabelEn = this.$i18n.t(parentLabel, "en_US") + this.$i18n.t("template.common.otherInfo", "en_US");
  895. } else if (!label && parentLabel == "template.common.remark") {
  896. fieldLabelCn = this.$i18n.t(parentLabel, "zh_CN") + this.$i18n.t("template.common.unit", "zh_CN");
  897. fieldLabelEn = this.$i18n.t(parentLabel, "en_US") + this.$i18n.t("template.common.unit", "en_US");
  898. }
  899. const commonInfo = {
  900. userNameCn: nickName,
  901. userNameEn: name,
  902. key: this.fieldKey,
  903. fieldCn: `${this.$i18n.t(this.fieldItemLabel, "zh_CN")}` + (fieldLabelCn ? ("-" + fieldLabelCn) : ""),
  904. fieldEn: `${this.$i18n.t(this.fieldItemLabel, "en_US")}` + (fieldLabelEn ? ("-" + fieldLabelEn) : ""),
  905. }
  906. return commonInfo;
  907. },
  908. // 判断值是否为空
  909. isValueEmpty(value) {
  910. if (value === null || value === undefined || value === '') {
  911. return true;
  912. }
  913. if (typeof value === 'string' && value.trim() === '') {
  914. return true;
  915. }
  916. if (Array.isArray(value) && value.length === 0) {
  917. return true;
  918. }
  919. return false;
  920. },
  921. // 判断checkboxList中特定otherCode输入框是否有错误
  922. isOtherInputError(otherCode) {
  923. if (!this.error) {
  924. return false;
  925. }
  926. // 检查该otherCode对应的输入框是否为空
  927. return this.isValueEmpty(this.otherValues[otherCode]);
  928. },
  929. handleClickable(item, event) {
  930. if (this.templateFillType !== 'actFill') {
  931. return
  932. }
  933. this.$emit("clickable", item)
  934. },
  935. //判断是否禁用复选框
  936. getCheckboxDisabled() {
  937. //只有qc能操作checkbox,其他都只能看。
  938. return this.templateFillType !== 'qc'
  939. },
  940. //判断是否显示复选框图标
  941. getIsShowCheckboxIcon() {
  942. if (this.templateFillType === 'qc') {
  943. return true;
  944. }
  945. return this.getChecked();
  946. },
  947. //判断是否显示复制按钮
  948. getIsShowCopyIcon() {
  949. const { copyFrom } = this.item;
  950. return copyFrom && this.templateFillType === "actFill";
  951. },
  952. //判断是否显示操作按钮
  953. isShowHandle() {
  954. const { fillType } = this.item;
  955. //只有当模板状态不是预填时,才显示操作按钮
  956. return this.templateFillType !== "preFill" && fillType === "actFill"
  957. },
  958. //判断是否禁用
  959. getDisabled() {
  960. const { item } = this;
  961. const { fillType } = item;
  962. if (item.hasOwnProperty("disabled")) {
  963. return item.disabled
  964. } else {
  965. if (fillType === "actFill") {//当模板状态是实际填写时,只有当fillType是actFill时才能填写
  966. return this.templateFillType !== "actFill"
  967. } else if (fillType === "preFill") {//当模板状态是预填写时,只有当fillType是preFill才能填写
  968. return this.templateFillType !== "preFill"
  969. } else {
  970. return true
  971. }
  972. }
  973. },
  974. getPlaceholder() {
  975. const { placeholder, label } = this.item;
  976. const { type } = this;
  977. if (this.getDisabled()) {
  978. return ""
  979. }
  980. if (this.regentType.includes(type) || type === "clickable") {
  981. return this.$t("template.common.pleaseSelect")
  982. }
  983. let prex = "template.common.pleaseFillIn"
  984. if (type === "select" || type === "dateTime") {
  985. prex = "template.common.pleaseSelect"
  986. }
  987. return placeholder ? this.$t(placeholder) : (this.$t(prex) + this.$t(label))
  988. },
  989. async onCopy() {
  990. // 触发复制事件
  991. this.$emit("copy");
  992. // 等待复制操作完成后,调用保存记录方法
  993. this.$nextTick(async () => {
  994. await this.onCommonHandleSaveRecord(this.inputValue);
  995. });
  996. },
  997. //判断是否显示问题图标
  998. getIsShowQuestionIcon() {
  999. if (this.templateFillType === "qc") {//qc可以直接查看
  1000. return true;
  1001. }
  1002. return this.getReplyRecords().length > 0;
  1003. },
  1004. //判断是否显示修改记录图标
  1005. getIsShowRecordIcon() {
  1006. return this.getModifyRecords().length > 0
  1007. },
  1008. //获取回复记录
  1009. getReplyRecords() {
  1010. const { fieldKey, getFhyjjl } = this;
  1011. const records = getFhyjjl()?.filter(item => item.key === fieldKey) || [];
  1012. return records;
  1013. },
  1014. //获取字段修改记录
  1015. getModifyRecords() {
  1016. const { fieldKey, getZdxgjl } = this;
  1017. const records = getZdxgjl().filter(item => item.key === fieldKey);
  1018. return records;
  1019. },
  1020. // 鼠标进入主容器
  1021. async onMouseEnter(type, event) {
  1022. this.currentRecordType = type;
  1023. clearTimeout(this.modalTimer);
  1024. let record = [];
  1025. if (type === "fieldChanged") {
  1026. record = this.getModifyRecords();
  1027. } else if (type === "replyRecord") {
  1028. record = this.getReplyRecords();
  1029. }
  1030. this.modificationRecords = record;
  1031. // 先计算模态框位置,避免闪烁
  1032. this.showModal = true;
  1033. this.$nextTick(() => {
  1034. if (this.$refs.modalRef) {
  1035. const elementRect = event.target.getBoundingClientRect();
  1036. const modalEl = this.$refs.modalRef;
  1037. // 获取模态框的宽度和高度
  1038. const modalWidth = modalEl.offsetWidth || 250; // 默认宽度
  1039. const modalHeight = modalEl.offsetHeight || 300; // 默认高度
  1040. const viewportWidth = window.innerWidth;
  1041. const viewportHeight = window.innerHeight;
  1042. // 计算模态框位置
  1043. let leftPos, topPos;
  1044. // 检查右侧空间是否足够
  1045. if (elementRect.right + modalWidth + 5 <= viewportWidth) {
  1046. // 右侧空间充足,显示在右侧
  1047. leftPos = elementRect.right + 5 + 'px';
  1048. } else if (elementRect.left - modalWidth - 5 >= 0) {
  1049. // 左侧空间充足,显示在左侧
  1050. leftPos = elementRect.left - modalWidth - 5 + 'px';
  1051. } else {
  1052. // 两侧空间都不足,选择空间更大的一边
  1053. if (elementRect.left > viewportWidth - elementRect.right) {
  1054. // 左侧空间更大,显示在左侧
  1055. leftPos = Math.max(5, elementRect.left - modalWidth - 5) + 'px';
  1056. } else {
  1057. // 右侧空间更大,显示在右侧(可能会超出屏幕,但尽量靠近边缘)
  1058. leftPos = Math.min(elementRect.right + 5, viewportWidth - 5) + 'px';
  1059. }
  1060. }
  1061. // 计算顶部位置,确保不超出屏幕上下边界
  1062. topPos = Math.max(5, Math.min(elementRect.top, viewportHeight - modalHeight - 5)) + 'px';
  1063. // 设置模态框位置
  1064. modalEl.style.left = leftPos;
  1065. modalEl.style.top = topPos;
  1066. }
  1067. });
  1068. },
  1069. // 鼠标离开主容器
  1070. onMouseLeave() {
  1071. // this.currentRecordType = '';
  1072. // this.modificationRecords = [];//清空数据源
  1073. // 延迟隐藏模态框,让用户有机会移动到模态框上
  1074. this.modalTimer = setTimeout(() => {
  1075. if (!this.isHoveringModal) {
  1076. this.showModal = false;
  1077. }
  1078. }, 100);
  1079. },
  1080. // 鼠标进入模态框
  1081. onModalEnter() {
  1082. this.isHoveringModal = true;
  1083. clearTimeout(this.modalTimer);
  1084. },
  1085. // 鼠标离开模态框
  1086. onModalLeave() {
  1087. this.isHoveringModal = false;
  1088. this.currentRecordType = "";
  1089. this.modificationRecords = [];//清空数据源
  1090. this.modalTimer = setTimeout(() => {
  1091. this.showModal = false;
  1092. }, 100);
  1093. },
  1094. },
  1095. }
  1096. </script>
  1097. <style lang="scss">
  1098. .flex {
  1099. display: flex;
  1100. align-items: center;
  1101. }
  1102. .flex1 {
  1103. flex: 1;
  1104. }
  1105. .handle-row {
  1106. margin-left: 5px;
  1107. display: flex;
  1108. align-items: center;
  1109. cursor: pointer;
  1110. }
  1111. .w-100 {
  1112. width: 100%;
  1113. }
  1114. .handle-icon {
  1115. width: 18px;
  1116. height: 18px;
  1117. &:not(:last-child) {
  1118. margin-right: 5px;
  1119. }
  1120. }
  1121. .mr-5 {
  1122. margin-right: 5px !important;
  1123. }
  1124. .orange {
  1125. color: #f9c588;
  1126. }
  1127. .green {
  1128. color: green;
  1129. }
  1130. .gray {
  1131. color: #b2b2b2;
  1132. }
  1133. .orange-border {
  1134. .el-input-group__prepend,
  1135. input,
  1136. textarea {
  1137. border-color: #f9c588;
  1138. &:focus {
  1139. border-color: #f9c588;
  1140. }
  1141. &:hover {
  1142. border-color: #f9c588;
  1143. }
  1144. &:disabled {
  1145. border-color: #f9c588 !important;
  1146. }
  1147. }
  1148. .el-checkbox__inner {
  1149. border-color: #f9c588 !important;
  1150. }
  1151. }
  1152. .el-button--primary {
  1153. &.orange-border {
  1154. background-color: #f79b31 !important;
  1155. border-color: #f79b31 !important;
  1156. &:hover {
  1157. background-color: #f79b31 !important;
  1158. }
  1159. &:disabled {
  1160. background-color: rgba(#f79b31, .8) !important;
  1161. border-color: rgba(#f79b31, .8) !important;
  1162. }
  1163. &:active {
  1164. background-color: rgba(#f79b31, .8) !important;
  1165. border-color: rgba(#f79b31, .8) !important;
  1166. }
  1167. }
  1168. &.blue-border {
  1169. background-color: #4ea2ff !important;
  1170. border-color: #4ea2ff !important;
  1171. &:hover {
  1172. background-color: #4ea2ff !important;
  1173. }
  1174. &:disabled {
  1175. background-color: rgba(#4ea2ff, .8) !important;
  1176. border-color: rgba(#4ea2ff, .8) !important;
  1177. }
  1178. &:active {
  1179. background-color: rgba(#4ea2ff, .8) !important;
  1180. border-color: rgba(#4ea2ff, .8) !important;
  1181. }
  1182. }
  1183. }
  1184. .green-border {
  1185. .el-input-group__prepend,
  1186. input,
  1187. textarea {
  1188. border-color: green;
  1189. &:focus {
  1190. border-color: green;
  1191. }
  1192. &:hover {
  1193. border-color: green;
  1194. }
  1195. &:disabled {
  1196. border-color: green !important;
  1197. }
  1198. }
  1199. }
  1200. .blue-border {
  1201. .el-input-group__prepend,
  1202. input,
  1203. .el-checkbox__inner,
  1204. textarea {
  1205. border-color: #4ea2ff;
  1206. &:focus {
  1207. border-color: #4ea2ff;
  1208. }
  1209. &:hover {
  1210. border-color: #4ea2ff;
  1211. }
  1212. &:disabled {
  1213. border-color: #4ea2ff !important;
  1214. }
  1215. }
  1216. }
  1217. .error-border {
  1218. .el-input-group__prepend,
  1219. input,
  1220. textarea,
  1221. .el-select,
  1222. .clickable,
  1223. .el-date-editor {
  1224. border-color: #ff5d5d;
  1225. box-shadow: 0 0 6px #ffc3c3 !important;
  1226. &:focus {
  1227. border-color: #ff5d5d;
  1228. box-shadow: 0 0 6px #ffc3c3 !important;
  1229. }
  1230. &:hover {
  1231. border-color: #ff5d5d;
  1232. box-shadow: 0 0 6px #ffc3c3 !important;
  1233. }
  1234. }
  1235. // 为 el-select 和 el-date-picker 添加错误边框样式
  1236. .el-select .el-input__inner,
  1237. .el-date-editor .el-input__inner .el-checkbox__inner {
  1238. border-color: #ff5d5d;
  1239. box-shadow: 0 0 6px #ffc3c3 !important;
  1240. }
  1241. // 处理 DecimalInput 组件的错误边框样式
  1242. :deep(.el-input-number) {
  1243. .el-input__inner {
  1244. border-color: #ff5d5d;
  1245. box-shadow: 0 0 6px #ffc3c3 !important;
  1246. }
  1247. }
  1248. // 为点击式表单项添加错误边框样式
  1249. .clickable {
  1250. border-color: #ff5d5d;
  1251. box-shadow: 0 0 6px #ffc3c3 !important;
  1252. }
  1253. }
  1254. .orange-bg {
  1255. background-color: #FFF1F1 !important; // 橙色背景,透明度适中
  1256. input,
  1257. textarea,
  1258. .el-input__inner,
  1259. .el-textarea__inner {
  1260. background-color: #FFF1F1 !important;
  1261. }
  1262. }
  1263. .modification-modal {
  1264. position: fixed;
  1265. z-index: 9999;
  1266. background-color: rgba(0, 0, 0, 0.7);
  1267. border-radius: 4px;
  1268. padding: 10px;
  1269. color: white;
  1270. max-height: 300px;
  1271. min-width: 250px;
  1272. overflow: hidden;
  1273. pointer-events: auto;
  1274. opacity: 0;
  1275. transform: scale(0.9);
  1276. transition: opacity 0.2s ease, transform 0.2s ease;
  1277. }
  1278. .modification-modal.show {
  1279. opacity: 1;
  1280. transform: scale(1);
  1281. }
  1282. .modification-modal .modal-content {
  1283. max-height: 280px;
  1284. overflow-y: auto;
  1285. padding-right: 5px;
  1286. }
  1287. .modification-modal .modal-content h4 {
  1288. margin: 0 0 10px 0;
  1289. font-size: 14px;
  1290. border-bottom: 1px solid #ccc;
  1291. padding-bottom: 5px;
  1292. }
  1293. .modification-modal .records-list {
  1294. font-size: 12px;
  1295. }
  1296. .modification-modal .record-item p {
  1297. margin: 5px 0;
  1298. word-break: break-all;
  1299. }
  1300. .modification-modal .record-item hr {
  1301. border: 0;
  1302. border-top: 1px solid #555;
  1303. margin: 8px 0;
  1304. }
  1305. .modification-modal .no-records {
  1306. text-align: center;
  1307. color: #aaa;
  1308. font-style: italic;
  1309. }
  1310. .clickable {
  1311. cursor: pointer;
  1312. width: auto;
  1313. // margin-left: 10px;
  1314. min-height: 28px;
  1315. line-height: 28px;
  1316. word-break: break-all;
  1317. border-radius: 4px;
  1318. border: 1px solid #4ea2ff;
  1319. display: flex;
  1320. align-items: center;
  1321. padding: 0 15px;
  1322. font-size: 14px;
  1323. font-weight: normal;
  1324. color: #606266;
  1325. flex: 1;
  1326. &.disabled {
  1327. cursor: not-allowed;
  1328. color: #c0c4cc;
  1329. background-color: #f5f7fa;
  1330. }
  1331. &.error-border {
  1332. border-color: #ff5d5d !important;
  1333. box-shadow: 0 0 6px #ffc3c3 !important;
  1334. }
  1335. }
  1336. .dialog-footer {
  1337. display: flex;
  1338. justify-content: flex-end;
  1339. }
  1340. .atta-tips {
  1341. color: #ff5d5d;
  1342. font-size: 12px;
  1343. margin-left: 5px;
  1344. }
  1345. .checkbox-list-container {
  1346. padding: 12px;
  1347. border: 1px solid #dcdfe6;
  1348. border-radius: 4px;
  1349. transition: all 0.3s;
  1350. &.error-border {
  1351. border-color: #ff5d5d !important;
  1352. box-shadow: 0 0 6px #ffc3c3 !important;
  1353. }
  1354. .checkbox-item {
  1355. margin-right: 16px;
  1356. display: flex;
  1357. align-items: center;
  1358. &:not(:last-child) {
  1359. margin-bottom: 10px;
  1360. }
  1361. // display: inline-block;
  1362. .el-input {
  1363. width: 200px;
  1364. margin-left: 10px;
  1365. &.error-border {
  1366. .el-input__inner {
  1367. border-color: #ff5d5d !important;
  1368. box-shadow: 0 0 6px #ffc3c3 !important;
  1369. }
  1370. }
  1371. }
  1372. }
  1373. .el-checkbox {
  1374. &.is-checked {
  1375. .el-checkbox__label {
  1376. color: #606266;
  1377. }
  1378. .el-checkbox__inner {
  1379. background-color: #f9c588;
  1380. border-color: #f9c588;
  1381. }
  1382. }
  1383. }
  1384. }
  1385. .orange-border {
  1386. .checkbox-list-container {
  1387. border-color: #f9c588;
  1388. &:hover {
  1389. border-color: #f79b31;
  1390. }
  1391. }
  1392. }
  1393. .orange-bg {
  1394. .checkbox-list-container {
  1395. background-color: #FFF1F1 !important;
  1396. border-color: #f9c588;
  1397. }
  1398. }
  1399. </style>