Commit 4fa4ae75 by DaiJiezhang

Refine phone record adapter flow

parent 755bba6d
......@@ -59,6 +59,16 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<release>17</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
......@@ -156,7 +156,8 @@ public class WxPhoneService {
return null;
}
String trimmedValue = value.trim();
return trimmedValue.isEmpty() ? null : trimmedValue;
// 支持空值提交:空字符串表示用户主动清空,应保存为空字符串而非 null
return trimmedValue;
}
/**
......
......@@ -65,7 +65,7 @@
* 代码作用(白话):初始化手机号卡页面的分页、筛选、全字段渲染和图片列辅助能力,让壳层传入的数据与动作变成真正可交互的表格;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):renderExternalRuntime('phone') -> mountPhoneListComponent() -> PhoneListTable.setup() -> 列表渲染/弹窗/抽屉/删除。
*/
setup(props) {
const { computed, ref } = window.Vue;
const { computed, ref, onBeforeUnmount } = window.Vue;
const currentPage = ref(props.mountState.currentPage || 1);
const pageSize = ref(props.mountState.pageSize || props.phoneTablePageSizes[0]);
const selectedRecordId = ref(props.globalState.selectedBySource.phone || null);
......@@ -89,6 +89,10 @@
* 代码作用(白话):内联编辑状态,记录当前正在编辑的单元格 { recordId, field, value },同一时刻只允许一个单元格处于编辑态;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):双击单元格 -> handleCellDblclick() -> editingCell -> 渲染输入控件 -> 失焦/回车 -> handleInlineSave() -> buildSavePayload() -> props.savePhoneRecord() -> loadAllRecords()。
*/
const editingCell = ref(null);
/**
* 代码作用(白话):防止重复保存的标志位,避免 mousedown 和 blur 同时触发导致多次提交;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):handleInlineSave() -> inlineSaving -> 防止并发保存。
*/
let inlineSaving = false;
/**
* 代码作用(白话):定义哪些字段支持双击内联编辑以及对应的控件类型,避免模板里写死判断逻辑;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):handleCellDblclick() -> isFieldEditable() -> 渲染 input/select/switch。
......@@ -133,12 +137,18 @@
}
/**
* 代码作用(白话):内联编辑确认后构造完整保存载荷并调用保存接口,成功后刷新列表并退出编辑态;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):回车/失焦 -> handleInlineSave() -> buildSavePayload() -> props.savePhoneRecord() -> loadAllRecords() -> editingCell = null。
* 代码作用(白话):内联编辑确认后构造完整保存载荷并调用保存接口,成功后刷新列表并退出编辑态;支持空值提交,允许用户清空字段内容后保存空字符串;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):回车/失焦/点击外部 -> handleInlineSave() -> buildSavePayload() -> props.savePhoneRecord() -> loadAllRecords() -> editingCell = null。
*/
async function handleInlineSave() {
if (!editingCell.value) {
return;
}
// 防止重复保存(blur 和 mousedown 可能同时触发)
if (inlineSaving) {
return;
}
inlineSaving = true;
try {
const { recordId, field, value } = editingCell.value;
const record = allRecordsCache.value.find((item) => item.id === recordId);
if (!record) {
......@@ -149,7 +159,8 @@
if (field === 'outbound') {
updatedRecord.outboundCall = value === '可外呼';
} else {
updatedRecord[field] = value;
// 支持空值提交:允许保存空字符串
updatedRecord[field] = value ?? '';
}
const images = getDisplayImages(record);
const payload = buildSavePayload(updatedRecord, images);
......@@ -164,6 +175,26 @@
} finally {
editingCell.value = null;
}
} finally {
inlineSaving = false;
}
}
/**
* 代码作用(白话):处理 document 级 mousedown 事件,点击非内联编辑区域时自动提交保存;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):点击页面任意位置 -> handleDocumentMousedown() -> 判断是否点击编辑区域外 -> handleInlineSave()。
*/
function handleDocumentMousedown(event) {
if (!editingCell.value) {
return;
}
// 检查点击目标是否在编辑单元格内
const target = event.target;
const editingElement = document.querySelector('.phone-inline-edit-cell');
if (editingElement && editingElement.contains(target)) {
return; // 点击在编辑区域内,不处理
}
// 点击在编辑区域外,触发保存
handleInlineSave();
}
/**
......@@ -405,28 +436,28 @@
}
/**
* 代码作用(白话):按后端 WxPhoneSaveRequest 字段把列表记录拼成完整保存载荷,保证删除图片走 PUT 全量更新时不丢字段;关联文件:F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):handleClearImage() -> buildSavePayload() -> props.savePhoneRecord() -> 后端全量更新。
* 代码作用(白话):按后端 WxPhoneSaveRequest 字段把列表记录拼成完整保存载荷,保证删除图片走 PUT 全量更新时不丢字段;支持空值提交,使用 ?? 或显式判断区分 undefined/null 与空字符串;关联文件:F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):handleInlineSave()/handleClearImage() -> buildSavePayload() -> props.savePhoneRecord() -> 后端全量更新。
*/
function buildSavePayload(record, images) {
return {
id: record.id,
phoneNumber: record.phoneNumber || record.phone || '',
realPerson: record.realPerson || '',
iccid: record.iccid || '',
city: record.city || '',
imageAttachment1: images[0] || '',
imageAttachment2: images[1] || '',
cardStatus: record.cardStatus || record.status || '正常',
cardUsageLocation: record.cardUsageLocation || record.usageLocation || '',
phoneNumber: record.phoneNumber ?? record.phone ?? '',
realPerson: record.realPerson ?? '',
iccid: record.iccid ?? '',
city: record.city ?? '',
imageAttachment1: images[0] ?? '',
imageAttachment2: images[1] ?? '',
cardStatus: record.cardStatus ?? record.status ?? '正常',
cardUsageLocation: record.cardUsageLocation ?? record.usageLocation ?? '',
wecom: record.wecom === true,
wechat: record.wechat === true,
outboundCall: record.outboundCall === true,
douyinAccount: record.douyinAccount || '',
douyinAccount: record.douyinAccount ?? '',
miniProgramFiling: record.miniProgramFiling === true,
packageChange5yuan: record.packageChange5yuan === true,
channelOperator: record.channelOperator || record.carrier || '',
channelOperator: record.channelOperator ?? record.carrier ?? '',
numberStatus: record.numberStatus ?? true,
linkedWecom: record.linkedWecom || ''
linkedWecom: record.linkedWecom ?? ''
};
}
......@@ -617,8 +648,17 @@
props.openAddDialog('add');
}
window.Vue.onMounted(loadAllRecords);
window.Vue.onMounted(() => {
loadAllRecords();
syncMountState();
// 添加 document 级 mousedown 监听,点击非编辑区域时自动保存
document.addEventListener('mousedown', handleDocumentMousedown, true);
});
window.Vue.onBeforeUnmount(() => {
// 清理事件监听
document.removeEventListener('mousedown', handleDocumentMousedown, true);
});
return {
formatCellText,
......
(function attachRecordAdapters() {
(function attachRecordAdapters() {
/**
* 代码作用(白话):把企微原始记录整理成列表、详情、编辑弹窗都能直接消费的统一字段;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/wechat/wechat-list-runtime.js、F:/Project/xyw_console/src/modules/wechat/wechat-add-dialog.js;关联逻辑(调用链/消息链/数据流):后端/本地记录 -> normalizeWechatRecord() -> app-v2.js 注入 -> 企微列表/详情/编辑弹窗。
*/
......@@ -81,7 +81,7 @@
}
/**
* 代码作用(白话):把手机号原始记录整理成列表、详情抽屉、编辑弹窗共用的统一字段,避免每个组件自己猜字段名;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):PhoneApiClient/DATA_SOURCES.phone -> normalizePhoneRecord() -> app-v2.js 注入 -> 手机号列表/详情/编辑弹窗。
* 代码作用(白话):把手机号原始记录整理成列表、详情抽屉、编辑弹窗共用的统一字段,避免每个组件自己猜字段名;支持空值提交,使用 ?? 运算符区分 undefined/null 与空字符串;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):PhoneApiClient/DATA_SOURCES.phone -> normalizePhoneRecord() -> app-v2.js 注入 -> 手机号列表/详情/编辑弹窗。
*/
/**
* ????????????????????????????????????????????? status ??????????????F:/Project/xyw_console/src/modules/shared/record-adapters.js?F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js?F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js?????????/???/????????? -> normalizePhoneRecord() -> ??/??/?????
......@@ -89,41 +89,47 @@
function normalizePhoneRecord(row) {
const source = row || {};
const images = buildPhoneImages(source);
// cardStatus 支持空值:空字符串表示用户主动清空,应保持为空
const cardStatusRaw = source.cardStatus ?? source.card_status ?? source.status;
const cardStatus = cardStatusRaw === 0 || cardStatusRaw === '0' || cardStatusRaw === '异常'
? '异常停机'
: (cardStatusRaw || '正常');
const phoneNumber = source.phoneNumber || source.phone_number || source.phone || '';
const realPerson = source.realPerson || source.real_person || '';
const channelOperator = source.channelOperator || source.channel_operator || source.carrier || '';
const updatedAt = source.updatedAt || source.updated_at || source.updateTime || '';
const usageLocation = source.cardUsageLocation || source.card_usage_location || source.usageLocation || '';
: (cardStatusRaw ?? '正常');
const phoneNumber = source.phoneNumber ?? source.phone_number ?? source.phone ?? '';
// realPerson 支持空值:空字符串表示用户主动清空
const realPerson = source.realPerson ?? source.real_person ?? '';
// channelOperator 支持空值:空字符串表示用户主动清空
const channelOperator = source.channelOperator ?? source.channel_operator ?? source.carrier ?? '';
const updatedAt = source.updatedAt ?? source.updated_at ?? source.updateTime ?? '';
const usageLocation = source.cardUsageLocation ?? source.card_usage_location ?? source.usageLocation ?? '';
const wecomEnabled = toBooleanFlag(source.wecom, false);
const wechatEnabled = toBooleanFlag(source.wechat, false);
const outboundEnabled = toBooleanFlag(source.outboundCall ?? source.outbound_call ?? source.outbound, false);
const miniProgramFiled = toBooleanFlag(source.miniProgramFiling ?? source.mini_program_filing, false);
const packageChangeEnabled = toBooleanFlag(source.packageChange5yuan ?? source.package_change_5yuan, false);
const numberStatus = toBooleanFlag(source.numberStatus ?? source.mobile_status ?? source.number_status, true);
const wechatStatus = source.wechatStatus || (numberStatus ? '正常' : '异常停机');
const outbound = source.outbound || (outboundEnabled ? '可外呼' : '不可外呼');
const linkedWecom = source.linkedWecom || source.linked_wecom || '';
const douyinAccount = source.douyinAccount || source.douyin_account || '';
const wechatStatus = source.wechatStatus ?? (numberStatus ? '正常' : '异常停机');
const outbound = source.outbound ?? (outboundEnabled ? '可外呼' : '不可外呼');
// linkedWecom 支持空值:空字符串表示用户主动清空
const linkedWecom = source.linkedWecom ?? source.linked_wecom ?? '';
// douyinAccount 支持空值:空字符串表示用户主动清空
const douyinAccount = source.douyinAccount ?? source.douyin_account ?? '';
return {
...source,
id: source.id,
images,
primaryImage: images[0] || '',
imageAttachment1: images[0] || source.imageAttachment1 || source.image_attachment_1 || '',
image_attachment_1: images[0] || source.imageAttachment1 || source.image_attachment_1 || '',
imageAttachment2: images[1] || source.imageAttachment2 || source.image_attachment_2 || '',
image_attachment_2: images[1] || source.imageAttachment2 || source.image_attachment_2 || '',
primaryImage: images[0] ?? '',
imageAttachment1: images[0] ?? source.imageAttachment1 ?? source.image_attachment_1 ?? '',
image_attachment_1: images[0] ?? source.imageAttachment1 ?? source.image_attachment_1 ?? '',
imageAttachment2: images[1] ?? source.imageAttachment2 ?? source.image_attachment_2 ?? '',
image_attachment_2: images[1] ?? source.imageAttachment2 ?? source.image_attachment_2 ?? '',
phone: phoneNumber,
phoneNumber,
phone_number: phoneNumber,
realPerson,
real_person: realPerson,
city: source.city || '',
// city 支持空值:空字符串表示用户主动清空
city: source.city ?? '',
usageLocation,
cardUsageLocation: usageLocation,
card_usage_location: usageLocation,
......@@ -140,12 +146,13 @@
wechat: wechatEnabled,
wecom: wecomEnabled,
updatedAt,
updated_at: source.updated_at || updatedAt,
updateTime: source.updateTime || updatedAt,
owner: source.owner || realPerson,
project: source.project || source.note || '暂无备注',
note: source.note || source.project || '',
iccid: source.iccid || '',
updated_at: source.updated_at ?? updatedAt,
updateTime: source.updateTime ?? updatedAt,
owner: source.owner ?? realPerson,
project: source.project ?? source.note ?? '暂无备注',
note: source.note ?? source.project ?? '',
// iccid 支持空值:空字符串表示用户主动清空
iccid: source.iccid ?? '',
douyinAccount,
douyin_account: douyinAccount,
miniProgramFiling: miniProgramFiled,
......
......@@ -1325,7 +1325,7 @@
}
.phone-image-action--preview:hover {
color: #2563eb;
color: #fff;
background: rgba(255, 255, 255, 0.18);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment