华西海圻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.

2074 lines
80 KiB

  1. <template>
  2. <div class="flex " :class="getFlexClass()">
  3. <div class="flex" :class="getFlexClass()">
  4. <!-- @copy.native.capture.prevent="handleFalse"
  5. @cut.native.capture.prevent="handleFalse" -->
  6. <el-input
  7. v-if="type === 'input'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
  8. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" @blur="onBlur"
  9. type = "textarea" :autosize="{ minRows: 1, maxRows: 6 }" resize = "none"
  10. :placeholder="getPlaceholder()" v-model="inputValue" @input="onInputChange" @change="onInputChange" />
  11. <el-input v-else-if="type === 'textarea'" :maxlength="item.maxlength || 1000" :disabled="getDisabled()"
  12. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" type="textarea" show-word-limit
  13. resize="none" @blur="onBlur" :rows="item.rows || 3" :placeholder="getPlaceholder()" v-model="inputValue"
  14. @input="onInputChange" @change="onInputChange" />
  15. <DecimalInput v-else-if="type === 'inputNumber'" @blur="onCommonHandleSaveRecord"
  16. :maxlength="item.maxlength || 10" class="flex1" :disabled="getDisabled()"
  17. :controls="item.controls || false" :min="item.min || 0" :prepend="item.prepend"
  18. :decimalDigits="getDecimalDigits()" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  19. :placeholder="getPlaceholder()" v-model="inputValue" @input="onInputChange" @change="onInputChange" />
  20. <el-select v-else-if="type === 'select'" class="flex1" :multiple="item.multiple"
  21. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  22. :disabled="getDisabled()" :placeholder="getPlaceholder()" @remove-tag="onRemoveTag"
  23. :remote = "item.selectRemote || false"
  24. :remote-method="remoteMethod"
  25. @visible-change="onSelectBlur" @change="onInputChange" filterable>
  26. <el-option v-for="op in item.options" :key="op.value" :label="op.label" :value="op.value">
  27. </el-option>
  28. </el-select>
  29. <el-checkbox v-else-if="type === 'checkbox'" class="flex1" :multiple="item.multiple"
  30. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  31. :disabled="getDisabled()" :placeholder="getPlaceholder()" @change="onItemCheckboxChange">
  32. {{ item.checkboxLabel }}
  33. </el-checkbox>
  34. <el-radio-group v-else-if="type === 'radio'" v-model="inputValue"
  35. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" :disabled="getDisabled()"
  36. @change="onItemCheckboxChange">
  37. <el-radio v-for="option in item.options" :key="option.value" :label="option.value"
  38. :disabled="getDisabled()">
  39. {{ option.label }}
  40. </el-radio>
  41. </el-radio-group>
  42. <div v-else-if="type === 'checkboxTree'" class="flex1 checkbox-list-container"
  43. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '') + (error ? ' form-error-border' : '') + (item.noBorder ? ' no-border' : '') + (item.layout === 'horizontal' ? ' flex' : '')">
  44. <div v-for="group in item.options" :key="group.value" class="checkbox-tree-group"
  45. :class="{ 'item-center': isShowOtherByCheckboxTree(group.value) }">
  46. <el-checkbox :label="group.value" :disabled="getDisabled()"
  47. :value="getCheckboxTreeChecked(group.value)"
  48. :indeterminate="getCheckboxTreeIndeterminate(group.value)"
  49. @change="onCheckboxTreeParentChange(group, $event)">
  50. {{ group.label }}
  51. </el-checkbox>
  52. <div v-if="group.children && group.children.length > 0" class="checkbox-tree-children">
  53. <div v-for="child in group.children" :key="child.value" class="checkbox-tree-item">
  54. <el-checkbox :label="child.value" :disabled="getDisabled()"
  55. :value="getCheckboxTreeChecked(child.value)"
  56. @change="onCheckboxTreeChildChange(group, child.value, $event)">
  57. {{ child.label }}
  58. </el-checkbox>
  59. <div v-if="isShowOtherByCheckboxTree(child.value) && isCheckboxTreeChecked(child.value)"
  60. class="checkbox-tree-input-container">
  61. <el-input maxlength="100" v-model="inputValue.otherValues[child.value]"
  62. :disabled="getDisabled()" placeholder="请输入"
  63. @blur="onCheckboxTreeOtherBlur(child.value, $event)" />
  64. </div>
  65. </div>
  66. </div>
  67. <div v-if="isShowOtherByCheckboxTree(group.value) && isCheckboxTreeChecked(group.value)"
  68. class="checkbox-tree-input-container">
  69. <el-input maxlength="100" v-model="inputValue.otherValues[group.value]"
  70. :disabled="getDisabled()" placeholder="请输入"
  71. @blur="onCheckboxTreeOtherBlur(group.value, $event)" />
  72. </div>
  73. </div>
  74. </div>
  75. <el-date-picker v-else-if="type === 'dateTime' || type === 'datePicker'"
  76. :type="type === 'dateTime' ? 'datetime' : 'date'" class="flex1"
  77. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  78. :picker-options="pickerOptions" :disabled="getDisabled()"
  79. :format="type === 'dateTime' ? 'yyyy/MM/dd HH:mm:ss' : 'yyyy/MM/dd'" :placeholder="getPlaceholder()"
  80. @change="(val) => onDateChange(val, type === 'dateTime' ? 'yyyy/MM/DD HH:mm:ss' : 'yyyy/MM/DD')">
  81. </el-date-picker>
  82. <el-date-picker
  83. v-else-if="type === 'dateTimeRange'"
  84. v-model="inputValue"
  85. type="datetimerange"
  86. range-separator="至"
  87. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  88. :disabled="getDisabled()"
  89. :picker-options="pickerOptions"
  90. @change="(val) => onDateChange(val, 'yyyy/MM/DD HH:mm:ss')"
  91. start-placeholder="开始日期"
  92. end-placeholder="结束日期">
  93. </el-date-picker>
  94. <el-button v-else-if="type === 'button'" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  95. :disabled="getDisabled()" type="primary" @click="handleClickButton(item)">
  96. {{ $t(item.buttonName) }}
  97. <input type="hidden" v-model="inputValue">
  98. </el-button>
  99. <div class="clickable"
  100. :class="getFillTypeStyle() + (getDisabled() ? ' disabled' : '') + (orangeBg ? ' orange-bg' : '')"
  101. v-else-if="item.type === 'clickable'" @click="handleClickable(item, $event)">
  102. <span v-if="inputValue">{{ inputValue }}</span>
  103. <span v-else class="default-placeholder-text">{{ getPlaceholder() }}</span>
  104. </div>
  105. <div class="clickable"
  106. :class="getFillTypeStyle() + (getDisabled() ? ' disabled' : '') + (orangeBg ? ' orange-bg' : '')"
  107. v-else-if="isRegent(item)" @click="onCommonHandleRegent(item, item.type)">
  108. <span v-if="inputValue">{{ inputValue }}</span>
  109. <span v-else class="default-placeholder-text">{{ getPlaceholder() }}</span>
  110. </div>
  111. <template v-else-if="type === 'attachment'">
  112. <el-upload ref="uploadRef" class="upload-demo" :action="uploadFileUrl" :on-preview="handlePreview"
  113. :headers="headers" :before-remove="beforeRemove" :on-remove="handleRemove" multiple :limit="10"
  114. :disabled="getDisabled()"
  115. :on-success="handleSuccess" :on-change="handleChange" :on-exceed="handleExceed"
  116. :file-list="fileList" :auto-upload="false">
  117. <el-button :disabled="getDisabled()" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  118. size="small" type="primary">点击上传</el-button>
  119. <span v-if="error" class="atta-tips">请上传附件</span>
  120. <div slot="tip" class="el-upload__tip">支持扩展名.rar .zip .doc .docx .pdf .jpg文件大小不超过2MB</div>
  121. </el-upload>
  122. </template>
  123. <div v-else-if="type === 'checkboxTag'" class="flex1 checkbox-tag-wrapper"
  124. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')">
  125. <div v-for="(tag, tagIndex) in checkboxTagList" :key="tagIndex" class="checkbox-tag-container">
  126. <div class="checkbox-tag-item">
  127. <el-checkbox v-model="tag.checked" :disabled="getDisabled()"
  128. @change="onCheckboxTagChange(tagIndex, $event)"></el-checkbox>
  129. <div class="tag-content blue-border">
  130. <el-input v-if="templateFillType === 'preFill'" v-model="tag.tagValue"
  131. :ref="'tagInput_' + tagIndex" :maxlength="item.maxlength || 20"
  132. @blur="onTagBlur(tagIndex)" @keyup.enter.native="onTagBlur(tagIndex)" placeholder="请输入"
  133. size="mini" class="tag-input" />
  134. <el-tag v-else :type="'info'" class="tag-display" :closable="false">
  135. {{ tag.tagValue }}
  136. </el-tag>
  137. <el-popconfirm confirm-button-text='确认' cancel-button-text='取消' icon="el-icon-info"
  138. icon-color="red" title="确认删除当前编号?" @confirm="onDeleteTag(tagIndex)">
  139. <i slot="reference" v-if="templateFillType === 'preFill'"
  140. class="el-icon-close delete-icon"></i>
  141. </el-popconfirm>
  142. </div>
  143. </div>
  144. </div>
  145. </div>
  146. <div v-else-if="type === 'fqyq'" :class="getFillTypeStyle()">
  147. <el-radio-group v-model="fqyqValue.mainRadio" :disabled="getDisabled()"
  148. @input="onFqyqRadioChange($event, 'mainRadio')">
  149. <div class="item-center mb-10">
  150. <el-radio label="是"></el-radio>
  151. <div class="item-center" v-if="fqyqValue.mainRadio === '是'">
  152. <el-input class="fqyq-input" maxlength="100" v-model="fqyqValue.inputValue"
  153. :disabled="getDisabled()" placeholder="请输入" @blur="onFqyqInputBlur"></el-input>
  154. <div class="fs-14 mr-10">是否在规定时间完成</div>
  155. <el-radio-group v-model="fqyqValue.subRadio" :disabled="getDisabled()"
  156. @input="onFqyqRadioChange($event, 'subRadio')">
  157. <el-radio label="是"></el-radio>
  158. <el-radio label="否"></el-radio>
  159. </el-radio-group>
  160. </div>
  161. </div>
  162. <el-radio label="否"></el-radio>
  163. </el-radio-group>
  164. </div>
  165. </div>
  166. <div class="handle-row" v-if="isShowHandle()">
  167. <el-checkbox v-model="checkboxValue" v-if="getIsShowCheckboxIcon()" :disabled="getCheckboxDisabled()"
  168. class="mr-5" @change="onCheckboxChange"></el-checkbox>
  169. <div class="handle-icon" v-if="getIsShowQuestionIcon()" @click="onClickQuestion"
  170. @mouseenter="(e) => onMouseEnter('replyRecord', e)" @mouseleave="onMouseLeave">
  171. <Question :class="getQuestionColor()" />
  172. </div>
  173. <img v-if="getIsShowCopyIcon()" @click="onCopy" src="@/assets/images/copy-icon.svg" class="handle-icon"
  174. alt="" />
  175. <img v-if="getIsShowRecordIcon()" @mouseenter="(e) => onMouseEnter('fieldChanged', e)"
  176. @mouseleave="onMouseLeave" src="@/assets/images/record-icon.svg" class="handle-icon" alt="" />
  177. </div>
  178. <!-- 修改记录模态框 -->
  179. <div v-if="showModal && modificationRecords.length > 0" ref="modalRef"
  180. :class="['modification-modal', { 'show': showModal }]" @mouseenter="onModalEnter"
  181. @mouseleave="onModalLeave">
  182. <div class="modal-content">
  183. <div class="records-list">
  184. <div v-for="(record, index) in modificationRecords" :key="index" class="record-item">
  185. <!-- 字段修改记录 -->
  186. <div class="record-row" v-if="currentRecordType === 'fieldChanged'">
  187. <div>
  188. <span>{{ index + 1 }}.</span>
  189. <span> {{ getUserName(record) }} </span>
  190. <span>{{ record.time }} </span>
  191. <span>{{ modificationRecords.length - 1 == index ? "提交" : "修改" }}</span>
  192. </div>
  193. <div v-if="modificationRecords.length - 1 !== index">
  194. <div>原值{{ record.oldValue }}</div>
  195. <div>修改值{{ record.value }}</div>
  196. <div v-if="record.reason">备注{{ record.reason }}</div>
  197. </div>
  198. </div>
  199. <!-- 回复记录 -->
  200. <div class="record-row" v-if="currentRecordType === 'replyRecord'">
  201. <div>
  202. <span> {{ getUserName(record) }} </span>
  203. <span>{{ record.time }} </span>
  204. </div>
  205. <div>
  206. <div v-if="record.content">复核意见{{ record.content }}</div>
  207. <div v-if="record.reply">意见回复{{ record.reply }}</div>
  208. </div>
  209. </div>
  210. <hr v-if="index < modificationRecords.length - 1">
  211. </div>
  212. </div>
  213. </div>
  214. </div>
  215. <el-dialog :close-on-click-modal="false" append-to-body :title="(templateFillType == 'actFill' || templateFillType == 'blxjsh') ? '意见回复' : '复核意见'"
  216. :visible.sync="visible" width="30%">
  217. <el-input v-model="replyContent" type="textarea" show-word-limit resize="none" rows="8" placeholder="输入内容"
  218. maxlength="500" />
  219. <span slot="footer" class="dialog-footer">
  220. <el-button @click="visible = false"> </el-button>
  221. <el-button type="primary" @click="onReplyConfirm"> </el-button>
  222. </span>
  223. </el-dialog>
  224. </div>
  225. </template>
  226. <script>
  227. import Question from "./icons/Question.vue";
  228. import DecimalInput from "./DecimalInput.vue";
  229. import { EventBus } from "@/utils/eventBus";
  230. import moment from "moment";
  231. import { getuuid, isEqual, deepClone, getDefaultValueByOptions, isValueEmpty,isRegent } from "@/utils/index.js";
  232. import { getToken } from "@/utils/auth";
  233. import { isShowOtherByCheckboxTree } from "@/utils/formPackageCommon";
  234. export default {
  235. inject: ['templateData', 'templateFillType',"getSubmittedCodes","updateSubmittedCodes", "getZdxgjl", "getFhyjjl", "updateZdxgjl", "replaceFhyjjl", "updateFhyjjl", "getFieldCheckObj", "updateFieldCheckObj"],
  236. components: {
  237. Question,
  238. DecimalInput,
  239. },
  240. props: {
  241. type: {//form类型 input/select等
  242. type: String,
  243. default: "input"
  244. },
  245. item: {
  246. type: Object,
  247. default: () => {
  248. return {
  249. placeholder: "",
  250. maxlength: 30,
  251. label: "",
  252. disabled: false,
  253. }
  254. }
  255. },
  256. // v-model 值
  257. value: {
  258. type: [String, Number, Array, Boolean, Object],
  259. default: ''
  260. },
  261. // 错误状态
  262. error: {
  263. type: Boolean,
  264. default: false
  265. },
  266. // 橙色背景状态
  267. orangeBg: {
  268. type: Boolean,
  269. default: false
  270. },
  271. fieldKey: {
  272. type: String,
  273. default: ""
  274. },
  275. fieldItemLabel: {
  276. type: String,
  277. default: "",
  278. },
  279. //是否记录修改
  280. isFieldsRecord: {
  281. type: Boolean,
  282. default: true,
  283. },
  284. sourceFrom: {
  285. type: String,
  286. default: ""
  287. },
  288. },
  289. data() {
  290. let initialValue = this.value;
  291. let initialOtherValues = {}, checkboxTagList = [];
  292. if (this.type === 'checkboxTag' && Array.isArray(this.value)) {
  293. // checkboxTag类型,value是数组格式
  294. checkboxTagList = this.value.map(tag => ({
  295. checked: tag.checked,
  296. tagValue: tag.tagValue || ''
  297. }));
  298. } else if (this.type === 'fqyq' && !this.value) {
  299. initialValue = { mainRadio: '', subRadio: '', inputValue: "" };
  300. } else if (this.type === 'checkboxTree' && !this.value) {
  301. const defaultCheckedValue = getDefaultValueByOptions(this.item.options || []);
  302. initialValue = { checkedValues: defaultCheckedValue, otherValues: {} };
  303. }
  304. const {type} = this;
  305. return {
  306. inputValue: initialValue,
  307. oldValue: typeof initialValue === 'object' ? JSON.parse(JSON.stringify(initialValue)) : initialValue, // 记录上一次的值
  308. showModal: false, // 控制模态框显示
  309. modificationRecords: [], // 存储修改记录
  310. modalTimer: null, // 用于延迟隐藏模态框
  311. isHoveringModal: false, // 是否悬停在模态框上
  312. isHoveringMain: false, // 是否悬停在主元素上(这个实际上不需要,因为我们有事件处理)
  313. currentRecordType: '', // 当前悬停的记录类型(replyRecord 或 modifyRecord)
  314. replyContent: '', // 回复内容
  315. visible: false,//是否显示弹窗
  316. checkboxValue: this.getChecked(),//是否选中
  317. checkboxTagList: checkboxTagList, // checkboxTag类型的列表数据
  318. oldCheckboxTagList: JSON.parse(JSON.stringify(checkboxTagList)), // 记录上一次的checkboxTagList
  319. fqyqValue: initialValue, // fqyq类型的值
  320. oldFqyqValue: { ...initialValue }, // 记录上一次的fqyq值
  321. uuid: getuuid(), // 唯一标识符,用于EventBus事件匹配
  322. isRegent, //试剂/仪器/供试品等类型
  323. selectRegentInfo: {},//选择的试剂/仪器/供试品等信息
  324. fileList: [],//上传的文件列表
  325. uploadFileUrl: process.env.VUE_APP_BASE_API + "/file/upload",
  326. headers: {
  327. Authorization: "Bearer " + getToken(),
  328. },
  329. pendingUploadFile: null, // 用于存储待上传的文件
  330. pendingRemoveFile: null, // 用于存储待删除的文件
  331. currentTagIndex: -1,//当前选中的checkboxTag索引
  332. currentHandleType: '',//当前操作的类型
  333. currentOtherCode: '',//当前操作的otherCode
  334. currentCheckboxTreeValue: '',//当前操作的checkboxTree值
  335. isShowOtherByCheckboxTree,
  336. pickerOptions: {
  337. // disabledDate(time) {
  338. // return time.getTime() > Date.now();
  339. // },
  340. shortcuts: type === 'dateTimeRange' ? undefined:[{
  341. text: '今天',
  342. onClick(picker) {
  343. picker.$emit('pick', new Date());
  344. }
  345. }]
  346. }
  347. }
  348. },
  349. watch: {
  350. value(newVal) {
  351. if (this.type === 'checkboxTag' && Array.isArray(newVal)) {
  352. // checkboxTag类型,value是数组格式
  353. this.checkboxTagList = newVal.map(tag => ({
  354. checked: tag.checked,
  355. tagValue: tag.tagValue || ''
  356. }));
  357. } else if (this.type === 'fqyq' && newVal && typeof newVal === 'object') {
  358. // fqyq类型
  359. this.fqyqValue = {
  360. mainRadio: newVal.mainRadio || '',
  361. inputValue: newVal.inputValue || '',
  362. subRadio: newVal.subRadio || ''
  363. };
  364. } else {
  365. this.inputValue = typeof newVal === 'object' ? JSON.parse(JSON.stringify(newVal)) : newVal;
  366. }
  367. }
  368. },
  369. filters: {
  370. },
  371. mounted() {
  372. if (this.item.type === 'attachment') {
  373. try {
  374. this.fileList = JSON.parse(this.value);
  375. } catch (e) {
  376. this.fileList = [];
  377. }
  378. }
  379. EventBus.$on('onExternalFieldUpdate', this.handleExternalFieldUpdate);
  380. EventBus.$on('onEditSignCancel', this.handleEditSignCancel);
  381. EventBus.$on('onEditSignCallback', this.handleEditSignCallback);
  382. //试剂
  383. EventBus.$on("onReagentSubmit", this.onReagentSubmit)
  384. //仪器
  385. EventBus.$on("onInstrumentSubmit", this.onMixReagentSubmit)
  386. //供试品/试剂/给药制剂等
  387. EventBus.$on("onMixReagentSubmit", this.onMixReagentSubmit)
  388. },
  389. unmounted() {
  390. EventBus.$off('onExternalFieldUpdate', this.handleExternalFieldUpdate);
  391. EventBus.$off('onEditSignCancel', this.handleEditSignCancel);
  392. EventBus.$off('onEditSignCallback', this.handleEditSignCallback);
  393. EventBus.$off("onReagentSubmit", this.onReagentSubmit)
  394. EventBus.$off("onInstrumentSubmit", this.onMixReagentSubmit)
  395. EventBus.$off("onMixReagentSubmit", this.onMixReagentSubmit)
  396. },
  397. methods: {
  398. remoteMethod(query) {
  399. this.$emit('remoteMethod', query);
  400. },
  401. handleFalse() {
  402. return false;
  403. },
  404. getFlexClass() {
  405. const noFlexArr = ["radio", "checkboxTag", "fqyq"]
  406. return noFlexArr.includes(this.type) ? '' : 'flex1'
  407. },
  408. getDecimalDigits() {
  409. const { precision } = this.item;
  410. if (!isNaN(precision)) {
  411. return Number(precision)
  412. }
  413. return 6
  414. },
  415. onInstrumentSubmit(data) {
  416. if (data.uuid !== this.uuid) return;
  417. this.selectRegentInfo = data;
  418. },
  419. // 文件状态改变时的钩子,添加文件、上传成功、上传失败时都会被调用
  420. handleChange(file, fileList) {
  421. // 如果是新添加的文件(status为ready),进行验证
  422. if (file.status === 'ready') {
  423. const isAllowedType = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf',
  424. 'application/x-rar-compressed', 'application/zip',
  425. 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'].includes(file.raw.type)
  426. const isLt2M = file.size / 1024 / 1024 < 2
  427. if (!isAllowedType) {
  428. this.$message.error(`文件 ${file.name} 格式不支持!只能上传 JPG/PNG/GIF/PDF/RAR/ZIP/DOC/DOCX 格式的文件`)
  429. // 从fileList中移除该文件
  430. const index = fileList.indexOf(file);
  431. if (index > -1) {
  432. fileList.splice(index, 1);
  433. }
  434. this.fileList = [...fileList];
  435. return
  436. }
  437. if (!isLt2M) {
  438. this.$message.error(`文件 ${file.name} 大小超过2MB!`)
  439. // 从fileList中移除该文件
  440. const index = fileList.indexOf(file);
  441. if (index > -1) {
  442. fileList.splice(index, 1);
  443. }
  444. this.fileList = [...fileList];
  445. return
  446. }
  447. // 如果已经有文件在列表中(不包括当前新添加的),需要验证电子签名
  448. const existingFiles = fileList.filter(f => f !== file && f.status === 'success');
  449. if (existingFiles.length > 0) {
  450. // 保存待上传的文件信息
  451. this.pendingUploadFile = file;
  452. // 从fileList中暂时移除新文件,等待签名验证
  453. const index = fileList.indexOf(file);
  454. if (index > -1) {
  455. fileList.splice(index, 1);
  456. }
  457. this.fileList = [...fileList];
  458. // 触发电子签名弹窗
  459. EventBus.$emit('showEditSignDialog', { uuid: this.uuid });
  460. return;
  461. }
  462. // 没有现有文件或验证通过,手动提交上传
  463. this.$nextTick(() => {
  464. // 找到对应的upload组件并提交
  465. const uploadComponent = this.$refs.uploadRef;
  466. if (uploadComponent) {
  467. uploadComponent.submit();
  468. }
  469. });
  470. }
  471. // 更新fileList
  472. this.fileList = fileList;
  473. },
  474. handleSuccess(res, file, fileList) {
  475. if (res.code == 200) {
  476. this.fileList = fileList;
  477. // 更新inputValue为文件路径列表,方便后续保存
  478. this.inputValue = JSON.stringify(this.getFileList(fileList));
  479. this.$emit("change", this.inputValue)
  480. this.onCommonHandleSaveRecord();
  481. this.$message.success('文件上传成功');
  482. } else {
  483. this.$message.error(res.message || '文件上传失败');
  484. // 上传失败,从列表中移除
  485. const index = fileList.indexOf(file);
  486. if (index > -1) {
  487. fileList.splice(index, 1);
  488. this.fileList = [...fileList];
  489. }
  490. }
  491. },
  492. getFileList(fileList) {
  493. const list = [];
  494. fileList.forEach(item => {
  495. const o = { name: item.name };
  496. if (item.url) {//回填的数据
  497. o.url = item.url
  498. } else {//新上传的
  499. o.url = item.response.data.url
  500. }
  501. list.push(o)
  502. })
  503. return list;
  504. },
  505. // 删除前验证电子签名
  506. beforeRemove(file) {
  507. // 所有删除操作都需要验证电子签名
  508. // 保存待删除的文件信息
  509. this.pendingRemoveFile = { file, fileList: this.fileList };
  510. console.log("fillll")
  511. // 触发电子签名弹窗
  512. EventBus.$emit('showEditSignDialog', { uuid: this.uuid });
  513. // 返回false阻止默认删除行为,等待签名验证通过后再删除
  514. return false;
  515. },
  516. handleRemove(file, fileList) {
  517. // on-remove事件在before-remove返回false时不会触发
  518. // 这个方法在签名验证通过后通过executeRemove调用
  519. // 这里不需要额外处理,因为executeRemove已经处理了删除逻辑
  520. },
  521. // 执行实际的文件删除操作
  522. executeRemove(file, fileList) {
  523. // 从el-upload的内部uploadFiles中移除文件
  524. const uploadComponent = this.$refs.uploadRef;
  525. if (uploadComponent && uploadComponent.uploadFiles) {
  526. const uploadFileIndex = uploadComponent.uploadFiles.indexOf(file);
  527. if (uploadFileIndex > -1) {
  528. uploadComponent.uploadFiles.splice(uploadFileIndex, 1);
  529. }
  530. }
  531. // 从fileList中移除文件
  532. const index = fileList.indexOf(file);
  533. if (index > -1) {
  534. fileList.splice(index, 1);
  535. }
  536. // 更新fileList
  537. this.fileList = [...fileList];
  538. // 更新inputValue为剩余文件路径列表
  539. this.inputValue = JSON.stringify(this.getFileList(fileList));
  540. this.$emit("change", this.inputValue);
  541. // 触发保存记录
  542. this.onCommonHandleSaveRecord();
  543. this.$message.success(`文件 ${file.name} 已移除`);
  544. },
  545. handlePreview(file) {
  546. console.log(file)
  547. const url = file.url || file.response?.data?.url;
  548. if (url) {
  549. window.open(process.env.VUE_APP_FILE_DOMAIN + url, '_blank');
  550. }
  551. },
  552. handleExceed(files, fileList) {
  553. this.$message.warning(`当前限制选择 10 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
  554. },
  555. //试剂弹窗提交
  556. onMixReagentSubmit(data) {
  557. if (data.uuid !== this.uuid) return;
  558. // 创建一个验证控制器,用于收集各级验证结果
  559. const validationController = {
  560. isPrevented: false,
  561. errorMsg: '',
  562. prevent(msg) {
  563. this.isPrevented = true;
  564. if (msg) this.errorMsg = msg;
  565. }
  566. };
  567. // 触发自定义验证事件,让父组件判断是否满足需求
  568. // 父组件可以通过validationController.prevent(msg)来阻止提交
  569. this.$emit('beforeReagentSubmit', data, validationController);
  570. // 检查是否被阻止
  571. if (validationController.isPrevented) {
  572. // 验证不通过,显示错误信息
  573. if (validationController.errorMsg) {
  574. this.$message.error(validationController.errorMsg);
  575. }
  576. return;
  577. }
  578. // 验证通过,继续执行
  579. this.executeReagentSubmit(data);
  580. },
  581. // 执行试剂提交的共同逻辑
  582. executeReagentSubmit(data) {
  583. this.inputValue = data.selectedId;
  584. const { filledCodes = [] } = this.item;
  585. console.log(filledCodes, "filledCodes")
  586. const { selectInfo, row,checkType } = data;
  587. if (filledCodes.length > 0 && checkType !=="checkbox") {
  588. this.inputValue = row[filledCodes[0]] + "(" + row[filledCodes[1]] + ")";
  589. }
  590. this.selectRegentInfo = data;
  591. EventBus.$emit("hideSelectMixReagentDialog");
  592. this.onCommonHandleSaveRecord(this.inputValue);
  593. },
  594. //统一处理试剂/供试品等弹窗
  595. onCommonHandleRegent(item, type) {
  596. const {fillType = "actFill"} = item;
  597. if (this.templateFillType !== fillType) {
  598. return
  599. }
  600. let params = {
  601. studyFormId: this.templateData.id,
  602. uuid: this.uuid,
  603. sourceFrom: this.sourceFrom,
  604. }
  605. let eventName = "showSelectMixReagentDialog";
  606. if (type === "yq") {
  607. eventName = "showSelectInstrumentDialog";
  608. } else {
  609. const sjType = {
  610. sj: "1",//试剂
  611. gsp: "7",//供试品
  612. mix: "1",//试剂/供试品/试剂
  613. gyzj: "3",//给药制剂
  614. mjy: "5",//麻精药
  615. xj: "9",//细菌
  616. xb: "11",//细胞
  617. jcb: "13",//检测板
  618. qxbd: "15",//前序表单
  619. }
  620. params = {
  621. ...params,
  622. type: sjType[type]
  623. };
  624. if (type === "mix") {
  625. params.mixType = true;
  626. }
  627. if (item.qxbdType) {
  628. params.qxbdType = item.qxbdType;
  629. }
  630. if (item.checkType) {
  631. params.checkType = item.checkType;
  632. }
  633. }
  634. EventBus.$emit(eventName, params)
  635. // this.$emit('regent', item,type);
  636. },
  637. // 点击按钮
  638. handleClickButton(item) {
  639. this.inputValue = `button-${new Date().getTime()}`;
  640. this.onCommonHandleSaveRecord(this.inputValue);
  641. },
  642. onDateChange(val, format) {
  643. if (this.type === 'dateTimeRange') {
  644. this.inputValue = [moment(val[0]).format(format), moment(val[1]).format(format)];
  645. }else{
  646. this.inputValue = moment(val).format(format);
  647. }
  648. this.onCommonHandleSaveRecord(this.inputValue);
  649. },
  650. getUserName(record) {
  651. const locale = this.$i18n.locale;
  652. if (locale === 'zh_CN') {
  653. return record.userNameCn;
  654. }
  655. return record.userNameEn;
  656. },
  657. // 处理电子签名取消事件
  658. handleEditSignCancel(data) {
  659. if (data.uuid === this.uuid) {
  660. // 如果有待上传的文件,清空它
  661. if (this.pendingUploadFile) {
  662. this.pendingUploadFile = null;
  663. this.$message.info('已取消文件上传');
  664. } else if (this.pendingRemoveFile) {
  665. // 如果有待删除的文件,清空它
  666. this.pendingRemoveFile = null;
  667. this.$message.info('已取消文件删除');
  668. } else {
  669. this.resetRecord();
  670. }
  671. }
  672. },
  673. // 处理电子签名确认回调事件
  674. handleEditSignCallback(data) {
  675. if (data.uuid === this.uuid) {
  676. this.onEditSignSave(data.data);
  677. }
  678. },
  679. onEditSignSave(data) {
  680. // 检查是否有待上传的文件
  681. if (this.pendingUploadFile) {
  682. // 签名验证通过,将文件添加到列表并上传
  683. const file = this.pendingUploadFile;
  684. // 将文件添加到 fileList
  685. this.fileList.push(file);
  686. // 清空待上传文件标记
  687. this.pendingUploadFile = null;
  688. // 手动触发上传
  689. this.$nextTick(() => {
  690. const uploadComponent = this.$refs.uploadRef;
  691. if (uploadComponent) {
  692. uploadComponent.submit();
  693. }
  694. });
  695. } else if (this.pendingRemoveFile) {
  696. // 签名验证通过,执行文件删除
  697. const { file, fileList } = this.pendingRemoveFile;
  698. // 清空待删除文件标记
  699. this.pendingRemoveFile = null;
  700. // 执行删除操作
  701. this.executeRemove(file, fileList);
  702. } else {
  703. // 没有待上传/删除文件,执行正常的记录更新
  704. this.handleUpdateRecord(data);
  705. }
  706. },
  707. getChecked() {
  708. return !!this.getFieldCheckObj()[this.fieldKey]?.checked;
  709. },
  710. getFillTypeStyle(type) {
  711. const { fillType } = this.item;
  712. const filterType = ["attachment", "checkboxTag", "fqyq", "checkboxTree","radio"]
  713. const typeObj = {
  714. actFill: "orange-border",//实际填写的边框颜色
  715. blxjsh: "orange-border",//实际填写的边框颜色
  716. green: "green-border",
  717. preFill: "blue-border",//预填写的边框颜色
  718. }
  719. // 如果有错误状态,返回红色边框样式,覆盖原有的边框颜色
  720. if (this.error && !filterType.includes(this.type)) {
  721. return "error-border";
  722. }
  723. return typeObj[fillType] || ""
  724. },
  725. //确认回复
  726. onReplyConfirm() {
  727. if (!this.replyContent) {
  728. this.$message({
  729. message: '请输入内容',
  730. type: 'error'
  731. });
  732. return;
  733. }
  734. const baseInfo = this.getCommonRecordInfo();
  735. const record = {
  736. ...baseInfo,
  737. title: (this.templateFillType == 'actFill' || this.templateFillType == 'blxjsh') ? "意见回复" : "复核意见",
  738. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  739. }
  740. if (this.templateFillType == 'actFill' || this.templateFillType == 'blxjsh') {
  741. record.reply = this.replyContent;
  742. const deepList = deepClone(this.getFhyjjl());//实际填报应该是修改指定的字段
  743. const item = deepList.find(o => o.key == record.key);
  744. if (item) {
  745. item.reply = this.replyContent;
  746. }
  747. this.replaceFhyjjl(deepList);//实际填报应该是修改指定的字段
  748. } else {
  749. const records = this.getReplyRecords();
  750. record.content = this.replyContent;
  751. if (records.length > 0) {
  752. const o = records[0];
  753. if (o.reply && o.content) {//如果填报人员已回复,那么就产生一条新的记录。
  754. this.updateFhyjjl(record);//qc直接插入数据源
  755. } else {//如果填报人员未填报,只更新当条记录的复核内容
  756. const deepList = deepClone(this.getFhyjjl());
  757. const item = deepList.find(it => it.key == record.key);
  758. if (item) {
  759. item.content = this.replyContent;
  760. }
  761. this.replaceFhyjjl(deepList);
  762. }
  763. } else {
  764. this.updateFhyjjl(record);//qc直接插入数据源
  765. }
  766. }
  767. const params = {
  768. //reply:回复,content:复核
  769. type: (this.templateFillType == 'actFill' || this.templateFillType == 'blxjsh') ? "reply" : "content",
  770. newRecord: [record],
  771. resourceList: this.getFhyjjl(),
  772. }
  773. // 触发回复记录事件
  774. EventBus.$emit('onModifyRecord', params);
  775. // 清空回复内容
  776. this.replyContent = '';
  777. // 隐藏弹窗
  778. this.visible = false;
  779. },
  780. //获取question图标颜色
  781. getQuestionColor() {
  782. const records = this.getReplyRecords();
  783. if (records.length > 0) {
  784. const o = records[0];
  785. if (o.reply && o.content) {//有意见回复和复核意见
  786. return "green"
  787. } else if (o.content && !o.reply) {//只有复核意见
  788. return "orange"
  789. } else {
  790. return "gray"
  791. }
  792. } else {//没有回复记录
  793. return "gray"
  794. }
  795. },
  796. // 复选框变化处理
  797. onCheckboxChange(val) {
  798. //有提出意见就不能勾选
  799. if (this.templateFillType == 'qc' && this.getQuestionColor() === "orange") {
  800. this.checkboxValue = false;
  801. this.$message({
  802. message: '该表单还有质疑项未处理,无法勾选',
  803. type: 'error'
  804. });
  805. return;
  806. }
  807. this.checkboxValue = val;
  808. // 触发修改记录事件
  809. EventBus.$emit('onModifyRecord', {
  810. type: "checkbox",
  811. fieldCheckObj: JSON.stringify({ ...this.getFieldCheckObj(), [this.fieldKey]: { checked: val } }),//复选框状态对象
  812. });
  813. this.updateFieldCheckObj({ [this.fieldKey]: { checked: val } });
  814. // this.$emit('input', val);
  815. // this.$emit('change', val);
  816. },
  817. onRemoveTag(e) {
  818. this.onCommonHandleSaveRecord(this.inputValue);
  819. },
  820. onItemCheckboxChange() {
  821. this.onCommonHandleSaveRecord(this.inputValue);
  822. },
  823. // 下拉框失去焦点处理
  824. onSelectBlur(visible) {
  825. if (!visible) {
  826. this.onCommonHandleSaveRecord(this.inputValue);
  827. }
  828. },
  829. // 统一处理输入变化
  830. onInputChange(val) {
  831. let value = val !== undefined ? val : this.inputValue;
  832. this.$emit('input', value);
  833. this.$emit('change', value);
  834. // 根据输入值判断是否显示错误状态
  835. const isEmpty = isValueEmpty(value);
  836. if (this.error && !isEmpty) {
  837. this.$emit('update:error', false);
  838. } else if (!this.error && isEmpty) {
  839. this.$emit('update:error', true);
  840. }
  841. },
  842. // checkboxTag的checkbox变化处理
  843. onCheckboxTagChange(tagIndex, e) {
  844. this.currentTagIndex = tagIndex;
  845. this.checkboxTagList[tagIndex].checked = e;
  846. this.emitCheckboxTagValue();
  847. this.onCommonHandleSaveRecord();
  848. },
  849. // tag输入框失去焦点
  850. onTagBlur(tagIndex) {
  851. this.currentTagIndex = tagIndex;
  852. const value = this.checkboxTagList[tagIndex].tagValue;
  853. this.emitCheckboxTagValue();
  854. this.onCommonHandleSaveRecord(value);
  855. },
  856. // 删除tag
  857. onDeleteTag(tagIndex) {
  858. this.currentTagIndex = tagIndex;
  859. // 从列表中删除指定索引的tag
  860. this.emitCheckboxTagValue();
  861. this.$emit("deleteTag", tagIndex);
  862. },
  863. // 发送checkboxTag的值
  864. emitCheckboxTagValue() {
  865. // 发送整个数组
  866. this.$emit('input', [...this.checkboxTagList]);
  867. this.$emit('change', [...this.checkboxTagList]);
  868. },
  869. // fqyq 主radio变化处理
  870. onFqyqRadioChange(val, radioType) {
  871. this.fqyqValue[radioType] = val;
  872. this.currentHandleType = radioType;
  873. this.onCommonHandleSaveRecord();
  874. },
  875. // fqyq 输入框失去焦点
  876. onFqyqInputBlur(e) {
  877. this.fqyqValue.inputValue = e.target.value;
  878. this.currentHandleType = 'inputValue';
  879. this.onCommonHandleSaveRecord(this.fqyqValue.inputValue);
  880. },
  881. // 统一处理失去焦点事件
  882. onBlur(e) {
  883. this.onCommonHandleSaveRecord(e.target.value);
  884. },
  885. // 检查checkboxTree的某个值是否被选中
  886. isCheckboxTreeChecked(value) {
  887. if (!this.inputValue || !this.inputValue.checkedValues) {
  888. return false;
  889. }
  890. const checkedItem = this.inputValue.checkedValues.find(item => item.label === value);
  891. return checkedItem && checkedItem.checked;
  892. },
  893. // 检查checkboxTree的其他输入框是否有错误
  894. isCheckboxTreeOtherInputError(value) {
  895. if (!this.error) {
  896. return false;
  897. }
  898. return isValueEmpty(this.inputValue.otherValues[value]);
  899. },
  900. // 获取checkboxTree的选中值
  901. getCheckboxTreeChecked(value) {
  902. const { checkedValues } = this.inputValue
  903. const o = checkedValues.find(item => item.label === value) || {};
  904. return !!o.checked;
  905. },
  906. // 获取或创建checkedItem
  907. getOrCreateCheckedItem(value) {
  908. let checkedItem = this.inputValue.checkedValues.find(item => item.label === value);
  909. if (!checkedItem) {
  910. checkedItem = { label: value, checked: false };
  911. this.inputValue.checkedValues.push(checkedItem);
  912. }
  913. return checkedItem;
  914. },
  915. // 父级checkbox变化处理
  916. onCheckboxTreeParentChange(group, checked) {
  917. this.currentHandleType = 'checkboxTreeCheckbox';
  918. this.currentCheckboxTreeValue = group.value;
  919. // 设置父级状态
  920. const parentItem = this.getOrCreateCheckedItem(group.value);
  921. parentItem.checked = checked;
  922. // 同步所有子级状态
  923. if (group.children && group.children.length > 0) {
  924. group.children.forEach(child => {
  925. const childItem = this.getOrCreateCheckedItem(child.value);
  926. childItem.checked = checked;
  927. // 如果取消选中,清除otherValues
  928. if (!checked) {
  929. delete this.inputValue.otherValues[child.value];
  930. }
  931. });
  932. }
  933. this.onCommonHandleSaveRecord();
  934. },
  935. // 子级checkbox变化处理
  936. onCheckboxTreeChildChange(group, childValue, checked) {
  937. this.currentHandleType = 'checkboxTreeCheckbox';
  938. this.currentCheckboxTreeValue = childValue;
  939. // 设置子级状态
  940. const childItem = this.getOrCreateCheckedItem(childValue);
  941. childItem.checked = checked;
  942. // 如果取消选中,清除otherValues
  943. if (!checked) {
  944. delete this.inputValue.otherValues[childValue];
  945. }
  946. // 更新父级状态
  947. this.updateParentState(group);
  948. this.onCommonHandleSaveRecord();
  949. },
  950. // 更新父级状态(根据子级状态)
  951. updateParentState(group) {
  952. if (!group.children || group.children.length === 0) return;
  953. const parentItem = this.getOrCreateCheckedItem(group.value);
  954. const childValues = group.children.map(child => child.value);
  955. // 统计子级选中状态
  956. let checkedCount = 0;
  957. let totalCount = childValues.length;
  958. childValues.forEach(childValue => {
  959. const childItem = this.inputValue.checkedValues.find(item => item.label === childValue);
  960. if (childItem && childItem.checked) {
  961. checkedCount++;
  962. }
  963. });
  964. // 根据子级状态设置父级状态
  965. if (checkedCount === 0) {
  966. // 全部未选中,父级取消选中
  967. parentItem.checked = false;
  968. } else if (checkedCount === totalCount) {
  969. // 全部选中,父级选中
  970. parentItem.checked = true;
  971. }
  972. // 部分选中时,父级保持当前状态(由indeterminate显示半选)
  973. },
  974. // 获取父级的半选状态
  975. getCheckboxTreeIndeterminate(groupValue) {
  976. if (!this.inputValue || !this.inputValue.checkedValues) {
  977. return false;
  978. }
  979. // 找到对应的group
  980. const group = this.item.options.find(opt => opt.value === groupValue);
  981. if (!group || !group.children || group.children.length === 0) {
  982. return false;
  983. }
  984. const childValues = group.children.map(child => child.value);
  985. let checkedCount = 0;
  986. childValues.forEach(childValue => {
  987. const childItem = this.inputValue.checkedValues.find(item => item.label === childValue);
  988. if (childItem && childItem.checked) {
  989. checkedCount++;
  990. }
  991. });
  992. // 半选状态:有子级被选中但不是全部
  993. return checkedCount > 0 && checkedCount < childValues.length;
  994. },
  995. // checkboxTree的其他输入框失去焦点处理
  996. onCheckboxTreeOtherBlur(value, e) {
  997. this.currentHandleType = "checkboxTreeOther";
  998. this.currentCheckboxTreeValue = value;
  999. this.inputValue.otherValues[value] = e.target.value;
  1000. this.onCommonHandleSaveRecord(e.target.value);
  1001. },
  1002. // 点击question图标
  1003. onClickQuestion() {
  1004. const { templateFillType } = this;
  1005. if (templateFillType == 'actFill' || templateFillType == 'qc' || templateFillType == 'blxjsh') {
  1006. if (templateFillType == 'qc') {
  1007. const field = this.getFieldCheckObj()[this.fieldKey];
  1008. if (field && field.checked) {
  1009. this.$message({
  1010. message: '该字段已勾选复核框,请先取消勾选后再进行提交疑问',
  1011. type: 'error'
  1012. });
  1013. return;
  1014. }
  1015. }
  1016. const records = this.getReplyRecords();
  1017. let content = "";
  1018. if (records.length > 0) {
  1019. const o = records[0];
  1020. if (!o.reply && templateFillType == 'qc') {//如果填报人员没有回复,qc点击的时候需要回填上次填报的信息
  1021. content = o.content;
  1022. } else if (templateFillType == 'actFill' || templateFillType == 'blxjsh') {//如果qc没有复核,填报点击的时候需要回填上次填报的信息
  1023. content = o.reply;
  1024. }
  1025. }
  1026. this.replyContent = content;
  1027. this.visible = true;
  1028. }
  1029. },
  1030. getFqyqInfo() {
  1031. const { mainRadio, inputValue, subRadio } = this.fqyqValue;
  1032. const { mainRadio: oldMainRadio, inputValue: oldInputValue, subRadio: oldSubRadio } = this.oldFqyqValue;
  1033. const o = {
  1034. "mainRadio": { oldValue: oldMainRadio, newValue: mainRadio, des: "" },
  1035. "inputValue": { oldValue: oldInputValue, newValue: inputValue, des: "",key: this.fieldKey+"_inputValue" },
  1036. "subRadio": { oldValue: oldSubRadio, newValue: subRadio, des: "是否在规定时间完成:" }
  1037. }
  1038. console.log(o, this.currentHandleType, this.fqyqValue,"fqyqValue")
  1039. return o[this.currentHandleType];
  1040. },
  1041. getCheckboxTreeInfo() {
  1042. const { checkedValues, otherValues } = this.inputValue;
  1043. const { checkedValues: oldCheckedValues, otherValues: oldOtherValues } = this.oldValue;
  1044. const { currentHandleType, currentCheckboxTreeValue } = this;
  1045. const newItem = checkedValues.find(item => item.label === currentCheckboxTreeValue);
  1046. const oldItem = oldCheckedValues.find(item => item.label === currentCheckboxTreeValue);
  1047. const o = {
  1048. "checkboxTreeCheckbox": { oldValue: oldItem, newValue: newItem, des: "" },
  1049. "checkboxTreeOther": { oldValue: oldOtherValues[currentCheckboxTreeValue], newValue: otherValues[currentCheckboxTreeValue], des: `${currentCheckboxTreeValue}`,key: this.fieldKey+"_"+currentCheckboxTreeValue },
  1050. }
  1051. return o[currentHandleType];
  1052. },
  1053. async onCommonHandleSaveRecord(val) {
  1054. const isEmpty = isValueEmpty(this.inputValue);
  1055. if (this.error && !isEmpty) {
  1056. this.$emit('update:error', false);
  1057. } else if (!this.error && isEmpty) {
  1058. this.$emit('update:error', true);
  1059. }
  1060. // 创建验证控制器,让父组件可以在保存前进行验证
  1061. const validationController = {
  1062. isPrevented: false,
  1063. errorMsg: '',
  1064. prevent(msg) {
  1065. this.isPrevented = true;
  1066. if (msg) this.errorMsg = msg;
  1067. }
  1068. };
  1069. // 触发beforeSaveRecord事件,让父组件进行验证
  1070. this.$emit('beforeSaveRecord', {
  1071. value: this.inputValue,
  1072. oldValue: this.oldValue,
  1073. fieldKey: this.fieldKey
  1074. }, validationController);
  1075. // 检查是否被阻止
  1076. if (validationController.isPrevented) {
  1077. // 验证不通过,显示错误信息
  1078. if (validationController.errorMsg) {
  1079. this.$message.error(validationController.errorMsg);
  1080. }
  1081. // 回退到旧值
  1082. this.inputValue = this.oldValue;
  1083. this.$emit('input', this.inputValue);
  1084. return;
  1085. }
  1086. if (!this.isFieldsRecord) {//是否需要记录修改记录
  1087. this.$emit("blur", this.inputValue);
  1088. this.$emit('input', this.inputValue);
  1089. this.$emit("change", this.inputValue);
  1090. return;
  1091. }
  1092. // 值发生了变化,需要弹出密码输入框
  1093. let isSame = true, isOldValueEmpty = true;
  1094. const { currentHandleType } = this;
  1095. if (this.type === "checkboxTag") {
  1096. // checkboxTag类型,只比较当前tagIndex的数据
  1097. const currentTag = this.checkboxTagList[this.currentTagIndex];
  1098. const oldTag = this.oldCheckboxTagList[this.currentTagIndex] || {};
  1099. isSame = isEqual(oldTag.checked, currentTag.checked);
  1100. isOldValueEmpty = oldTag.checked === undefined;
  1101. } else if (this.type === "fqyq") {
  1102. const current = this.getFqyqInfo();
  1103. isSame = isEqual(current.oldValue, current.newValue);
  1104. if(this.currentHandleType === "inputValue"){
  1105. isOldValueEmpty =this.isUnSubmitted(current.key);
  1106. }else{
  1107. isOldValueEmpty = isValueEmpty(current.oldValue);
  1108. }
  1109. } else if (this.type === "checkboxTree") {
  1110. const current = this.getCheckboxTreeInfo() || {};
  1111. const { oldValue = {}, newValue = {} } = current;
  1112. if (currentHandleType === "checkboxTreeCheckbox") {
  1113. isSame = isEqual(oldValue.checked, newValue.checked);
  1114. isOldValueEmpty = oldValue.checked === undefined;
  1115. } else {
  1116. isSame = isEqual(current.oldValue, current.newValue);
  1117. isOldValueEmpty = this.isUnSubmitted(current.key);
  1118. }
  1119. } else if(this.type === "checkbox"){
  1120. isSame = isEqual(this.oldValue, this.inputValue)
  1121. isOldValueEmpty =this.isUnSubmitted();
  1122. }else {
  1123. isSame = isEqual(this.oldValue, this.inputValue)
  1124. isOldValueEmpty = this.isUnSubmitted();
  1125. }
  1126. console.log(isSame, isOldValueEmpty, this.currentCheckboxTreeValue, this.oldValue, this.inputValue, "isSame")
  1127. if (isSame) {
  1128. return;
  1129. }
  1130. if (!isOldValueEmpty && !(isSame) && (this.templateFillType === "actFill" || this.templateFillType === "blxjsh")&&this.type !== "attachment") {
  1131. console.log("需要电子签名")
  1132. // 通过EventBus触发电子签名弹窗
  1133. EventBus.$emit('showEditSignDialog', { uuid: this.uuid });
  1134. } else {//如果是第一次填写,不需要密码验证
  1135. this.handleUpdateRecord()
  1136. }
  1137. },
  1138. //是否提交过
  1139. isUnSubmitted(key){
  1140. const finallyKey = key || this.fieldKey;
  1141. const has = this.getSubmittedCodes().includes(finallyKey)
  1142. return !has;
  1143. },
  1144. //如果用户取消,那么回退到上一次的值
  1145. resetRecord() {
  1146. // 用户点击取消,还原数据
  1147. let oldValue = this.oldValue;
  1148. if (this.type === "checkboxTag") {
  1149. // checkboxTag类型,只回退当前tagIndex的数据
  1150. if (this.currentTagIndex >= 0 && this.currentTagIndex < this.oldCheckboxTagList.length) {
  1151. const oldTag = this.oldCheckboxTagList[this.currentTagIndex];
  1152. this.checkboxTagList[this.currentTagIndex] = { ...oldTag };
  1153. oldValue = [...this.checkboxTagList];
  1154. } else {
  1155. // 如果没有指定tagIndex,回退整个数组
  1156. this.checkboxTagList = JSON.parse(JSON.stringify(this.oldCheckboxTagList));
  1157. oldValue = [...this.checkboxTagList];
  1158. }
  1159. } else if (this.type === "fqyq") {
  1160. // 如果没有指定字段,回退整个对象
  1161. this.fqyqValue = JSON.parse(JSON.stringify(this.oldFqyqValue));
  1162. oldValue = { ...this.fqyqValue };
  1163. }
  1164. this.inputValue = typeof oldValue === 'object' ? JSON.parse(JSON.stringify(oldValue)) : oldValue;
  1165. this.$emit('input', oldValue); // 触发 v-model 更新
  1166. // this.$emit("blur", this.oldValue);
  1167. this.$emit("change", oldValue, "cancel");
  1168. },
  1169. //处理更新记录
  1170. handleUpdateRecord(data, recordData) {
  1171. const baseInfo = this.getCommonRecordInfo();
  1172. //有recordData表示从组件外部调用的更新操作,
  1173. if (!this.oldValue && !this.inputValue && !recordData) {
  1174. return
  1175. }
  1176. let finallyKey = this.fieldKey;
  1177. if (recordData) {
  1178. this.oldValue = recordData.oldValue;
  1179. this.inputValue = recordData.inputValue;
  1180. }
  1181. let recordOldVlaue = this.oldValue, recordValue = this.inputValue, isModify = !!this.oldValue;
  1182. if (this.type === "checkboxTag") {
  1183. // checkboxTag类型,只记录当前tagIndex的数据变化
  1184. const oldTag = this.oldCheckboxTagList[this.currentTagIndex] || {};
  1185. const currentTag = this.checkboxTagList[this.currentTagIndex] || {};
  1186. recordOldVlaue = `${oldTag.tagValue || ''}:${oldTag.checked ? '勾选' : '未勾选'}`;
  1187. recordValue = `${currentTag.tagValue || ''}:${currentTag.checked ? '勾选' : '未勾选'}`;
  1188. isModify = oldTag.checked !== undefined;
  1189. } else if (this.type === "fqyq") {
  1190. const current = this.getFqyqInfo();
  1191. recordOldVlaue = `${current.des + current.oldValue}`;
  1192. recordValue = `${current.des + current.newValue}`;
  1193. if(this.currentHandleType === "inputValue"){
  1194. finallyKey = current.key;
  1195. }
  1196. isModify = !!this.oldFqyqValue.mainRadio
  1197. } else if (this.type === "checkboxTree") {
  1198. // checkboxTree类型,记录当前操作的值变化
  1199. const current = this.getCheckboxTreeInfo();
  1200. if (this.currentHandleType === "checkboxTreeCheckbox") {
  1201. const { oldValue = {}, newValue = {} } = current;
  1202. recordOldVlaue = `${oldValue?.label || ''}:${oldValue?.checked ? '勾选' : '未勾选'}`;
  1203. recordValue = `${newValue.label || ''}:${newValue.checked ? '勾选' : '未勾选'}`;
  1204. isModify = newValue.checked !== undefined;
  1205. finallyKey = current.key;
  1206. } else {
  1207. recordOldVlaue = `${current.des + (current.oldValue || '')}`;
  1208. recordValue = `${current.des + (current.newValue || '')}`;
  1209. isModify = !!current.oldValue;
  1210. }
  1211. }else if(this.type === "checkbox"){
  1212. recordOldVlaue = `${this.item.checkboxLabel || ""}:${this.oldValue?'勾选':'未勾选'}`;
  1213. recordValue = `${this.item.checkboxLabel||""}:${this.inputValue?'勾选':'未勾选'}`;
  1214. isModify = this.oldValue !== '';
  1215. }else if(this.type === "attachment"){
  1216. const attList = JSON.parse(recordValue);
  1217. const oldAttList = JSON.parse(recordOldVlaue || "[]");
  1218. recordValue = attList.map(item => item.name).join(";");
  1219. recordOldVlaue = oldAttList.map(item => item.name).join(";");
  1220. }
  1221. const record = {
  1222. ...baseInfo,
  1223. oldValue: recordOldVlaue,
  1224. value: recordValue,
  1225. title: !this.isUnSubmitted(finallyKey) ? "修改" : "提交",
  1226. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  1227. }
  1228. if (data) {
  1229. record.reason = data.remark
  1230. }
  1231. const params = {
  1232. type: "fieldChanged",
  1233. newRecord: [record],
  1234. submittedCodes: this.getSubmittedCodes(),
  1235. resourceList: this.getZdxgjl(),
  1236. }
  1237. // 更新oldValue和oldOtherValues
  1238. if (this.type === "checkboxTag") {
  1239. // checkboxTag类型,只更新当前tagIndex的数据
  1240. if (this.currentTagIndex >= 0 && this.currentTagIndex < this.checkboxTagList.length) {
  1241. this.oldCheckboxTagList[this.currentTagIndex] = { ...this.checkboxTagList[this.currentTagIndex] };
  1242. } else {
  1243. // 如果没有指定tagIndex,更新整个数组
  1244. this.oldCheckboxTagList = JSON.parse(JSON.stringify(this.checkboxTagList));
  1245. }
  1246. } else if (this.type === "fqyq") {
  1247. // 如果没有指定字段,更新整个对象
  1248. this.oldFqyqValue = JSON.parse(JSON.stringify(this.fqyqValue));
  1249. }
  1250. let value = this.inputValue;
  1251. if (this.type === "checkboxTag") {
  1252. value = [...this.checkboxTagList];
  1253. } else if (this.type === "fqyq") {
  1254. value = { ...this.fqyqValue };
  1255. }
  1256. if (this.type === "button") {
  1257. this.$emit('clickButton', this.item, this.inputValue, data);
  1258. if (this.templateFillType === "preFill") {
  1259. return;
  1260. }
  1261. }
  1262. //用户输入密码并点击确定,保存修改
  1263. this.oldValue = typeof value === 'object' ? JSON.parse(JSON.stringify(value)) : value; // 更新旧值
  1264. this.$emit("blur", value);
  1265. this.$emit('input', value);
  1266. this.$emit("change", value, data ? "save" : "");
  1267. if (this.item.type === "clickable") {//clickable的丢给父级去处理
  1268. return;
  1269. }
  1270. if (this.templateFillType === "actFill" || this.templateFillType === "blxjsh") {//只有实际填报的时候才记录修改记录
  1271. this.updateZdxgjl(record);
  1272. this.updateSubmittedCodes(finallyKey);
  1273. }
  1274. this.$nextTick(() => {
  1275. EventBus.$emit('onModifyRecord', params,)
  1276. console.log(params, "onModifyRecord")
  1277. if (isRegent(this.item)) {
  1278. this.$emit("onRegentSubmit", this.selectRegentInfo, this.inputValue);
  1279. }
  1280. })
  1281. },
  1282. //获取公共记录信息
  1283. getCommonRecordInfo() {
  1284. const { nickName, name } = this.$store.getters;
  1285. //locale:zh-CN 中文 en-US 英文
  1286. const { label, parentLabel } = this.item;
  1287. let fieldLabelCn = this.$i18n.t(label, "zh_CN"), fieldLabelEn = this.$i18n.t(label, "en_US");
  1288. if (label === "template.common.other") {
  1289. fieldLabelCn = this.$i18n.t(parentLabel, "zh_CN") + this.$i18n.t("template.common.otherInfo", "zh_CN");
  1290. fieldLabelEn = this.$i18n.t(parentLabel, "en_US") + this.$i18n.t("template.common.otherInfo", "en_US");
  1291. } else if (!label && parentLabel == "template.common.remark") {
  1292. fieldLabelCn = this.$i18n.t(parentLabel, "zh_CN") + this.$i18n.t("template.common.unit", "zh_CN");
  1293. fieldLabelEn = this.$i18n.t(parentLabel, "en_US") + this.$i18n.t("template.common.unit", "en_US");
  1294. }
  1295. const commonInfo = {
  1296. userNameCn: nickName,
  1297. userNameEn: name,
  1298. key: this.fieldKey,
  1299. fieldCn: `${this.$i18n.t(this.fieldItemLabel, "zh_CN")}` + (fieldLabelCn ? ("-" + fieldLabelCn) : ""),
  1300. fieldEn: `${this.$i18n.t(this.fieldItemLabel, "en_US")}` + (fieldLabelEn ? ("-" + fieldLabelEn) : ""),
  1301. }
  1302. return commonInfo;
  1303. },
  1304. handleClickable(item, event) {
  1305. if (this.templateFillType !== 'actFill') {
  1306. return
  1307. }
  1308. this.$emit("clickable", item)
  1309. },
  1310. //判断是否禁用复选框
  1311. getCheckboxDisabled() {
  1312. //只有qc能操作checkbox,其他都只能看。
  1313. return this.templateFillType !== 'qc'
  1314. },
  1315. //判断是否显示复选框图标
  1316. getIsShowCheckboxIcon() {
  1317. if (this.templateFillType === 'qc') {
  1318. return true;
  1319. }
  1320. return this.getChecked();
  1321. },
  1322. //判断是否显示复制按钮
  1323. getIsShowCopyIcon() {
  1324. const { copyFrom } = this.item;
  1325. return copyFrom && this.templateFillType === "actFill";
  1326. },
  1327. //判断是否显示操作按钮
  1328. isShowHandle() {
  1329. const { fillType } = this.item;
  1330. //只有当模板状态不是预填时,才显示操作按钮
  1331. return this.templateFillType !== "preFill" && (fillType === "actFill" || fillType === "blxjsh") && this.type !== "button"
  1332. },
  1333. //判断是否禁用
  1334. getDisabled() {
  1335. const { item } = this;
  1336. const { fillType } = item;
  1337. if (item.hasOwnProperty("disabled")) {
  1338. return item.disabled
  1339. } else {
  1340. if (fillType === "actFill") {//当模板状态是实际填写时,只有当fillType是actFill时才能填写
  1341. return this.templateFillType !== "actFill"
  1342. } else if (fillType === "preFill") {//当模板状态是预填写时,只有当fillType是preFill才能填写
  1343. return this.templateFillType !== "preFill"
  1344. } else if (fillType === "blxjsh") {//当模板状态是预填写时,只有当fillType是blxjsh才能填写
  1345. return this.templateFillType !== "blxjsh"
  1346. }else {
  1347. return true
  1348. }
  1349. }
  1350. },
  1351. getPlaceholder() {
  1352. const { placeholder, label } = this.item;
  1353. const { type } = this;
  1354. if (this.getDisabled()) {
  1355. return ""
  1356. }
  1357. if (isRegent(this.item) || type === "clickable" || type === "fqyq") {
  1358. return this.$t("template.common.pleaseSelect")
  1359. }
  1360. let prex = "template.common.pleaseFillIn"
  1361. if (type === "select" || type === "dateTime" || type === "datePicker") {
  1362. prex = "template.common.pleaseSelect"
  1363. }
  1364. return placeholder ? this.$t(placeholder) : (this.$t(prex) + this.$t(label))
  1365. },
  1366. async onCopy() {
  1367. // 触发复制事件
  1368. this.$emit("copy");
  1369. // 等待复制操作完成后,调用保存记录方法
  1370. this.$nextTick(async () => {
  1371. await this.onCommonHandleSaveRecord(this.inputValue);
  1372. });
  1373. },
  1374. //判断是否显示问题图标
  1375. getIsShowQuestionIcon() {
  1376. if (this.templateFillType === "qc") {//qc可以直接查看
  1377. return true;
  1378. }
  1379. return this.getReplyRecords().length > 0;
  1380. },
  1381. //判断是否显示修改记录图标
  1382. getIsShowRecordIcon() {
  1383. return this.getModifyRecords().length > 0
  1384. },
  1385. //获取回复记录
  1386. getReplyRecords() {
  1387. const { fieldKey, getFhyjjl } = this;
  1388. const records = getFhyjjl()?.filter(item => item.key === fieldKey) || [];
  1389. return records;
  1390. },
  1391. //获取字段修改记录
  1392. getModifyRecords() {
  1393. const { fieldKey, getZdxgjl } = this;
  1394. const records = getZdxgjl().filter(item => item.key === fieldKey);
  1395. return records;
  1396. },
  1397. // 鼠标进入主容器
  1398. async onMouseEnter(type, event) {
  1399. this.currentRecordType = type;
  1400. clearTimeout(this.modalTimer);
  1401. let record = [];
  1402. if (type === "fieldChanged") {
  1403. record = this.getModifyRecords();
  1404. } else if (type === "replyRecord") {
  1405. record = this.getReplyRecords();
  1406. }
  1407. this.modificationRecords = record;
  1408. // 先计算模态框位置,避免闪烁
  1409. this.showModal = true;
  1410. this.$nextTick(() => {
  1411. if (this.$refs.modalRef) {
  1412. const elementRect = event.target.getBoundingClientRect();
  1413. const modalEl = this.$refs.modalRef;
  1414. // 获取模态框的宽度和高度
  1415. const modalWidth = modalEl.offsetWidth || 250; // 默认宽度
  1416. const modalHeight = modalEl.offsetHeight || 300; // 默认高度
  1417. const viewportWidth = window.innerWidth;
  1418. const viewportHeight = window.innerHeight;
  1419. // 计算模态框位置
  1420. let leftPos, topPos;
  1421. // 检查右侧空间是否足够
  1422. if (elementRect.right + modalWidth + 5 <= viewportWidth) {
  1423. // 右侧空间充足,显示在右侧
  1424. leftPos = elementRect.right + 5 + 'px';
  1425. } else if (elementRect.left - modalWidth - 5 >= 0) {
  1426. // 左侧空间充足,显示在左侧
  1427. leftPos = elementRect.left - modalWidth - 5 + 'px';
  1428. } else {
  1429. // 两侧空间都不足,选择空间更大的一边
  1430. if (elementRect.left > viewportWidth - elementRect.right) {
  1431. // 左侧空间更大,显示在左侧
  1432. leftPos = Math.max(5, elementRect.left - modalWidth - 5) + 'px';
  1433. } else {
  1434. // 右侧空间更大,显示在右侧(可能会超出屏幕,但尽量靠近边缘)
  1435. leftPos = Math.min(elementRect.right + 5, viewportWidth - 5) + 'px';
  1436. }
  1437. }
  1438. // 计算顶部位置,确保不超出屏幕上下边界
  1439. topPos = Math.max(5, Math.min(elementRect.top, viewportHeight - modalHeight - 5)) + 'px';
  1440. // 设置模态框位置
  1441. modalEl.style.left = leftPos;
  1442. modalEl.style.top = topPos;
  1443. }
  1444. });
  1445. },
  1446. // 鼠标离开主容器
  1447. onMouseLeave() {
  1448. // this.currentRecordType = '';
  1449. // this.modificationRecords = [];//清空数据源
  1450. // 延迟隐藏模态框,让用户有机会移动到模态框上
  1451. this.modalTimer = setTimeout(() => {
  1452. if (!this.isHoveringModal) {
  1453. this.showModal = false;
  1454. }
  1455. }, 100);
  1456. },
  1457. // 鼠标进入模态框
  1458. onModalEnter() {
  1459. this.isHoveringModal = true;
  1460. clearTimeout(this.modalTimer);
  1461. },
  1462. // 鼠标离开模态框
  1463. onModalLeave() {
  1464. this.isHoveringModal = false;
  1465. this.currentRecordType = "";
  1466. this.modificationRecords = [];//清空数据源
  1467. this.modalTimer = setTimeout(() => {
  1468. this.showModal = false;
  1469. }, 100);
  1470. },
  1471. },
  1472. }
  1473. </script>
  1474. <style lang="scss">
  1475. .flex {
  1476. display: flex;
  1477. align-items: center;
  1478. }
  1479. .flex1 {
  1480. flex: 1;
  1481. }
  1482. .handle-row {
  1483. margin-left: 5px;
  1484. display: flex;
  1485. align-items: center;
  1486. cursor: pointer;
  1487. }
  1488. .w-100 {
  1489. width: 100%;
  1490. }
  1491. .handle-icon {
  1492. width: 18px;
  1493. height: 18px;
  1494. &:not(:last-child) {
  1495. margin-right: 5px;
  1496. }
  1497. }
  1498. .mr-5 {
  1499. margin-right: 5px !important;
  1500. }
  1501. .orange {
  1502. color: #f9c588;
  1503. }
  1504. .green {
  1505. color: green;
  1506. }
  1507. .gray {
  1508. color: #b2b2b2;
  1509. }
  1510. .orange-border {
  1511. .el-input-group__prepend,
  1512. input,
  1513. textarea {
  1514. border-color: #f9c588;
  1515. &:focus {
  1516. border-color: #f9c588;
  1517. }
  1518. &:hover {
  1519. border-color: #f9c588;
  1520. }
  1521. &:disabled {
  1522. border-color: #f9c588 !important;
  1523. }
  1524. }
  1525. .el-checkbox__inner {
  1526. border-color: #f9c588 !important;
  1527. }
  1528. &.is-checked {
  1529. .el-checkbox__label {
  1530. color: #606266;
  1531. }
  1532. .el-checkbox__inner {
  1533. background-color: #f9c588;
  1534. border-color: #f9c588;
  1535. }
  1536. }
  1537. }
  1538. .el-button--primary {
  1539. &.orange-border {
  1540. background-color: #f79b31 !important;
  1541. border-color: #f79b31 !important;
  1542. &:hover {
  1543. background-color: #f79b31 !important;
  1544. }
  1545. &:disabled {
  1546. background-color: rgba(#f79b31, .8) !important;
  1547. border-color: rgba(#f79b31, .8) !important;
  1548. }
  1549. &:active {
  1550. background-color: rgba(#f79b31, .8) !important;
  1551. border-color: rgba(#f79b31, .8) !important;
  1552. }
  1553. }
  1554. &.blue-border {
  1555. background-color: #4ea2ff !important;
  1556. border-color: #4ea2ff !important;
  1557. &:hover {
  1558. background-color: #4ea2ff !important;
  1559. }
  1560. &:disabled {
  1561. background-color: rgba(#4ea2ff, .8) !important;
  1562. border-color: rgba(#4ea2ff, .8) !important;
  1563. }
  1564. &:active {
  1565. background-color: rgba(#4ea2ff, .8) !important;
  1566. border-color: rgba(#4ea2ff, .8) !important;
  1567. }
  1568. }
  1569. }
  1570. .green-border {
  1571. .el-input-group__prepend,
  1572. input,
  1573. textarea {
  1574. border-color: green;
  1575. &:focus {
  1576. border-color: green;
  1577. }
  1578. &:hover {
  1579. border-color: green;
  1580. }
  1581. &:disabled {
  1582. border-color: green !important;
  1583. }
  1584. }
  1585. }
  1586. .blue-border {
  1587. .el-input-group__prepend,
  1588. input,
  1589. .el-checkbox__inner,
  1590. textarea {
  1591. border-color: #4ea2ff;
  1592. &:focus {
  1593. border-color: #4ea2ff;
  1594. }
  1595. &:hover {
  1596. border-color: #4ea2ff;
  1597. }
  1598. &:disabled {
  1599. border-color: #4ea2ff !important;
  1600. }
  1601. }
  1602. }
  1603. .error-border {
  1604. .el-input-group__prepend,
  1605. .el-input__inner,
  1606. textarea,
  1607. .el-select,
  1608. .clickable,
  1609. .el-date-editor,
  1610. .el-checkbox__inner {
  1611. border-color: #ff5d5d;
  1612. box-shadow: 0 0 6px #ffc3c3 !important;
  1613. &:focus {
  1614. border-color: #ff5d5d;
  1615. box-shadow: 0 0 6px #ffc3c3 !important;
  1616. }
  1617. &:hover {
  1618. border-color: #ff5d5d;
  1619. box-shadow: 0 0 6px #ffc3c3 !important;
  1620. }
  1621. }
  1622. // 为 el-select 和 el-date-picker 添加错误边框样式
  1623. .el-select .el-input__inner,
  1624. .el-date-editor .el-input__inner .el-checkbox__inner {
  1625. border-color: #ff5d5d;
  1626. box-shadow: 0 0 6px #ffc3c3 !important;
  1627. }
  1628. // 处理 DecimalInput 组件的错误边框样式
  1629. :deep(.el-input-number) {
  1630. .el-input__inner {
  1631. border-color: #ff5d5d;
  1632. box-shadow: 0 0 6px #ffc3c3 !important;
  1633. }
  1634. }
  1635. // 为点击式表单项添加错误边框样式
  1636. .clickable {
  1637. border-color: #ff5d5d;
  1638. box-shadow: 0 0 6px #ffc3c3 !important;
  1639. }
  1640. }
  1641. .orange-bg {
  1642. background-color: #FFF1F1 !important; // 橙色背景,透明度适中
  1643. input,
  1644. textarea,
  1645. .el-input__inner,
  1646. .el-textarea__inner {
  1647. background-color: #FFF1F1 !important;
  1648. }
  1649. }
  1650. .modification-modal {
  1651. position: fixed;
  1652. z-index: 9999;
  1653. background-color: rgba(0, 0, 0, 0.7);
  1654. border-radius: 4px;
  1655. padding: 10px;
  1656. color: white;
  1657. max-height: 300px;
  1658. min-width: 250px;
  1659. overflow: hidden;
  1660. pointer-events: auto;
  1661. opacity: 0;
  1662. transform: scale(0.9);
  1663. transition: opacity 0.2s ease, transform 0.2s ease;
  1664. }
  1665. .modification-modal.show {
  1666. opacity: 1;
  1667. transform: scale(1);
  1668. }
  1669. .modification-modal .modal-content {
  1670. max-height: 280px;
  1671. overflow-y: auto;
  1672. padding-right: 5px;
  1673. }
  1674. .modification-modal .modal-content h4 {
  1675. margin: 0 0 10px 0;
  1676. font-size: 14px;
  1677. border-bottom: 1px solid #ccc;
  1678. padding-bottom: 5px;
  1679. }
  1680. .modification-modal .records-list {
  1681. font-size: 12px;
  1682. }
  1683. .modification-modal .record-item p {
  1684. margin: 5px 0;
  1685. word-break: break-all;
  1686. }
  1687. .modification-modal .record-item hr {
  1688. border: 0;
  1689. border-top: 1px solid #555;
  1690. margin: 8px 0;
  1691. }
  1692. .modification-modal .no-records {
  1693. text-align: center;
  1694. color: #aaa;
  1695. font-style: italic;
  1696. }
  1697. .clickable {
  1698. cursor: pointer;
  1699. width: auto;
  1700. // margin-left: 10px;
  1701. min-height: 28px;
  1702. line-height: 28px;
  1703. word-break: break-all;
  1704. border-radius: 4px;
  1705. border: 1px solid #4ea2ff;
  1706. display: flex;
  1707. align-items: center;
  1708. padding: 0 15px;
  1709. font-size: 14px;
  1710. font-weight: normal;
  1711. color: #606266;
  1712. flex: 1;
  1713. background-color: #fff;
  1714. &.disabled {
  1715. cursor: not-allowed;
  1716. color: #c0c4cc;
  1717. background-color: #f5f7fa;
  1718. }
  1719. &.error-border {
  1720. border-color: #ff5d5d !important;
  1721. box-shadow: 0 0 6px #ffc3c3 !important;
  1722. }
  1723. }
  1724. .dialog-footer {
  1725. display: flex;
  1726. justify-content: flex-end;
  1727. }
  1728. .atta-tips {
  1729. color: #ff5d5d;
  1730. font-size: 12px;
  1731. margin-left: 5px;
  1732. }
  1733. .checkbox-list-container {
  1734. padding: 12px;
  1735. border: 1px solid #dcdfe6;
  1736. border-radius: 4px;
  1737. transition: all 0.3s;
  1738. &.error-border {
  1739. border-color: #ff5d5d !important;
  1740. box-shadow: 0 0 6px #ffc3c3 !important;
  1741. }
  1742. .checkbox-item {
  1743. margin-right: 16px;
  1744. display: flex;
  1745. align-items: center;
  1746. &:not(:last-child) {
  1747. margin-bottom: 10px;
  1748. }
  1749. // display: inline-block;
  1750. .el-input {
  1751. width: 200px;
  1752. margin-left: 10px;
  1753. &.error-border {
  1754. .el-input__inner {
  1755. border-color: #ff5d5d !important;
  1756. box-shadow: 0 0 6px #ffc3c3 !important;
  1757. }
  1758. }
  1759. }
  1760. }
  1761. }
  1762. .orange-border {
  1763. .checkbox-list-container {
  1764. border-color: #f9c588;
  1765. &:hover {
  1766. border-color: #f79b31;
  1767. }
  1768. }
  1769. .el-checkbox {
  1770. &.is-checked {
  1771. .el-checkbox__label {
  1772. color: #606266;
  1773. }
  1774. .el-checkbox__inner {
  1775. background-color: #f9c588;
  1776. border-color: #f9c588;
  1777. }
  1778. }
  1779. }
  1780. .el-checkbox__input.is-indeterminate .el-checkbox__inner {
  1781. background-color: #f9c588;
  1782. border-color: #f9c588;
  1783. }
  1784. .el-radio {
  1785. &.is-checked {
  1786. .el-radio__label {
  1787. color: #606266;
  1788. }
  1789. .el-radio__inner {
  1790. background-color: #f9c588;
  1791. border-color: #f9c588;
  1792. }
  1793. }
  1794. .el-radio__inner {
  1795. border-color: #f9c588;
  1796. }
  1797. }
  1798. }
  1799. .orange-bg {
  1800. .checkbox-list-container {
  1801. background-color: #FFF1F1 !important;
  1802. border-color: #f9c588;
  1803. }
  1804. }
  1805. // checkboxTag样式
  1806. .checkbox-tag-wrapper {
  1807. display: flex;
  1808. flex-wrap: wrap;
  1809. gap: 10px;
  1810. padding: 8px;
  1811. }
  1812. .checkbox-tag-container {
  1813. border-radius: 4px;
  1814. .checkbox-tag-item {
  1815. display: flex;
  1816. align-items: center;
  1817. gap: 8px;
  1818. .tag-content {
  1819. cursor: pointer;
  1820. position: relative;
  1821. .tag-input {
  1822. width: 100px;
  1823. }
  1824. .tag-display {
  1825. cursor: pointer;
  1826. user-select: none;
  1827. }
  1828. }
  1829. .delete-icon {
  1830. cursor: pointer;
  1831. color: #909399;
  1832. font-size: 12px;
  1833. padding: 2px;
  1834. border-radius: 50%;
  1835. position: absolute;
  1836. top: 6px;
  1837. right: 5px;
  1838. color: red;
  1839. background-color: #f5f5f5;
  1840. }
  1841. }
  1842. }
  1843. .fqyq-input {
  1844. width: 500px;
  1845. margin-right: 10px;
  1846. }
  1847. .mb-10 {
  1848. margin-bottom: 10px;
  1849. }
  1850. .fs-14 {
  1851. font-size: 14px;
  1852. }
  1853. .mr-10 {
  1854. margin-right: 10px;
  1855. }
  1856. .checkbox-tree-children {
  1857. margin-left: 30px;
  1858. padding: 16px 0;
  1859. gap: 16px;
  1860. display: grid;
  1861. grid-template-columns: repeat(4, 1fr);
  1862. }
  1863. .checkbox-tree-item {
  1864. box-sizing: border-box;
  1865. display: flex;
  1866. align-items: center;
  1867. }
  1868. .checkbox-tree-input-container {
  1869. margin-left: 10px;
  1870. width: 500px;
  1871. }
  1872. .item-center {
  1873. display: flex;
  1874. align-items: center;
  1875. }
  1876. .checkbox-tree-group {
  1877. padding: 5px 10px 5px 0;
  1878. }
  1879. .form-error-border {
  1880. box-shadow: 0 0 6px #ffc3c3;
  1881. padding: 8px;
  1882. border-radius: 4px;
  1883. border: 1px solid #ff5d5d;
  1884. }
  1885. .no-border {
  1886. border: none;
  1887. }
  1888. </style>