提交 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, '发送图片链接成功')
......
......@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论