Commit da65cb92 by DaiJiezhang

Refine fetch handling and phone workflow

parent f2fe141d
......@@ -40,6 +40,5 @@ public class WxPhoneSaveRequest {
private Boolean numberStatus;
private String linkedWecom;
private Boolean numberRetentionStatus;
}
......@@ -64,8 +64,7 @@ public class WxPhoneEntity {
@TableField("linked_wecom")
private String linkedWecom;
@TableField("number_retention_status")
private Boolean numberRetentionStatus;
@TableField(exist = false)
private String wechatStatus;
......@@ -73,3 +72,4 @@ public class WxPhoneEntity {
@TableField(exist = false)
private String outbound;
}
......@@ -23,7 +23,7 @@
channelOperator: '',
numberStatus: true,
linkedWecom: '',
numberRetentionStatus: true,
imageAttachment1: '',
imageAttachment2: ''
};
......@@ -124,7 +124,7 @@
formData.channelOperator = record.channelOperator || record.carrier || '';
formData.numberStatus = record.numberStatus !== false;
formData.linkedWecom = record.linkedWecom || '';
formData.numberRetentionStatus = record.numberRetentionStatus !== false;
imageItems.value = buildImageItemsFromRecord(record);
syncImagesToFormData();
}
......@@ -347,12 +347,7 @@
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="是否保留号码" prop="numberRetentionStatus">
<el-switch v-model="formData.numberRetentionStatus" active-text="保留" inactive-text="注销"></el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="24">
<el-form-item label="可变更 5 元套餐" prop="packageChange5yuan">
<el-switch v-model="formData.packageChange5yuan"></el-switch>
</el-form-item>
......@@ -412,3 +407,6 @@
app.use(ElementPlus);
app.mount('#phoneDialogRoot');
})();
......@@ -34,6 +34,17 @@
return recordTime >= startTime && recordTime <= endTime + 24 * 60 * 60 * 1000 - 1;
}
/**
* 代码作用(白话):把列表里的长文本压成 20 个字符内的单行展示,鼠标悬浮时还能看到完整内容;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):表格单元格原始值 -> formatCellText() -> 列表展示/tooltip 提示。
*/
function formatCellText(value) {
const text = String(value ?? '-');
if (text.length <= 20) {
return text;
}
return `${text.slice(0, 20)}...`;
}
const PhoneListTable = {
name: 'PhoneListTable',
props: {
......@@ -454,9 +465,21 @@
<a href="#" class="domain-link" @click.prevent="focusRow(scope.row.id)">{{ scope.row.phone || '-' }}</a>
</template>
</el-table-column>
<el-table-column property="realPerson" label="真实联系人" min-width="130" />
<el-table-column property="iccid" label="ICCID" min-width="190" />
<el-table-column property="city" label="所在城市" min-width="120" />
<el-table-column label="真实联系人" min-width="150">
<template #default="scope">
<span :title="scope.row.realPerson || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.realPerson) }}</span>
</template>
</el-table-column>
<el-table-column label="ICCID" min-width="220">
<template #default="scope">
<span :title="scope.row.iccid || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.iccid) }}</span>
</template>
</el-table-column>
<el-table-column label="所在城市" min-width="140">
<template #default="scope">
<span :title="scope.row.city || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.city) }}</span>
</template>
</el-table-column>
<el-table-column label="号卡状态" min-width="120">
<template #default="scope">
<span class="status-chip-wrap">
......@@ -464,9 +487,7 @@
<span>{{ scope.row.status }}</span>
</span>
</template>
</el-table-column>
<el-table-column property="cardUsageLocation" label="卡使用地点" min-width="160" />
<el-table-column label="企业微信" min-width="110">
</el-table-column> <el-table-column label="企业微信" min-width="110">
<template #default="scope">{{ formatBooleanText(scope.row.wecom, '已开通', '未开通') }}</template>
</el-table-column>
<el-table-column label="微信" min-width="100">
......@@ -475,20 +496,28 @@
<el-table-column label="可外呼" min-width="100">
<template #default="scope">{{ formatBooleanText(scope.row.outboundCall, '可外呼', '不可外呼') }}</template>
</el-table-column>
<el-table-column property="douyinAccount" label="抖音账号" min-width="160" />
<el-table-column label="抖音账号" min-width="180">
<template #default="scope">
<span :title="scope.row.douyinAccount || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.douyinAccount) }}</span>
</template>
</el-table-column>
<el-table-column label="小程序备案" min-width="120">
<template #default="scope">{{ formatBooleanText(scope.row.miniProgramFiling, '已备案', '未备案') }}</template>
</el-table-column>
<el-table-column property="channelOperator" label="渠道运营商" min-width="130" />
<el-table-column label="渠道运营商" min-width="150">
<template #default="scope">
<span :title="scope.row.channelOperator || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.channelOperator) }}</span>
</template>
</el-table-column>
<el-table-column label="手机号状态" min-width="120">
<template #default="scope">{{ formatStatusText(scope.row.numberStatus) }}</template>
</el-table-column>
<el-table-column property="linkedWecom" label="绑定企业微信" min-width="160" />
<el-table-column label="号码保留状态" min-width="130">
<template #default="scope">{{ formatRetentionText(scope.row.numberRetentionStatus) }}</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<el-table-column label="绑定企业微信" min-width="180">
<template #default="scope">
<span :title="scope.row.linkedWecom || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.linkedWecom) }}</span>
</template>
</el-table-column> <el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<div class="action-cluster" style="display: flex; flex-direction: column; align-items: flex-start; gap: 8px;">
<button class="action-pill action-pill-primary" type="button" @click.stop="handleRowAction('view', scope.row.id)" style="border: none; background: transparent; color: #2563eb; cursor: pointer; padding: 0; font-weight: 600; line-height: 1;">查看</button>
......@@ -544,3 +573,5 @@
......@@ -13,11 +13,33 @@
}
/**
* 代码作用(白话):把浏览器在本地后端没启动时抛出的网络错误翻成稳定中文,避免页面直接展示原始 Failed to fetch;关联文件:F:/Project/xyw_console/src/modules/shared/phone-api-client.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):fetch() 失败 -> requestJson() -> 抛出中文错误 -> 外层 catch -> 页面提示。
*/
function isFetchUnavailableError(error) {
const message = String(error?.message || '').toLowerCase();
return error instanceof TypeError && message.includes('failed to fetch');
}
/**
* 代码作用(白话):先请求接口再统一解析 JSON,并在网络断开或本地后端未启动时返回稳定错误文案;关联文件:F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):listPhones()/savePhone()/deletePhone() -> requestJson() -> parseJsonResponse() -> 外层页面提示。
*/
async function requestJson(url, options, networkMessage) {
try {
const response = await fetch(url, options);
return await parseJsonResponse(response);
} catch (error) {
if (isFetchUnavailableError(error)) {
throw new Error(networkMessage);
}
throw error;
}
}
/**
* 代码作用(白话):从本地手机号接口拉取完整列表,返回后端 data 数组给外层适配;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):phone 页面挂载/刷新 -> listPhones() -> app-v2.js -> normalizePhoneRecord() -> 列表渲染。
*/
async function listPhones() {
const response = await fetch(BASE_URL);
const json = await parseJsonResponse(response);
const json = await requestJson(BASE_URL, undefined, '手机号服务暂时不可用,请确认本地后端已启动');
return Array.isArray(json.data) ? json.data : [];
}
......@@ -28,12 +50,11 @@
const payload = { ...record };
const isEdit = Boolean(payload.id);
const targetUrl = isEdit ? `${BASE_URL}/${payload.id}` : BASE_URL;
const response = await fetch(targetUrl, {
const json = await requestJson(targetUrl, {
method: isEdit ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const json = await parseJsonResponse(response);
}, '手机号保存失败,请确认本地后端已启动');
return json.data || json;
}
......@@ -41,10 +62,9 @@
* 代码作用(白话):删除指定手机号记录,并把失败信息统一抛给外层处理;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):手机号列表删除按钮 -> deletePhoneRecord() -> deletePhone() -> 刷新列表或提示失败。
*/
async function deletePhone(id) {
const response = await fetch(`${BASE_URL}/${id}`, {
const json = await requestJson(`${BASE_URL}/${id}`, {
method: 'DELETE'
});
const json = await parseJsonResponse(response);
}, '手机号删除失败,请确认本地后端已启动');
return json.data || json;
}
......
......@@ -99,7 +99,6 @@
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 numberRetentionStatus = toBooleanFlag(source.numberRetentionStatus ?? source.number_retention_status, true);
const wechatStatus = source.wechatStatus || (numberStatus ? '正常' : '异常停机');
const outbound = source.outbound || (outboundEnabled ? '可外呼' : '不可外呼');
const linkedWecom = source.linkedWecom || source.linked_wecom || '';
......@@ -152,8 +151,6 @@
mobile_status: numberStatus,
linkedWecom,
linked_wecom: linkedWecom,
numberRetentionStatus,
number_retention_status: numberRetentionStatus
};
}
......@@ -162,3 +159,4 @@
normalizePhoneRecord
};
})();
......@@ -14,11 +14,33 @@
}
/**
* 代码作用(白话):把浏览器在本地后端没启动时抛出的网络错误翻成稳定中文,避免页面直接展示原始 Failed to fetch;关联文件:F:/Project/xyw_console/src/modules/shared/wechat-api-client.js、F:/Project/xyw_console/src/modules/wechat/wechat-list-runtime.js、F:/Project/xyw_console/src/modules/wechat/wechat-add-dialog.js;关联逻辑(调用链/消息链/数据流):fetch() 失败 -> requestJson() -> 抛出中文错误 -> 外层 catch -> 页面提示。
*/
function isFetchUnavailableError(error) {
const message = String(error?.message || '').toLowerCase();
return error instanceof TypeError && message.includes('failed to fetch');
}
/**
* 代码作用(白话):先请求接口再统一解析 JSON,并在网络断开或本地后端未启动时返回稳定错误文案;关联文件:F:/Project/xyw_console/src/modules/shared/wechat-api-client.js;关联逻辑(调用链/消息链/数据流):listWechatRecords()/getWechatRecord()/saveWechatRecord() -> requestJson() -> parseJsonResponse() -> 外层页面提示。
*/
async function requestJson(url, options, networkMessage) {
try {
const response = await fetch(url, options);
return await parseJsonResponse(response);
} catch (error) {
if (isFetchUnavailableError(error)) {
throw new Error(networkMessage);
}
throw error;
}
}
/**
* 代码作用(白话):从后端拉取企微资料全量列表,交给页面继续做本地筛选和分页;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/wechat/wechat-list-runtime.js;关联逻辑(调用链/消息链/数据流):wechat 页面挂载/刷新 -> listWechatRecords() -> app-v2.js -> normalizeWechatRecord() -> 列表渲染。
*/
async function listWechatRecords() {
const response = await fetch(BASE_URL);
const json = await parseJsonResponse(response);
const json = await requestJson(BASE_URL, undefined, '企微服务暂时不可用,请确认本地后端已启动');
return Array.isArray(json.data) ? json.data : [];
}
......@@ -26,8 +48,7 @@
* 代码作用(白话):按主键读取单条企微资料,让详情抽屉和编辑弹窗拿到数据库最新值;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/wechat/wechat-detail-drawer.js;关联逻辑(调用链/消息链/数据流):查看/编辑按钮 -> getWechatRecord() -> app-v2.js -> 详情抽屉或编辑弹窗打开。
*/
async function getWechatRecord(id) {
const response = await fetch(`${BASE_URL}/${id}`);
const json = await parseJsonResponse(response);
const json = await requestJson(`${BASE_URL}/${id}`, undefined, '企微详情读取失败,请确认本地后端已启动');
return json.data || null;
}
......@@ -41,12 +62,11 @@
if (!isEdit) {
delete payload.id;
}
const response = await fetch(targetUrl, {
const json = await requestJson(targetUrl, {
method: isEdit ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const json = await parseJsonResponse(response);
}, '企微保存失败,请确认本地后端已启动');
return json.data || json;
}
......@@ -56,4 +76,3 @@
saveWechatRecord
};
})();
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