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

875 lines
30 KiB

  1. <template>
  2. <div class="flex flex1">
  3. <div class="flex1 flex">
  4. <el-input v-if="type === 'input'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
  5. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" @blur="onBlur"
  6. :placeholder="getPlaceholder()" v-model="inputValue" @input="onInputChange" @change="onInputChange" />
  7. <el-input v-else-if="type === 'textarea'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
  8. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" type="textarea" show-word-limit
  9. resize="none" @blur="onBlur" :rows="item.rows || 3" :placeholder="getPlaceholder()" v-model="inputValue"
  10. @input="onInputChange" @change="onInputChange" />
  11. <DecimalInput v-else-if="type === 'inputNumber'" @blur="onCommonHandleSaveRecord"
  12. :maxlength="item.maxlength || 10" class="flex1" :disabled="getDisabled()"
  13. :controls="item.controls || false" :min="item.min || 0" :prepend="item.prepend"
  14. :decimalDigits="item.precision || 6" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  15. :placeholder="getPlaceholder()" v-model="inputValue" @input="onInputChange" @change="onInputChange" />
  16. <el-select v-else-if="type === 'select'" class="flex1" :multiple="item.multiple"
  17. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  18. :disabled="getDisabled()" :placeholder="getPlaceholder()" @remove-tag="onRemoveTag"
  19. @visible-change="onSelectBlur" @change="onInputChange">
  20. <el-option v-for="op in item.options" :key="op.value" :label="op.label" :value="op.value">
  21. </el-option>
  22. </el-select>
  23. <el-date-picker v-else-if="type === 'dateTime'" type="datetime" class="flex1"
  24. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue"
  25. :disabled="getDisabled()" format="yyyy/MM/DD HH:mm:ss" value-format="yyyy/MM/DD HH:mm:ss"
  26. :placeholder="getPlaceholder()" @change="onCommonHandleSaveRecord">
  27. </el-date-picker>
  28. <div class="clickable"
  29. :class="getFillTypeStyle() + (getDisabled() ? ' disabled' : '') + (orangeBg ? ' orange-bg' : '')"
  30. v-else-if="item.type === 'clickable'" @click="handleClickable(item, $event)">
  31. <span v-if="value">{{ value }}</span>
  32. <span v-else class="default-placeholder-text">{{ getPlaceholder() }}</span>
  33. </div>
  34. </div>
  35. <div class="handle-row" v-if="isShowHandle()">
  36. <el-checkbox :checked="getChecked()" v-if="getIsShowCheckboxIcon()" :disabled="getCheckboxDisabled()" class="mr-5"
  37. @change="onCheckboxChange"></el-checkbox>
  38. <div class="handle-icon" v-if="getIsShowQuestionIcon()" @click="onClickQuestion"
  39. @mouseenter="(e) => onMouseEnter('replyRecord', e)" @mouseleave="onMouseLeave">
  40. <Question :class="getQuestionColor()" />
  41. </div>
  42. <img v-if="getIsShowCopyIcon()" @click="onCopy" src="@/assets/images/copy-icon.svg" class="handle-icon"
  43. alt="" />
  44. <img v-if="getIsShowRecordIcon()" @mouseenter="(e) => onMouseEnter('fieldChanged', e)"
  45. @mouseleave="onMouseLeave" src="@/assets/images/record-icon.svg" class="handle-icon" alt="" />
  46. </div>
  47. <!-- 修改记录模态框 -->
  48. <div v-if="showModal && modificationRecords.length > 0" ref="modalRef"
  49. :class="['modification-modal', { 'show': showModal }]" @mouseenter="onModalEnter"
  50. @mouseleave="onModalLeave">
  51. <div class="modal-content">
  52. <div class="records-list">
  53. <div v-for="(record, index) in modificationRecords" :key="index" class="record-item">
  54. <!-- 字段修改记录 -->
  55. <div class="record-row" v-if="currentRecordType === 'fieldChanged'">
  56. <div>
  57. <span>{{ index + 1 }}.</span>
  58. <span> {{ getUserName(record) }} </span>
  59. <span>{{ record.time }} </span>
  60. <span>{{ modificationRecords.length-1==index?"提交":"修改" }}</span>
  61. </div>
  62. <div v-if = "modificationRecords.length-1!==index">
  63. <div>原值{{ record.oldValue }}</div>
  64. <div>修改值{{ record.value }}</div>
  65. <div v-if="record.reason">备注{{ record.reason }}</div>
  66. </div>
  67. </div>
  68. <!-- 回复记录 -->
  69. <div class="record-row" v-if="currentRecordType === 'replyRecord'">
  70. <div>
  71. <span> {{ getUserName(record) }} </span>
  72. <span>{{ record.time }} </span>
  73. </div>
  74. <div>
  75. <div v-if="record.content">复核意见{{ record.content }}</div>
  76. <div v-if="record.reply">回复意见{{ record.reply }}</div>
  77. </div>
  78. </div>
  79. <hr v-if="index < modificationRecords.length - 1">
  80. </div>
  81. </div>
  82. </div>
  83. </div>
  84. <el-dialog :close-on-click-modal = "false" append-to-body :title="templateFillType == 'actFill' ? '回复意见' : '复核意见'" :visible.sync="visible"
  85. width="30%">
  86. <el-input v-model="replyContent" type="textarea" show-word-limit resize="none" rows="8" placeholder="输入内容"
  87. maxlength="500" />
  88. <span slot="footer" class="dialog-footer">
  89. <el-button @click="visible = false"> </el-button>
  90. <el-button type="primary" @click="onReplyConfirm"> </el-button>
  91. </span>
  92. </el-dialog>
  93. <EditSign @cancel="resetRecord" ref="editSignRef" @callback="onEditSignSave" />
  94. </div>
  95. </template>
  96. <script>
  97. import Question from "./icons/Question.vue";
  98. import DecimalInput from "./DecimalInput.vue";
  99. import { EventBus } from "@/utils/eventBus";
  100. import moment from "moment";
  101. import { deepClone } from "@/utils/index";
  102. import EditSign from "@/views/business/comps/template/dialog/EditSign.vue";
  103. export default {
  104. inject: ['templateFillType', "getZdxgjl", "getFhyjjl", "updateZdxgjl", "replaceFhyjjl", "updateFhyjjl", "getFieldCheckObj", "updateFieldCheckObj"],
  105. components: {
  106. Question,
  107. DecimalInput,
  108. EditSign,
  109. },
  110. props: {
  111. type: {//form类型 input/select等
  112. type: String,
  113. default: "input"
  114. },
  115. item: {
  116. type: Object,
  117. default: () => {
  118. return {
  119. placeholder: "",
  120. maxlength: 30,
  121. label: "",
  122. disabled: false,
  123. }
  124. }
  125. },
  126. // v-model 值
  127. value: {
  128. type: [String, Number, Array],
  129. default: ''
  130. },
  131. // 错误状态
  132. error: {
  133. type: Boolean,
  134. default: false
  135. },
  136. // 橙色背景状态
  137. orangeBg: {
  138. type: Boolean,
  139. default: false
  140. },
  141. fieldKey: {
  142. type: String,
  143. default: ""
  144. },
  145. fieldItemLabel: {
  146. type: String,
  147. default: "",
  148. }
  149. },
  150. data() {
  151. return {
  152. inputValue: this.value,
  153. oldValue: this.value, // 记录上一次的值
  154. showModal: false, // 控制模态框显示
  155. modificationRecords: [], // 存储修改记录
  156. modalTimer: null, // 用于延迟隐藏模态框
  157. isHoveringModal: false, // 是否悬停在模态框上
  158. isHoveringMain: false, // 是否悬停在主元素上(这个实际上不需要,因为我们有事件处理)
  159. currentRecordType: '', // 当前悬停的记录类型(replyRecord 或 modifyRecord)
  160. replyContent: '', // 回复内容
  161. visible: false,//是否显示弹窗
  162. checked: false,//是否选中
  163. }
  164. },
  165. watch: {
  166. value(newVal) {
  167. this.inputValue = newVal;
  168. }
  169. },
  170. filters: {
  171. },
  172. mounted() {
  173. },
  174. methods: {
  175. getUserName(record){
  176. const locale = this.$i18n.locale;
  177. if(locale === 'zh_CN'){
  178. return record.userNameCn;
  179. }
  180. return record.userNameEn;
  181. },
  182. onEditSignSave(data) {
  183. this.handleUpdateRecord(data)
  184. },
  185. getChecked() {
  186. return !!this.getFieldCheckObj()[this.fieldKey]?.checked;
  187. },
  188. getFillTypeStyle(type) {
  189. const { fillType } = this.item;
  190. const typeObj = {
  191. actFill: "orange-border",//实际填写的边框颜色
  192. green: "green-border",
  193. preFill: "blue-border",//预填写的边框颜色
  194. }
  195. // 如果有错误状态,返回红色边框样式,覆盖原有的边框颜色
  196. if (this.error) {
  197. return "error-border";
  198. }
  199. return typeObj[fillType] || ""
  200. },
  201. //确认回复
  202. onReplyConfirm() {
  203. if (!this.replyContent) {
  204. this.$message({
  205. message: '请输入内容',
  206. type: 'error'
  207. });
  208. return;
  209. }
  210. const baseInfo = this.getCommonRecordInfo();
  211. const record = {
  212. ...baseInfo,
  213. title: this.templateFillType == 'actFill' ? "回复意见" : "复核意见",
  214. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  215. }
  216. if (this.templateFillType == 'actFill') {
  217. record.reply = this.replyContent;
  218. const deepList = deepClone(this.getFhyjjl());//实际填报应该是修改指定的字段
  219. const item = deepList.find(o => o.key == record.key);
  220. if (item) {
  221. item.reply = this.replyContent;
  222. }
  223. this.replaceFhyjjl(deepList);//实际填报应该是修改指定的字段
  224. } else {
  225. const records = this.getReplyRecords();
  226. record.content = this.replyContent;
  227. if (records.length > 0) {
  228. const o = records[0];
  229. if (o.reply && o.content) {//如果填报人员已回复,那么就产生一条新的记录。
  230. this.updateFhyjjl(record);//qc直接插入数据源
  231. } else {//如果填报人员未填报,只更新当条记录的复核内容
  232. const deepList = deepClone(this.getFhyjjl());
  233. const item = deepList.find(it => it.key == record.key);
  234. if (item) {
  235. item.content = this.replyContent;
  236. }
  237. this.replaceFhyjjl(deepList);
  238. }
  239. } else {
  240. this.updateFhyjjl(record);//qc直接插入数据源
  241. }
  242. }
  243. const params = {
  244. //reply:回复,content:复核
  245. type: this.templateFillType == 'actFill' ? "reply" : "content",
  246. newRecord: record,
  247. resourceList: this.getFhyjjl(),
  248. }
  249. // 触发回复记录事件
  250. EventBus.$emit('onModifyRecord', params);
  251. // 清空回复内容
  252. this.replyContent = '';
  253. // 隐藏弹窗
  254. this.visible = false;
  255. },
  256. //获取question图标颜色
  257. getQuestionColor() {
  258. const records = this.getReplyRecords();
  259. if (records.length > 0) {
  260. const o = records[0];
  261. if (o.reply && o.content) {//有回复意见和复核意见
  262. return "green"
  263. } else if (o.content && !o.reply) {//只有复核意见
  264. return "orange"
  265. } else {
  266. return "gray"
  267. }
  268. } else {//没有回复记录
  269. return "gray"
  270. }
  271. },
  272. // 复选框变化处理
  273. onCheckboxChange(val) {
  274. // 触发修改记录事件
  275. EventBus.$emit('onModifyRecord', {
  276. type: "checkbox",
  277. fieldCheckObj: JSON.stringify({ ...this.getFieldCheckObj(), [this.fieldKey]: { checked: val } }),//复选框状态对象
  278. });
  279. this.updateFieldCheckObj({ [this.fieldKey]: { checked: val } });
  280. // this.$emit('input', val);
  281. // this.$emit('change', val);
  282. },
  283. onRemoveTag(e) {
  284. this.onCommonHandleSaveRecord(this.inputValue);
  285. },
  286. // 下拉框失去焦点处理
  287. onSelectBlur(visible) {
  288. if (!visible) {
  289. console.log(this.inputValue, "onSelectBlur")
  290. this.onCommonHandleSaveRecord(this.inputValue);
  291. }
  292. },
  293. // 统一处理输入变化
  294. onInputChange(val) {
  295. const value = val !== undefined ? val : this.inputValue;
  296. this.$emit('input', value);
  297. this.$emit('change', value);
  298. // 根据输入值判断是否显示错误状态
  299. const isEmpty = this.isValueEmpty(value);
  300. if (this.error && !isEmpty) {
  301. this.$emit('update:error', false);
  302. } else if (!this.error && isEmpty) {
  303. this.$emit('update:error', true);
  304. }
  305. },
  306. // 统一处理失去焦点事件
  307. onBlur(e) {
  308. this.onCommonHandleSaveRecord(e.target.value);
  309. },
  310. // 点击question图标
  311. onClickQuestion() {
  312. const { templateFillType } = this;
  313. if (templateFillType == 'actFill' || templateFillType == 'qc') {
  314. if (templateFillType == 'qc') {
  315. const field = this.getFieldCheckObj()[this.fieldKey];
  316. if (field && field.checked) {
  317. this.$message({
  318. message: '该字段已勾选复核框,请先取消勾选后再进行提交疑问',
  319. type: 'error'
  320. });
  321. return;
  322. }
  323. }
  324. const records = this.getReplyRecords();
  325. let content = "";
  326. if (records.length > 0) {
  327. const o = records[0];
  328. if (!o.reply && templateFillType == 'qc') {//如果填报人员没有回复,qc点击的时候需要回填上次填报的信息
  329. content = o.content;
  330. } else if (!o.content && templateFillType == 'actFill') {//如果qc没有复核,填报点击的时候需要回填上次填报的信息
  331. content = o.reply;
  332. }
  333. }
  334. this.replyContent = content;
  335. this.visible = true;
  336. }
  337. },
  338. async onCommonHandleSaveRecord(val) {
  339. const isEmpty = this.isValueEmpty(this.inputValue);
  340. if (this.error && !isEmpty) {
  341. this.$emit('update:error', false);
  342. } else if (!this.error && isEmpty) {
  343. this.$emit('update:error', true);
  344. }
  345. // 值发生了变化,需要弹出密码输入框
  346. const isSame = this.isEqual(this.oldValue, this.inputValue);
  347. if(isSame){
  348. return;
  349. }
  350. if (this.oldValue && !isSame && this.templateFillType === "actFill") {
  351. this.$refs.editSignRef.show()
  352. // this.handleUpdateRecord();
  353. }else{//如果是第一次填写,不需要密码验证
  354. this.handleUpdateRecord()
  355. }
  356. },
  357. //如果用户取消,那么回退到上一次的值
  358. resetRecord() {
  359. // 用户点击取消,还原数据
  360. this.inputValue = this.oldValue;
  361. this.$emit('input', this.inputValue); // 触发 v-model 更新
  362. this.$emit("blur", this.oldValue);
  363. this.$emit("change", this.oldValue);
  364. },
  365. //处理更新记录
  366. handleUpdateRecord(data) {
  367. const baseInfo = this.getCommonRecordInfo();
  368. const record = {
  369. ...baseInfo,
  370. oldValue: this.oldValue,
  371. value: this.inputValue,
  372. title: this.oldValue ? "修改" : "提交",
  373. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  374. }
  375. if (data) {
  376. record.reason = data.remark
  377. }
  378. if(this.templateFillType === "actFill"){//只有实际填报的时候才记录修改记录
  379. this.updateZdxgjl(record);
  380. }
  381. const params = {
  382. type: "fieldChanged",
  383. newRecord: record,
  384. resourceList: this.getZdxgjl(),
  385. }
  386. //用户输入密码并点击确定,保存修改
  387. this.oldValue = this.inputValue; // 更新旧值
  388. this.$emit("blur", this.inputValue);
  389. this.$emit('input', this.inputValue);
  390. this.$emit("change", this.inputValue);
  391. setTimeout(() => {
  392. EventBus.$emit('onModifyRecord', params,)
  393. }, 10);
  394. },
  395. //判断两个值是否相等
  396. isEqual(oldValue, nowValue) {
  397. if (oldValue === null || nowValue === null) {
  398. return oldValue === nowValue;
  399. }
  400. if (typeof oldValue === 'object' && typeof nowValue === 'object') {
  401. return JSON.stringify(oldValue) === JSON.stringify(nowValue);
  402. }
  403. return oldValue === nowValue;
  404. },
  405. //获取公共记录信息
  406. getCommonRecordInfo() {
  407. const { nickName, name } = this.$store.getters;
  408. //locale:zh-CN 中文 en-US 英文
  409. const lang = this.$i18n.locale === "zh_CN" ? "cn" : "en";
  410. const { label, parentLabel } = this.item;
  411. let fieldLabelCn = this.$i18n.t(label,"zh_CN"),fieldLabelEn = this.$i18n.t(label,"en_US");
  412. if (label === "template.common.other") {
  413. fieldLabelCn = this.$i18n.t(parentLabel,"zh_CN")+this.$i18n.t("template.common.otherInfo","zh_CN");
  414. fieldLabelEn = this.$i18n.t(parentLabel,"en_US")+this.$i18n.t("template.common.otherInfo","en_US");
  415. } else if (!label && parentLabel == "template.common.remark") {
  416. fieldLabelCn = this.$i18n.t(parentLabel,"zh_CN")+this.$i18n.t("template.common.unit","zh_CN");
  417. fieldLabelEn = this.$i18n.t(parentLabel,"en_US")+this.$i18n.t("template.common.unit","en_US");
  418. }
  419. const commonInfo = {
  420. userNameCn: nickName,
  421. userNameEn: name,
  422. key: this.fieldKey,
  423. fieldCn: `${this.$i18n.t(this.fieldItemLabel,"zh_CN")}`+(fieldLabelCn?("-"+fieldLabelCn):""),
  424. fieldEn: `${this.$i18n.t(this.fieldItemLabel,"en_US")}`+(fieldLabelEn?("-"+fieldLabelEn):""),
  425. }
  426. return commonInfo;
  427. },
  428. // 判断值是否为空
  429. isValueEmpty(value) {
  430. if (value === null || value === undefined || value === '') {
  431. return true;
  432. }
  433. if (typeof value === 'string' && value.trim() === '') {
  434. return true;
  435. }
  436. if (Array.isArray(value) && value.length === 0) {
  437. return true;
  438. }
  439. return false;
  440. },
  441. handleClickable(item, event) {
  442. if (item.fillType !== 'actFill') {
  443. return
  444. }
  445. this.$emit("clickable", item)
  446. },
  447. //判断是否禁用复选框
  448. getCheckboxDisabled(){
  449. //只有qc能操作checkbox,其他都只能看。
  450. return this.templateFillType !== 'qc'
  451. },
  452. //判断是否显示复选框图标
  453. getIsShowCheckboxIcon() {
  454. if(this.templateFillType === 'qc'){
  455. return true;
  456. }
  457. return this.getChecked();
  458. },
  459. //判断是否显示复制按钮
  460. getIsShowCopyIcon() {
  461. const { copyFrom } = this.item;
  462. return copyFrom && this.templateFillType === "actFill";
  463. },
  464. //判断是否显示操作按钮
  465. isShowHandle() {
  466. const { fillType } = this.item;
  467. //只有当模板状态不是预填时,才显示操作按钮
  468. return this.templateFillType !== "preFill" && fillType === "actFill"
  469. },
  470. //判断是否禁用
  471. getDisabled() {
  472. const { item } = this;
  473. const { fillType } = item;
  474. if (item.hasOwnProperty("disabled")) {
  475. return item.disabled
  476. } else {
  477. if (fillType === "actFill") {//当模板状态是实际填写时,只有当fillType是actFill时才能填写
  478. return this.templateFillType !== "actFill"
  479. } else if (fillType === "preFill") {//当模板状态是预填写时,只有当fillType是preFill才能填写
  480. return this.templateFillType !== "preFill"
  481. } else {
  482. return true
  483. }
  484. }
  485. },
  486. getPlaceholder() {
  487. const { placeholder, label } = this.item;
  488. const { type } = this;
  489. if (this.getDisabled()) {
  490. return ""
  491. }
  492. if (type === "clickable") {
  493. return this.$t("template.common.pleaseSelect")
  494. }
  495. let prex = "template.common.pleaseFillIn"
  496. if (type === "select" || type === "dateTime") {
  497. prex = "template.common.pleaseSelect"
  498. }
  499. return placeholder ? this.$t(placeholder) : (this.$t(prex) + this.$t(label))
  500. },
  501. onCopy() {
  502. this.$emit("copy")
  503. },
  504. //判断是否显示问题图标
  505. getIsShowQuestionIcon() {
  506. if (this.templateFillType === "qc") {//qc可以直接查看
  507. return true;
  508. }
  509. return this.getReplyRecords().length > 0;
  510. },
  511. //判断是否显示修改记录图标
  512. getIsShowRecordIcon() {
  513. return this.getModifyRecords().length > 0
  514. },
  515. //获取回复记录
  516. getReplyRecords() {
  517. const { fieldKey, getFhyjjl } = this;
  518. const records = getFhyjjl()?.filter(item => item.key === fieldKey) || [];
  519. return records;
  520. },
  521. //获取字段修改记录
  522. getModifyRecords() {
  523. const { fieldKey, getZdxgjl } = this;
  524. const records = getZdxgjl().filter(item => item.key === fieldKey);
  525. return records;
  526. },
  527. // 鼠标进入主容器
  528. async onMouseEnter(type, event) {
  529. this.currentRecordType = type;
  530. clearTimeout(this.modalTimer);
  531. let record = [];
  532. if (type === "fieldChanged") {
  533. record = this.getModifyRecords();
  534. } else if (type === "replyRecord") {
  535. record = this.getReplyRecords();
  536. }
  537. this.modificationRecords = record;
  538. // 先计算模态框位置,避免闪烁
  539. this.showModal = true;
  540. this.$nextTick(() => {
  541. if (this.$refs.modalRef) {
  542. const elementRect = event.target.getBoundingClientRect();
  543. const modalEl = this.$refs.modalRef;
  544. // 获取模态框的宽度和高度
  545. const modalWidth = modalEl.offsetWidth || 250; // 默认宽度
  546. const modalHeight = modalEl.offsetHeight || 300; // 默认高度
  547. const viewportWidth = window.innerWidth;
  548. const viewportHeight = window.innerHeight;
  549. // 计算模态框位置
  550. let leftPos, topPos;
  551. // 检查右侧空间是否足够
  552. if (elementRect.right + modalWidth + 5 <= viewportWidth) {
  553. // 右侧空间充足,显示在右侧
  554. leftPos = elementRect.right + 5 + 'px';
  555. } else if (elementRect.left - modalWidth - 5 >= 0) {
  556. // 左侧空间充足,显示在左侧
  557. leftPos = elementRect.left - modalWidth - 5 + 'px';
  558. } else {
  559. // 两侧空间都不足,选择空间更大的一边
  560. if (elementRect.left > viewportWidth - elementRect.right) {
  561. // 左侧空间更大,显示在左侧
  562. leftPos = Math.max(5, elementRect.left - modalWidth - 5) + 'px';
  563. } else {
  564. // 右侧空间更大,显示在右侧(可能会超出屏幕,但尽量靠近边缘)
  565. leftPos = Math.min(elementRect.right + 5, viewportWidth - 5) + 'px';
  566. }
  567. }
  568. // 计算顶部位置,确保不超出屏幕上下边界
  569. topPos = Math.max(5, Math.min(elementRect.top, viewportHeight - modalHeight - 5)) + 'px';
  570. // 设置模态框位置
  571. modalEl.style.left = leftPos;
  572. modalEl.style.top = topPos;
  573. }
  574. });
  575. },
  576. // 鼠标离开主容器
  577. onMouseLeave() {
  578. // this.currentRecordType = '';
  579. // this.modificationRecords = [];//清空数据源
  580. // 延迟隐藏模态框,让用户有机会移动到模态框上
  581. this.modalTimer = setTimeout(() => {
  582. if (!this.isHoveringModal) {
  583. this.showModal = false;
  584. }
  585. }, 100);
  586. },
  587. // 鼠标进入模态框
  588. onModalEnter() {
  589. this.isHoveringModal = true;
  590. clearTimeout(this.modalTimer);
  591. },
  592. // 鼠标离开模态框
  593. onModalLeave() {
  594. this.isHoveringModal = false;
  595. this.currentRecordType = "";
  596. this.modificationRecords = [];//清空数据源
  597. this.modalTimer = setTimeout(() => {
  598. this.showModal = false;
  599. }, 100);
  600. },
  601. },
  602. }
  603. </script>
  604. <style lang="scss">
  605. .flex {
  606. display: flex;
  607. align-items: center;
  608. }
  609. .flex1 {
  610. flex: 1;
  611. }
  612. .handle-row {
  613. margin-left: 10px;
  614. display: flex;
  615. align-items: center;
  616. cursor: pointer;
  617. }
  618. .w-100 {
  619. width: 100%;
  620. }
  621. .handle-icon {
  622. width: 18px;
  623. height: 18px;
  624. &:not(:last-child) {
  625. margin-right: 5px;
  626. }
  627. }
  628. .mr-5 {
  629. margin-right: 5px !important;
  630. }
  631. .orange {
  632. color: #f9c588;
  633. }
  634. .green {
  635. color: green;
  636. }
  637. .gray {
  638. color: #b2b2b2;
  639. }
  640. .orange-border {
  641. .el-input-group__prepend,
  642. input,
  643. textarea {
  644. border-color: #f9c588;
  645. &:focus {
  646. border-color: #f9c588;
  647. }
  648. &:hover {
  649. border-color: #f9c588;
  650. }
  651. &:disabled {
  652. border-color: #f9c588 !important;
  653. }
  654. }
  655. }
  656. .green-border {
  657. .el-input-group__prepend,
  658. input,
  659. textarea {
  660. border-color: green;
  661. &:focus {
  662. border-color: green;
  663. }
  664. &:hover {
  665. border-color: green;
  666. }
  667. &:disabled {
  668. border-color: green !important;
  669. }
  670. }
  671. }
  672. .blue-border {
  673. .el-input-group__prepend,
  674. input,
  675. textarea {
  676. border-color: #4ea2ff;
  677. &:focus {
  678. border-color: #4ea2ff;
  679. }
  680. &:hover {
  681. border-color: #4ea2ff;
  682. }
  683. &:disabled {
  684. border-color: #4ea2ff !important;
  685. }
  686. }
  687. }
  688. .error-border {
  689. .el-input-group__prepend,
  690. input,
  691. textarea,
  692. .el-select,
  693. .clickable,
  694. .el-date-editor {
  695. border-color: #f56c6c;
  696. &:focus {
  697. border-color: #f56c6c;
  698. }
  699. &:hover {
  700. border-color: #f56c6c;
  701. }
  702. }
  703. // 为 el-select 和 el-date-picker 添加错误边框样式
  704. .el-select .el-input__inner,
  705. .el-date-editor .el-input__inner {
  706. border-color: #f56c6c;
  707. }
  708. // 处理 DecimalInput 组件的错误边框样式
  709. :deep(.el-input-number) {
  710. .el-input__inner {
  711. border-color: #f56c6c;
  712. }
  713. }
  714. // 为点击式表单项添加错误边框样式
  715. .clickable {
  716. border-color: #f56c6c;
  717. }
  718. }
  719. .orange-bg {
  720. background-color: #FFF1F1 !important; // 橙色背景,透明度适中
  721. input,
  722. textarea,
  723. .el-input__inner,
  724. .el-textarea__inner {
  725. background-color: #FFF1F1 !important;
  726. }
  727. }
  728. .modification-modal {
  729. position: fixed;
  730. z-index: 9999;
  731. background-color: rgba(0, 0, 0, 0.7);
  732. border-radius: 4px;
  733. padding: 10px;
  734. color: white;
  735. max-height: 300px;
  736. min-width: 250px;
  737. overflow: hidden;
  738. pointer-events: auto;
  739. opacity: 0;
  740. transform: scale(0.9);
  741. transition: opacity 0.2s ease, transform 0.2s ease;
  742. }
  743. .modification-modal.show {
  744. opacity: 1;
  745. transform: scale(1);
  746. }
  747. .modification-modal .modal-content {
  748. max-height: 280px;
  749. overflow-y: auto;
  750. padding-right: 5px;
  751. }
  752. .modification-modal .modal-content h4 {
  753. margin: 0 0 10px 0;
  754. font-size: 14px;
  755. border-bottom: 1px solid #ccc;
  756. padding-bottom: 5px;
  757. }
  758. .modification-modal .records-list {
  759. font-size: 12px;
  760. }
  761. .modification-modal .record-item p {
  762. margin: 5px 0;
  763. word-break: break-all;
  764. }
  765. .modification-modal .record-item hr {
  766. border: 0;
  767. border-top: 1px solid #555;
  768. margin: 8px 0;
  769. }
  770. .modification-modal .no-records {
  771. text-align: center;
  772. color: #aaa;
  773. font-style: italic;
  774. }
  775. .clickable {
  776. cursor: pointer;
  777. width: auto;
  778. // margin-left: 10px;
  779. min-width: 178px;
  780. height: 28px;
  781. border-radius: 4px;
  782. border: 1px solid #4ea2ff;
  783. display: flex;
  784. align-items: center;
  785. padding: 0 15px;
  786. font-size: 14px;
  787. font-weight: normal;
  788. color: #606266;
  789. flex: 1;
  790. &.disabled {
  791. cursor: not-allowed;
  792. color: #c0c4cc;
  793. background-color: #f5f7fa;
  794. }
  795. &.error-border {
  796. border-color: #f56c6c !important;
  797. }
  798. }
  799. .dialog-footer {
  800. display: flex;
  801. justify-content: flex-end;
  802. }
  803. </style>