提交 72b75eb3 作者: 毛细亚

交给 agent

上级 4ab56808
......@@ -34,7 +34,7 @@ alwaysApply: false
- 状态管理 使用 vuex 3.6.2 管理状态
- 使用 element-ui 2.15.6 作为 UI 库
- axios 请求 api
- 基于企业微信 新版本 jssdk 来进行开发 企业微信的变量是 `ww`
# 开发规范
- 组件命名规范
- Props类型声明 Props 尽量用规范的写法 并且声明默认值
......@@ -54,3 +54,5 @@ alwaysApply: false
# UI 规范
因为 企业微信侧边栏 宽度有限 所以尽量使用 自适应的样式来进行开发
# cebianlan
# 基于企业微信客户端开发 企业微信侧边栏网页应用
基于企业微信客户端 开发侧边栏功能 企业微信的文档 [企业微信文档]([链接地址](https://developer.work.weixin.qq.com/document/path/94352) )
## 项目介绍
企微微信中内嵌掌微第三方页面 来支持开发企业微信侧边栏功能
## 项目结构
## Project setup
```
pnpm install
```
- @src/:主源码目录,包括:
- @main.js:应用入口文件。
- @App.vue:根组件,负责全局认证逻辑。
- @router/:路由配置,主文件为 @index.js。
- @store/:Vuex 状态管理,主文件为 @index.js。
- @api/:接口请求封装,示例:@user.js。
- @utils/:工具函数和请求封装,如 @request.js。
- @mixins/:Vue 混入逻辑,如 @externalUserId.js。
- @views/:页面组件,如 @HomeView.vue、@AboutView.vue。
- @styles/:全局和局部样式文件。
- @components/:可复用组件(当前为空)。
- @assets/:静态资源(如图片等)。
- @docs: 文档内容
- @public/:静态资源目录,供打包时复制到最终输出。
- @package.json:项目依赖和脚本配置。
- @README.md:项目说明文档。
### Compiles and hot-reloads for development
```
pnpm run serve
```
### Compiles and minifies for production
```
pnpm run build
```
如需详细了解某一目录或文件,可参考上述路径。
### Lints and fixes files
```
pnpm run lint
```
## 技术库
- 前端框架 vue 2.6
- 脚手架 用 vue-cli 5.0.0 创建
- 路由 使用 vue-router 3.5.1 管理路由
- 状态管理 使用 vuex 3.6.2 管理状态
- 使用 element-ui 2.15.6 作为 UI 库
- axios 请求 api
- 基于企业微信 新版本 jssdk 来进行开发 企业微信的变量是 `ww`
## 开发规范
- 组件命名规范
- Props类型声明 Props 尽量用规范的写法 并且声明默认值
- 响应式最佳实践
- 生命周期规范
- 事件处理规范
- 样式作用域
- 使用 try/catch 块处理异步操作
- 使用组件化的概念 尽量按照功能切分成多个组件 一个组件的代码不要太多
工程化要求:
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
- ESLint + Prettier
- 性能优化
- 代码分割
- 懒加载实现
## UI 规范
\ No newline at end of file
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>cebianlan</title><script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script><script defer="defer" src="static/js/chunk-vendors.6fa09188.js"></script><script defer="defer" src="static/js/app.ebbeaa03.js"></script><link href="static/css/app.9dbe61af.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but cebianlan doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>cebianlan</title><script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script><script defer="defer" src="static/js/chunk-vendors.fca6e705.js"></script><script defer="defer" src="static/js/app.e2a51498.js"></script><link href="static/css/chunk-vendors.f50bf6ae.css" rel="stylesheet"><link href="static/css/app.bd071c00.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but cebianlan doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
......@@ -16,6 +16,8 @@
"core-js": "^3.8.3",
"dingtalk-jsapi": "^3.1.0",
"element-ui": "^2.15.14",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"sass": "^1.89.0",
"sass-loader": "^16.0.5",
......
......@@ -32,6 +32,12 @@ importers:
element-ui:
specifier: ^2.15.14
version: 2.15.14(vue@2.7.16)
js-cookie:
specifier: ^3.0.5
version: 3.0.5
lodash:
specifier: ^4.17.21
version: 4.17.21
moment:
specifier: ^2.29.1
version: 2.30.1
......
......@@ -7,7 +7,6 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
</head>
<body>
<noscript>
......
......@@ -38,7 +38,7 @@ export default {
if(this.num>3){
return
}
// window.location.href = returnUrl;
window.location.href = returnUrl;
},
handleAuthCode() {
console.log('进入handleAuthCode')
......
import request from '@/utils/request'
// import { getSignatureData } from '@wecom/jssdk'
export function cross_systemRequest(data) {
!data.noApi ? data.api = '/api' + data.api : ''
return request({
url: '/api/cross_system/request',
method: 'post',
headers:{
"Corp-Id":"wweaefe716636df3d1"
},
data
})
}
export function getAuthUser(data) {
return request({
url: '/api/sidebar_login/workWeiXin',
......@@ -8,6 +19,9 @@ export function getAuthUser(data) {
data
})
}
export function getSignature(data) {
// return getSignature(data)
}
export function getOrganization(data) {
return new Promise((resovle, reject) => {
cross_systemRequest({
......@@ -19,14 +33,16 @@ export function getAuthUser(data) {
})
})
}
export function cross_systemRequest(data) {
!data.noApi ? data.api = '/api' + data.api : ''
export function getDingLogin(data) {
return request({
url: '/api/cross_system/request',
url: '/api/sidebar_login/ding',
method: 'post',
headers:{
"Corp-Id":"wweaefe716636df3d1"
},
data
})
}
\ No newline at end of file
}
\ No newline at end of file
# 企业微信客户端侧边栏应用开发
基于企业微信客户端 开发侧边栏功能 企业微信的文档 [企业微信文档]([链接地址](https://developer.work.weixin.qq.com/document/path/94352) )
基于企业微信客户端 开发侧边栏功能 企业微信的文档 [企业微信文档]([链接地址](https://developer.work.weixin.qq.com/document/path/94352) ) 首先需要调用微信的 授权接口 进行授权拿到 用户的 code 等信息
以下是初始化页面需要完成的功能
# 需求流程
## 1.企微授权
每次进去页面都需要去进行企微授权 授权成功后 通过接口 获取 当前客服号的 userid 和 企业微信签名的一些信息 去校验签名 然后调用 企业微信的 js sdk 来获取 企业微信的一些api 然后再进行 侧边栏的开发
主要页面的功能在 `@/App.vue` 中 这个逻辑只是暂时写在 App.vue 中 需要你帮我重构一下 重新规划页面逻辑处理
`@/App.vue` 中 的方法和功能如下
- `Authorize` 方法: 如果当前页面没有授权过 则重定向到企业微信的授权页面
- `handleAuthCode` 方法: 每次进入页面都要重新调用这个方法 先查看 当前 url 中 是否有 code 参数 如果有 code 参数 则调用 `getAuthUser` 方法 去获取 用户的信息 然后再进行 企业微信的签名校验 然后再调用 企业微信的 js sdk 来获取 企业微信的一些api 然后再进行 侧边栏的开发
- `getAuthUser` 方法:通过 code 获取当前登录企业微信的用户信息 userid 这个userid 要储存在 cookie 中 过期时间 为 7 天
- `getSignature` 方法: 通过接口获取 企业微信的签名信息 然后调用 企业微信的 js sdk 来获取 企业微信的一些api 然后再进行 侧边栏的开发 这个接口的调用应该在 钉钉授权成功以后 调用 如果没有钉钉授权成功 则不调用 说明使用人 没有登录 不需要调用企微微信的一些功能
- `register`方法: 钉钉登录成功以后 回调到当前页面地址 需要 用之前获取的签名信息 去校验 企业签名和应用签名 通过后就可以使用 企业微信的 js sdk 来获取一些 api 方法
## 2. 在企业微信jssdk 初始化完成后 钉钉扫码界面的开发
### 2.1 钉钉扫码界面的开发
进入侧边栏页面需要钉钉扫码登录 所以需要先进入钉钉扫码登录页面 所有的页面都需要先登录 扫码登录页面是 `@/views/ddLogin.vue` 这个页面的逻辑如下
- `initOrganization` 方法: 获取当前的组织架构列表 可以通过 选择组织架构来决定获取
- `handleLogin` 方法: 通过钉钉的扫码登录 扫码成功后钉钉会重定向一个后端地址 会携带钉钉的 code 参数 后端获取到 code 参数 后 会重定向当前的前端页面 url 上 会携带 token 参数 然后把 token 存在 cookie 中 过期时间 为 7 天
- 每次刷新重新进入页面的时候 都要检查一下 cookie 中是否有 token 如果有 说明已经登录过了 不需要重新钉钉扫码登录 如果没有 则需要重新钉钉扫码登录
### 2.2 钉钉扫码登录成功以后
1. 钉钉扫码登录成功以后 才可以查看其他的页面 如果钉钉没有扫码登录 则无法查看其他的页面 页面只展示钉钉扫码登录的页面
2. 钉钉扫码登录成功以后 会把 token 存在 cookie 中 过期时间 为 7 天
3. 登录成功以后会有一个退出的按钮 点击退出按钮 会把 cookie 中的 token 删除 清除 token 然后跳转到钉钉扫码登录页面 重新钉钉扫码登录
4. 需要把钉钉扫码成功后的 token 每次请求接口的时候 放在请求头中 每个接口需要放上 token 才能获取到数据 后端需要校验 请你帮我完成这部分工作
### 3.cookie 中需要缓存 通过企业微信获取的 userid 和 钉钉扫码成功后 获取的 token
### 4.关于回调地址
1. 本项目中会经历两次页面回调 一次是企微的授权验证回调 一次是 钉钉登录成功的回调 回调页面必须在首页完成 因为这个回调地址已经配置过了 不能更改 至于写在 app.vue 中还是 vuex 中 或者 另外的 js 中 你自己决定 只需要保证 在用户登录的时候 能够拿到 token 就可以了
2. 回调的时候 引入
```js
import {getParams} from '@/utils/index.js'
```
获取 url 后面的参数 储存在 vuex 中
\ No newline at end of file
# 企业微信开发接入文档
# 企业微信开发接入文档
## 一、前期准备
### 1.1 开发者平台配置
1. 登录 企业微信开发者平台
2. 创建应用并获取以下信息:
- corpid(企业ID)
- agentid(应用ID)
- Secret(应用密钥)
3. 配置可信域名
4. 开通相关接口权限
## 二、项目配置
在配置签名的时候 后端签名时候的 url 应该是 重定向回来的那个 url 带 code 和 state 参数 而不是 在企微后台设置的那个 url 就是 后端签名的 url 要和 授权的时候 当前的那个页面的 url 一样 但是不包含 # 和 # 后面的url 内容
应该是 https://companywx.jianshuwenhua.com/company_app/index.html?code=-aXitj4Tt8UNLq_evPRgECSVS6zHpP6-r3ofYzk0BeM&state=STATE 带上参数去签名
### 2.1 引入企业微信 JS-SDK
在项目的 index.html 中添加:
老版本引入 方式:
```
<script src="https://res.wx.qq.com/open/js/
jweixin-1.2.0.js"></script>
```
新版本引入方法
```
<script src="https://wwcdn.weixin.qq.com/node/open/js/wecom-jssdk-2.0.2.js"></script>
```
### 2.2 基础配置示例
查看文档 [文档](https://developer.work.weixin.qq.com/document/path/94325)
以下是老版本的配置内容
先配置 企业应用 id 签名 然后 在配置 应用id 签名 必须先配置企业 id的签名人 然后再配置应用id的签名
```
wx.config({
  beta: true, // 必须这么写,否则wx.invoke调用形式
  的jsapi会有问题
  debug: false, // 开启调试模式
  appId: corp_id, // 必填,企业微信的corpID
  timestamp: time, // 必填,生成签名的时间戳
  nonceStr: nonceStr, // 必填,生成签名的随机串
  signature: signature, // 必填,签名
  jsApiList: ["getContext", 
  "getCurExternalContact"] // 必填,需要使用的JS
  接口列表
});
```
### 2.3 应用配置示例
```
wx.agentConfig({
  corpid: corp_id, // 必填,企业微信的corpid
  agentid: agentid, // 必填,企业微信的应用id
  timestamp: time, // 必填,生成签名的时间戳
  nonceStr: nonceStr, // 必填,生成签名的随机串
  signature: signature, // 必填,签名
  jsApiList: ["getContext", 
  "getCurExternalContact"], // 必填,需要使用的JS
  接口列表
  success: function(res) {
    // 回调
  },
  fail: function(res) {
    if (res.errMsg.indexOf('function not 
    exist') > -1) {
      alert('版本过低请升级')
    }
  }
});
```
新版本的配置
同时配置企业签名 和 应用签名 配置完成调用 wx的方法即可
```
ww.register({
corpId: that.userInfo.corp_id, // 必填,当前用户企业所属企业ID
agentId: 1000013, // 必填,当前应用的AgentID
jsApiList: jsApiList, // 必填,需要使用的JSAPI列表
getConfigSignature:function(){ // // 必填,根据url生成企业签名的回调函数
return new Promise((resolve, reject) => {
resolve({
nonceStr: that.userInfo.nonceStr,
timestamp: that.userInfo.time,
signature: that.userInfo.corp_signature,
})
})
},
getAgentConfigSignature:function(){ // 必填,根据url生成应用签名的回调函数
return new Promise((resolve, reject) => {
resolve({
nonceStr: that.userInfo.nonceStr,
timestamp: that.userInfo.time,
signature: that.userInfo.agent_signature,
})
})
}
})
```
## 三、常用接口
### 3.1 获取外部联系人信息
```
wx.invoke('getCurExternalContact', {
}, function(res) {
  if (res.err_msg == 
  "getCurExternalContact:ok") {
    var userId = res.userId; //返回当前外部联系人
    userId
  } else {
    //错误处理
  }
});
```
## 四、环境配置
### 4.1 开发环境配置
```
// .env.development
VUE_APP_CORPID=your_development_corpid
VUE_APP_AGENTID=your_development_agentid
```
### 4.2 生产环境配置
```
// .env.production
VUE_APP_CORPID=your_production_corpid
VUE_APP_AGENTID=your_production_agentid
```
## 六、常见问题
### 6.1 接口调用失败
- 检查域名配置
- 验证 corpid 和 agentid 正确性
- 确认环境是否支持 HTTPS
- 检查签名是否正确
- 后端授权时的 url 是否和当前授权页面的 url 是否一直 包含参数和是否一直 但是不包含 # 和 # 后面的url 内容
......@@ -2,6 +2,11 @@ import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import Cookies from 'js-cookie';
import _ from 'lodash';
Vue.use(ElementUI);
// import '@/styles/index.scss' // global css
Vue.config.productionTip = false
import VConsole from 'vconsole';
......@@ -11,3 +16,13 @@ new Vue({
store,
render: h => h(App)
}).$mount('#app')
Vue.prototype.$cookies = Cookies;
Vue.prototype.$lodash = _;
const name = Cookies.get('name');
if(!name){
console.log('cookie 不存在');
Cookies.set('name', 'maoxiya');
}else{
console.log('cookie 存在');
}
......@@ -20,7 +20,7 @@ const routes = [
// }
]
const router = new VueRouter({
mode: 'hash',
mode: 'history',
base: process.env.BASE_URL,
routes
})
......
......@@ -103,7 +103,7 @@ export function formatTime(time, option) {
* @param {string} url
* @returns {Object}
*/
export function param2Obj(url) {
export function getParams(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
......
<template>
<div class="DDlogin">
钉钉扫码
<h1 @click="about">钉钉扫码1231312</h1>
<div v-if="!organizationShow" class="contain-contain">
<div class="back">
<span style="font-size: 12px">当前组织:</span>
......@@ -21,7 +21,7 @@
</div>
</template>
<script>
import { getOrganization } from "@/api/user";
import { getOrganization,getDingLogin } from "@/api/user";
export default {
name: "App",
components: {},
......@@ -31,17 +31,21 @@ export default {
currentApp: {},
organizationShow: false,
dingAppid: window.location.host === 'zq.wozhangwan.com' ? 'dingoafvrnicn48bydk92l' : 'dingoamtigagqd7h2mxawd',
dingRedirect_uri: process.env.NODE_ENV === 'production' ? encodeURIComponent(window.location.href) : 'https://companywx.jianshuwenhua.com/company_app/index.html?type=zq',
dingRedirect_uri: process.env.NODE_ENV === 'production' ? encodeURIComponent(window.location.href) : 'https://companywx.zwwlkj03.top/api/api/sidebar_login/ding',
};
},
created() {
this.initOrganization();
},
mounted() {
this.$nextTick(() => {
// this.getDingLogin()
});
},
methods: {
about(){
this.$router.push('/about')
},
async initOrganization() {
this.organizationList = [];
const res = await getOrganization();
......@@ -64,7 +68,7 @@ export default {
const obj = DDLogin({
id: 'login_container',
// goto这里需要对url整体做一个urlencode编码
goto: encodeURIComponent(`https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${this.currentApp.app_key}&type=login&response_type=code&scope=snsapi_login&state=${this.currentApp.app_key}&redirect_uri=${this.dingRedirect_uri}`),
goto: encodeURIComponent(`https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${this.currentApp.app_key}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${this.dingRedirect_uri}`),
style: 'border:none;background-color:#FFFFFF;margin:0',
width: '210',
height: '250'
......@@ -75,7 +79,8 @@ export default {
if (origin === 'https://login.dingtalk.com') {
const loginTmpCode = event.data
// 这里url不用进行urlencode编码
const url = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=' + this.currentApp.app + '&response_type=code&scope=snsapi_login&state=' + this.currentApp.app_key + '&redirect_uri=' + this.dingRedirect_uri + '&loginTmpCode=' + loginTmpCode
const urlparam = encodeURIComponent('dingjigp0ksn9nbljdli&corp_id=wweaefe716636df3d1&template_code=DING_CON_9KDF4N7GC&userid=JinDuoXia')
const url = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=' + this.currentApp.app + `&response_type=code&scope=snsapi_login&state=${urlparam}&redirect_uri=` + this.dingRedirect_uri + '&loginTmpCode=' + loginTmpCode
window.location.href = url
}
}
......@@ -85,6 +90,19 @@ export default {
window.attachEvent('onmessage', hanndleMessage)
}
},
async getDingLogin() {
const data = {
corp_id:'wweaefe716636df3d1',
userid:'JinDuoXia',
template_code:'DING_CON_9KDF4N7GC',
state:'dingjigp0ksn9nbljdli',
code:'dfcdc6bb54db3bd4be9340772fcdc1d5'
}
const res = await getDingLogin(data);
if (res.status_code === 1) {
console.log(res)
}
}
},
};
</script>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论