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
1b367d0d
Commit
1b367d0d
authored
Jul 06, 2026
by
DaiJiezhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refine phone list workspace
parent
00d54727
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
284 additions
and
23 deletions
+284
-23
app-v2.js
app-v2.js
+1
-0
PhoneMybatisConfig.java
...java/com/xyw/console/phone/config/PhoneMybatisConfig.java
+13
-0
index.html
index.html
+1
-0
phone-add-dialog.js
src/modules/phone/phone-add-dialog.js
+1
-1
phone-image-cell.js
src/modules/phone/phone-image-cell.js
+99
-0
phone-list-runtime.js
src/modules/phone/phone-list-runtime.js
+68
-21
styles-v2.css
styles-v2.css
+101
-1
No files found.
app-v2.js
View file @
1b367d0d
...
...
@@ -539,6 +539,7 @@ function renderExternalRuntime(moduleKey) {
globalState
:
state
,
loadPhoneRows
,
deletePhoneRecord
,
savePhoneRecord
,
openAddDialog
:
(
mode
,
record
)
=>
{
window
.
PhoneAddDialogAPI
?.
open
?.(
mode
,
record
);
},
...
...
backend/src/main/java/com/xyw/console/phone/config/PhoneMybatisConfig.java
View file @
1b367d0d
...
...
@@ -72,4 +72,17 @@ public class PhoneMybatisConfig {
public
SqlSessionTemplate
phoneSqlSessionTemplate
(
@Qualifier
(
"phoneSqlSessionFactory"
)
SqlSessionFactory
phoneSqlSessionFactory
)
{
return
new
SqlSessionTemplate
(
phoneSqlSessionFactory
);
}
@Bean
public
org
.
springframework
.
boot
.
ApplicationRunner
phoneDatabaseInitializer
(
@Qualifier
(
"phoneDataSource"
)
DataSource
phoneDataSource
)
{
return
args
->
{
try
(
java
.
sql
.
Connection
conn
=
phoneDataSource
.
getConnection
();
java
.
sql
.
Statement
stmt
=
conn
.
createStatement
())
{
stmt
.
execute
(
"ALTER TABLE wx_phone MODIFY COLUMN image_attachment_1 LONGTEXT"
);
stmt
.
execute
(
"ALTER TABLE wx_phone MODIFY COLUMN image_attachment_2 LONGTEXT"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
};
}
}
index.html
View file @
1b367d0d
...
...
@@ -91,6 +91,7 @@
<script
src=
"src/modules/wechat/wechat-list-runtime.js"
></script>
<script
src=
"src/modules/phone/phone-add-dialog.js"
></script>
<script
src=
"src/modules/phone/phone-detail-drawer.js"
></script>
<script
src=
"src/modules/phone/phone-image-cell.js"
></script>
<script
src=
"src/modules/phone/phone-list-runtime.js"
></script>
<script
src=
"src/modules/shared/record-adapters.js"
></script>
<script
src=
"src/modules/shared/wechat-api-client.js"
></script>
...
...
src/modules/phone/phone-add-dialog.js
View file @
1b367d0d
...
...
@@ -289,7 +289,7 @@
<el-option label="中国联通" value="中国联通"></el-option>
<el-option label="中国电信" value="中国电信"></el-option>
<el-option label="中国广电" value="中国广电"></el-option>
<el-option label="
虚商" value="虚商
"></el-option>
<el-option label="
亮哥渠道(虚拟)" value="亮哥渠道(虚拟)
"></el-option>
</el-select>
</el-form-item>
</el-col>
...
...
src/modules/phone/phone-image-cell.js
0 → 100644
View file @
1b367d0d
(
function
attachPhoneImageCell
()
{
const
{
ref
}
=
window
.
Vue
;
/**
* 代码作用(白话):手机号卡列表图片列的图片单元组件,负责缩略图展示、占位图回退、预览与删除悬浮操作;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):PhoneListTable 图片列 -> <phone-card-image-cell> -> 预览/删除回调 -> 列表刷新。
*/
function
handleImageError
(
event
)
{
const
image
=
event
?.
target
;
if
(
!
image
)
{
return
;
}
image
.
style
.
display
=
'none'
;
const
placeholder
=
image
.
nextElementSibling
;
if
(
placeholder
)
{
placeholder
.
style
.
display
=
'flex'
;
}
}
const
PhoneCardImageCell
=
{
name
:
'PhoneCardImageCell'
,
props
:
{
images
:
{
type
:
Array
,
default
:
()
=>
[]
},
recordId
:
{
type
:
[
String
,
Number
],
default
:
null
},
clearImage
:
{
type
:
Function
,
default
:
null
}
},
setup
(
props
)
{
const
previewVisible
=
ref
(
false
);
const
previewIndex
=
ref
(
0
);
function
openPreview
(
index
)
{
previewIndex
.
value
=
index
;
previewVisible
.
value
=
true
;
}
function
closePreview
()
{
previewVisible
.
value
=
false
;
}
function
handleDelete
(
index
)
{
if
(
typeof
props
.
clearImage
===
'function'
)
{
props
.
clearImage
(
index
);
}
}
return
{
previewVisible
,
previewIndex
,
openPreview
,
closePreview
,
handleDelete
,
handleImageError
};
},
template
:
`
<div class="phone-image-cell">
<template v-if="images && images.length">
<div
v-for="(image, index) in images"
:key="(recordId ?? 'row') + '-img-' + index"
class="phone-image-thumb"
>
<img :src="image" alt="号卡图片缩略图" class="phone-image-img" @error="handleImageError" />
<div class="phone-image-fallback">占位图</div>
<div class="phone-image-actions" @click.stop @mousedown.stop @dblclick.stop>
<button
type="button"
class="phone-image-action phone-image-action--preview"
title="预览图片"
@click.stop="openPreview(index)"
@mousedown.stop
><svg viewBox="0 0 1024 1024" width="16" height="16"><path fill="currentColor" d="M515.2 224c-307.2 0-492.8 313.6-492.8 313.6s214.4 304 492.8 304 492.8-304 492.8-304S822.4 224 515.2 224zM832 652.8c-102.4 86.4-211.2 140.8-320 140.8s-217.6-51.2-320-140.8c-35.2-32-70.4-64-99.2-99.2-6.4-6.4-9.6-12.8-16-19.2 3.2-6.4 9.6-12.8 12.8-19.2 25.6-35.2 57.6-70.4 92.8-102.4 99.2-89.6 208-144 329.6-144s230.4 54.4 329.6 144c35.2 32 64 67.2 92.8 102.4 3.2 6.4 9.6 12.8 12.8 19.2-3.2 6.4-9.6 12.8-16 19.2C902.4 585.6 870.4 620.8 832 652.8z"/><path fill="currentColor" d="M512 345.6c-96 0-169.6 76.8-169.6 169.6 0 96 76.8 169.6 169.6 169.6 96 0 169.6-76.8 169.6-169.6C681.6 422.4 604.8 345.6 512 345.6zM512 640c-67.2 0-121.6-54.4-121.6-121.6 0-67.2 54.4-121.6 121.6-121.6 67.2 0 121.6 54.4 121.6 121.6C633.6 582.4 579.2 640 512 640z"/></svg></button>
<button
type="button"
class="phone-image-action phone-image-action--delete"
title="删除图片"
@click.stop="handleDelete(index)"
@mousedown.stop
><svg viewBox="0 0 1024 1024" width="16" height="16"><path fill="currentColor" d="M709.469091 209.454545H930.909091a34.909091 34.909091 0 0 1 0 69.818182h-81.454546v607.185455c0 56.366545-44.311273 102.632727-99.746909 102.632727H274.292364c-55.435636 0-99.746909-46.266182-99.746909-102.632727V279.272727H93.090909a34.909091 34.909091 0 0 1 0-69.818182h244.712727a186.181818 186.181818 0 0 1 371.665455 0z m-70.050909 0a116.363636 116.363636 0 0 0-231.563637 0h231.563637z m140.218182 69.818182h-535.272728v607.185455c0 18.455273 13.730909 32.814545 29.928728 32.814545h475.415272c16.174545 0 29.928727-14.359273 29.928728-32.814545V279.272727z m-418.909091 147.2a34.909091 34.909091 0 0 1 69.818182 0v338.897455a34.909091 34.909091 0 0 1-69.818182 0V426.472727z m232.727272 0a34.909091 34.909091 0 0 1 69.818182 0v338.897455a34.909091 34.909091 0 0 1-69.818182 0V426.472727z"/></svg></button>
</div>
</div>
</template>
<div v-else class="phone-image-thumb phone-image-thumb--empty">
<span class="phone-image-fallback">占位图</span>
</div>
<el-image-viewer
v-if="previewVisible"
:url-list="images"
:initial-index="previewIndex"
:z-index="3000"
:hide-on-click-modal="true"
teleported
@close="closePreview"
/>
</div>
`
};
window
.
PhoneCardImageCell
=
PhoneCardImageCell
;
})();
src/modules/phone/phone-list-runtime.js
View file @
1b367d0d
...
...
@@ -55,6 +55,7 @@
globalState
:
{
type
:
Object
,
required
:
true
},
loadPhoneRows
:
{
type
:
Function
,
required
:
true
},
deletePhoneRecord
:
{
type
:
Function
,
required
:
true
},
savePhoneRecord
:
{
type
:
Function
,
required
:
true
},
openAddDialog
:
{
type
:
Function
,
required
:
true
},
openDrawer
:
{
type
:
Function
,
required
:
true
},
getPhoneStatusDotClass
:
{
type
:
Function
,
required
:
true
},
...
...
@@ -316,6 +317,52 @@
}
/**
* 代码作用(白话):按后端 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() -> 后端全量更新。
*/
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
||
''
,
wecom
:
record
.
wecom
===
true
,
wechat
:
record
.
wechat
===
true
,
outboundCall
:
record
.
outboundCall
===
true
,
douyinAccount
:
record
.
douyinAccount
||
''
,
miniProgramFiling
:
record
.
miniProgramFiling
===
true
,
packageChange5yuan
:
record
.
packageChange5yuan
===
true
,
channelOperator
:
record
.
channelOperator
||
record
.
carrier
||
''
,
numberStatus
:
record
.
numberStatus
??
true
,
linkedWecom
:
record
.
linkedWecom
||
''
};
}
/**
* 代码作用(白话):删除列表图片列中指定位置的单张图片,复用现有保存逻辑把对应 imageAttachment1/2 置空并刷新列表;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-image-cell.js;关联逻辑(调用链/消息链/数据流):图片单元删除 icon -> handleClearImage() -> buildSavePayload() -> props.savePhoneRecord() -> loadAllRecords()。
*/
async
function
handleClearImage
(
record
,
imageIndex
)
{
if
(
!
window
.
confirm
(
'确认删除这张图片吗?'
))
{
return
;
}
const
remaining
=
getDisplayImages
(
record
).
filter
((
_
,
idx
)
=>
idx
!==
imageIndex
);
const
payload
=
buildSavePayload
(
record
,
remaining
);
try
{
await
props
.
savePhoneRecord
(
payload
);
if
(
window
.
ElementPlus
?.
ElMessage
)
{
window
.
ElementPlus
.
ElMessage
.
success
(
'图片已删除'
);
}
await
loadAllRecords
();
}
catch
(
error
)
{
props
.
notifyError
(
error
.
message
||
'删除图片失败,请稍后重试'
);
}
}
/**
* 代码作用(白话):把某一行设为当前选中手机号,并同步到壳层全局状态,方便详情抽屉和编辑弹窗拿到当前对象;关联文件: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
)
{
...
...
@@ -425,6 +472,7 @@
formatRetentionText
,
getDisplayImages
,
handleImageError
,
handleClearImage
,
focusRow
,
resetTableState
,
handleRowAction
,
...
...
@@ -523,23 +571,19 @@
:data="visibleRows"
row-key="id"
style="width: 100%"
class="domain-table domain-table-refined"
class="domain-table domain-table-refined
phone-table-header-nowrap
"
@selection-change="handleSelectionChange"
:header-cell-style="{ background: 'linear-gradient(180deg, #f9fafb 0%, #f3f4f6 100%)', color: '#374151', fontWeight: 600, padding: '1
4px 20px', borderBottom: '1px solid #e5e7eb
' }"
:cell-style="{ padding: '1
4px 20
px', borderBottom: '1px solid #f3f4f6', color: '#111827', whiteSpace: 'nowrap' }"
:header-cell-style="{ background: 'linear-gradient(180deg, #f9fafb 0%, #f3f4f6 100%)', color: '#374151', fontWeight: 600, padding: '1
0px 12px', borderBottom: '1px solid #e5e7eb', whiteSpace: 'nowrap
' }"
:cell-style="{ padding: '1
0px 12
px', borderBottom: '1px solid #f3f4f6', color: '#111827', whiteSpace: 'nowrap' }"
>
<el-table-column type="selection" width="50" fixed="left" />
<el-table-column label="图片" min-width="160">
<template #default="scope">
<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>
<div v-else style="width: 52px; height: 52px; border-radius: 10px; border: 1px dashed #cbd5e1; background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); display: flex; align-items: center; justify-content: center; font-size: 11px; color: #94a3b8; flex: 0 0 auto;">占位图</div>
</div>
<phone-card-image-cell
:images="getDisplayImages(scope.row)"
:record-id="scope.row.id"
:clear-image="(imageIndex) => handleClearImage(scope.row, imageIndex)"
/>
</template>
</el-table-column>
<el-table-column label="手机号" min-width="150">
...
...
@@ -549,18 +593,18 @@
</el-table-column>
<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>
<span :title="scope.row.realPerson || '-'" style="
white-space: nowrap;">{{ 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>
<span :title="scope.row.iccid || '-'" style="
white-space: nowrap;">{{ 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>
<span :title="scope.row.city || '-'" style="
white-space: nowrap;">{{ scope.row.city || '-'
}}</span>
</template>
</el-table-column>
<el-table-column label="号卡状态" min-width="120">
...
...
@@ -573,7 +617,7 @@
</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="
10
0">
<el-table-column label="微信" min-width="
9
0">
<template #default="scope">{{ formatBooleanText(scope.row.wechat, '已开通', '未开通') }}</template>
</el-table-column>
<el-table-column label="可外呼" min-width="100">
...
...
@@ -581,24 +625,24 @@
</el-table-column>
<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>
<span :title="scope.row.douyinAccount || '-'" style="
white-space: nowrap;">{{ 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 label="渠道运营商" min-width="1
5
0">
<el-table-column label="渠道运营商" min-width="1
7
0">
<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>
<span :title="scope.row.channelOperator || '-'" style="
white-space: nowrap;">{{ scope.row.channelOperator || '-'
}}</span>
</template>
</el-table-column>
<el-table-column label="号码状态" min-width="120">
<template #default="scope">{{ formatStatusText(scope.row.numberStatus ?? scope.row.mobile_status ?? scope.row.number_status) }}</template>
</el-table-column>
<el-table-column label="绑定企业微信" min-width="
18
0">
<el-table-column label="绑定企业微信" min-width="
26
0">
<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>
<span :title="scope.row.linkedWecom || '-'" style="
white-space: nowrap;">{{ scope.row.linkedWecom || '-'
}}</span>
</template>
</el-table-column> <el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
...
...
@@ -644,6 +688,9 @@
if
(
window
.
ElementPlus
)
{
app
.
use
(
window
.
ElementPlus
);
}
if
(
window
.
PhoneCardImageCell
)
{
app
.
component
(
'PhoneCardImageCell'
,
window
.
PhoneCardImageCell
);
}
app
.
mount
(
options
.
mountTarget
);
return
app
;
}
...
...
styles-v2.css
View file @
1b367d0d
/* Import original styles first */
/* Import original styles first */
@import
url('styles.css')
;
/* Overrides for right side replication */
...
...
@@ -1229,3 +1229,103 @@
.phone-date-range
.el-range-separator
{
padding
:
0
2px
!important
;
}
.phone-table-header-nowrap
.cell
{
white-space
:
nowrap
!important
;
text-overflow
:
clip
!important
;
padding
:
0
8px
!important
;
}
/* Phone card image column cell: thumbnail + hover preview/delete actions */
.phone-image-cell
{
display
:
flex
;
gap
:
8px
;
align-items
:
center
;
min-height
:
52px
;
}
.phone-image-thumb
{
position
:
relative
;
width
:
52px
;
height
:
52px
;
border-radius
:
10px
;
overflow
:
hidden
;
border
:
1px
solid
#e5e7eb
;
background
:
#f8fafc
;
flex
:
0
0
auto
;
}
.phone-image-thumb--empty
{
border
:
1px
dashed
#cbd5e1
;
background
:
linear-gradient
(
135deg
,
#f8fafc
0%
,
#f1f5f9
100%
);
}
.phone-image-img
{
width
:
100%
;
height
:
100%
;
object-fit
:
cover
;
display
:
block
;
}
.phone-image-fallback
{
display
:
none
;
align-items
:
center
;
justify-content
:
center
;
width
:
100%
;
height
:
100%
;
font-size
:
11px
;
color
:
#94a3b8
;
background
:
#f8fafc
;
}
.phone-image-thumb--empty
.phone-image-fallback
{
display
:
flex
;
}
.phone-image-actions
{
position
:
absolute
;
inset
:
0
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
6px
;
background
:
rgba
(
17
,
24
,
39
,
0.5
);
opacity
:
0
;
transition
:
opacity
0.18s
ease
;
}
.phone-image-thumb
:hover
.phone-image-actions
{
opacity
:
1
;
}
.phone-image-action
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
width
:
26px
;
height
:
26px
;
padding
:
0
;
border
:
none
;
border-radius
:
6px
;
background
:
transparent
;
color
:
#bfbfbf
;
cursor
:
pointer
;
line-height
:
1
;
transition
:
color
0.15s
ease
,
background
0.15s
ease
;
}
.phone-image-action
svg
{
width
:
16px
;
height
:
16px
;
display
:
block
;
}
.phone-image-action--preview
:hover
{
color
:
#2563eb
;
background
:
rgba
(
255
,
255
,
255
,
0.18
);
}
.phone-image-action--delete
:hover
{
color
:
#ef4444
;
background
:
rgba
(
255
,
255
,
255
,
0.18
);
}
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