|
|
|
@ -34,12 +34,12 @@ |
|
|
|
<!-- qc才能操作 --> |
|
|
|
<div class="handle-row" v-if="isShowHandle()"> |
|
|
|
<el-checkbox class="ml-5" @change="onCheckboxChange"></el-checkbox> |
|
|
|
<div @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"> |
|
|
|
<div v-if="getIsShowQuestionIcon()" @mouseenter="(e)=>onMouseEnter('replyRecord',e)" @mouseleave="onMouseLeave"> |
|
|
|
<Question class="handle-icon" :class="getQuestionColor()" /> |
|
|
|
</div> |
|
|
|
<img v-if="getIsShowCopyIcon()" @click="onCopy" src="@/assets/images/copy-icon.svg" class="handle-icon" |
|
|
|
<img v-if="getIsShowCopyIcon()" @click="onCopy" src="@/assets/images/copy-icon.svg" class="handle-icon" |
|
|
|
alt="" /> |
|
|
|
<img src="@/assets/images/record-icon.svg" class="handle-icon" alt="" /> |
|
|
|
<img v-if="getIsShowRecordIcon()" @mouseenter="(e)=>onMouseEnter('modifyRecord',e)" @mouseleave="onMouseLeave" src="@/assets/images/record-icon.svg" class="handle-icon" alt="" /> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 修改记录模态框 --> |
|
|
|
@ -51,21 +51,38 @@ |
|
|
|
@mouseleave="onModalLeave" |
|
|
|
> |
|
|
|
<div class="modal-content"> |
|
|
|
<h4>修改记录</h4> |
|
|
|
<div class="records-list"> |
|
|
|
<div |
|
|
|
v-for="(record, index) in modificationRecords" |
|
|
|
:key="index" |
|
|
|
class="record-item" |
|
|
|
> |
|
|
|
<p><strong>时间:</strong> {{ record.timestamp }}</p> |
|
|
|
<p><strong>旧值:</strong> {{ record.oldValue }}</p> |
|
|
|
<p><strong>新值:</strong> {{ record.newValue }}</p> |
|
|
|
<!-- 字段修改记录 --> |
|
|
|
<div class="record-row" v-if="currentRecordType === 'modifyRecord'"> |
|
|
|
<div> |
|
|
|
<span>{{index+1}}.</span> |
|
|
|
<span> {{ record.userName }} </span> |
|
|
|
<span>{{ record.time }} </span> |
|
|
|
<span>{{ record.title }}</span> |
|
|
|
</div> |
|
|
|
<div v-if="record.oldValue && record.value"> |
|
|
|
<div>原值:{{record.oldValue}}</div> |
|
|
|
<div>修改值:{{record.value}}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- 回复记录 --> |
|
|
|
<div class="record-row" v-if="currentRecordType === 'replyRecord'"> |
|
|
|
<div> |
|
|
|
<span> {{ record.userName }} </span> |
|
|
|
<span>{{ record.time }} </span> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<div v-if="record.content">复核意见:{{record.content}}</div> |
|
|
|
<div v-if="record.replay">回复意见:{{record.replay}}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<hr v-if="index < modificationRecords.length - 1"> |
|
|
|
</div> |
|
|
|
<div v-if="!modificationRecords || modificationRecords.length === 0" class="no-records"> |
|
|
|
暂无修改记录 |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -77,7 +94,7 @@ import Question from "./icons/Question.vue"; |
|
|
|
import DecimalInput from "./DecimalInput.vue"; |
|
|
|
import { EventBus } from "@/utils/eventBus" |
|
|
|
export default { |
|
|
|
inject: ['templateFillType'], |
|
|
|
inject: ['templateFillType',"zdxgjl","fhyjjl"], |
|
|
|
components: { |
|
|
|
Question, |
|
|
|
DecimalInput |
|
|
|
@ -113,6 +130,14 @@ export default { |
|
|
|
type: Boolean, |
|
|
|
default: false |
|
|
|
}, |
|
|
|
fieldKey: { |
|
|
|
type: String, |
|
|
|
default: "" |
|
|
|
}, |
|
|
|
fieldItemLabel: { |
|
|
|
type: String, |
|
|
|
default: "", |
|
|
|
} |
|
|
|
}, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
@ -123,7 +148,7 @@ export default { |
|
|
|
modalTimer: null, // 用于延迟隐藏模态框 |
|
|
|
isHoveringModal: false, // 是否悬停在模态框上 |
|
|
|
isHoveringMain: false, // 是否悬停在主元素上(这个实际上不需要,因为我们有事件处理) |
|
|
|
db: null, // IndexedDB 实例 |
|
|
|
currentRecordType: '', // 当前悬停的记录类型(replyRecord 或 modifyRecord) |
|
|
|
} |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
@ -135,7 +160,11 @@ export default { |
|
|
|
|
|
|
|
|
|
|
|
}, |
|
|
|
mounted() { |
|
|
|
console.log(this.$i18n.locale,"locale") |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
|
|
|
|
getFillTypeStyle(type) { |
|
|
|
const {fillType} = this.item; |
|
|
|
const typeObj = { |
|
|
|
@ -156,8 +185,7 @@ export default { |
|
|
|
}, |
|
|
|
// 复选框变化处理 |
|
|
|
onCheckboxChange(val) { |
|
|
|
const {templateStatus} = this.$store.state.template; |
|
|
|
console.log(this.templateFillType,"templateStatus") |
|
|
|
console.log(this.zdxgjl,"zdxgjl") |
|
|
|
// 触发修改记录事件 |
|
|
|
EventBus.$emit('onModifyRecord', { |
|
|
|
timestamp: new Date().toLocaleString(), |
|
|
|
@ -167,6 +195,7 @@ export default { |
|
|
|
// this.$emit('input', val); |
|
|
|
// this.$emit('change', val); |
|
|
|
}, |
|
|
|
// 下拉框失去焦点处理 |
|
|
|
onSelectBlur(visible) { |
|
|
|
if (!visible) { |
|
|
|
this.onCommonHandleSaveRecord(this.inputValue); |
|
|
|
@ -198,9 +227,8 @@ export default { |
|
|
|
} else if (!this.error && isEmpty) { |
|
|
|
this.$emit('update:error', true); |
|
|
|
} |
|
|
|
const {templateStatus} = this.$store.state.template; |
|
|
|
// 检查值是否发生变化 |
|
|
|
if (templateStatus === "actFill") { |
|
|
|
if (this.templateFillType === "actFill") { |
|
|
|
// 值发生了变化,需要弹出密码输入框 |
|
|
|
try { |
|
|
|
if(this.oldValue && this.oldValue !== this.inputValue){ |
|
|
|
@ -213,6 +241,15 @@ export default { |
|
|
|
zIndex: 10000, |
|
|
|
}); |
|
|
|
} |
|
|
|
const {nickName,name} = this.$store.getters; |
|
|
|
//locale:zh-CN 中文 en-US 英文 |
|
|
|
const lang = this.$i18n.locale === "zh-CN"?"cn":"en"; |
|
|
|
const commonInfo = { |
|
|
|
userNameCn:nickName, |
|
|
|
userNameEn:name, |
|
|
|
key:this.fieldKey, |
|
|
|
field:`${this.fieldItemLabel}-${this.item.label}` |
|
|
|
} |
|
|
|
this.$emit("onModifyRecord", { |
|
|
|
timestamp: new Date().toLocaleString(), |
|
|
|
oldValue: this.oldValue, |
|
|
|
@ -264,16 +301,13 @@ export default { |
|
|
|
//判断是否显示复制按钮 |
|
|
|
getIsShowCopyIcon() { |
|
|
|
const { copyFrom } = this.item; |
|
|
|
const { templateStatus } = this.$store.state.template; |
|
|
|
return copyFrom && templateStatus === "actFill"; |
|
|
|
return copyFrom && this.templateFillType === "actFill"; |
|
|
|
}, |
|
|
|
//判断是否显示操作按钮 |
|
|
|
isShowHandle() { |
|
|
|
const { fillType } = this.item; |
|
|
|
const { templateStatus } = this.$store.state.template; |
|
|
|
return true; |
|
|
|
//只有当模板状态是qc和实际填报时,才显示操作按钮 |
|
|
|
return (templateStatus === "qc" || templateStatus === "actFill") && fillType === "actFill"; |
|
|
|
return (this.templateFillType === "qc" || this.templateFillType === "actFill") && fillType === "actFill"; |
|
|
|
}, |
|
|
|
//判断是否禁用 |
|
|
|
getDisabled() { |
|
|
|
@ -282,11 +316,10 @@ export default { |
|
|
|
if (item.hasOwnProperty("disabled")) { |
|
|
|
return item.disabled |
|
|
|
} else { |
|
|
|
const { templateStatus } = this.$store.state.template; |
|
|
|
if (fillType === "actFill") {//当模板状态是实际填写时,只有当fillType是actFill时才能填写 |
|
|
|
return templateStatus !== "actFill" |
|
|
|
return this.templateFillType !== "actFill" |
|
|
|
} else if (fillType === "preFill") {//当模板状态是预填写时,只有当fillType是preFill才能填写 |
|
|
|
return templateStatus !== "preFill" |
|
|
|
return this.templateFillType !== "preFill" |
|
|
|
} else { |
|
|
|
return true |
|
|
|
} |
|
|
|
@ -311,210 +344,41 @@ export default { |
|
|
|
onCopy() { |
|
|
|
this.$emit("copy") |
|
|
|
}, |
|
|
|
|
|
|
|
// 记录数据修改 |
|
|
|
|
|
|
|
|
|
|
|
// 鼠标进入主容器 |
|
|
|
|
|
|
|
|
|
|
|
// 初始化 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() { |
|
|
|
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(); |
|
|
|
//判断是否显示问题图标 |
|
|
|
getIsShowQuestionIcon(){ |
|
|
|
if(this.templateFillType === "qc"){//qc可以直接查看 |
|
|
|
return true; |
|
|
|
} |
|
|
|
const transaction = this.db.transaction([storeName], mode); |
|
|
|
return transaction.objectStore(storeName); |
|
|
|
}, |
|
|
|
|
|
|
|
// 保存单条修改记录到 IndexedDB |
|
|
|
async saveRecordToDB(record) { |
|
|
|
const objectStore = await this.getObjectStore('modificationRecords', 'readwrite'); |
|
|
|
|
|
|
|
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); |
|
|
|
}; |
|
|
|
}); |
|
|
|
//判断是否显示修改记录图标 |
|
|
|
getIsShowRecordIcon(){ |
|
|
|
return this.getModifyRecords().length>0 |
|
|
|
}, |
|
|
|
|
|
|
|
// 同步后端修改记录到 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 ? '***' : '' // 不直接存储密码 |
|
|
|
}; |
|
|
|
|
|
|
|
const request = objectStore.add(newRecord); |
|
|
|
|
|
|
|
request.onsuccess = () => { |
|
|
|
completed++; |
|
|
|
if (completed === total) { |
|
|
|
resolve(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
request.onerror = (event) => { |
|
|
|
console.error('同步单条记录失败:', event.target.error); |
|
|
|
completed++; |
|
|
|
if (completed === total) { |
|
|
|
resolve(); // 即使有错误也继续,避免单条记录失败影响整体同步 |
|
|
|
} |
|
|
|
}; |
|
|
|
}); |
|
|
|
}); |
|
|
|
//获取回复记录 |
|
|
|
getReplyRecords(){ |
|
|
|
const {fieldKey,fhyjjl = []} = this; |
|
|
|
const records = fhyjjl.filter(item => item.key === fieldKey); |
|
|
|
return records; |
|
|
|
}, |
|
|
|
|
|
|
|
// 清空当前字段的记录 |
|
|
|
async clearFieldRecords() { |
|
|
|
if (!this.db) { |
|
|
|
await this.initDB(); |
|
|
|
} |
|
|
|
|
|
|
|
const transaction = this.db.transaction(['modificationRecords'], 'readwrite'); |
|
|
|
const objectStore = transaction.objectStore('modificationRecords'); |
|
|
|
const fieldIdIndex = objectStore.index('fieldId'); |
|
|
|
|
|
|
|
const fieldId = this.getFieldId(); |
|
|
|
|
|
|
|
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); |
|
|
|
}; |
|
|
|
}); |
|
|
|
//获取字段修改记录 |
|
|
|
getModifyRecords(){ |
|
|
|
const {fieldKey,zdxgjl = []} = this; |
|
|
|
const records = zdxgjl.filter(item => item.key === fieldKey); |
|
|
|
return records; |
|
|
|
}, |
|
|
|
|
|
|
|
// 鼠标进入主容器 |
|
|
|
async onMouseEnter(event) { |
|
|
|
async onMouseEnter(type,event) { |
|
|
|
this.currentRecordType = type; |
|
|
|
clearTimeout(this.modalTimer); |
|
|
|
|
|
|
|
// 从 IndexedDB 加载修改记录 |
|
|
|
try { |
|
|
|
const records = await this.getRecordsFromDB(); |
|
|
|
this.modificationRecords = records; |
|
|
|
} catch (error) { |
|
|
|
console.error('获取修改记录失败:', error); |
|
|
|
this.modificationRecords = []; |
|
|
|
let record = []; |
|
|
|
if(type === "modifyRecord"){ |
|
|
|
record = this.getModifyRecords(); |
|
|
|
}else if(type === "replyRecord"){ |
|
|
|
record = this.getReplyRecords(); |
|
|
|
} |
|
|
|
|
|
|
|
console.log(record,"reeee") |
|
|
|
this.modificationRecords = record; |
|
|
|
// 先计算模态框位置,避免闪烁 |
|
|
|
this.showModal = true; |
|
|
|
this.$nextTick(() => { |
|
|
|
@ -531,6 +395,8 @@ export default { |
|
|
|
|
|
|
|
// 鼠标离开主容器 |
|
|
|
onMouseLeave() { |
|
|
|
this.currentRecordType = ''; |
|
|
|
this.modificationRecords = [];//清空数据源 |
|
|
|
// 延迟隐藏模态框,让用户有机会移动到模态框上 |
|
|
|
this.modalTimer = setTimeout(() => { |
|
|
|
if (!this.isHoveringModal) { |
|
|
|
@ -548,37 +414,12 @@ export default { |
|
|
|
// 鼠标离开模态框 |
|
|
|
onModalLeave() { |
|
|
|
this.isHoveringModal = false; |
|
|
|
this.currentRecordType = ""; |
|
|
|
this.modificationRecords = [];//清空数据源 |
|
|
|
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, |
|
|
|
}); |
|
|
|
} |
|
|
|
}, |
|
|
|
} |
|
|
|
</script> |
|
|
|
|