diff --git a/src/components/Template/HandleFormItem.vue b/src/components/Template/HandleFormItem.vue index dd70793..6d35314 100644 --- a/src/components/Template/HandleFormItem.vue +++ b/src/components/Template/HandleFormItem.vue @@ -2,14 +2,14 @@
- + :placeholder="getPlaceholder()" @change="onCommonHandleSaveRecord"> + :placeholder="getPlaceholder()" @change="onCommonHandleSaveRecord">
{{ value }} @@ -44,9 +44,9 @@
@@ -117,23 +117,11 @@ export default { inputValue: this.value, oldValue: this.value, // 记录上一次的值 showModal: false, // 控制模态框显示 - modificationRecords: [ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - ], // 存储修改记录 + modificationRecords: [], // 存储修改记录 modalTimer: null, // 用于延迟隐藏模态框 isHoveringModal: false, // 是否悬停在模态框上 isHoveringMain: false, // 是否悬停在主元素上(这个实际上不需要,因为我们有事件处理) + db: null, // IndexedDB 实例 } }, watch: { @@ -147,13 +135,6 @@ export default { }, methods: { - // 鼠标移入事件 - onHover() { - - }, - // 鼠标移出事件 - onLeave() { - }, getFillTypeStyle(type) { const {fillType} = this.item; const typeObj = { @@ -186,6 +167,75 @@ export default { this.$emit('update:error', true); } }, + async onCommonHandleSaveRecord(val){ + const isEmpty = this.isValueEmpty(this.inputValue); + if (this.error && !isEmpty) { + this.$emit('update:error', false); + } else if (!this.error && isEmpty) { + this.$emit('update:error', true); + } + const {templateStatus} = this.$store.state.template; + // 检查值是否发生变化 + if (this.inputValue !== this.oldValue && templateStatus === "actFill") { + // 值发生了变化,需要弹出密码输入框 + try { + // const passwordResult = await this.$prompt('请输入密码以确认修改', '密码验证', { + // confirmButtonText: '确定', + // cancelButtonText: '取消', + // inputType: 'password', + // inputPattern: /.+/, + // inputErrorMessage: '请输入密码', + // zIndex: 10000, + // }); + // 用户输入密码并点击确定,保存修改 + this.oldValue = this.inputValue; // 更新旧值 + this.$emit("blur", val); + this.$emit("change", val); + // 调用后端接口记录修改记录 + await this.saveModificationRecord(); + } catch { + // 用户点击取消,还原数据 + this.inputValue = this.oldValue; + this.$emit('input', this.oldValue); // 触发 v-model 更新 + this.$emit("blur", this.oldValue); + this.$emit("change", this.oldValue); + } + } else { + // 值没有变化,正常触发 blur和change 事件 + this.$emit("blur", val) + this.$emit("change", val) + } + }, + // 通用的值变化处理方法 + async handleValueChange(val, componentType = '') { + const oldValue = this.oldValue; // 保存旧值 + this.$emit('input', val); + this.$emit('change', val); + + // 根据输入值判断是否显示错误状态 + const isEmpty = this.isValueEmpty(val); + if (this.error && !isEmpty) { + this.$emit('update:error', false); + } else if (!this.error && isEmpty) { + this.$emit('update:error', true); + } + + // 值发生改变,记录修改 + const { templateStatus } = this.$store.state.template; + if (val !== oldValue && templateStatus === "actFill") { + // 值发生了变化,记录修改 + try { + this.oldValue = val; // 更新旧值 + + // 调用后端接口记录修改记录 + await this.saveModificationRecord(); + } catch (error) { + const componentName = componentType || '组件'; + console.error(`记录${componentName}修改失败:`, error); + } + } + }, + // 判断值是否为空 isValueEmpty(value) { if (value === null || value === undefined || value === '') { @@ -215,9 +265,8 @@ export default { isShowHandle() { const { fillType } = this.item; const { templateStatus } = this.$store.state.template; - return true; //只有当模板状态是qc和实际填报时,才显示操作按钮 - // return (templateStatus === "qc" || templateStatus === "actFill") && fillType === "actFill"; + return (templateStatus === "qc" || templateStatus === "actFill") && fillType === "actFill"; }, //判断是否禁用 getDisabled() { @@ -255,92 +304,213 @@ export default { onCopy() { this.$emit("copy") }, - async onBlur(val) { - // 根据输入值判断是否显示错误状态 - const isEmpty = this.isValueEmpty(this.inputValue); - if (this.error && !isEmpty) { - this.$emit('update:error', false); - } else if (!this.error && isEmpty) { - this.$emit('update:error', true); + + // 记录数据修改 + + + // 鼠标进入主容器 + + + // 初始化 IndexedDB + initDB() { + return new Promise((resolve, reject) => { + const request = indexedDB.open('ModificationRecordsDB', 1); + + request.onerror = (event) => { + console.error('IndexedDB error:', event.target.error); + reject(event.target.error); + }; + + request.onsuccess = (event) => { + this.db = event.target.result; + resolve(this.db); + }; + + request.onupgradeneeded = (event) => { + const db = event.target.result; + if (!db.objectStoreNames.contains('modificationRecords')) { + const objectStore = db.createObjectStore('modificationRecords', { keyPath: 'id' }); + objectStore.createIndex('fieldId', 'fieldId', { unique: false }); + objectStore.createIndex('timestamp', 'timestamp', { unique: false }); + } + }; + }); + }, + + // 生成唯一字段ID (id + key值) + getFieldId() { + // 使用写死的id作为示例,后续您可以用实际的id替换 + const templateId = 'template_123'; // 这里是写死的id + const fieldKey = this.item.key || this.item.prop || this.item.label || 'default_key'; + + // 考虑到CustomTable组件可能有重复的key值,我们需要额外标识 + // 如果在表格中,可能需要添加行索引等信息 + const tableRowIndex = this.item.rowIndex !== undefined ? `_${this.item.rowIndex}` : ''; + + return `${templateId}_${fieldKey}${tableRowIndex}`; + }, + + // 获取 IndexedDB 对象存储实例 + async getObjectStore(storeName = 'modificationRecords', mode = 'readonly') { + if (!this.db) { + await this.initDB(); } + const transaction = this.db.transaction([storeName], mode); + return transaction.objectStore(storeName); + }, + + // 保存单条修改记录到 IndexedDB + async saveRecordToDB(record) { + const objectStore = await this.getObjectStore('modificationRecords', 'readwrite'); - // 检查值是否发生变化 - if (this.inputValue !== this.oldValue && this.fillType === "actFill") { - return; - // 值发生了变化,需要弹出密码输入框 - try { - const passwordResult = await this.$prompt('请输入密码以确认修改', '密码验证', { - confirmButtonText: '确定', - cancelButtonText: '取消', - inputType: 'password', - inputPattern: /.+/, - inputErrorMessage: '请输入密码' + const fieldId = this.getFieldId(); + const newRecord = { + id: `${fieldId}_${Date.now()}`, // 使用时间戳确保唯一性 + fieldId: fieldId, + oldValue: record.oldValue, + newValue: record.newValue, + timestamp: new Date().toLocaleString(), + }; + + return new Promise((resolve, reject) => { + const request = objectStore.add(newRecord); + + request.onsuccess = () => { + resolve(request.result); + }; + + request.onerror = (event) => { + reject(event.target.error); + }; + }); + }, + + // 从 IndexedDB 获取修改记录 + async getRecordsFromDB() { + if (!this.db) { + await this.initDB(); + } + + const transaction = this.db.transaction(['modificationRecords'], 'readonly'); + const objectStore = transaction.objectStore('modificationRecords'); + const fieldIdIndex = objectStore.index('fieldId'); + + const fieldId = this.getFieldId(); + + return new Promise((resolve, reject) => { + const request = fieldIdIndex.getAll(IDBKeyRange.only(fieldId)); + + request.onsuccess = (event) => { + // 按时间戳排序,最新的在前 + const records = event.target.result.sort((a, b) => { + return new Date(b.timestamp) - new Date(a.timestamp); }); + resolve(records); + }; + + request.onerror = (event) => { + reject(event.target.error); + }; + }); + }, + + // 同步后端修改记录到 IndexedDB + async syncRecordsToDB(backendRecords) { + // 清空当前字段的记录 + await this.clearFieldRecords(); + + // 批量添加后端记录到 IndexedDB + const objectStore = await this.getObjectStore('modificationRecords', 'readwrite'); + + const fieldId = this.getFieldId(); + + return new Promise((resolve, reject) => { + let completed = 0; + const total = backendRecords.length; + + if (total === 0) { + resolve(); + return; + } + + backendRecords.forEach((record) => { + // 标准化记录格式以匹配 IndexedDB 存储格式 + const newRecord = { + id: `${fieldId}_${Date.parse(record.timestamp) || Date.now()}`, // 使用时间戳或当前时间戳作为 ID + fieldId: fieldId, + oldValue: record.oldValue, + newValue: record.newValue, + timestamp: record.timestamp || new Date().toLocaleString(), + password: record.password ? '***' : '' // 不直接存储密码 + }; - // 用户输入密码并点击确定,保存修改 - this.oldValue = this.inputValue; // 更新旧值 - this.$emit("blur", val); + const request = objectStore.add(newRecord); - // 调用后端接口记录修改记录 - await this.saveModificationRecord(passwordResult.value); - } catch { - // 用户点击取消,还原数据 - this.inputValue = this.oldValue; - this.$emit('input', this.oldValue); // 触发 v-model 更新 - this.$emit("blur", this.oldValue); - } - } else { - // 值没有变化,正常触发 blur 事件 - this.$emit("blur", val) - } + request.onsuccess = () => { + completed++; + if (completed === total) { + resolve(); + } + }; + + request.onerror = (event) => { + console.error('同步单条记录失败:', event.target.error); + completed++; + if (completed === total) { + resolve(); // 即使有错误也继续,避免单条记录失败影响整体同步 + } + }; + }); + }); }, - // 记录数据修改 - async saveModificationRecord(password) { - // 这里可以调用后端API记录修改记录 - // 示例: - // const recordData = { - // field: this.item.label || '', // 字段名 - // oldValue: this.oldValue, // 旧值 - // newValue: this.inputValue, // 新值 - // timestamp: Date.now(), // 时间戳 - // password: password // 用户输入的密码 - // }; - // try { - // await api.recordModification(recordData); - // } catch (error) { - // this.$message.error('记录修改失败'); - // console.error('记录修改失败:', error); - // } + + // 清空当前字段的记录 + async clearFieldRecords() { + if (!this.db) { + await this.initDB(); + } - // 添加修改记录到本地存储 - const record = { - oldValue: this.oldValue, - newValue: this.inputValue, - timestamp: new Date().toLocaleString(), // 格式化时间 - password: password ? '***' : '' // 不直接存储密码 - }; + const transaction = this.db.transaction(['modificationRecords'], 'readwrite'); + const objectStore = transaction.objectStore('modificationRecords'); + const fieldIdIndex = objectStore.index('fieldId'); - // 限制记录数量,只保留最近的20条记录 - if (this.modificationRecords.length >= 20) { - this.modificationRecords.shift(); // 移除最早的记录 - } - this.modificationRecords.push(record); + const fieldId = this.getFieldId(); - // 发送事件告知父组件值已修改 - this.$emit('modification-recorded', { - field: this.item.label || '', - oldValue: this.oldValue, - newValue: this.inputValue, - password: password + return new Promise((resolve, reject) => { + const request = fieldIdIndex.openCursor(IDBKeyRange.only(fieldId)); + + request.onsuccess = (event) => { + const cursor = event.target.result; + if (cursor) { + cursor.delete(); // 删除匹配的记录 + cursor.continue(); + } else { + // 所有匹配的记录已删除 + resolve(); + } + }; + + request.onerror = (event) => { + reject(event.target.error); + }; }); }, // 鼠标进入主容器 - onMouseEnter(event) { + async onMouseEnter(event) { clearTimeout(this.modalTimer); - this.showModal = true; - // 计算模态框位置,显示在当前DOM右边 + // 从 IndexedDB 加载修改记录 + try { + const records = await this.getRecordsFromDB(); + this.modificationRecords = records; + } catch (error) { + console.error('获取修改记录失败:', error); + this.modificationRecords = []; + } + + // 先计算模态框位置,避免闪烁 + this.showModal = true; this.$nextTick(() => { if (this.$refs.modalRef) { const elementRect = event.target.getBoundingClientRect(); @@ -375,6 +545,33 @@ export default { this.modalTimer = setTimeout(() => { this.showModal = false; }, 300); + }, + + // 记录数据修改 + async saveModificationRecord() { + // 添加修改记录到本地存储 + const record = { + oldValue: this.oldValue, + newValue: this.inputValue, + timestamp: new Date().toLocaleString(), // 格式化时间 + }; + + // 保存到 IndexedDB + try { + await this.saveRecordToDB(record); + // 保存成功后更新本地记录(可选,取决于是否需要立即显示) + // const records = await this.getRecordsFromDB(); + // this.modificationRecords = records; + } catch (error) { + console.error('保存修改记录失败:', error); + } + + // 发送事件告知父组件值已修改 + this.$emit('modification-recorded', { + field: this.item.label || '', + oldValue: this.oldValue, + newValue: this.inputValue, + }); } }, } @@ -541,6 +738,14 @@ export default { min-width: 250px; overflow: hidden; pointer-events: auto; + opacity: 0; + transform: scale(0.9); + transition: opacity 0.2s ease, transform 0.2s ease; +} + +.modification-modal.show { + opacity: 1; + transform: scale(1); } .modification-modal .modal-content { diff --git a/src/views/business/comps/template/comps/sp/IndexDBDemo.vue b/src/views/business/comps/template/comps/sp/IndexDBDemo.vue index f325d41..3e5bd15 100644 --- a/src/views/business/comps/template/comps/sp/IndexDBDemo.vue +++ b/src/views/business/comps/template/comps/sp/IndexDBDemo.vue @@ -16,7 +16,7 @@ v-model="form.basicInfo.name" placeholder="请输入姓名" @blur="onFieldChange('basicInfo.name', $event)" - @focus="loadTooltip('basicInfo.name')" + @mouseenter="loadTooltip('basicInfo.name')" @mouseleave="hideTooltip" /> @@ -36,7 +36,7 @@ v-model="form.contact.email" placeholder="请输入邮箱" @blur="onFieldChange('contact.email', $event)" - @focus="loadTooltip('contact.email')" + @mouseenter="loadTooltip('contact.email')" @mouseleave="hideTooltip" /> @@ -57,7 +57,7 @@ placeholder="请输入年龄" type="number" @blur="onFieldChange('basicInfo.age', $event)" - @focus="loadTooltip('basicInfo.age')" + @mouseenter="loadTooltip('basicInfo.age')" @mouseleave="hideTooltip" /> @@ -197,7 +197,7 @@ export default { }, async loadTooltip(fieldPath) { this.isTooltipVisible = false; // 先禁用,避免闪烁 - console.log("lofo") + console.log() const logs = await this.loadLogs(fieldPath); if (logs.length === 0) { this.currentTooltipContent = '
暂无修改记录
'; diff --git a/src/views/business/comps/template/comps/sp/SWYPFXRYPZB.vue b/src/views/business/comps/template/comps/sp/SWYPFXRYPZB.vue index 7c5398f..e36dae5 100644 --- a/src/views/business/comps/template/comps/sp/SWYPFXRYPZB.vue +++ b/src/views/business/comps/template/comps/sp/SWYPFXRYPZB.vue @@ -257,9 +257,9 @@ export default { return await this.validFormFields(["baseInfoRef", "storageConditionRef","stepFormPackageRef","stepRef","remarkRef"]); }, async onSave() { - // const formData = await this.getFormData(); + const formData = await this.getFormData(); - // console.log(formData, "formData") + console.log(formData, "formData") }, } diff --git a/src/views/business/comps/template/mixins/templateMixin.js b/src/views/business/comps/template/mixins/templateMixin.js index 811d4e8..0fedd5f 100644 --- a/src/views/business/comps/template/mixins/templateMixin.js +++ b/src/views/business/comps/template/mixins/templateMixin.js @@ -31,8 +31,8 @@ export default { } }, mounted() { - // this.setTemplateStatus("actFill"); - this.setTemplateStatus(this.fillType); + this.setTemplateStatus("actFill"); + // this.setTemplateStatus(this.fillType); }, unmounted() { this.setTemplateStatus("");