Browse Source

feat[模板管理][修改记录]

master
luojie 2 days ago
parent
commit
309ab01539
4 changed files with 314 additions and 109 deletions
  1. +306
    -101
      src/components/Template/HandleFormItem.vue
  2. +4
    -4
      src/views/business/comps/template/comps/sp/IndexDBDemo.vue
  3. +2
    -2
      src/views/business/comps/template/comps/sp/SWYPFXRYPZB.vue
  4. +2
    -2
      src/views/business/comps/template/mixins/templateMixin.js

+ 306
- 101
src/components/Template/HandleFormItem.vue View File

@ -2,14 +2,14 @@
<div class="flex flex1"> <div class="flex flex1">
<div class="flex1 flex"> <div class="flex1 flex">
<el-input v-if="type === 'input'" :maxlength="item.maxlength || 50" :disabled="getDisabled()" <el-input v-if="type === 'input'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
:class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" @blur="onBlur"
:class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" @blur="onCommonHandleSaveRecord"
:placeholder="getPlaceholder()" v-model="inputValue" :placeholder="getPlaceholder()" v-model="inputValue"
@input="onInputChange" @change="onInputChange" /> @input="onInputChange" @change="onInputChange" />
<el-input v-else-if="type === 'textarea'" :maxlength="item.maxlength || 50" :disabled="getDisabled()" <el-input v-else-if="type === 'textarea'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
:class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" type="textarea" show-word-limit resize="none" @blur="onBlur"
:class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" type="textarea" show-word-limit resize="none" @blur="onCommonHandleSaveRecord"
:rows="item.rows || 3" :placeholder="getPlaceholder()" :rows="item.rows || 3" :placeholder="getPlaceholder()"
v-model="inputValue" @input="onInputChange" @change="onInputChange" /> v-model="inputValue" @input="onInputChange" @change="onInputChange" />
<DecimalInput v-else-if="type === 'inputNumber'" @blur="onBlur" :maxlength="item.maxlength || 10"
<DecimalInput v-else-if="type === 'inputNumber'" @blur="onCommonHandleSaveRecord" :maxlength="item.maxlength || 10"
class="flex1" :disabled="getDisabled()" :controls="item.controls || false" :min="item.min || 0" class="flex1" :disabled="getDisabled()" :controls="item.controls || false" :min="item.min || 0"
:prepend = "item.prepend" :prepend = "item.prepend"
:decimalDigits="item.precision" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" :decimalDigits="item.precision" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
@ -17,14 +17,14 @@
@input="onInputChange" @change="onInputChange" /> @input="onInputChange" @change="onInputChange" />
<el-select v-else-if="type === 'select'" class="flex1" :multiple="item.multiple" <el-select v-else-if="type === 'select'" class="flex1" :multiple="item.multiple"
:class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue" :disabled="getDisabled()" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue" :disabled="getDisabled()"
:placeholder="getPlaceholder()" @change="onInputChange">
:placeholder="getPlaceholder()" @change="onCommonHandleSaveRecord">
<el-option v-for="op in item.options" :key="op.value" :label="op.label" :value="op.value"> <el-option v-for="op in item.options" :key="op.value" :label="op.label" :value="op.value">
</el-option> </el-option>
</el-select> </el-select>
<el-date-picker v-else-if="type === 'dateTime'" type="datetime" class="flex1" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" <el-date-picker v-else-if="type === 'dateTime'" type="datetime" class="flex1" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
v-model="inputValue" :disabled="getDisabled()" format="yyyy/MM/DD HH:mm:ss" v-model="inputValue" :disabled="getDisabled()" format="yyyy/MM/DD HH:mm:ss"
value-format="yyyy/MM/DD HH:mm:ss" value-format="yyyy/MM/DD HH:mm:ss"
:placeholder="getPlaceholder()" @change="onInputChange">
:placeholder="getPlaceholder()" @change="onCommonHandleSaveRecord">
</el-date-picker> </el-date-picker>
<div class="clickable" :class="getFillTypeStyle() + (getDisabled()?' disabled':'') + (orangeBg ? ' orange-bg' : '')" v-else-if = "item.type ==='clickable'" @click="handleClickable(item,$event)"> <div class="clickable" :class="getFillTypeStyle() + (getDisabled()?' disabled':'') + (orangeBg ? ' orange-bg' : '')" v-else-if = "item.type ==='clickable'" @click="handleClickable(item,$event)">
<span v-if="value">{{ value }}</span> <span v-if="value">{{ value }}</span>
@ -44,9 +44,9 @@
<!-- 修改记录模态框 --> <!-- 修改记录模态框 -->
<div <div
v-if="showModal"
v-if="showModal && modificationRecords.length > 0"
ref="modalRef" ref="modalRef"
class="modification-modal"
:class="['modification-modal', {'show': showModal}]"
@mouseenter="onModalEnter" @mouseenter="onModalEnter"
@mouseleave="onModalLeave" @mouseleave="onModalLeave"
> >
@ -117,23 +117,11 @@ export default {
inputValue: this.value, inputValue: this.value,
oldValue: this.value, // oldValue: this.value, //
showModal: false, // showModal: false, //
modificationRecords: [
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
], //
modificationRecords: [], //
modalTimer: null, // modalTimer: null, //
isHoveringModal: false, // isHoveringModal: false, //
isHoveringMain: false, // isHoveringMain: false, //
db: null, // IndexedDB
} }
}, },
watch: { watch: {
@ -147,13 +135,6 @@ export default {
}, },
methods: { methods: {
//
onHover() {
},
//
onLeave() {
},
getFillTypeStyle(type) { getFillTypeStyle(type) {
const {fillType} = this.item; const {fillType} = this.item;
const typeObj = { const typeObj = {
@ -186,6 +167,75 @@ export default {
this.$emit('update:error', true); 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 {
// blurchange
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) { isValueEmpty(value) {
if (value === null || value === undefined || value === '') { if (value === null || value === undefined || value === '') {
@ -215,9 +265,8 @@ export default {
isShowHandle() { isShowHandle() {
const { fillType } = this.item; const { fillType } = this.item;
const { templateStatus } = this.$store.state.template; const { templateStatus } = this.$store.state.template;
return true;
//qc //qc
// return (templateStatus === "qc" || templateStatus === "actFill") && fillType === "actFill";
return (templateStatus === "qc" || templateStatus === "actFill") && fillType === "actFill";
}, },
// //
getDisabled() { getDisabled() {
@ -255,92 +304,213 @@ export default {
onCopy() { onCopy() {
this.$emit("copy") 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() {
// 使idid
const templateId = 'template_123'; // id
const fieldKey = this.item.key || this.item.prop || this.item.label || 'default_key';
// CustomTablekey
//
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); 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(() => { this.$nextTick(() => {
if (this.$refs.modalRef) { if (this.$refs.modalRef) {
const elementRect = event.target.getBoundingClientRect(); const elementRect = event.target.getBoundingClientRect();
@ -375,6 +545,33 @@ export default {
this.modalTimer = setTimeout(() => { this.modalTimer = setTimeout(() => {
this.showModal = false; this.showModal = false;
}, 300); }, 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; min-width: 250px;
overflow: hidden; overflow: hidden;
pointer-events: auto; 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 { .modification-modal .modal-content {

+ 4
- 4
src/views/business/comps/template/comps/sp/IndexDBDemo.vue View File

@ -16,7 +16,7 @@
v-model="form.basicInfo.name" v-model="form.basicInfo.name"
placeholder="请输入姓名" placeholder="请输入姓名"
@blur="onFieldChange('basicInfo.name', $event)" @blur="onFieldChange('basicInfo.name', $event)"
@focus="loadTooltip('basicInfo.name')"
@mouseenter="loadTooltip('basicInfo.name')"
@mouseleave="hideTooltip" @mouseleave="hideTooltip"
/> />
</el-tooltip> </el-tooltip>
@ -36,7 +36,7 @@
v-model="form.contact.email" v-model="form.contact.email"
placeholder="请输入邮箱" placeholder="请输入邮箱"
@blur="onFieldChange('contact.email', $event)" @blur="onFieldChange('contact.email', $event)"
@focus="loadTooltip('contact.email')"
@mouseenter="loadTooltip('contact.email')"
@mouseleave="hideTooltip" @mouseleave="hideTooltip"
/> />
</el-tooltip> </el-tooltip>
@ -57,7 +57,7 @@
placeholder="请输入年龄" placeholder="请输入年龄"
type="number" type="number"
@blur="onFieldChange('basicInfo.age', $event)" @blur="onFieldChange('basicInfo.age', $event)"
@focus="loadTooltip('basicInfo.age')"
@mouseenter="loadTooltip('basicInfo.age')"
@mouseleave="hideTooltip" @mouseleave="hideTooltip"
/> />
</el-tooltip> </el-tooltip>
@ -197,7 +197,7 @@ export default {
}, },
async loadTooltip(fieldPath) { async loadTooltip(fieldPath) {
this.isTooltipVisible = false; // this.isTooltipVisible = false; //
console.log("lofo")
console.log()
const logs = await this.loadLogs(fieldPath); const logs = await this.loadLogs(fieldPath);
if (logs.length === 0) { if (logs.length === 0) {
this.currentTooltipContent = '<div class="empty">暂无修改记录</div>'; this.currentTooltipContent = '<div class="empty">暂无修改记录</div>';

+ 2
- 2
src/views/business/comps/template/comps/sp/SWYPFXRYPZB.vue View File

@ -257,9 +257,9 @@ export default {
return await this.validFormFields(["baseInfoRef", "storageConditionRef","stepFormPackageRef","stepRef","remarkRef"]); return await this.validFormFields(["baseInfoRef", "storageConditionRef","stepFormPackageRef","stepRef","remarkRef"]);
}, },
async onSave() { async onSave() {
// const formData = await this.getFormData();
const formData = await this.getFormData();
// console.log(formData, "formData")
console.log(formData, "formData")
}, },
} }

+ 2
- 2
src/views/business/comps/template/mixins/templateMixin.js View File

@ -31,8 +31,8 @@ export default {
} }
}, },
mounted() { mounted() {
// this.setTemplateStatus("actFill");
this.setTemplateStatus(this.fillType);
this.setTemplateStatus("actFill");
// this.setTemplateStatus(this.fillType);
}, },
unmounted() { unmounted() {
this.setTemplateStatus(""); this.setTemplateStatus("");

Loading…
Cancel
Save