Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
company_app
概览
概览
详情
活动
周期分析
版本库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
毛细亚
company_app
Commits
22a18be7
提交
22a18be7
authored
6月 05, 2025
作者:
毛细亚
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
通讯录调试完成
上级
cfcd937b
显示空白字符变更
内嵌
并排
正在显示
13 个修改的文件
包含
1246 行增加
和
83 行删除
+1246
-83
App.vue
src/App.vue
+4
-0
works.js
src/api/works.js
+44
-0
searchSelectUser.vue
src/components/searchSelectUser.vue
+166
-0
企业微信SDK使用指南.md
src/docs/企业微信SDK使用指南.md
+202
-0
index.js
src/router/index.js
+6
-0
user.js
src/store/modules/user.js
+115
-2
jsApiList.js
src/utils/jsApiList.js
+2
-0
addErrorHandle.vue
src/views/components/ApplyRecords/addErrorHandle.vue
+9
-10
errorHandle.vue
src/views/components/ApplyRecords/errorHandle.vue
+10
-1
skillCompany.vue
src/views/components/skill/skillCompany.vue
+11
-10
skillPersonal.vue
src/views/components/skill/skillPersonal.vue
+12
-11
mailList.vue
src/views/mailList.vue
+663
-0
quickReply.vue
src/views/quickReply.vue
+2
-49
没有找到文件。
src/App.vue
浏览文件 @
22a18be7
...
...
@@ -89,6 +89,10 @@ export default {
label
:
'申请记录'
,
path
:
'/applyRecord'
},
{
label
:
'通讯录'
,
path
:
'/mailList'
},
// {
// label: '快捷发送',
// path: '/quickSend'
...
...
src/api/works.js
浏览文件 @
22a18be7
...
...
@@ -107,3 +107,46 @@ export function searchTags(data) {
data
})
}
// 通讯录
export
function
externalUserList
(
data
)
{
return
request
({
url
:
returnApi
(
'/corp_user/externalUserList'
),
method
:
'post'
,
data
})
}
// 获取图片id
export
function
getMediaId
(
data
)
{
return
request
({
url
:
returnApi
(
'/common/getMedia'
),
method
:
'post'
,
data
})
}
// 通讯录红点
export
function
mailRedTip
(
data
)
{
return
request
({
url
:
returnApi
(
'/external_user/redTip'
),
method
:
'post'
,
data
})
}
// 同步通讯录
export
function
refreshBindMail
(
data
)
{
return
request
({
url
:
returnApi
(
'/external_user/refreshBind'
),
method
:
'post'
,
data
})
}
// 搜索客户
export
function
remarkSearchSelect
(
data
)
{
return
request
({
url
:
returnApi
(
'/follow_user/preview'
),
method
:
'post'
,
data
})
}
\ No newline at end of file
src/components/searchSelectUser.vue
0 → 100644
浏览文件 @
22a18be7
<
template
>
<div
class=
"search-item"
>
<div
v-if=
"label && label.length
<6
"
class=
"item-label"
>
{{
label
}}
</div>
<div
v-else-if=
"label && label.length>=6 "
class=
"item-label"
>
{{
label
.
slice
(
0
,
4
)
}}
<br>
{{
label
.
slice
(
4
,
label
.
length
)
}}
</div>
<div
v-else
class=
"item-label"
>
{{
label
}}
</div>
<div
class=
"item-content selectUser"
>
<el-select
v-model=
"resulte"
v-loadmore=
"loadMoreList"
filterable
:disabled=
"disabled"
remote
:remote-method=
"remoteMethod"
:placeholder=
"placeholder"
clearable
reserve-keyword
:loading=
"loading"
:style=
"
{width:width}"
@change="selectChange"
>
<el-option
v-for=
"(item,index) in searchUserOption"
:key=
"index"
:value=
"item.external_userid+'¢'+item.user.userid"
:label=
"item.remark"
style=
"height:50px;"
>
<div
class=
"rowFlex columnCenter selectItem"
>
<el-image
fit=
"fill"
:src=
"item.external_user.avatar"
class=
"tableImage"
></el-image>
<div
class=
"infoSpan columnFlex rowCenter"
>
<p
class=
"hidden"
>
{{
item
.
remark
&&
item
.
remark
!=
''
?
item
.
remark
:
item
.
external_user
.
name
}}
</p>
<p
class=
"rowFlex columnCenter"
>
所属成员:
<label
class=
"hidden"
style=
"max-width:120px;"
>
{{
item
.
user
.
alias
&&
item
.
user
.
alias
!=
''
?
item
.
user
.
alias
:
item
.
user
.
name
}}
</label></p>
</div>
</div>
</el-option>
</el-select>
</div>
</div>
</
template
>
<
script
>
import
{
remarkSearchSelect
}
from
'@/api/user'
import
{
mapState
}
from
'vuex'
export
default
{
name
:
'SearchSelectUser'
,
props
:
[
'placeholder'
,
'label'
,
'isResize'
,
'userid'
,
'disabled'
,
'width'
],
// End of Selection
data
()
{
return
{
loading
:
false
,
noMore
:
false
,
searchUserOption
:
[],
pageInfo
:
{
page
:
1
,
page_size
:
20
,
total
:
0
},
resulte
:
''
}
},
watch
:
{
// 监听是否重置
isResize
(
newVal
,
oldVal
)
{
if
(
newVal
)
{
this
.
resulte
=
''
this
.
searchUserOption
=
[]
this
.
pageInfo
=
{
page
:
1
,
page_size
:
20
,
total
:
0
}
}
},
},
mounted
()
{
},
methods
:
{
loadMoreList
()
{
this
.
pageInfo
.
page
++
if
(
!
this
.
noMore
)
{
this
.
requestAccountList
()
}
else
{
console
.
log
(
'没有更新数据了'
)
}
},
selectChange
(
value
)
{
this
.
$emit
(
'result'
,
this
.
resulte
.
split
(
'¢'
)[
0
],
this
.
resulte
.
split
(
'¢'
)[
1
])
},
requestAccountList
()
{
const
data
=
{
remark
:
this
.
resulte
.
trim
(),
...
this
.
pageInfo
,
userid
:
this
.
userid
||
''
,
}
remarkSearchSelect
(
data
).
then
((
res
)
=>
{
this
.
loading
=
false
this
.
searchUserOption
=
this
.
searchUserOption
.
concat
(
res
.
data
.
data
)
this
.
$forceUpdate
()
if
(
res
.
data
.
data
.
length
===
0
)
{
this
.
noMore
=
true
}
else
{
this
.
noMore
=
false
// this.pageInfo = res.data.page_info
}
})
},
// 删选过滤
remoteMethod
(
query
)
{
this
.
pageInfo
=
{
page
:
1
,
page_size
:
20
,
total
:
0
}
this
.
resulte
=
query
.
trim
()
if
(
this
.
resulte
!==
''
)
{
this
.
searchUserOption
=
[]
this
.
pageInfo
.
page
=
1
this
.
loading
=
true
this
.
noMore
=
false
this
.
requestAccountList
()
}
else
{
return
(
this
.
searchUserOption
=
[])
}
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.selectItem
{
height
:
50px
;
}
.infoSpan
{
font-size
:
12px
;
font-family
:
PingFangSC-Regular
,
PingFang
SC
;
font-weight
:
400
;
max-width
:
250px
;
height
:
50px
;
p
{
font-size
:
12px
;
max-width
:
100%
;
line-height
:
20px
;
}
span
{
color
:
#ffa81d
;
}
}
.tableImage
{
width
:
30px
;
height
:
30px
;
border-radius
:
30px
;
margin-right
:
10px
;
}
</
style
>
\ No newline at end of file
src/docs/企业微信SDK使用指南.md
0 → 100644
浏览文件 @
22a18be7
# 企业微信 SDK 使用指南
# 企业微信 SDK 使用指南
## 📋 概述
企业微信相关方法已封装到 Vuex
`user`
模块中,提供了简洁的 API 和 Promise 支持。
## 🎯 可用的 Actions
### 1. `getWecomSignature` - 获取企业微信签名
```
javascript
// 自动获取签名(使用当前页面和缓存的 corp_id)
const
signData
=
await
this
.
getWecomSignature
()
// 指定参数获取签名
const
signData
=
await
this
.
getWecomSignature
({
corp_id
:
'your_corp_id'
,
path
:
'https://your-domain.com/path'
})
```
### 2. `registerWecomSDK` - 注册企业微信 SDK
```
javascript
// 使用已获取的签名数据注册
const
registerResult
=
await
this
.
registerWecomSDK
(
signData
)
// 或者使用 state 中的签名数据
const
registerResult
=
await
this
.
registerWecomSDK
()
```
### 3. `initWecom` - 一键初始化(推荐)
```
javascript
// 完整初始化(获取签名 + 注册 SDK)
try
{
const
result
=
await
this
.
initWecom
()
console
.
log
(
'初始化成功:'
,
result
)
// result 包含:{ signData, registerResult, success: true }
}
catch
(
error
)
{
console
.
error
(
'初始化失败:'
,
error
)
}
```
## 🚀 使用方法
### 在组件中使用
```
javascript
import
{
mapActions
,
mapState
}
from
'vuex'
export
default
{
computed
:
{
...
mapState
(
'user'
,
[
'isWecomSDKReady'
,
'signData'
])
},
methods
:
{
// 映射 Vuex actions
...
mapActions
(
'user'
,
[
'getWecomSignature'
,
'registerWecomSDK'
,
'initWecom'
]),
// 初始化企业微信
async
initializeWecom
()
{
try
{
const
result
=
await
this
.
initWecom
()
// 注册成功后的操作
this
.
onWecomReady
(
result
.
registerResult
)
}
catch
(
error
)
{
console
.
error
(
'初始化失败:'
,
error
)
this
.
$message
.
error
(
'企业微信初始化失败'
)
}
},
// SDK 准备就绪后的回调
onWecomReady
(
registerResult
)
{
console
.
log
(
'企业微信 SDK 已准备就绪'
)
// 现在可以安全地使用企业微信 API
this
.
openEnterpriseChat
()
this
.
getCurExternalContact
()
// ... 其他企业微信 API 调用
},
// 检查 SDK 是否已准备就绪
checkSDKReady
()
{
if
(
this
.
isWecomSDKReady
)
{
console
.
log
(
'SDK 已准备就绪'
)
return
true
}
else
{
console
.
log
(
'SDK 尚未准备就绪'
)
return
false
}
}
},
// 在组件挂载时初始化
async
mounted
()
{
await
this
.
initializeWecom
()
}
}
```
### 在页面中使用
```
javascript
// 在 login.vue、quickReply.vue 等页面中
export
default
{
async
created
()
{
// 替代原有的 getSignature() 调用
await
this
.
initializeWecom
()
},
methods
:
{
...
mapActions
(
'user'
,
[
'initWecom'
]),
async
initializeWecom
()
{
try
{
const
result
=
await
this
.
initWecom
()
console
.
log
(
'企业微信初始化成功'
)
// 执行需要在 SDK 注册成功后的操作
this
.
handleWecomReady
()
}
catch
(
error
)
{
console
.
error
(
'企业微信初始化失败:'
,
error
)
}
},
handleWecomReady
()
{
// 原来在 onAgentConfigSuccess 中的逻辑
this
.
getCurExternalContact
()
// ... 其他操作
}
}
}
```
## 📊 状态管理
新增的 state:
```
javascript
// user store 中的状态
{
signData
:
{},
// 企业微信签名数据
isWecomSDKReady
:
false
// SDK 是否已准备就绪
}
```
## ✅ 优势
1.
**统一管理**
:所有企业微信相关逻辑集中在 Vuex 中
2.
**Promise 支持**
:注册成功/失败都会返回 Promise
3.
**状态追踪**
:可以通过
`isWecomSDKReady`
检查 SDK 状态
4.
**错误处理**
:统一的错误处理机制
5.
**复用性**
:多个组件可以共享同一套初始化逻辑
## 🔄 迁移指南
### 原有代码:
```
javascript
// 旧的方式
async
getSignature
()
{
const
res
=
await
getSignature
({
corp_id
,
path
})
this
.
registerWeComSDK
(
res
.
data
)
}
registerWeComSDK
(
signData
)
{
this
.
$ww
.
register
({
// ... 配置
onAgentConfigSuccess
:
(
res
)
=>
{
console
.
log
(
'注册成功'
)
// 执行后续操作
}
})
}
```
### 新代码:
```
javascript
// 新的方式
async
initializeWecom
()
{
try
{
const
result
=
await
this
.
initWecom
()
console
.
log
(
'注册成功'
)
// 执行后续操作
}
catch
(
error
)
{
console
.
error
(
'注册失败'
)
}
}
```
## 🎉 示例项目
参考
`skillPersonal.vue`
和
`quickReply.vue`
中的实现方式。
\ No newline at end of file
src/router/index.js
浏览文件 @
22a18be7
...
...
@@ -9,6 +9,7 @@ import orderList from '../views/orderList.vue'
import
roleInfo
from
'../views/roleInfo.vue'
import
violationRecord
from
'../views/ViolationRecord.vue'
import
taskRecord
from
'../views/taskRecord.vue'
import
mailList
from
'@/views/mailList.vue'
import
Cookies
from
'js-cookie'
import
store
from
'@/store'
Vue
.
use
(
VueRouter
)
...
...
@@ -68,6 +69,11 @@ const routes = [
component
:
taskRecord
},
{
path
:
'/mailList'
,
name
:
'mailList'
,
component
:
mailList
},
{
path
:
'/login'
,
name
:
'login'
,
component
:
()
=>
import
(
'../views/login.vue'
)
...
...
src/store/modules/user.js
浏览文件 @
22a18be7
import
Cookies
from
'js-cookie'
import
{
companyviewConfig
}
from
'@/api/user'
import
{
companyviewConfig
,
getSignature
}
from
'@/api/user'
import
jsApiList
from
'@/utils/jsApiList'
import
*
as
ww
from
'@wecom/jssdk'
const
state
=
{
userInfo
:
{
"userid"
:
"JinDuoXia"
,
...
...
@@ -26,7 +29,8 @@ const state = {
corp_signature
:
''
,
time
:
''
},
weixin_blongs_id_list
:[]
weixin_blongs_id_list
:[],
isWecomSDKReady
:
false
,
// 添加企业微信 SDK 就绪状态
// 六子的 用户id wm5rUgMgAAjqjOcqp8i3lEhFZDQieWug
// 我的 userid JinDuoXia cser_id 4090 corp_id wweaefe716636df3d1 cser_id 4090 token token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJpc3MiOjQwOTAsImlhdCI6MTc0NzgxMjMxMiwiZXhwIjoxNzQ4NDE3MTEyLCJuYmYiOjE3NDc4MTIzMTIsInN1YiI6InRva2Vu6K6k6K-BIiwianRpIjoiMjBkOTY3MDZiYzI1MDdmY2MxOWI2MjU1YTM0YWQ3M2YifQ.yX7E7QHV7x2ubpa8iK3Avy794EiHNCaW2CtB4A4UQWo
}
...
...
@@ -66,7 +70,11 @@ const mutations = {
state
.
weixin_blongs_id_list
=
data
}
},
set_isWecomSDKReady
(
state
,
status
)
{
state
.
isWecomSDKReady
=
status
},
}
const
actions
=
{
async
requestCompanyviewConfig
({
commit
,
state
},
data
)
{
const
res
=
await
companyviewConfig
(
data
)
...
...
@@ -80,8 +88,113 @@ const actions = {
}))
commit
(
'set_weixin_blongs_id_list'
,
returnList
)
}
},
// 获取企业微信签名
async
getWecomSignature
({
commit
,
state
},
{
corp_id
,
path
}
=
{})
{
try
{
console
.
log
(
'获取企业微信签名'
,
path
||
window
.
location
.
href
)
const
requestCorpId
=
corp_id
||
Cookies
.
get
(
'corp_id'
)
||
state
.
corp_id
const
requestPath
=
path
||
window
.
location
.
href
if
(
!
requestCorpId
)
{
throw
new
Error
(
'corp_id 不能为空'
)
}
const
res
=
await
getSignature
({
corp_id
:
requestCorpId
,
path
:
requestPath
})
if
(
res
.
status_code
===
1
)
{
commit
(
'set_signData'
,
res
.
data
)
console
.
log
(
'企业微信签名获取成功'
)
return
res
.
data
}
else
{
throw
new
Error
(
res
.
msg
||
'获取签名失败'
)
}
}
catch
(
error
)
{
console
.
error
(
'获取企业微信签名失败:'
,
error
)
throw
error
}
},
// 注册企业微信 SDK
async
registerWecomSDK
({
commit
,
state
},
signData
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
try
{
console
.
log
(
'开始注册企业微信 SDK'
)
const
corpId
=
Cookies
.
get
(
'corp_id'
)
||
state
.
corp_id
const
finalSignData
=
signData
||
state
.
signData
if
(
!
corpId
)
{
reject
(
new
Error
(
'corp_id 不能为空'
))
return
}
if
(
!
finalSignData
||
!
finalSignData
.
agent_id
)
{
reject
(
new
Error
(
'签名数据不完整'
))
return
}
ww
.
register
({
corpId
:
corpId
,
agentId
:
finalSignData
.
agent_id
,
jsApiList
:
jsApiList
,
// 只用到应用的 api 可以只进行应用的签名
getAgentConfigSignature
:
()
=>
Promise
.
resolve
({
nonceStr
:
finalSignData
.
nonce_str
,
timestamp
:
finalSignData
.
signature_time
,
signature
:
finalSignData
.
agent_signature
,
}),
onAgentConfigSuccess
:
(
res
)
=>
{
console
.
log
(
'✅ 企业微信 SDK 注册成功'
,
res
)
commit
(
'set_isWecomSDKReady'
,
true
)
resolve
(
res
)
// 在这里返回 Promise resolve
},
onAgentConfigFail
:
(
err
)
=>
{
console
.
error
(
'❌ 企业微信 SDK 注册失败'
,
err
)
commit
(
'set_isWecomSDKReady'
,
false
)
reject
(
err
)
// 在这里返回 Promise reject
}
})
}
catch
(
error
)
{
console
.
error
(
'注册企业微信 SDK 出错:'
,
error
)
commit
(
'set_isWecomSDKReady'
,
false
)
reject
(
error
)
}
})
},
// 初始化企业微信(获取签名 + 注册 SDK)
async
initWecom
({
dispatch
},
{
corp_id
,
path
}
=
{})
{
try
{
console
.
log
(
'🚀 开始初始化企业微信'
)
// 1. 获取签名
const
signData
=
await
dispatch
(
'getWecomSignature'
,
{
corp_id
,
path
})
// 2. 注册 SDK
const
registerResult
=
await
dispatch
(
'registerWecomSDK'
,
signData
)
console
.
log
(
'🎉 企业微信初始化完成'
)
return
{
signData
,
registerResult
,
success
:
true
}
}
catch
(
error
)
{
console
.
error
(
'💥 企业微信初始化失败:'
,
error
)
throw
error
}
}
}
export
default
{
...
...
src/utils/jsApiList.js
浏览文件 @
22a18be7
const
jsApiList
=
[
'getCurExternalContact'
,
'sendChatMessage'
,
'openEnterpriseChat'
,
]
export
default
jsApiList
\ No newline at end of file
src/views/components/ApplyRecords/addErrorHandle.vue
浏览文件 @
22a18be7
...
...
@@ -64,7 +64,7 @@
>
</el-option>
</el-select>
<el-input-number
v-model=
"item.num"
:min=
"1"
style=
"margin:0 20px;
"
></el-input-number>
<el-input-number
size=
"small"
v-model=
"item.num"
:min=
"1
"
></el-input-number>
<i
class=
"el-icon-remove-outline icon"
@
click=
"removeExtra(item,index)"
></i>
</div>
</el-form-item>
...
...
@@ -92,7 +92,7 @@
>
</el-option>
</el-select>
<el-input-number
v-model=
"item.num"
:min=
"1"
style=
"margin:0 20px;
"
></el-input-number>
<el-input-number
size=
"small"
v-model=
"item.num"
:min=
"1
"
></el-input-number>
<i
class=
"el-icon-remove-outline icon"
@
click=
"removeBack(item,index)"
></i>
</div>
</el-form-item>
...
...
@@ -106,8 +106,8 @@
</div>
<span
class=
"dialog-footer rowFlex"
>
<el-button
class=
"btn"
type=
"primary"
:loading=
"loading"
@
click=
"submit"
>
确 定
</el-button>
<el-button
class=
"btn"
@
click=
"close"
>
取 消
</el-button>
<el-button
class=
"btn"
type=
"primary"
size=
"small"
:loading=
"loading"
@
click=
"submit"
>
确 定
</el-button>
<el-button
class=
"btn"
size=
"small"
@
click=
"close"
>
取 消
</el-button>
</span>
</el-drawer>
</template>
...
...
@@ -122,8 +122,8 @@
components
:
{
searchSelect
,
uploadMultiple
,
textEditor
},
props
:
[
'show'
,
'width'
,
'title'
,
'info'
],
computed
:
{
...
mapState
(
'game'
,
[
'accountSelect'
,
'gameTabActive'
]),
...
mapState
(
'user'
,
[
'isGameSystem'
,
'userInfo
'
])
...
mapState
(
'game'
,
[
'accountSelect'
]),
...
mapState
(
'user'
,
[
'cser_name'
,
'cser_id
'
])
},
data
()
{
return
{
...
...
@@ -326,7 +326,7 @@
this
.
ruleForm
.
username
=
info
.
username
this
.
ruleForm
.
server_id
=
info
.
server_id
}
this
.
ruleForm
.
creator_name
=
this
.
userInfo
.
user
name
this
.
ruleForm
.
creator_name
=
this
.
cser_
name
this
.
ruleForm
.
role_id
=
value
this
.
numErrorHandle
(
value
)
},
...
...
@@ -375,10 +375,9 @@
}
res
=
await
updateErrorHandle
(
data
)
}
else
{
const
{
id
,
username
}
=
this
.
userInfo
const
params
=
this
.
$clone
(
this
.
ruleForm
)
params
.
user_id
=
id
params
.
user_name
=
user
name
params
.
user_id
=
this
.
cser_
id
params
.
user_name
=
this
.
cser_
name
setTimeout
(()
=>
{
this
.
loading
=
false
},
3000
)
...
...
src/views/components/ApplyRecords/errorHandle.vue
浏览文件 @
22a18be7
<
template
>
<div
class=
"detailsErrorHandle columnFlex"
>
<div
class=
"detailsErrorHandleContent"
>
<div
class=
"addApply rowFlex spaceBetween"
>
<span></span>
<el-button
type=
"primary"
size=
"small"
icon=
"el-icon-plus"
@
click=
"showAddErrorHandle = true,info = null"
>
新增误操作
</el-button>
</div>
<div
class=
"filterList"
>
<div
class=
"rowFlex columnCenter"
style=
"margin-top:10px;"
>
角色名称:
...
...
@@ -91,7 +100,7 @@
</div>
<!-- 编辑误操作 -->
<addErrorHandle
v-if=
"showAddErrorHandle"
:show
.
sync=
"showAddErrorHandle"
:info=
"info"
title=
"
编辑玩家误操作"
width=
"30%
"
@
updateList=
"updateList"
/>
<addErrorHandle
v-if=
"showAddErrorHandle"
:show
.
sync=
"showAddErrorHandle"
:info=
"info"
title=
"
玩家误操作"
width=
"320px
"
@
updateList=
"updateList"
/>
</div>
</template>
...
...
src/views/components/skill/skillCompany.vue
浏览文件 @
22a18be7
...
...
@@ -39,7 +39,7 @@
<
/div
>
<
div
v
-
if
=
"i.msgtype == 'image'"
class
=
"contentItemDetails rowFlex spaceBetween columnCenter"
>
<
el
-
image
class
=
"image"
:
src
=
"i.image.picurl"
:
preview
-
src
-
list
=
"[i.image.picurl]"
fit
=
"contain"
><
/el-image
>
<
!--
<
el
-
button
class
=
"sendButton rowFlex allCenter"
:
disabled
=
"Boolean(setIntervalTimer)"
@
click
.
stop
=
"sendMessageEdit(i, items._id)"
>
发送
<
/el-button> --
>
<
el
-
button
class
=
"sendButton rowFlex allCenter"
:
disabled
=
"Boolean(setIntervalTimer)"
@
click
.
stop
=
"sendMessageEdit(i, items._id)"
>
发送
<
/el-button
>
<
/div
>
<
/div
>
<
/div
>
...
...
@@ -56,6 +56,7 @@
import
{
procedure_group
,
procedureList
,
procedureSort
,
procedureGroupSort
,
skillQuote
}
from
'@/api/skill'
import
{
mapState
,
mapMutations
,
mapActions
}
from
'vuex'
import
{
throttle
,
copyToClipboard
}
from
'@/utils/index'
import
{
getMediaId
}
from
'@/api/works'
export
default
{
components
:
{
}
,
props
:
{
...
...
@@ -234,10 +235,13 @@ export default {
this
.
sendMessageImage
(
item
)
}
}
,
sendMessageImage
(
item
,
id
){
async
sendMessageImage
(
item
,
id
){
// 发送图片作为链接消息
if
(
item
.
image
&&
item
.
image
.
picurl
)
{
this
.
sendImageAsLink
(
item
.
image
.
picurl
)
const
res
=
await
getMediaId
({
url
:
item
.
image
.
picurl
}
)
if
(
res
.
status_code
==
1
){
this
.
sendImageAsMedia
(
res
.
data
.
media_id
)
}
}
else
{
// 如果没有图片URL,提示用户
this
.
$message
.
error
(
'图片链接不存在,无法发送'
)
...
...
@@ -245,14 +249,11 @@ export default {
}
,
// 发送图片作为链接消息
sendImageAs
Link
(
picurl
)
{
sendImageAs
Media
(
media_id
)
{
this
.
$ww
.
sendChatMessage
({
msgtype
:
'link'
,
link
:
{
title
:
'图片消息'
,
description
:
'点击查看图片'
,
url
:
picurl
,
picurl
:
picurl
msgtype
:
'image'
,
image
:
{
mediaid
:
media_id
}
,
success
:
(
res
)
=>
{
console
.
log
(
res
,
'发送图片链接成功'
)
...
...
src/views/components/skill/skillPersonal.vue
浏览文件 @
22a18be7
...
...
@@ -39,7 +39,7 @@
<
/div
>
<
div
v
-
if
=
"i.msgtype == 'image'"
class
=
"contentItemDetails rowFlex spaceBetween columnCenter"
>
<
el
-
image
class
=
"image"
:
src
=
"i.image.picurl"
:
preview
-
src
-
list
=
"[i.image.picurl]"
fit
=
"contain"
><
/el-image
>
<
!--
<
el
-
button
class
=
"sendButton rowFlex allCenter"
@
click
.
stop
=
"sendMessageEdit(i, items._id)"
>
发送
<
/el-button> --
>
<
el
-
button
class
=
"sendButton rowFlex allCenter"
@
click
.
stop
=
"sendMessageEdit(i, items._id)"
>
发送
<
/el-button
>
<
/div
>
<
/div
>
<
/div
>
...
...
@@ -54,6 +54,7 @@
<
/template
>
<
script
>
import
{
procedure_group
,
procedureList
,
procedureSort
,
procedureGroupSort
}
from
'@/api/skill'
import
{
getMediaId
}
from
'@/api/works'
import
{
mapState
,
mapMutations
,
mapActions
}
from
'vuex'
import
{
debounce
,
copyToClipboard
}
from
'@/utils/index'
export
default
{
...
...
@@ -101,6 +102,7 @@ export default {
}
,
computed
:
{
...
mapState
(
'game'
,
[
'accountSelect'
]),
...
mapState
(
'user'
,
[
'userid'
,
'external_userid'
]),
}
,
watch
:
{
accountSelect
(
newVal
,
oldVal
)
{
...
...
@@ -144,25 +146,24 @@ export default {
this
.
sendMessageImage
(
item
)
}
}
,
sendMessageImage
(
item
,
id
){
async
sendMessageImage
(
item
,
id
){
// 发送图片作为链接消息
if
(
item
.
image
&&
item
.
image
.
picurl
)
{
this
.
sendImageAsLink
(
item
.
image
.
picurl
)
const
res
=
await
getMediaId
({
url
:
item
.
image
.
picurl
}
)
if
(
res
.
status_code
==
1
){
this
.
sendImageAsMedia
(
res
.
data
.
media_id
)
}
}
else
{
// 如果没有图片URL,提示用户
this
.
$message
.
error
(
'图片链接不存在,无法发送'
)
}
}
,
// 发送图片作为链接消息
sendImageAs
Link
(
picurl
)
{
sendImageAs
Media
(
media_id
)
{
this
.
$ww
.
sendChatMessage
({
msgtype
:
'link'
,
link
:
{
title
:
'图片消息'
,
description
:
'点击查看图片'
,
url
:
picurl
,
picurl
:
picurl
msgtype
:
'image'
,
image
:
{
mediaid
:
media_id
}
,
success
:
(
res
)
=>
{
console
.
log
(
res
,
'发送图片链接成功'
)
...
...
src/views/mailList.vue
0 → 100644
浏览文件 @
22a18be7
<
template
>
<div
class=
"mail-list-container"
>
<!-- 搜索过滤区域 -->
<div
class=
"search-header"
>
<div
class=
"search-row"
>
<el-select
v-model=
"searchType"
class=
"search-type-select"
placeholder=
"备注"
:clearable=
"false"
style=
"width: 100px;"
@
change=
"onSearchTypeChange"
>
<el-option
v-for=
"item in searchTypeOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
>
</el-option>
</el-select>
<!-- 用户ID搜索组件 -->
<searchSelectUser
v-if=
"searchType === 'remark'"
class=
"search-input"
:userid=
"userid"
width=
"100%"
placeholder=
"请输入"
@
result=
"onUserSelect"
/>
<!-- W账号搜索输入框 -->
<el-input
v-else
v-model=
"searchKeyword"
class=
"search-input"
placeholder=
"请输入"
@
change=
"onKeywordInput"
>
</el-input>
</div>
</div>
<!-- 筛选按钮区域 -->
<div
v-if=
'false'
class=
"filter-tabs"
>
<div
class=
"filter-tab"
:class=
"
{ active: mailFilterType === 'all' }"
@click="onMailFilterChange('all')"
>
全部
</div>
<div
class=
"filter-tab"
:class=
"
{ active: mailFilterType === 'unbind' }"
@click="onMailFilterChange('unbind')"
>
未绑定用户
<span
v-if=
"mailRedTipInfo.unbind_count"
class=
"badge"
>
{{
Number
(
mailRedTipInfo
.
unbind_count
)
}}
</span>
</div>
</div>
<!-- 同步通讯录提示 -->
<div
v-if=
"mailFilterType === 'unbind'"
class=
"sync-tip"
>
<div
class=
"sync-actions"
>
<el-button
type=
"text"
:disabled=
"!mailRedTipInfo.enable"
class=
"sync-btn"
icon=
"el-icon-refresh"
@
click=
"startRefreshMail"
>
同步通讯录
</el-button>
<span
class=
"sync-time"
>
上次刷新时间:
{{
mailRedTipInfo
.
last_refresh_time
||
'暂无'
}}
</span>
</div>
<div
class=
"sync-desc"
>
半小时内仅能同步一次通讯录,请将当前所有待绑定的用户添加备注后再同步
</div>
</div>
<!-- 通讯录列表 -->
<div
ref=
"mailListScroll"
v-infinite-scroll=
"loadMoreMail"
:infinite-scroll-disabled=
"!hasMore"
:infinite-scroll-immediate=
"false"
class=
"contact-list"
>
<div
v-for=
"(item, index) in mailList"
:key=
"item._id || index"
class=
"contact-item"
:class=
"
{ active: item.external_userid === chatUserInfo.external_userid }"
>
<!-- 左侧头像 -->
<div
class=
"avatar-wrapper"
>
<el-image
:src=
"item.avatar"
class=
"contact-avatar"
fit=
"cover"
/>
<!-- 流失状态标签 -->
<div
v-if=
"item.loss_status_text == '已流失' || item.loss_status == '已流失'"
class=
"loss-badge"
>
{{
item
.
loss_status_text
||
item
.
loss_status
}}
</div>
</div>
<!-- 中间信息区域 -->
<div
class=
"contact-info"
>
<div
class=
"contact-name"
>
{{
item
.
remark
&&
item
.
remark
!=
""
?
item
.
remark
:
item
.
name
}}
</div>
<div
class=
"contact-source"
>
@
{{
item
.
origin
||
'微信'
}}
</div>
</div>
<!-- 右侧操作区域 -->
<div
class=
"contact-actions"
>
<!-- 待绑定标签 -->
<div
v-if=
"item.red_tip == 1"
class=
"unbind-tag"
>
待绑定
</div>
<!-- 发起会话按钮 -->
<el-button
v-else
type=
"primary"
size=
"small"
class=
"chat-btn"
@
click=
"onMailItemClick(item)"
>
发起会话
</el-button>
</div>
</div>
<!-- 加载状态 -->
<div
v-if=
"mailLoading"
class=
"loading-wrapper"
>
<i
class=
"el-icon-loading"
></i>
<span>
加载中...
</span>
</div>
<!-- 无更多数据 -->
<div
v-if=
"!hasMore && mailList.length > 0"
class=
"no-more"
>
没有更多数据了
</div>
<!-- 空状态 -->
<div
v-if=
"!mailLoading && mailList.length === 0"
class=
"empty-state"
>
<i
class=
"el-icon-user"
></i>
<div>
暂无通讯录数据
</div>
</div>
</div>
</div>
</
template
>
<
script
>
import
{
externalUserList
,
refreshBindMail
,
mailRedTip
}
from
'@/api/works'
import
{
mapState
,
mapActions
}
from
'vuex'
import
{
debounce
}
from
'@/utils/index'
import
searchSelectUser
from
'@/components/searchSelectUser.vue'
export
default
{
name
:
'MailList'
,
components
:
{
searchSelectUser
},
computed
:
{
...
mapState
(
'game'
,
[
'chatUserInfo'
]),
...
mapState
(
'user'
,
[
'userid'
]),
},
data
()
{
return
{
// 搜索相关
searchType
:
'remark'
,
// 搜索类型:'remark' | 'w_account'
searchKeyword
:
''
,
// 搜索关键词(W账号)
searchParams
:
{},
// 当前搜索参数
// 搜索选项
searchTypeOptions
:
[
{
label
:
'备注'
,
value
:
'remark'
},
{
label
:
'W账号'
,
value
:
'w_account'
}
],
// 通讯录相关
mailList
:
[],
// 通讯录列表
mailFilterType
:
'all'
,
// 筛选类型:'all' | 'unbind'
mailLoading
:
false
,
mailRedTipInfo
:
{},
// 分页相关
pagination
:
{
page
:
1
,
page_size
:
20
,
total
:
0
},
hasMore
:
false
,
// 是否还有更多数据
}
},
mounted
()
{
this
.
initializeWecom
()
this
.
initMailList
()
},
methods
:
{
...
mapActions
(
'user'
,
[
'initWecom'
]),
async
initializeWecom
()
{
try
{
console
.
log
(
'🚀 开始初始化企业微信 SDK'
)
const
result
=
await
this
.
initWecom
()
console
.
log
(
'✅ 企业微信 SDK 初始化成功'
,
result
)
}
catch
(
error
)
{
console
.
error
(
'❌ 企业微信 SDK 初始化失败:'
,
error
)
}
},
// 初始化通讯录
async
initMailList
()
{
this
.
resetPagination
()
this
.
loadMailList
()
// await Promise.all([
// this.loadMailList(),
// // this.requestMailRedTip()
// ])
},
// 搜索类型改变
onSearchTypeChange
(
searchType
)
{
this
.
searchKeyword
=
''
this
.
searchParams
=
{}
this
.
resetAndReload
()
},
// W账号输入处理
onKeywordInput
:
debounce
(
function
(
keyword
)
{
this
.
buildSearchParams
()
this
.
resetAndReload
()
},
500
),
// 用户选择结果处理
onUserSelect
(
external_userid
,
userid
)
{
this
.
searchParams
=
{
external_userid
,
userid
}
this
.
resetAndReload
()
},
// 构建搜索参数
buildSearchParams
()
{
const
baseParams
=
{
...
this
.
pagination
,
userid
:
this
.
userid
,
}
// 根据搜索类型构建不同的参数
if
(
this
.
searchType
===
'remark'
)
{
this
.
searchParams
=
{
...
baseParams
,
...
this
.
searchParams
}
}
else
if
(
this
.
searchType
===
'w_account'
)
{
this
.
searchParams
=
{
...
baseParams
,
w_account
:
this
.
searchKeyword
}
}
else
{
this
.
searchParams
=
baseParams
}
// 添加筛选参数
if
(
this
.
mailFilterType
===
'unbind'
)
{
this
.
searchParams
.
red_tip
=
1
}
},
// 通讯录筛选类型改变
onMailFilterChange
(
filterType
)
{
this
.
mailFilterType
=
filterType
this
.
resetAndReload
()
},
// 重置并重新加载
resetAndReload
()
{
this
.
resetPagination
()
this
.
mailList
=
[]
this
.
loadMailList
()
},
// 重置分页
resetPagination
()
{
this
.
pagination
=
{
page
:
1
,
page_size
:
20
,
total
:
0
}
},
// 加载通讯录列表
async
loadMailList
()
{
if
(
!
this
.
userid
)
{
this
.
$message
.
warning
(
'通讯录为空'
)
return
}
this
.
mailLoading
=
true
try
{
this
.
buildSearchParams
()
const
res
=
await
externalUserList
(
this
.
searchParams
)
if
(
res
.
status_code
===
1
)
{
const
newList
=
this
.
formatMailList
(
res
.
data
.
data
)
this
.
mailList
=
this
.
pagination
.
page
===
1
?
newList
:
[...
this
.
mailList
,
...
newList
]
this
.
hasMore
=
res
.
data
.
data
.
length
>=
this
.
pagination
.
page_size
}
}
catch
(
error
)
{
console
.
error
(
'加载通讯录失败:'
,
error
)
this
.
$message
.
error
(
'加载通讯录失败'
)
}
finally
{
this
.
mailLoading
=
false
}
},
// 加载更多通讯录
loadMoreMail
()
{
if
(
this
.
hasMore
&&
!
this
.
mailLoading
)
{
this
.
pagination
.
page
++
this
.
loadMailList
()
}
},
// 格式化通讯录数据
formatMailList
(
data
)
{
return
data
.
map
(
item
=>
({
...
item
,
external_user
:
{
avatar
:
item
.
avatar
,
external_userid
:
item
.
external_userid
,
name
:
item
.
name
},
user
:
{
userid
:
item
.
userid
,
name
:
this
.
chatUserInfo
.
name
,
avatar
:
this
.
chatUserInfo
.
avatar
}
}))
},
// 通讯录项点击
onMailItemClick
(
item
)
{
console
.
log
(
'发起会话:'
,
item
)
this
.
$ww
.
openEnterpriseChat
({
externalUserIds
:
item
.
external_userid
,
success
:
(
res
)
=>
{
console
.
log
(
res
,
'打开会话窗口成功12313'
)
},
fail
:
(
err
)
=>
{
console
.
log
(
err
,
'打开会话窗口失败'
)
}
})
},
// 同步通讯录
async
startRefreshMail
()
{
try
{
const
res
=
await
refreshBindMail
({
userid
:
this
.
userid
})
if
(
res
.
status_code
===
1
)
{
this
.
$message
.
success
(
res
.
msg
)
this
.
requestMailRedTip
()
}
}
catch
(
error
)
{
console
.
error
(
'同步通讯录失败:'
,
error
)
this
.
$message
.
error
(
'同步通讯录失败'
)
}
},
// 请求通讯录红点信息
async
requestMailRedTip
()
{
try
{
const
res
=
await
mailRedTip
({
userid
:
this
.
userid
})
if
(
res
.
status_code
===
1
)
{
this
.
mailRedTipInfo
=
res
.
data
}
}
catch
(
error
)
{
console
.
error
(
'获取通讯录红点信息失败:'
,
error
)
}
},
}
}
</
script
>
<
style
scoped
lang=
"scss"
>
.mail-list-container
{
background
:
#fff
;
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
}
//
搜索头部
.search-header
{
padding
:
16px
;
border-bottom
:
1px
solid
#f0f0f0
;
.search-row
{
display
:
flex
;
gap
:
12px
;
align-items
:
center
;
}
.search-type-select
{
width
:
80px
;
flex-shrink
:
0
;
::v-deep
.el-input__inner
{
border
:
1px
solid
#dcdcdc
;
border-radius
:
4px
;
height
:
36px
;
line-height
:
36px
;
}
}
.search-input
{
flex
:
1
;
::v-deep
.el-input__inner
{
border
:
1px
solid
#dcdcdc
;
border-radius
:
4px
;
height
:
36px
;
line-height
:
36px
;
}
}
}
//
筛选标签
.filter-tabs
{
display
:
flex
;
border-bottom
:
1px
solid
#f0f0f0
;
.filter-tab
{
flex
:
1
;
padding
:
12px
16px
;
text-align
:
center
;
cursor
:
pointer
;
color
:
#666
;
font-size
:
14px
;
position
:
relative
;
&.active
{
color
:
#1890ff
;
background
:
#f6f6f6
;
}
.badge
{
background
:
#ff4d4f
;
color
:
#fff
;
font-size
:
12px
;
padding
:
2px
6px
;
border-radius
:
10px
;
margin-left
:
4px
;
}
}
}
//
同步提示
.sync-tip
{
padding
:
12px
16px
;
background
:
#f6f8fa
;
border-bottom
:
1px
solid
#f0f0f0
;
.sync-actions
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
8px
;
.sync-btn
{
padding
:
0
;
font-size
:
14px
;
color
:
#1890ff
;
}
.sync-time
{
margin-left
:
16px
;
font-size
:
12px
;
color
:
#999
;
}
}
.sync-desc
{
font-size
:
12px
;
color
:
#666
;
line-height
:
1.4
;
}
}
//
通讯录列表
.contact-list
{
flex
:
1
;
overflow-y
:
auto
;
.contact-item
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
border-bottom
:
1px
solid
#f0f0f0
;
cursor
:
pointer
;
transition
:
background-color
0.2s
;
&:hover
{
background
:
#f6f8fa
;
}
&
.active
{
background
:
#e6f7ff
;
}
&
:last-child
{
border-bottom
:
none
;
}
}
}
//
头像区域
.avatar-wrapper
{
position
:
relative
;
margin-right
:
12px
;
.contact-avatar
{
width
:
40px
;
height
:
40px
;
border-radius
:
50%
;
object-fit
:
cover
;
}
.loss-badge
{
position
:
absolute
;
bottom
:
-2px
;
left
:
0
;
right
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.6
);
color
:
#fff
;
font-size
:
10px
;
text-align
:
center
;
padding
:
1px
0
;
border-radius
:
0
0
20px
20px
;
}
}
//
联系人信息
.contact-info
{
flex
:
1
;
min-width
:
0
;
.contact-name
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#333
;
margin-bottom
:
4px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.contact-source
{
font-size
:
12px
;
color
:
#07c160
;
font-weight
:
500
;
}
}
//
操作区域
.contact-actions
{
display
:
flex
;
align-items
:
center
;
.unbind-tag
{
font-size
:
12px
;
color
:
#ff4d4f
;
border
:
1px
solid
#ff4d4f
;
padding
:
2px
8px
;
border-radius
:
4px
;
background
:
#fff2f0
;
}
.chat-btn
{
font-size
:
12px
;
padding
:
6px
12px
;
border-radius
:
4px
;
height
:
auto
;
}
}
//
加载状态
.loading-wrapper
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
20px
;
color
:
#999
;
i
{
margin-right
:
8px
;
}
}
//
无更多数据
.no-more
{
text-align
:
center
;
padding
:
16px
;
color
:
#999
;
font-size
:
12px
;
}
//
空状态
.empty-state
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
padding
:
60px
20px
;
color
:
#999
;
i
{
font-size
:
48px
;
margin-bottom
:
16px
;
color
:
#ddd
;
}
div
{
font-size
:
14px
;
}
}
//
响应式处理
@media
(
max-width
:
768px
)
{
.search-header
{
padding
:
12px
;
.search-row
{
gap
:
8px
;
}
.search-type-select
{
width
:
70px
;
}
}
.contact-item
{
padding
:
10px
12px
;
}
.contact-info
.contact-name
{
font-size
:
15px
;
}
}
</
style
>
\ No newline at end of file
src/views/quickReply.vue
浏览文件 @
22a18be7
...
...
@@ -51,11 +51,6 @@
import
skillCompany
from
'./components/skill/skillCompany.vue'
import
skillPersonal
from
'./components/skill/skillPersonal.vue'
import
skillLibrary
from
'./components/skill/skillLibrary.vue'
// import aiLibrary from './components/skill/aiLibrary.vue'
import
{
mapState
,
mapMutations
,
mapActions
}
from
'vuex'
import
{
getSignature
}
from
'@/api/user'
import
Cookies
from
'js-cookie'
import
jsApiList
from
'@/utils/jsApiList'
export
default
{
components
:
{
skillCompany
,
...
...
@@ -69,54 +64,12 @@ export default {
}
},
created
()
{
this
.
getSignature
()
this
.
initializeWecom
()
},
mounted
()
{
},
methods
:
{
async
getSignature
()
{
console
.
log
(
'获取签名'
,
window
.
location
.
href
)
const
corp_id
=
Cookies
.
get
(
'corp_id'
)
try
{
const
res
=
await
getSignature
({
corp_id
:
corp_id
,
path
:
window
.
location
.
href
});
if
(
res
.
status_code
===
1
)
{
try
{
this
.
registerWeComSDK
(
res
.
data
);
}
catch
(
err
)
{
console
.
log
(
err
,
'初始化sdk 失败'
)
}
}
}
catch
(
err
)
{
console
.
log
(
err
,
'获取签名失败'
)
// window.location.href = window.location.origin + '/company_app/index.html?corp_id=' + corp_id + '&msg=signerror'
}
},
registerWeComSDK
(
signData
)
{
this
.
$ww
.
register
({
corpId
:
Cookies
.
get
(
'corp_id'
),
agentId
:
signData
.
agent_id
,
jsApiList
:
jsApiList
,
// getConfigSignature: () => Promise.resolve({
// nonceStr: this.signData.nonce_str,
// timestamp: this.signData.signature_time,
// signature: this.signData.corp_signature,
// }),
// 只用到应用的 api 可以只进行应用的签名
getAgentConfigSignature
:
()
=>
Promise
.
resolve
({
nonceStr
:
signData
.
nonce_str
,
timestamp
:
signData
.
signature_time
,
signature
:
signData
.
agent_signature
,
}),
onAgentConfigSuccess
:
(
res
)
=>
{
console
.
log
(
'注册成功可以调用企微 js-sdk'
,
res
)
// 注册成功后不立即获取外部联系人,等钉钉扫码后再获取
},
onAgentConfigFail
:
(
err
)
=>
{
console
.
log
(
'注册失败不能使用企微js-sdk'
,
err
)
// 错误处理123
}
});
},
}
}
</
script
>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论