提交 22a18be7 作者: 毛细亚

通讯录调试完成

上级 cfcd937b
......@@ -89,6 +89,10 @@ export default {
label: '申请记录',
path: '/applyRecord'
},
{
label: '通讯录',
path: '/mailList'
},
// {
// label: '快捷发送',
// path: '/quickSend'
......
......@@ -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
<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
# 企业微信 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
......@@ -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')
......
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 {
......
const jsApiList = [
'getCurExternalContact',
'sendChatMessage',
'openEnterpriseChat',
]
export default jsApiList
\ No newline at end of file
......@@ -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.username
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 = username
params.user_id = this.cser_id
params.user_name = this.cser_name
setTimeout(() => {
this.loading = false
}, 3000)
......
<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>
......
......@@ -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 {
},
// 发送图片作为链接消息
sendImageAsLink(picurl) {
sendImageAsMedia(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, '发送图片链接成功')
......
......@@ -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('图片链接不存在,无法发送')
}
},
// 发送图片作为链接消息
sendImageAsLink(picurl) {
sendImageAsMedia(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, '发送图片链接成功')
......
<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
......@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论