Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xyw_asset_console
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DaiJiezhang
xyw_asset_console
Commits
f2fe141d
Commit
f2fe141d
authored
Jul 02, 2026
by
DaiJiezhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refine phone workflow and ignore local backend files
parent
85fee5d2
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
334 additions
and
168 deletions
+334
-168
.gitignore
.gitignore
+2
-0
application.yml
backend/src/main/resources/application.yml
+0
-16
phone-add-dialog.js
src/modules/phone/phone-add-dialog.js
+157
-33
phone-list-runtime.js
src/modules/phone/phone-list-runtime.js
+100
-58
record-adapters.js
src/modules/shared/record-adapters.js
+75
-18
start-backend.bat
start-backend.bat
+0
-43
No files found.
.gitignore
View file @
f2fe141d
...
...
@@ -11,3 +11,5 @@ backend/target/
F_TMP_WRITE_TEST.txt
localhost
wechat-page.png
backend/src/main/resources/application.yml
start-backend.bat
backend/src/main/resources/application.yml
deleted
100644 → 0
View file @
85fee5d2
server
:
port
:
8080
spring
:
application
:
name
:
xyw-console-backend
datasource
:
driver-class-name
:
com.mysql.cj.jdbc.Driver
url
:
${XYW_DB_URL:jdbc:mysql://localhost:3306/xyw_private_data?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}
username
:
${XYW_DB_USERNAME:root}
password
:
${XYW_DB_PASSWORD:}
mybatis-plus
:
configuration
:
map-underscore-to-camel-case
:
true
log-impl
:
org.apache.ibatis.logging.stdout.StdOutImpl
src/modules/phone/phone-add-dialog.js
View file @
f2fe141d
...
...
@@ -2,13 +2,11 @@
const
{
createApp
,
ref
,
reactive
}
=
window
.
Vue
;
const
ElementPlus
=
window
.
ElementPlus
;
const
PhoneAddDialog
=
{
setup
()
{
const
visible
=
ref
(
false
);
const
formRef
=
ref
(
null
);
const
isEdit
=
ref
(
false
);
const
formData
=
reactive
({
/**
* 代码作用(白话):给手机号卡新增/编辑弹窗准备一份干净的默认表单对象,避免多次打开时残留上一次输入;关联文件:F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js、F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):open('add') -> buildEmptyFormData() -> reactive formData -> 弹窗输入框和图片区域渲染。
*/
function
buildEmptyFormData
()
{
return
{
id
:
null
,
phoneNumber
:
''
,
realPerson
:
''
,
...
...
@@ -25,8 +23,64 @@
channelOperator
:
''
,
numberStatus
:
true
,
linkedWecom
:
''
,
numberRetentionStatus
:
true
numberRetentionStatus
:
true
,
imageAttachment1
:
''
,
imageAttachment2
:
''
};
}
/**
* 代码作用(白话):把本地图片文件转成当前接口可直接保存的文本内容,避免新增文件上传接口;关联文件:F:/Project/xyw_console/src/modules/shared/phone-api-client.js、F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java;关联逻辑(调用链/消息链/数据流):本地文件 -> readFileAsDataUrl() -> imageItems -> submitAfterValidate() -> savePhone()。
*/
function
readFileAsDataUrl
(
file
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
const
reader
=
new
FileReader
();
reader
.
onload
=
()
=>
resolve
(
typeof
reader
.
result
===
'string'
?
reader
.
result
:
''
);
reader
.
onerror
=
()
=>
reject
(
new
Error
(
'读取图片失败,请重新选择'
));
reader
.
readAsDataURL
(
file
);
});
}
/**
* 代码作用(白话):把已有记录里的两个 SQL 图片字段整理成弹窗统一图片预览集合,保证编辑时能按一个图片区域回显;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):record.images/imageAttachment1/2 -> buildImageItemsFromRecord() -> populateFormData() -> 图片上传区域预览。
*/
function
buildImageItemsFromRecord
(
record
)
{
const
rawImages
=
Array
.
isArray
(
record
?.
images
)
?
record
.
images
:
[
record
?.
imageAttachment1
,
record
?.
image_attachment_1
,
record
?.
imageAttachment2
,
record
?.
image_attachment_2
].
filter
(
Boolean
);
return
rawImages
.
filter
(
Boolean
)
.
filter
((
value
,
index
,
array
)
=>
array
.
indexOf
(
value
)
===
index
)
.
slice
(
0
,
2
)
.
map
((
url
,
index
)
=>
({
id
:
`existing-
${
index
}
`
,
url
,
name
:
`已保存图片
${
index
+
1
}
`
}));
}
/**
* 代码作用(白话):读取外层注入的手机号保存动作,没有配置时直接给出明确错误,避免弹窗假保存;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):保存按钮 -> getPhoneAddDialogActions() -> save()/afterSave() -> 刷新列表。
*/
function
getPhoneAddDialogActions
()
{
if
(
!
window
.
PhoneAddDialogActions
?.
save
)
{
throw
new
Error
(
'手机号保存动作尚未初始化'
);
}
return
window
.
PhoneAddDialogActions
;
}
const
PhoneAddDialog
=
{
/**
* 代码作用(白话):初始化手机号卡弹窗的表单状态、图片多选上传和统一提交流程,让列表与详情都能共用一个弹窗;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js;关联逻辑(调用链/消息链/数据流):新增/编辑入口 -> PhoneAddDialogAPI.open() -> setup() 状态变化 -> 表单与图片区域渲染。
*/
setup
()
{
const
visible
=
ref
(
false
);
const
formRef
=
ref
(
null
);
const
imageInputRef
=
ref
(
null
);
const
isEdit
=
ref
(
false
);
const
imageItems
=
ref
([]);
const
formData
=
reactive
(
buildEmptyFormData
());
const
rules
=
{
phoneNumber
:
[{
required
:
true
,
message
:
'请输入手机号'
,
trigger
:
'blur'
}],
...
...
@@ -34,30 +88,24 @@
};
/**
* 代码作用(白话):把手机号表单恢复成新增状态,避免上一次编辑数据残留到下一次打开弹窗;关联文件:F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):open('add') -> resetFormData() -> formData 默认值 -> 表单渲染。
* 代码作用(白话):把图片预览集合回写到两个 SQL 对应的表单字段,保证一个图片区域里的两张图最终能落回后端两个字段;关联文件:F:/Project/xyw_console/src/modules/shared/phone-api-client.js、F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java;关联逻辑(调用链/消息链/数据流):imageItems -> syncImagesToFormData() -> formData.imageAttachment1/2 -> submitAfterValidate()。
*/
function
syncImagesToFormData
()
{
formData
.
imageAttachment1
=
imageItems
.
value
[
0
]?.
url
||
''
;
formData
.
imageAttachment2
=
imageItems
.
value
[
1
]?.
url
||
''
;
}
/**
* 代码作用(白话):把手机号表单恢复成新增状态,避免上一次编辑数据残留到下一次打开弹窗;关联文件:F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):open('add') -> resetFormData() -> formData 默认值和图片区域清空 -> 表单渲染。
*/
function
resetFormData
()
{
formData
.
id
=
null
;
formData
.
phoneNumber
=
''
;
formData
.
realPerson
=
''
;
formData
.
iccid
=
''
;
formData
.
city
=
''
;
formData
.
cardStatus
=
'正常'
;
formData
.
cardUsageLocation
=
''
;
formData
.
wecom
=
false
;
formData
.
wechat
=
false
;
formData
.
outboundCall
=
false
;
formData
.
douyinAccount
=
''
;
formData
.
miniProgramFiling
=
false
;
formData
.
packageChange5yuan
=
false
;
formData
.
channelOperator
=
''
;
formData
.
numberStatus
=
true
;
formData
.
linkedWecom
=
''
;
formData
.
numberRetentionStatus
=
true
;
Object
.
assign
(
formData
,
buildEmptyFormData
());
imageItems
.
value
=
[];
syncImagesToFormData
();
}
/**
* 代码作用(白话):把外层传入的手机号记录回填到表单里,兼容列表记录和详情记录两种字段名
;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js;关联逻辑(调用链/消息链/数据流):编辑按钮/详情抽屉编辑 -> PhoneAddDialogAPI.open('edit', record) -> populateFormData() -> 表单
回填。
* 代码作用(白话):把外层传入的手机号记录回填到表单里,兼容列表记录和详情记录两种字段名
,并把两张旧图按一个图片区域回显;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):编辑按钮/详情抽屉编辑 -> PhoneAddDialogAPI.open('edit', record) -> populateFormData() -> 表单与图片预览
回填。
*/
function
populateFormData
(
record
)
{
formData
.
id
=
record
.
id
||
null
;
...
...
@@ -77,10 +125,12 @@
formData
.
numberStatus
=
record
.
numberStatus
!==
false
;
formData
.
linkedWecom
=
record
.
linkedWecom
||
''
;
formData
.
numberRetentionStatus
=
record
.
numberRetentionStatus
!==
false
;
imageItems
.
value
=
buildImageItemsFromRecord
(
record
);
syncImagesToFormData
();
}
/**
* 代码作用(白话):按新增或编辑模式打开手机号弹窗,并决定是重置表单还是回填旧记录;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):新增/编辑按钮 -> PhoneAddDialogAPI.open() -> open() -> visible/formData 更新。
* 代码作用(白话):按新增或编辑模式打开手机号弹窗,并决定是重置表单还是回填旧记录;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):新增/编辑按钮 -> PhoneAddDialogAPI.open() -> open() -> visible/formData
/imageItems
更新。
*/
function
open
(
mode
=
'add'
,
record
=
null
)
{
visible
.
value
=
true
;
...
...
@@ -102,7 +152,57 @@
}
/**
* 代码作用(白话):在表单校验通过后调用外层统一保存动作,并在成功后刷新列表;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):保存按钮 -> handleSubmit() -> PhoneAddDialogActions.save() -> afterSave() -> phone 列表刷新。
* 代码作用(白话):触发系统文件选择框,让图片上传区域保持一个按钮入口而不是暴露两个底层字段;关联文件:F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):点击“选择图片” -> triggerImageSelect() -> hidden file input -> handleImageFileChange()。
*/
function
triggerImageSelect
()
{
imageInputRef
.
value
?.
click
();
}
/**
* 代码作用(白话):读取用户一次多选的图片文件,限制最多 2 张,并生成可预览可保存的图片集合;关联文件:F:/Project/xyw_console/src/modules/shared/phone-api-client.js、F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java;关联逻辑(调用链/消息链/数据流):file input change -> handleImageFileChange() -> readFileAsDataUrl() -> imageItems/syncImagesToFormData() -> submitAfterValidate()。
*/
async
function
handleImageFileChange
(
event
)
{
const
files
=
Array
.
from
(
event
?.
target
?.
files
||
[]);
if
(
!
files
.
length
)
{
return
;
}
if
(
imageItems
.
value
.
length
+
files
.
length
>
2
)
{
ElementPlus
.
ElMessage
.
warning
(
'最多上传 2 张图片'
);
event
.
target
.
value
=
''
;
return
;
}
try
{
const
nextItems
=
[];
for
(
let
index
=
0
;
index
<
files
.
length
;
index
+=
1
)
{
const
file
=
files
[
index
];
const
url
=
await
readFileAsDataUrl
(
file
);
nextItems
.
push
({
id
:
`
${
Date
.
now
()}
-
${
index
}
`
,
url
,
name
:
file
.
name
||
`图片
${
imageItems
.
value
.
length
+
index
+
1
}
`
});
}
imageItems
.
value
=
imageItems
.
value
.
concat
(
nextItems
).
slice
(
0
,
2
);
syncImagesToFormData
();
}
catch
(
error
)
{
ElementPlus
.
ElMessage
.
error
(
error
.
message
||
'读取图片失败,请稍后重试'
);
}
finally
{
event
.
target
.
value
=
''
;
}
}
/**
* 代码作用(白话):删除当前图片区域中的单张图片,并把剩余图片重新同步回两个 SQL 字段;关联文件:F:/Project/xyw_console/src/modules/shared/phone-api-client.js、F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java;关联逻辑(调用链/消息链/数据流):点击删除图片 -> removeImage() -> imageItems -> syncImagesToFormData() -> 保存请求。
*/
function
removeImage
(
index
)
{
imageItems
.
value
=
imageItems
.
value
.
filter
((
_
,
itemIndex
)
=>
itemIndex
!==
index
);
syncImagesToFormData
();
}
/**
* 代码作用(白话):在表单校验通过后调用外层统一保存动作,并在成功后刷新列表;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):保存按钮 -> handleSubmit() -> submitAfterValidate() -> PhoneAddDialogActions.save() -> afterSave() -> phone 列表刷新。
*/
async
function
submitAfterValidate
(
valid
)
{
if
(
!
valid
)
{
...
...
@@ -110,10 +210,8 @@
}
try
{
const
actions
=
window
.
PhoneAddDialogActions
||
{};
if
(
typeof
actions
.
save
!==
'function'
)
{
throw
new
Error
(
'手机号保存动作尚未初始化'
);
}
const
actions
=
getPhoneAddDialogActions
();
syncImagesToFormData
();
await
actions
.
save
({
...
formData
});
ElementPlus
.
ElMessage
.
success
(
formData
.
id
?
'修改成功'
:
'新增成功'
);
close
();
...
...
@@ -146,9 +244,14 @@
visible
,
isEdit
,
formRef
,
imageInputRef
,
imageItems
,
formData
,
rules
,
close
,
triggerImageSelect
,
handleImageFileChange
,
removeImage
,
handleSubmit
};
},
...
...
@@ -204,6 +307,27 @@
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="图片字段">
<div style="width: 100%; display: flex; flex-direction: column; gap: 12px;">
<input ref="imageInputRef" type="file" accept="image/*" multiple style="display: none;" @change="handleImageFileChange" />
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
<el-button type="primary" plain @click="triggerImageSelect">选择图片</el-button>
<span style="font-size: 12px; color: #6b7280;">一个图片字段区域内最多展示 2 张图,保存时会拆回两个 SQL 字段。</span>
</div>
<div style="display: flex; gap: 12px; flex-wrap: wrap; min-height: 104px;">
<div v-for="(image, index) in imageItems" :key="image.id" style="position: relative; width: 104px; height: 104px; border-radius: 12px; overflow: hidden; border: 1px solid #e5e7eb; background: #f8fafc;">
<img :src="image.url" :alt="image.name" style="width: 100%; height: 100%; object-fit: cover; display: block;" />
<button type="button" @click="removeImage(index)" style="position: absolute; top: 6px; right: 6px; width: 24px; height: 24px; border: none; border-radius: 999px; background: rgba(17, 24, 39, 0.72); color: #ffffff; cursor: pointer; line-height: 1;">x</button>
</div>
<div v-if="!imageItems.length" style="width: 104px; height: 104px; border-radius: 12px; border: 1px dashed #cbd5e1; background: #f8fafc; display: flex; align-items: center; justify-content: center; color: #94a3b8; font-size: 12px; text-align: center; padding: 8px;">暂无图片<br/>占位图</div>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
<el-divider border-style="dashed" style="margin: 16px 0;"></el-divider>
<div style="font-size: 15px; font-weight: 600; color: #303133; margin: 0 0 16px 0; line-height: 1;">状态与保留</div>
<el-row :gutter="24">
...
...
src/modules/phone/phone-list-runtime.js
View file @
f2fe141d
(
function
attachPhoneListRuntime
()
{
const
PHONE_TABLE_COLUMN_WIDTHS
=
[
'48px'
,
'calc((100% - 48px) * 0.12)'
,
'calc((100% - 48px) * 0.10)'
,
'calc((100% - 48px) * 0.10)'
,
'calc((100% - 48px) * 0.10)'
,
'calc((100% - 48px) * 0.10)'
,
'calc((100% - 48px) * 0.10)'
,
'calc((100% - 48px) * 0.10)'
,
'calc((100% - 48px) * 0.16)'
,
'calc((100% - 48px) * 0.12)'
];
/**
* 代码作用(白话):把各种日期输入统一转成可比较的时间戳,方便手机号卡页面按更新时间筛选;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):表格记录.updatedAt -> toTimestamp() -> matchesDateRange() -> filteredRecords
()
。
* 代码作用(白话):把各种日期输入统一转成可比较的时间戳,方便手机号卡页面按更新时间筛选;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):表格记录.updatedAt -> toTimestamp() -> matchesDateRange() -> filteredRecords。
*/
function
toTimestamp
(
value
)
{
if
(
!
value
)
{
...
...
@@ -29,7 +16,7 @@
}
/**
* 代码作用(白话):判断一条手机号记录是否落在当前日期筛选范围内,没有选择日期时直接放行;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):filterDate -> matchesDateRange() -> filteredRecords() -> visibleRows
()
。
* 代码作用(白话):判断一条手机号记录是否落在当前日期筛选范围内,没有选择日期时直接放行;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):filterDate -> matchesDateRange() -> filteredRecords() -> visibleRows。
*/
function
matchesDateRange
(
record
,
rangeValue
)
{
if
(
!
Array
.
isArray
(
rangeValue
)
||
rangeValue
.
length
!==
2
)
{
...
...
@@ -63,7 +50,7 @@
notifyError
:
{
type
:
Function
,
required
:
true
}
},
/**
* 代码作用(白话):初始化手机号卡页面的分页、筛选、
选中和操作入口
,让壳层传入的数据与动作变成真正可交互的表格;关联文件: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() -> 列表渲染/弹窗/抽屉/删除。
* 代码作用(白话):初始化手机号卡页面的分页、筛选、
全字段渲染和图片列辅助能力
,让壳层传入的数据与动作变成真正可交互的表格;关联文件: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
;
...
...
@@ -153,10 +140,10 @@
record
.
realPerson
,
record
.
city
,
record
.
carrier
,
record
.
project
,
record
.
owner
,
record
.
usageLocation
,
record
.
iccid
record
.
iccid
,
record
.
douyinAccount
,
record
.
linkedWecom
]
.
filter
(
Boolean
)
.
some
((
value
)
=>
String
(
value
).
toLowerCase
().
includes
(
query
));
...
...
@@ -225,6 +212,49 @@
}
/**
* 代码作用(白话):把真假值字段翻译成人能看懂的文案,避免列表里直接出现 true/false;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):normalizePhoneRecord() -> formatBooleanText() -> 手机号卡全字段列表列渲染。
*/
function
formatBooleanText
(
value
,
trueText
=
'是'
,
falseText
=
'否'
)
{
return
value
?
trueText
:
falseText
;
}
/**
* 代码作用(白话):把手机号状态和号码保留状态统一翻译成业务文案,方便列表按库表字段直观看;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):normalizePhoneRecord() -> formatStatusText()/formatRetentionText() -> 状态列渲染。
*/
function
formatStatusText
(
value
)
{
return
value
?
'正常'
:
'异常'
;
}
/**
* 代码作用(白话):把号码保留布尔值翻译成“保留/注销”,避免业务同学自己推断 0 和 1;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):normalizePhoneRecord() -> formatRetentionText() -> 号码保留状态列。
*/
function
formatRetentionText
(
value
)
{
return
value
?
'保留'
:
'注销'
;
}
/**
* 代码作用(白话):从统一图片集合里挑出当前记录应展示的最多两张缩略图,让两个 SQL 图片字段以前端一个图片列表现;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):normalizePhoneRecord() -> getDisplayImages() -> 列表图片列双缩略图渲染。
*/
function
getDisplayImages
(
record
)
{
return
Array
.
isArray
(
record
.
images
)
?
record
.
images
.
slice
(
0
,
2
)
:
[];
}
/**
* 代码作用(白话):给没有图片或图片打不开的情况显示统一占位块,避免列表出现空洞或破图;关联文件:F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):getDisplayImages() -> handleImageError() -> 图片列占位图回退。
*/
function
handleImageError
(
event
)
{
const
image
=
event
?.
target
;
if
(
!
image
)
{
return
;
}
image
.
style
.
display
=
'none'
;
const
placeholder
=
image
.
nextElementSibling
;
if
(
placeholder
)
{
placeholder
.
style
.
display
=
'flex'
;
}
}
/**
* 代码作用(白话):把某一行设为当前选中手机号,并同步到壳层全局状态,方便详情抽屉和编辑弹窗拿到当前对象;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js;关联逻辑(调用链/消息链/数据流):点击表格手机号/操作按钮 -> focusRow() -> globalState.selectedBySource.phone -> openDrawer()/详情联动。
*/
function
focusRow
(
recordId
)
{
...
...
@@ -315,7 +345,6 @@
syncMountState
();
return
{
PHONE_TABLE_COLUMN_WIDTHS
,
carrierOptions
,
outboundOptions
,
currentPage
,
...
...
@@ -324,6 +353,11 @@
checkedIds
,
isRowSelected
,
getStatusClass
,
formatBooleanText
,
formatStatusText
,
formatRetentionText
,
getDisplayImages
,
handleImageError
,
focusRow
,
resetTableState
,
handleRowAction
,
...
...
@@ -399,33 +433,31 @@
class="domain-table domain-table-refined"
@selection-change="handleSelectionChange"
:header-cell-style="{ background: '#fafafa', color: '#4b5563', fontWeight: 500, padding: '12px 20px', borderBottom: '1px solid #f3f4f6' }"
:cell-style="{ padding: '12px 20px', borderBottom: '1px solid #f3f4f6', color: '#111827' }"
:cell-style="{ padding: '12px 20px', borderBottom: '1px solid #f3f4f6', color: '#111827'
, whiteSpace: 'nowrap'
}"
>
<el-table-column type="selection" width="50" />
<el-table-column label="
手机号" min-width="14
0">
<el-table-column type="selection" width="50"
fixed="left"
/>
<el-table-column label="
图片" min-width="16
0">
<template #default="scope">
<a href="#" class="domain-link" @click.prevent="focusRow(scope.row.id)">{{ scope.row.phone }}</a>
<div class="cell-sub">{{ scope.row.project || '-' }}</div>
<div style="display: flex; gap: 8px; align-items: center; min-height: 52px;">
<template v-if="getDisplayImages(scope.row).length">
<div v-for="(image, imageIndex) in getDisplayImages(scope.row)" :key="scope.row.id + '-image-' + imageIndex" style="width: 52px; height: 52px; border-radius: 10px; overflow: hidden; border: 1px solid #e5e7eb; background: #f8fafc; position: relative; flex: 0 0 auto;">
<img :src="image" alt="号卡图片缩略图" style="width: 100%; height: 100%; object-fit: cover; display: block;" @error="handleImageError" />
<div style="display: none; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 11px; color: #94a3b8; background: #f8fafc;">占位图</div>
</div>
</template>
</el-table-column>
<el-table-column label="实名人" min-width="120">
<template #default="scope">
<div class="date-stack">
<span>{{ scope.row.realPerson }}</span>
<div class="cell-sub">{{ scope.row.owner || '-' }}</div>
<div v-else style="width: 52px; height: 52px; border-radius: 10px; border: 1px dashed #cbd5e1; background: #f8fafc; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #94a3b8; flex: 0 0 auto;">占位图</div>
</div>
</template>
</el-table-column>
<el-table-column label="
归属地" min-width="12
0">
<el-table-column label="
手机号" min-width="15
0">
<template #default="scope">
<div class="date-stack">
<span>{{ scope.row.city }}</span>
<div class="cell-sub">{{ scope.row.usageLocation || '-' }}</div>
</div>
<a href="#" class="domain-link" @click.prevent="focusRow(scope.row.id)">{{ scope.row.phone || '-' }}</a>
</template>
</el-table-column>
<el-table-column property="carrier" label="运营商" min-width="100" />
<el-table-column label="号卡状态" min-width="100">
<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="120">
<template #default="scope">
<span class="status-chip-wrap">
<span class="status-dot" :class="getStatusClass(scope.row.status)"></span>
...
...
@@ -433,29 +465,35 @@
</span>
</template>
</el-table-column>
<el-table-column label="微信开通" min-width="100">
<template #default="scope">
<span class="status-chip-wrap">
<span class="status-dot" :class="getStatusClass(scope.row.wechatStatus)"></span>
<span>{{ scope.row.wechatStatus }}</span>
</span>
</template>
<el-table-column property="cardUsageLocation" label="卡使用地点" min-width="160" />
<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">
<template #default="scope">
<span class="status-chip-wrap">
<span class="status-dot" :class="getStatusClass(scope.row.outbound)"></span>
<span>{{ scope.row.outbound }}</span>
</span>
</template>
<el-table-column label="微信" min-width="100">
<template #default="scope">{{ formatBooleanText(scope.row.wechat, '已开通', '未开通') }}</template>
</el-table-column>
<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="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="120">
<template #default="scope">{{ formatStatusText(scope.row.numberStatus) }}</template>
</el-table-column>
<el-table-column property="updatedAt" label="更新时间" min-width="160" />
<el-table-column label="操作" width="160" fixed="right">
<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">
<template #default="scope">
<div class="action-cluster" style="display: flex; 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:
4px; font-weight: 500
;">查看</button>
<button class="action-pill" type="button" @click.stop="handleRowAction('edit', scope.row.id)" style="border: none; background: transparent; color: #4b5563; cursor: pointer; padding:
4px
;">编辑</button>
<button class="action-pill" type="button" @click.stop="handleRowAction('delete', scope.row.id)" style="border: none; background: transparent; color: #ef4444; cursor: pointer; padding:
4px
;">删除</button>
<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>
<button class="action-pill" type="button" @click.stop="handleRowAction('edit', scope.row.id)" style="border: none; background: transparent; color: #4b5563; cursor: pointer; padding:
0; font-weight: 500; line-height: 1
;">编辑</button>
<button class="action-pill" type="button" @click.stop="handleRowAction('delete', scope.row.id)" style="border: none; background: transparent; color: #ef4444; cursor: pointer; padding:
0; font-weight: 500; line-height: 1
;">删除</button>
</div>
</template>
</el-table-column>
...
...
@@ -502,3 +540,7 @@
mountPhoneListComponent
};
})();
src/modules/shared/record-adapters.js
View file @
f2fe141d
...
...
@@ -55,48 +55,105 @@
}
/**
* 代码作用(白话):把手机号真假值统一转成布尔值,避免不同接口口径导致列表和弹窗判断不一致;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):后端字段原值 -> toBooleanFlag() -> normalizePhoneRecord() -> 列表显示/弹窗回填。
*/
function
toBooleanFlag
(
value
,
defaultValue
)
{
if
(
value
===
null
||
value
===
undefined
||
value
===
''
)
{
return
defaultValue
;
}
if
(
value
===
true
||
value
===
1
||
value
===
'1'
||
value
===
'true'
||
value
===
'enabled'
)
{
return
true
;
}
if
(
value
===
false
||
value
===
0
||
value
===
'0'
||
value
===
'false'
||
value
===
'disabled'
)
{
return
false
;
}
return
Boolean
(
value
);
}
/**
* 代码作用(白话):把两个 SQL 图片字段整理成前端统一图片集合,让列表和弹窗都按一个图片字段来理解;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):image_attachment_1/image_attachment_2 -> buildPhoneImages() -> normalizePhoneRecord() -> 图片列/图片上传区域。
*/
function
buildPhoneImages
(
source
)
{
return
[
source
.
imageAttachment1
,
source
.
image_attachment_1
,
source
.
imageAttachment2
,
source
.
image_attachment_2
]
.
filter
(
Boolean
)
.
filter
((
value
,
index
,
array
)
=>
array
.
indexOf
(
value
)
===
index
)
.
slice
(
0
,
2
);
}
/**
* 代码作用(白话):把手机号原始记录整理成列表、详情抽屉、编辑弹窗共用的统一字段,避免每个组件自己猜字段名;关联文件: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 注入 -> 手机号列表/详情/编辑弹窗。
*/
function
normalizePhoneRecord
(
row
)
{
const
source
=
row
||
{};
const
cardStatus
=
source
.
cardStatus
||
source
.
status
||
'正常'
;
const
phoneNumber
=
source
.
phoneNumber
||
source
.
phone
||
''
;
const
channelOperator
=
source
.
channelOperator
||
source
.
carrier
||
''
;
const
updatedAt
=
source
.
updatedAt
||
source
.
updateTime
||
''
;
const
usageLocation
=
source
.
cardUsageLocation
||
source
.
usageLocation
||
''
;
const
wechatStatus
=
source
.
wechatStatus
||
(
source
.
numberStatus
===
false
?
'异常停机'
:
'正常'
);
const
outbound
=
source
.
outbound
||
(
source
.
outboundCall
?
'可外呼'
:
'不可外呼'
);
const
images
=
buildPhoneImages
(
source
);
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
||
''
;
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
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
||
''
;
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
||
''
,
phone
:
phoneNumber
,
phoneNumber
,
realPerson
:
source
.
realPerson
||
''
,
phone_number
:
phoneNumber
,
realPerson
,
real_person
:
realPerson
,
city
:
source
.
city
||
''
,
usageLocation
,
cardUsageLocation
:
usageLocation
,
card_usage_location
:
usageLocation
,
carrier
:
channelOperator
,
channelOperator
,
channel_operator
:
channelOperator
,
status
:
cardStatus
,
cardStatus
,
card_status
:
cardStatus
,
wechatStatus
,
outbound
,
outboundCall
:
source
.
outboundCall
===
true
||
source
.
outbound
===
'available'
||
source
.
outbound
===
'可外呼'
,
wechat
:
source
.
wechat
===
true
||
source
.
wechat
===
'enabled'
,
wecom
:
source
.
wecom
===
true
||
source
.
wecom
===
'enabled'
,
outboundCall
:
outboundEnabled
,
outbound_call
:
outboundEnabled
,
wechat
:
wechatEnabled
,
wecom
:
wecomEnabled
,
updatedAt
,
updated_at
:
source
.
updated_at
||
updatedAt
,
updateTime
:
source
.
updateTime
||
updatedAt
,
owner
:
source
.
owner
||
source
.
realPerson
||
''
,
owner
:
source
.
owner
||
realPerson
,
project
:
source
.
project
||
source
.
note
||
'暂无备注'
,
note
:
source
.
note
||
source
.
project
||
''
,
iccid
:
source
.
iccid
||
''
,
douyinAccount
:
source
.
douyinAccount
||
''
,
miniProgramFiling
:
source
.
miniProgramFiling
===
true
,
packageChange5yuan
:
source
.
packageChange5yuan
===
true
,
numberStatus
:
source
.
numberStatus
!==
false
,
linkedWecom
:
source
.
linkedWecom
||
''
,
numberRetentionStatus
:
source
.
numberRetentionStatus
!==
false
douyinAccount
,
douyin_account
:
douyinAccount
,
miniProgramFiling
:
miniProgramFiled
,
mini_program_filing
:
miniProgramFiled
,
packageChange5yuan
:
packageChangeEnabled
,
package_change_5yuan
:
packageChangeEnabled
,
numberStatus
,
mobile_status
:
numberStatus
,
linkedWecom
,
linked_wecom
:
linkedWecom
,
numberRetentionStatus
,
number_retention_status
:
numberRetentionStatus
};
}
...
...
start-backend.bat
deleted
100644 → 0
View file @
85fee5d2
@echo off
chcp 65001 >nul
set "BACKEND_DIR=%~dp0backend"
set "PROJECT_JAVA_HOME=F:\jdk-17.0.12_windows-x64_bin\jdk-17.0.12"
set "XYW_DB_USERNAME=root"
set "XYW_DB_PASSWORD=root"
echo ===================================================
echo [Starting Backend]
echo Backend Dir: %BACKEND_DIR%
echo Backend URL: http://localhost:8080
echo JAVA_HOME: %PROJECT_JAVA_HOME%
echo DB User: %XYW_DB_USERNAME%
echo ===================================================
echo.
if not exist "%BACKEND_DIR%\pom.xml" (
echo [Error] Backend pom.xml not found: %BACKEND_DIR%\pom.xml
pause
exit /b 1
)
if not exist "%PROJECT_JAVA_HOME%\bin\java.exe" (
echo [Error] java.exe not found: %PROJECT_JAVA_HOME%\bin\java.exe
pause
exit /b 1
)
if not exist "%PROJECT_JAVA_HOME%\bin\javac.exe" (
echo [Error] javac.exe not found: %PROJECT_JAVA_HOME%\bin\javac.exe
pause
exit /b 1
)
set "JAVA_HOME=%PROJECT_JAVA_HOME%"
set "PATH=%JAVA_HOME%\bin;%PATH%"
java -version
cd /d "%BACKEND_DIR%"
echo (Press Ctrl+C or close this window to stop backend)
mvn spring-boot:run
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment