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

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