Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
company_app
概览
概览
详情
活动
周期分析
版本库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
毛细亚
company_app
Commits
4907f587
提交
4907f587
authored
12月 15, 2025
作者:
毛细亚
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
准备更新 7.2 版本
上级
7f2fd673
显示空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
1077 行增加
和
11 行删除
+1077
-11
game.js
src/api/game.js
+58
-0
sendGame.vue
src/views/components/quickSendGame/sendGame.vue
+51
-0
SendTransAppGame.vue
...ws/components/quickSendGame/sendGame/SendTransAppGame.vue
+21
-0
SendTransWxGame.vue
...ews/components/quickSendGame/sendGame/SendTransWxGame.vue
+447
-9
roleInfoPanel.vue
src/views/components/roleInfo/roleInfoPanel.vue
+156
-2
mentorRecord.vue
src/views/userInfo/components/gameInfo/mentorRecord.vue
+344
-0
没有找到文件。
src/api/game.js
浏览文件 @
4907f587
...
@@ -1509,3 +1509,61 @@ export function teachingVideoVideoListApi(data) {
...
@@ -1509,3 +1509,61 @@ export function teachingVideoVideoListApi(data) {
})
})
})
})
}
}
// 发送分身包
export
function
memberRegGameCloneLink
(
data
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
cross_systemRequest
({
system
:
'zhangyou'
,
api
:
'/api/member/memberRegGameCloneLink'
,
params
:
data
}).
then
((
res
)
=>
{
resolve
(
res
)
}).
catch
((
error
)
=>
{
reject
(
error
)
})
})
}
// 带教记录列表
export
function
roleTeachingList
(
data
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
cross_systemRequest
({
system
:
'zhangyou'
,
api
:
'/api/role/roleTeachingList'
,
params
:
data
}).
then
((
res
)
=>
{
resolve
(
res
)
}).
catch
((
error
)
=>
{
reject
(
error
)
})
})
}
// 新增带教
export
function
roleTeachingAdd
(
data
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
cross_systemRequest
({
system
:
'zhangyou'
,
api
:
'/api/role/roleTeachingAdd'
,
params
:
data
}).
then
((
res
)
=>
{
resolve
(
res
)
}).
catch
((
error
)
=>
{
reject
(
error
)
})
})
}
// 获取带教次数
export
function
roleTeachingNum
(
data
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
cross_systemRequest
({
system
:
'zhangyou'
,
api
:
'/api/role/roleTeachingNum'
,
params
:
data
}).
then
((
res
)
=>
{
resolve
(
res
)
}).
catch
((
error
)
=>
{
reject
(
error
)
})
})
}
src/views/components/quickSendGame/sendGame.vue
浏览文件 @
4907f587
...
@@ -97,6 +97,8 @@
...
@@ -97,6 +97,8 @@
>
>
下载二维码
下载二维码
</p>
</p>
<!-- h5 安卓游戏 IOS游戏 发送分身包 -->
<p
v-if=
"[2,3,4].includes(item.game_type)"
class=
"sendLink"
@
click=
"sendTransferCloneGame(item.game_type)"
>
发送分身包
</p>
</div>
</div>
<el-button
<el-button
slot=
"reference"
slot=
"reference"
...
@@ -426,6 +428,7 @@ import {
...
@@ -426,6 +428,7 @@ import {
getClonePackageLink
,
getClonePackageLink
,
getLandingPageConfig
,
getLandingPageConfig
,
getMemberTransStatus
,
getMemberTransStatus
,
memberRegGameCloneLink
}
from
"@/api/game"
;
}
from
"@/api/game"
;
import
{
import
{
getRecentSendLog
,
getRecentSendLog
,
...
@@ -1135,6 +1138,54 @@ export default {
...
@@ -1135,6 +1138,54 @@ export default {
);
);
this
.
showSendPage
=
true
;
this
.
showSendPage
=
true
;
},
500
),
},
500
),
// 转端发送游戏分身包 h5 安卓游戏 IOS游戏 发送分身包
async
sendTransferCloneGame
(
type
)
{
const
res
=
await
memberRegGameCloneLink
({
member_id
:
this
.
accountSelect
})
if
(
res
.
status_code
==
1
)
{
// 通过 type 判断 用 switch
switch
(
type
)
{
case
2
:
if
(
!
res
?.
data
?.
data
?.
h5_download_url
)
{
this
.
$message
.
warning
(
'H5安卓分身包链接不存在,请联系掌游配置'
)
return
}
break
case
3
:
if
(
!
res
?.
data
?.
data
?.
android_download_url
)
{
this
.
$message
.
warning
(
'安卓分身包链接不存在,请联系掌游配置'
)
return
}
break
case
4
:
if
(
!
res
?.
data
?.
data
?.
ios_download_url
)
{
this
.
$message
.
warning
(
'IOS分身包链接不存在,请联系掌游配置'
)
return
}
break
default
:
this
.
$message
.
warning
(
'不支持的游戏类型'
)
return
}
let
srt
=
''
switch
(
type
)
{
case
'android'
:
srt
=
'安卓分身包链接: '
+
res
.
data
.
data
.
android_download_url
break
case
'ios'
:
srt
=
'IOS分身包链接: '
+
res
.
data
.
data
.
ios_download_url
break
}
const
list
=
[
{
msgtype
:
'text'
,
text
:
{
content
:
srt
}
}
]
this
.
set_sendSkillMessage
(
list
)
}
else
{
this
.
$message
.
warning
(
res
.
msg
)
}
},
// 转端发送游戏二维码
// 转端发送游戏二维码
sendDownLoadQrCode
:
throttleStart
(
async
function
(
items
,
type
,
index
)
{
sendDownLoadQrCode
:
throttleStart
(
async
function
(
items
,
type
,
index
)
{
if
(
!
this
.
transMemberStatus
)
{
if
(
!
this
.
transMemberStatus
)
{
...
...
src/views/components/quickSendGame/sendGame/SendTransAppGame.vue
浏览文件 @
4907f587
...
@@ -321,11 +321,32 @@
...
@@ -321,11 +321,32 @@
}
}
const
res
=
await
getLandingPageMemberLink
(
params
)
const
res
=
await
getLandingPageMemberLink
(
params
)
if
(
res
&&
res
.
data
.
data
)
{
if
(
res
&&
res
.
data
.
data
)
{
const
responseData
=
res
.
data
.
data
// 适配新接口结构:如果有 background_imgs 数组,转换为旧结构格式
let
finalData
=
responseData
if
(
responseData
.
background_imgs
&&
responseData
.
background_imgs
.
length
>
0
)
{
// 新结构:使用第一个背景图的信息
const
firstBg
=
responseData
.
background_imgs
[
0
]
finalData
=
{
channel_qrcode
:
responseData
.
channel_qrcode
||
''
,
background_img
:
firstBg
.
background_img
||
''
,
'x-coordinate'
:
firstBg
[
'x-coordinate'
]
||
0
,
'y-coordinate'
:
firstBg
[
'y-coordinate'
]
||
0
,
background_imgs
:
responseData
.
background_imgs
// 保留原始数据
}
}
else
if
(
!
responseData
.
background_img
)
{
// 如果没有 background_img 也没有 background_imgs,保持原数据结构
finalData
=
responseData
}
this
.
loading
=
false
this
.
loading
=
false
this
.
close
()
this
.
close
()
this
.
$emit
(
'confirm'
,
res
.
data
.
data
)
this
.
$emit
(
'confirm'
,
res
.
data
.
data
)
this
.
$emit
(
'confirm'
,
finalData
)
this
.
$message
.
success
(
'发送成功'
)
this
.
$message
.
success
(
'发送成功'
)
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
this
.
loading
=
false
this
.
loading
=
false
console
.
error
(
'获取链接失败:'
,
error
)
console
.
error
(
'获取链接失败:'
,
error
)
...
...
src/views/components/quickSendGame/sendGame/SendTransWxGame.vue
浏览文件 @
4907f587
...
@@ -3,7 +3,8 @@
...
@@ -3,7 +3,8 @@
<el-drawer
<el-drawer
title=
"选择游戏"
title=
"选择游戏"
:visible=
"show"
:visible=
"show"
size=
"360px"
size=
"400px"
append-to-body
@
close=
"close"
@
close=
"close"
>
>
<el-form
ref=
"wxGameForm"
:model=
"wxGameForm"
:rules=
"wxGameRules"
label-position=
"top"
class=
"game-select-container"
>
<el-form
ref=
"wxGameForm"
:model=
"wxGameForm"
:rules=
"wxGameRules"
label-position=
"top"
class=
"game-select-container"
>
...
@@ -33,6 +34,7 @@
...
@@ -33,6 +34,7 @@
placeholder=
"请选择渠道"
placeholder=
"请选择渠道"
style=
"width: 100%"
style=
"width: 100%"
:clearable=
"true"
:clearable=
"true"
@
change=
"handleChannelChange"
>
>
<el-option
<el-option
v-for=
"(item,index) in wxGameChannelList"
v-for=
"(item,index) in wxGameChannelList"
...
@@ -43,6 +45,49 @@
...
@@ -43,6 +45,49 @@
</el-option>
</el-option>
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<!-- 选择背景图 新增背景图的选项 -->
<el-form-item
v-if=
"backgroundImgsList.length > 0"
label=
""
>
<
template
slot=
"label"
>
<p
class=
"formLabel"
>
<span
class=
"required-mark"
>
*
</span>
<span>
背景图
</span>
</p>
</
template
>
<el-radio-group
v-model=
"wxGameForm.selectedBackgroundIndex"
@
change=
"handleBackgroundChange"
>
<div
class=
"background-options"
>
<el-radio
v-for=
"(item, index) in backgroundImgsList"
:key=
"index"
:label=
"index"
class=
"background-radio-item"
>
<div
class=
"background-card"
>
<div
class=
"background-preview-wrapper"
>
<img
:ref=
"`bgImg_${index}`"
:src=
"item.background_img"
class=
"background-preview"
alt=
"背景图预览"
@
load=
"handleBackgroundImageLoad(index, item)"
>
<!-- 二维码叠加显示 -->
<img
v-if=
"channelQrcode && item.qrStyle"
:src=
"channelQrcode"
class=
"background-qrcode"
:style=
"item.qrStyle"
alt=
"二维码"
>
</div>
<!-- 左上角小眼睛图标 -->
<div
class=
"eye-icon"
@
click
.
stop=
"showPreview(item, index)"
>
<i
class=
"el-icon-view"
></i>
</div>
</div>
</el-radio>
</div>
</el-radio-group>
</el-form-item>
</el-form>
</el-form>
<span
class=
"dialog-footer rowFlex"
>
<span
class=
"dialog-footer rowFlex"
>
<el-button
class=
"btn"
@
click=
"close"
size=
"small"
>
取消
</el-button>
<el-button
class=
"btn"
@
click=
"close"
size=
"small"
>
取消
</el-button>
...
@@ -75,6 +120,38 @@
...
@@ -75,6 +120,38 @@
>
>
</div>
</div>
</div>
</div>
<!-- 大图预览弹窗 -->
<el-drawer
title=
"预览"
:visible
.
sync=
"previewVisible"
size=
"500px"
:close-on-click-modal=
"false"
append-to-body
class=
"preview-dialog"
>
<div
class=
"preview-content"
>
<div
class=
"preview-wrapper"
v-if=
"previewImageInfo"
>
<img
ref=
"previewBgImgDisplay"
:src=
"previewImageInfo.background_img"
class=
"preview-background"
alt=
"背景图"
@
load=
"handlePreviewImageLoad"
>
<img
v-if=
"previewImageInfo.channel_qrcode"
ref=
"previewQrImgDisplay"
:src=
"previewImageInfo.channel_qrcode"
class=
"preview-qrcode"
:style=
"previewQrcodeStyle"
alt=
"二维码"
>
</div>
</div>
<span
slot=
"footer"
class=
"dialog-footer"
>
<el-button
@
click=
"previewVisible = false"
>
关闭
</el-button>
</span>
</el-drawer>
</el-drawer>
</el-drawer>
</template>
</template>
...
@@ -119,11 +196,17 @@
...
@@ -119,11 +196,17 @@
wxGameForm
:
{
wxGameForm
:
{
wx_game_send_id
:
''
,
wx_game_send_id
:
''
,
wx_game_send_info
:
{},
wx_game_send_info
:
{},
wx_game_channel
:
''
wx_game_channel
:
''
,
selectedBackgroundIndex
:
0
// 选中的背景图索引
},
},
messageInfo
:
null
,
messageInfo
:
null
,
handleConfirmWithDebounce
:
{},
handleConfirmWithDebounce
:
{},
wxGameChannelList
:
[],
wxGameChannelList
:
[],
backgroundImgsList
:
[],
// 背景图列表
channelQrcode
:
''
,
// 二维码链接
previewVisible
:
false
,
// 预览弹窗显示状态
previewImageInfo
:
null
,
// 预览图片信息(包含背景图和二维码)
previewQrcodeStyle
:
{},
// 预览二维码样式
wxGameRules
:
{
wxGameRules
:
{
wx_game_send_id
:
[
wx_game_send_id
:
[
{
required
:
true
,
message
:
'请选择微信小游戏'
,
trigger
:
'change'
}
{
required
:
true
,
message
:
'请选择微信小游戏'
,
trigger
:
'change'
}
...
@@ -150,6 +233,8 @@
...
@@ -150,6 +233,8 @@
wx_game_channel
:
''
wx_game_channel
:
''
}
}
this
.
wxGameChannelList
=
[]
this
.
wxGameChannelList
=
[]
this
.
backgroundImgsList
=
[]
this
.
channelQrcode
=
''
this
.
closeMessage
()
this
.
closeMessage
()
this
.
loading
=
false
this
.
loading
=
false
// 清理图片资源
// 清理图片资源
...
@@ -320,6 +405,19 @@
...
@@ -320,6 +405,19 @@
}
}
})
})
},
},
// 处理渠道选择变化
async
handleChannelChange
(
value
)
{
if
(
!
value
)
{
// 清空渠道时,清空背景图列表
this
.
backgroundImgsList
=
[]
this
.
channelQrcode
=
''
this
.
wxGameForm
.
selectedBackgroundIndex
=
0
this
.
imageInfo
=
{}
return
}
// 选择渠道后自动请求接口
await
this
.
handleWxGameChannel
()
},
async
uploadCos
(
File
)
{
async
uploadCos
(
File
)
{
const
uploadConfig
=
{
const
uploadConfig
=
{
dir
:
'/company_wx/service/avatars/'
dir
:
'/company_wx/service/avatars/'
...
@@ -336,7 +434,22 @@
...
@@ -336,7 +434,22 @@
}
}
},
},
handleConfirm
()
{
handleConfirm
()
{
this
.
handleWxGameChannel
()
// 检查是否选择了渠道
if
(
!
this
.
wxGameForm
.
wx_game_channel
)
{
this
.
$message
.
warning
(
'请选择渠道'
)
return
}
// 如果有背景图列表,直接合成图片
if
(
this
.
backgroundImgsList
.
length
>
0
)
{
this
.
confirmAndCompose
()
}
else
{
// 如果没有背景图列表,说明接口返回的是旧格式,直接合成
if
(
this
.
imageInfo
.
background_img
)
{
this
.
confirmAndCompose
()
}
else
{
this
.
$message
.
warning
(
'请先选择渠道'
)
}
}
},
},
// 保存微信小游戏的信息
// 保存微信小游戏的信息
...
@@ -387,11 +500,37 @@
...
@@ -387,11 +500,37 @@
const
linkRes
=
await
getLandingPageMemberLink
(
params
)
const
linkRes
=
await
getLandingPageMemberLink
(
params
)
console
.
log
(
'获取到的落地页数据:'
,
linkRes
)
console
.
log
(
'获取到的落地页数据:'
,
linkRes
)
if
(
linkRes
?.
data
?.
data
)
{
if
(
linkRes
?.
data
?.
data
)
{
// 检查返回的数据结构
const
responseData
=
linkRes
.
data
.
data
console
.
log
(
'设置前的imageInfo:'
,
this
.
imageInfo
)
// 保存二维码链接
this
.
imageInfo
=
linkRes
.
data
.
data
this
.
channelQrcode
=
responseData
.
channel_qrcode
||
''
console
.
log
(
'设置后的imageInfo:'
,
this
.
imageInfo
)
await
this
.
composeImage
()
// 处理背景图列表
if
(
responseData
.
background_imgs
&&
responseData
.
background_imgs
.
length
>
0
)
{
// 如果有背景图列表,使用列表中的图片
// 初始化二维码样式
this
.
backgroundImgsList
=
responseData
.
background_imgs
.
map
(
item
=>
({
...
item
,
qrStyle
:
null
// 等待图片加载后计算
}))
// 默认选中第一个
this
.
wxGameForm
.
selectedBackgroundIndex
=
0
// 设置默认的 imageInfo
this
.
updateImageInfo
(
0
)
// 不立即合成,等待用户选择后点击确认
this
.
loading
=
false
return
}
else
{
// 如果没有背景图列表,使用默认背景图(兼容旧逻辑)
this
.
backgroundImgsList
=
[]
this
.
imageInfo
=
{
background_img
:
responseData
.
background_img
||
''
,
channel_qrcode
:
this
.
channelQrcode
,
'x-coordinate'
:
responseData
[
'x-coordinate'
]
||
0
,
'y-coordinate'
:
responseData
[
'y-coordinate'
]
||
0
}
// 不立即合成,等待用户点击确认
this
.
loading
=
false
}
}
else
{
}
else
{
throw
new
Error
(
'获取落地页数据失败'
)
throw
new
Error
(
'获取落地页数据失败'
)
}
}
...
@@ -405,9 +544,164 @@
...
@@ -405,9 +544,164 @@
},
},
close
()
{
close
()
{
this
.
closeMessage
()
this
.
closeMessage
()
// 清理背景图相关数据
this
.
backgroundImgsList
=
[]
this
.
channelQrcode
=
''
this
.
wxGameForm
.
selectedBackgroundIndex
=
0
this
.
imageInfo
=
{}
this
.
previewVisible
=
false
this
.
previewImageInfo
=
null
this
.
$emit
(
'close'
)
this
.
$emit
(
'close'
)
this
.
$emit
(
'update:show'
,
false
)
this
.
$emit
(
'update:show'
,
false
)
},
// 处理背景图选择变化
handleBackgroundChange
(
index
)
{
this
.
updateImageInfo
(
index
)
},
// 更新 imageInfo
updateImageInfo
(
index
)
{
if
(
this
.
backgroundImgsList
.
length
>
0
&&
this
.
backgroundImgsList
[
index
])
{
const
selectedBg
=
this
.
backgroundImgsList
[
index
]
this
.
imageInfo
=
{
background_img
:
selectedBg
.
background_img
,
channel_qrcode
:
this
.
channelQrcode
,
'x-coordinate'
:
selectedBg
[
'x-coordinate'
]
||
0
,
'y-coordinate'
:
selectedBg
[
'y-coordinate'
]
||
0
}
}
},
// 确认选择背景图后合成图片
async
confirmAndCompose
()
{
if
(
this
.
backgroundImgsList
.
length
===
0
)
{
// 如果没有背景图列表,直接合成(兼容旧逻辑)
await
this
.
composeImage
()
return
}
// 确保 imageInfo 已设置
if
(
!
this
.
imageInfo
.
background_img
)
{
this
.
updateImageInfo
(
this
.
wxGameForm
.
selectedBackgroundIndex
)
}
await
this
.
composeImage
()
},
// 显示大图预览
showPreview
(
item
,
index
)
{
// 构建预览图片信息
const
previewInfo
=
{
background_img
:
item
.
background_img
,
channel_qrcode
:
this
.
channelQrcode
,
'x-coordinate'
:
item
[
'x-coordinate'
]
||
0
,
'y-coordinate'
:
item
[
'y-coordinate'
]
||
0
}
this
.
previewImageInfo
=
previewInfo
this
.
previewVisible
=
true
// 重置二维码样式,等待图片加载后计算
this
.
previewQrcodeStyle
=
{}
// 等待 DOM 更新后,检查图片是否已加载
this
.
$nextTick
(()
=>
{
if
(
this
.
$refs
.
previewBgImgDisplay
)
{
const
bgImg
=
this
.
$refs
.
previewBgImgDisplay
// 如果图片已经加载完成(complete 属性为 true),直接计算样式
if
(
bgImg
.
complete
&&
bgImg
.
naturalWidth
>
0
)
{
this
.
calculatePreviewQrcodeStyle
()
}
}
})
},
// 处理预览背景图加载完成
handlePreviewImageLoad
()
{
this
.
calculatePreviewQrcodeStyle
()
},
// 计算预览二维码样式
calculatePreviewQrcodeStyle
()
{
if
(
!
this
.
previewImageInfo
||
!
this
.
$refs
.
previewBgImgDisplay
)
{
return
}
const
bgImg
=
this
.
$refs
.
previewBgImgDisplay
// 参考生成图片时的逻辑:背景图宽度固定为 750px
const
bgOriginalWidth
=
750
// 原始宽度(生成图片时的宽度)
// 等待下一帧,确保图片已渲染
this
.
$nextTick
(()
=>
{
// 获取背景图的实际显示尺寸(参考生成图片时的逻辑)
// 使用 getBoundingClientRect 获取实际渲染尺寸
const
imgRect
=
bgImg
.
getBoundingClientRect
()
const
displayWidth
=
imgRect
.
width
const
displayHeight
=
imgRect
.
height
// 如果图片还没有渲染完成,延迟重试
if
(
displayWidth
===
0
||
displayHeight
===
0
)
{
setTimeout
(()
=>
{
this
.
calculatePreviewQrcodeStyle
()
},
100
)
return
}
}
// 计算缩放比例(基于实际显示宽度与原始宽度的比例)
// 参考生成图片时:width = 750, height = (naturalHeight / naturalWidth) * width
const
scale
=
displayWidth
/
bgOriginalWidth
// 计算二维码位置和尺寸(参考生成图片时的逻辑)
// x-coordinate 和 y-coordinate 是相对于 750px 宽度的绝对位置
// 二维码直接使用坐标值,相对于背景图容器定位
const
qrSize
=
180
*
scale
const
qrLeft
=
(
this
.
previewImageInfo
[
'x-coordinate'
]
||
0
)
*
scale
const
qrTop
=
(
this
.
previewImageInfo
[
'y-coordinate'
]
||
0
)
*
scale
this
.
previewQrcodeStyle
=
{
position
:
'absolute'
,
left
:
`
${
qrLeft
}
px`
,
top
:
`
${
qrTop
}
px`
,
width
:
`
${
qrSize
}
px`
,
height
:
`
${
qrSize
}
px`
}
})
},
// 处理背景图列表中的图片加载完成
handleBackgroundImageLoad
(
index
,
item
)
{
const
bgImgRef
=
this
.
$refs
[
`bgImg_
${
index
}
`
]
// Vue 2 中,ref 可能是数组或单个元素
const
bgImg
=
Array
.
isArray
(
bgImgRef
)
?
bgImgRef
[
0
]
:
bgImgRef
if
(
!
bgImg
)
{
return
}
// 参考生成图片时的逻辑:背景图宽度固定为 750px
const
bgOriginalWidth
=
750
// 原始宽度(生成图片时的宽度)
// 等待下一帧,确保图片已渲染
this
.
$nextTick
(()
=>
{
// 获取背景图的实际显示尺寸(参考生成图片时的逻辑)
// 使用 getBoundingClientRect 获取实际渲染尺寸
const
imgRect
=
bgImg
.
getBoundingClientRect
()
const
displayWidth
=
imgRect
.
width
const
displayHeight
=
imgRect
.
height
// 计算缩放比例(基于实际显示宽度与原始宽度的比例)
// 参考生成图片时:width = 750, height = (naturalHeight / naturalWidth) * width
const
scale
=
displayWidth
/
bgOriginalWidth
// 计算二维码位置和尺寸(参考生成图片时的逻辑)
// x-coordinate 和 y-coordinate 是相对于 750px 宽度的绝对位置
// 二维码直接使用坐标值,相对于背景图容器定位
const
qrSize
=
180
*
scale
const
qrLeft
=
(
item
[
'x-coordinate'
]
||
0
)
*
scale
const
qrTop
=
(
item
[
'y-coordinate'
]
||
0
)
*
scale
// 更新二维码样式
this
.
$set
(
this
.
backgroundImgsList
[
index
],
'qrStyle'
,
{
position
:
'absolute'
,
left
:
`
${
qrLeft
}
px`
,
top
:
`
${
qrTop
}
px`
,
width
:
`
${
qrSize
}
px`
,
height
:
`
${
qrSize
}
px`
})
})
}
}
}
}
}
</
script
>
</
script
>
...
@@ -473,5 +767,148 @@
...
@@ -473,5 +767,148 @@
object-fit
:
contain
;
object-fit
:
contain
;
pointer-events
:
none
;
pointer-events
:
none
;
}
}
</
style
>
.required-mark
{
color
:
#f53f3f
;
margin-right
:
4px
;
}
.background-options
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
16px
;
}
.background-radio-item
{
margin-right
:
0
!important
;
margin-bottom
:
0
!important
;
flex
:
1
0
0
;
min-width
:
0
;
::v-deep
.el-radio__input
{
display
:
none
;
}
::v-deep
.el-radio__label
{
padding-left
:
0
;
cursor
:
pointer
;
width
:
100%
;
}
}
.background-card
{
position
:
relative
;
width
:
100%
;
height
:
235px
;
background
:
#fff
;
border
:
1.5px
solid
#e5e7eb
;
border-radius
:
8px
;
padding
:
10px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
overflow
:
hidden
;
transition
:
all
0.3s
;
}
.background-radio-item.is-checked
.background-card
{
border-color
:
#00bf8a
;
}
.background-preview-wrapper
{
position
:
relative
;
width
:
100%
;
height
:
auto
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.background-preview
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
pointer-events
:
none
;
border-radius
:
5px
;
}
.background-qrcode
{
pointer-events
:
none
;
object-fit
:
contain
;
}
.eye-icon
{
position
:
absolute
;
left
:
6.5px
;
top
:
6.5px
;
width
:
16px
;
height
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
z-index
:
10
;
i
{
font-size
:
16px
;
color
:
#00bf8a
;
}
&
:hover
{
opacity
:
0.8
;
}
}
.status-icon
{
position
:
absolute
;
right
:
6.5px
;
top
:
6.5px
;
width
:
16px
;
height
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
10
;
.status-icon-checked
{
font-size
:
14px
;
color
:
#00bf8a
;
}
.status-icon-unchecked
{
font-size
:
14px
;
color
:
#909399
;
}
}
.preview-dialog
{
right
:
400px
;
}
.preview-content
{
width
:
100%
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
min-height
:
400px
;
}
.preview-wrapper
{
position
:
relative
;
display
:
inline-block
;
max-width
:
100%
;
}
.preview-background
{
max-width
:
100%
;
max-height
:
70vh
;
object-fit
:
contain
;
display
:
block
;
border-radius
:
10px
;
}
.preview-qrcode
{
object-fit
:
contain
;
pointer-events
:
none
;
}
</
style
>
\ No newline at end of file
src/views/components/roleInfo/roleInfoPanel.vue
浏览文件 @
4907f587
...
@@ -176,6 +176,17 @@
...
@@ -176,6 +176,17 @@
</p>
</p>
</div>
</div>
</div>
</div>
<!-- 带教记录 -->
<div
class=
"item rowFlex columnCenter spaceBetween"
>
<div
class=
"rowFlex columnCenter"
>
<span
class=
"label"
>
带教记录:
</span>
<el-button
type=
"text"
class=
"text cursor-pointer"
@
click
.
stop=
"openMentorRecordDrawer(items)"
>
{{ items.teach_num || '-' }} 次
</el-button>
</div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse-item>
</div>
</div>
...
@@ -196,24 +207,40 @@
...
@@ -196,24 +207,40 @@
:show
.
sync=
"showAppeal"
:show
.
sync=
"showAppeal"
:appeal-info=
"appealInfo"
:appeal-info=
"appealInfo"
/>
/>
<!-- 带教记录弹窗 -->
<el-drawer
v-model=
"showMentorRecord"
drawer-title=
"带教记录"
drawer-size=
"400px"
:append-to-body=
"true"
>
<mentorRecord
v-if=
"showMentorRecord"
:role-id=
"currentRoleId"
:role-name=
"currentRoleName"
@
refresh=
"handleMentorRecordRefresh"
/>
</el-drawer>
</div>
</div>
</template>
</template>
<
script
>
<
script
>
import
{
mapState
,
mapMutations
,
mapActions
}
from
"vuex"
;
import
{
mapState
,
mapMutations
,
mapActions
}
from
"vuex"
;
import
{
getRoleHoLo
,
marketingRoleGrade
,
getServerDayApi
}
from
"@/api/game"
;
import
{
getRoleHoLo
,
marketingRoleGrade
,
getServerDayApi
,
roleTeachingNum
}
from
"@/api/game"
;
import
noContent
from
"@/components/noContent.vue"
;
import
noContent
from
"@/components/noContent.vue"
;
import
appeal
from
"./layer/appeal.vue"
;
import
appeal
from
"./layer/appeal.vue"
;
import
watchMember
from
"@/mixins/watchMember"
;
import
watchMember
from
"@/mixins/watchMember"
;
import
{
createDetails
}
from
"@/views/popup/RecentActivitiesPopup/index.js"
;
import
{
createDetails
}
from
"@/views/popup/RecentActivitiesPopup/index.js"
;
import
{
createRoleRecentActivityNotPushNum
}
from
"@/views/hooks/useGetCount.js"
;
import
{
createRoleRecentActivityNotPushNum
}
from
"@/views/hooks/useGetCount.js"
;
import
vipLevel
from
"@/views/userInfo/components/gameInfo/vipLevel.vue"
;
import
vipLevel
from
"@/views/userInfo/components/gameInfo/vipLevel.vue"
;
import
mentorRecord
from
"@/views/userInfo/components/gameInfo/mentorRecord.vue"
;
export
default
{
export
default
{
name
:
"roleInfo"
,
name
:
"roleInfo"
,
components
:
{
components
:
{
noContent
,
noContent
,
appeal
,
appeal
,
vipLevel
,
vipLevel
,
mentorRecord
,
},
},
data
()
{
data
()
{
return
{
return
{
...
@@ -232,6 +259,9 @@ export default {
...
@@ -232,6 +259,9 @@ export default {
recentActivitiesPopupInstance
:
null
,
//近期要开模块弹框
recentActivitiesPopupInstance
:
null
,
//近期要开模块弹框
roleRecentActivityNotPushNumInstance
:
null
,
//侧边栏计数弹框
roleRecentActivityNotPushNumInstance
:
null
,
//侧边栏计数弹框
numRoleIdList
:
[],
numRoleIdList
:
[],
showMentorRecord
:
false
,
// 带教记录弹窗显示状态
currentRoleId
:
null
,
// 当前查看带教记录的角色ID
currentRoleName
:
''
// 当前查看带教记录的角色名称
};
};
},
},
computed
:
{
computed
:
{
...
@@ -240,7 +270,11 @@ export default {
...
@@ -240,7 +270,11 @@ export default {
watch
:
{
watch
:
{
collapseActive
(
newVal
,
oldVal
)
{
collapseActive
(
newVal
,
oldVal
)
{
if
(
newVal
.
length
>
0
)
{
if
(
newVal
.
length
>
0
)
{
this
.
handleChange
(
newVal
.
filter
((
item
)
=>
!
oldVal
.
includes
(
item
)));
const
newOpenedItems
=
newVal
.
filter
(
item
=>
!
oldVal
.
includes
(
item
))
this
.
handleChange
(
newOpenedItems
)
// 处理带教次数获取
this
.
handleChangeRoleTeachingNum
(
newOpenedItems
)
}
}
},
},
},
},
...
@@ -295,6 +329,66 @@ export default {
...
@@ -295,6 +329,66 @@ export default {
});
});
}
}
},
},
/**
* 打开带教记录弹窗
*/
openMentorRecordDrawer
(
roleItem
)
{
this
.
currentRoleId
=
roleItem
.
role_id
this
.
currentRoleName
=
`
${
roleItem
.
role_name
}
-
${
roleItem
.
server_name
}
`
this
.
showMentorRecord
=
true
},
/**
* 带教记录刷新后,更新角色列表中的带教次数
*/
async
handleMentorRecordRefresh
(
roleId
,
teachingListLength
)
{
// 重新获取角色列表,更新带教次数
const
index
=
this
.
roleList
.
findIndex
(
item
=>
item
.
role_id
===
roleId
)
if
(
index
!==
-
1
)
{
this
.
$set
(
this
.
roleList
[
index
],
'teach_num'
,
teachingListLength
)
}
this
.
roleList
=
this
.
roleList
.
concat
([])
},
/**
* 处理角色展开时获取带教次数
* @param {Array} openedRoleIds - 新展开的角色ID数组
*/
handleChangeRoleTeachingNum
(
openedRoleIds
)
{
if
(
!
openedRoleIds
||
openedRoleIds
.
length
===
0
)
{
return
}
// 遍历新展开的角色,获取带教次数
openedRoleIds
.
forEach
(
roleId
=>
{
const
roleItem
=
this
.
roleList
.
find
(
item
=>
item
.
role_id
===
roleId
)
if
(
roleItem
)
{
// 如果已经存在 teach_num 字段,则不请求接口
if
(
roleItem
.
teach_num
===
undefined
||
roleItem
.
teach_num
===
null
)
{
this
.
getRoleTeachingNum
(
roleId
)
}
}
})
},
/**
* 获取角色带教次数
* @param {String|Number} roleId - 角色ID
*/
async
getRoleTeachingNum
(
roleId
)
{
try
{
const
res
=
await
roleTeachingNum
({
role_id
:
roleId
})
console
.
log
(
res
,
'res'
)
if
(
res
.
status_code
===
1
)
{
// 更新对应角色的 teach_num 字段
const
index
=
this
.
roleList
.
findIndex
(
item
=>
item
.
role_id
===
roleId
)
if
(
index
!==
-
1
)
{
this
.
$set
(
this
.
roleList
[
index
],
'teach_num'
,
res
.
data
.
data
?.
role_teaching_num
||
0
)
this
.
roleList
=
[...
this
.
roleList
]
}
}
}
catch
(
error
)
{
console
.
error
(
'获取带教次数失败:'
,
error
)
}
},
async
handleChange
(
v
)
{
async
handleChange
(
v
)
{
const
index
=
this
.
roleList
.
findIndex
(
const
index
=
this
.
roleList
.
findIndex
(
...
@@ -344,6 +438,66 @@ export default {
...
@@ -344,6 +438,66 @@ export default {
}
}
);
);
},
},
/**
* 打开带教记录弹窗
*/
openMentorRecordDrawer
(
roleItem
)
{
this
.
currentRoleId
=
roleItem
.
role_id
this
.
currentRoleName
=
`
${
roleItem
.
role_name
}
-
${
roleItem
.
server_name
}
`
this
.
showMentorRecord
=
true
},
/**
* 带教记录刷新后,更新角色列表中的带教次数
*/
async
handleMentorRecordRefresh
(
roleId
,
teachingListLength
)
{
// 重新获取角色列表,更新带教次数
const
index
=
this
.
roleList
.
findIndex
(
item
=>
item
.
role_id
===
roleId
)
if
(
index
!==
-
1
)
{
this
.
$set
(
this
.
roleList
[
index
],
'teach_num'
,
teachingListLength
)
}
this
.
roleList
=
this
.
roleList
.
concat
([])
},
/**
* 处理角色展开时获取带教次数
* @param {Array} openedRoleIds - 新展开的角色ID数组
*/
handleChangeRoleTeachingNum
(
openedRoleIds
)
{
if
(
!
openedRoleIds
||
openedRoleIds
.
length
===
0
)
{
return
}
// 遍历新展开的角色,获取带教次数
openedRoleIds
.
forEach
(
roleId
=>
{
const
roleItem
=
this
.
roleList
.
find
(
item
=>
item
.
role_id
===
roleId
)
if
(
roleItem
)
{
// 如果已经存在 teach_num 字段,则不请求接口
if
(
roleItem
.
teach_num
===
undefined
||
roleItem
.
teach_num
===
null
)
{
this
.
getRoleTeachingNum
(
roleId
)
}
}
})
},
/**
* 获取角色带教次数
* @param {String|Number} roleId - 角色ID
*/
async
getRoleTeachingNum
(
roleId
)
{
try
{
const
res
=
await
roleTeachingNum
({
role_id
:
roleId
})
console
.
log
(
res
,
'res'
)
if
(
res
.
status_code
===
1
)
{
// 更新对应角色的 teach_num 字段
const
index
=
this
.
roleList
.
findIndex
(
item
=>
item
.
role_id
===
roleId
)
if
(
index
!==
-
1
)
{
this
.
$set
(
this
.
roleList
[
index
],
'teach_num'
,
res
.
data
.
data
?.
role_teaching_num
||
0
)
this
.
roleList
=
[...
this
.
roleList
]
}
}
}
catch
(
error
)
{
console
.
error
(
'获取带教次数失败:'
,
error
)
}
}
},
},
beforeDestroy
()
{
beforeDestroy
()
{
this
.
recentActivitiesPopupInstance
.
destroy
();
this
.
recentActivitiesPopupInstance
.
destroy
();
...
...
src/views/userInfo/components/gameInfo/mentorRecord.vue
0 → 100644
浏览文件 @
4907f587
<!--
* @Author: 金多虾 937667504@qq.com
* @Date: 2025-12-11 11:01:15
* @LastEditors: 金多虾 937667504@qq.com
* @LastEditTime: 2025-12-15 14:32:47
* @FilePath: /company_wx_frontend/src/views/works/component/gameInfo/roleInfo/mentorRecord.vue
* @Description: 带教记录组件
-->
<
template
>
<div
class=
"mentor-record-page"
>
<!-- 标题栏 -->
<div
class=
"mentor-record-page__header"
>
<p
class=
"mentor-record-page__header-title"
>
{{
roleName
}}
带教记录
</p>
</div>
<!-- 提示信息和添加按钮区域 -->
<div
class=
"mentor-record-page__toolbar"
>
<p
class=
"mentor-record-page__toolbar-tip"
>
当天添加的备注,第二天才会统计带教次数
</p>
<el-button
v-if=
"teachingList.length
<
5
"
type=
"primary"
size=
"small"
@
click=
"showAddForm = !showAddForm"
>
添加记录
</el-button>
</div>
<!-- 新增记录表单 -->
<div
v-if=
"showAddForm && teachingList.length
<
5
"
class=
"mentor-record-page__add-form"
>
<el-input
v-model=
"formData.content"
type=
"textarea"
:rows=
"3"
placeholder=
"请输入"
maxlength=
"500"
show-word-limit
class=
"mentor-record-page__add-form-input"
/>
<div
class=
"mentor-record-page__add-form-buttons"
>
<el-button
size=
"small"
@
click=
"handleCancelAdd"
>
取消
</el-button>
<el-button
type=
"primary"
size=
"small"
:loading=
"submitLoading"
@
click=
"handleSubmitAdd"
>
保存
</el-button>
</div>
</div>
<!-- 带教记录列表 -->
<div
class=
"mentor-record-page__list"
>
<div
v-for=
"(item, index) in teachingList"
:key=
"item.id || index"
class=
"mentor-record-page__list-item"
>
<p
class=
"mentor-record-page__list-item-title"
>
第
{{
item
.
teaching_num
}}
次带教
</p>
<p
class=
"mentor-record-page__list-item-content"
>
{{
item
.
teaching_text
}}
</p>
<div
class=
"mentor-record-page__list-item-footer"
>
<span
class=
"mentor-record-page__list-item-creator"
>
新增人:
{{
item
.
update_user
||
'-'
}}
</span>
<span
class=
"mentor-record-page__list-item-divider"
>
|
</span>
<span
class=
"mentor-record-page__list-item-time"
>
{{
item
.
update_time
}}
</span>
</div>
</div>
<div
v-if=
"teachingList.length === 0 && !loading"
class=
"mentor-record-page__empty"
>
<svg-icon
icon-class=
"noContent"
/>
<p>
暂无带教记录
</p>
</div>
</div>
</div>
</
template
>
<
script
>
import
{
roleTeachingList
,
roleTeachingAdd
}
from
'@/api/game'
import
{
mapState
}
from
'vuex'
export
default
{
name
:
'MentorRecord'
,
props
:
{
roleId
:
{
type
:
[
String
,
Number
],
required
:
true
},
roleName
:
{
type
:
String
,
default
:
''
}
},
data
()
{
return
{
loading
:
false
,
submitLoading
:
false
,
showAddForm
:
false
,
teachingList
:
[],
formData
:
{
content
:
''
}
}
},
watch
:
{
roleId
:
{
immediate
:
true
,
handler
(
newVal
)
{
if
(
newVal
)
{
this
.
getTeachingList
()
}
}
}
},
computed
:
{
...
mapState
(
'user'
,
[
'userInfo'
])
},
methods
:
{
/**
* 获取带教记录列表
*/
async
getTeachingList
()
{
if
(
!
this
.
roleId
)
{
return
}
try
{
this
.
loading
=
true
const
res
=
await
roleTeachingList
({
role_id
:
this
.
roleId
})
if
(
res
.
status_code
===
1
)
{
this
.
teachingList
=
res
.
data
.
data
||
[]
// 按teaching_num倒序排列
this
.
teachingList
.
sort
((
a
,
b
)
=>
{
return
(
b
.
teaching_num
||
0
)
-
(
a
.
teaching_num
||
0
)
})
}
else
{
this
.
$message
({
message
:
res
.
data
.
msg
||
'获取带教记录失败'
,
type
:
'error'
})
}
}
catch
(
error
)
{
console
.
error
(
'获取带教记录失败:'
,
error
)
this
.
$message
({
message
:
'获取带教记录失败,请稍后重试'
,
type
:
'error'
})
}
finally
{
this
.
loading
=
false
}
},
/**
* 新增带教记录
*/
async
handleSubmitAdd
()
{
if
(
!
this
.
formData
.
content
||
!
this
.
formData
.
content
.
trim
())
{
this
.
$message
({
message
:
'请输入带教记录内容'
,
type
:
'warning'
})
return
}
try
{
this
.
submitLoading
=
true
const
res
=
await
roleTeachingAdd
({
role_id
:
this
.
roleId
,
teaching_num
:
this
.
teachingList
.
length
+
1
,
teaching_text
:
this
.
formData
.
content
.
trim
(),
create_user_id
:
this
.
userInfo
.
id
,
create_user
:
this
.
userInfo
.
username
})
if
(
res
.
status_code
===
1
)
{
this
.
$message
({
message
:
res
.
data
.
msg
||
'添加成功'
,
type
:
'success'
})
this
.
formData
.
content
=
''
this
.
showAddForm
=
false
// 重新获取列表
await
this
.
getTeachingList
()
// 通知父组件更新带教次数
this
.
$emit
(
'refresh'
,
this
.
roleId
,
this
.
teachingList
.
length
)
}
else
{
this
.
$message
({
message
:
res
.
data
.
msg
||
'添加失败'
,
type
:
'error'
})
}
}
catch
(
error
)
{
console
.
error
(
'新增带教记录失败:'
,
error
)
this
.
$message
({
message
:
'添加失败,请稍后重试'
,
type
:
'error'
})
}
finally
{
this
.
submitLoading
=
false
}
},
/**
* 取消新增
*/
handleCancelAdd
()
{
this
.
formData
.
content
=
''
this
.
showAddForm
=
false
},
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.mentor-record-page
{
width
:
100%
;
height
:
100%
;
padding
:
12px
;
padding-top
:
0
;
background
:
#fff
;
border-right
:
1px
solid
#ebedf0
;
display
:
flex
;
flex-direction
:
column
;
overflow
:
hidden
;
&__header
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
0
;
padding-top
:
0
;
border-bottom
:
1px
solid
#ebedf0
;
&-title
{
font-family
:
PingFangSC-Medium
,
PingFang
SC
;
font-size
:
14px
;
font-weight
:
500
;
line-height
:
22px
;
color
:
#131920
;
}
}
&
__toolbar
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
12px
0
;
margin-top
:
12px
;
&-tip
{
font-family
:
PingFangSC-Regular
,
PingFang
SC
;
font-size
:
12px
;
font-weight
:
400
;
line-height
:
20px
;
color
:
#909399
;
}
}
&
__add-form
{
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
margin-bottom
:
12px
;
&-input
{
::v-deep
.el-textarea__inner
{
border
:
1px
solid
#d6d9e0
;
border-radius
:
6px
;
padding
:
4px
6px
;
font-size
:
13px
;
line-height
:
22px
;
color
:
#323335
;
min-height
:
60px
;
resize
:
none
;
&::placeholder
{
color
:
#c9cdd4
;
}
}
}
&
-buttons
{
display
:
flex
;
gap
:
8px
;
justify-content
:
flex-end
;
}
}
&
__list
{
flex
:
1
;
overflow-y
:
auto
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
&-item
{
background
:
#fff
;
border
:
1px
solid
#ebedf0
;
border-radius
:
6px
;
padding
:
8px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
4px
;
&-title
{
font-family
:
PingFangSC-Regular
,
PingFang
SC
;
font-size
:
12px
;
font-weight
:
400
;
line-height
:
20px
;
color
:
#6d7176
;
}
&
-content
{
font-family
:
PingFangSC-Regular
,
PingFang
SC
;
font-size
:
15px
;
font-weight
:
500
;
line-height
:
22px
;
color
:
#131920
;
word-break
:
break-all
;
}
&
-footer
{
display
:
flex
;
align-items
:
center
;
gap
:
4px
;
}
&
-creator
,
&
-time
{
font-family
:
PingFangSC-Regular
,
PingFang
SC
;
font-size
:
12px
;
font-weight
:
400
;
line-height
:
20px
;
color
:
#b0b2b5
;
}
&
-divider
{
color
:
#ebedf0
;
font-size
:
12px
;
}
}
}
&
__empty
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
padding
:
40px
0
;
color
:
#909399
;
font-size
:
14px
;
p
{
margin-top
:
12px
;
}
}
}
</
style
>
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论