初始化

This commit is contained in:
Cc 2025-04-15 15:52:19 +08:00
commit 2f504b56ff
21 changed files with 2882 additions and 0 deletions

8
.env.development Normal file
View File

@ -0,0 +1,8 @@
# 页面标题
VITE_APP_TITLE = 企业微信客户
# 开发环境配置
VITE_APP_ENV = 'development'
# 若依管理系统/开发环境
VITE_APP_BASE_API = '/dev-api'

12
.env.production Normal file
View File

@ -0,0 +1,12 @@
# 页面标题
VITE_APP_TITLE = 企业微信客户
# 生产环境配置
VITE_APP_ENV = 'production'
# 若依管理系统/生产环境
VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

View File

@ -0,0 +1 @@
Qui5f0IP67T3uIhB

18
index.html Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>客户选择</title>
</head>
<body>
<div id="app"></div>
<script>
</script>
<script type="module" src="/src/main.js"></script>
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script>
</body>
</html>

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "wxjs",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"build-only": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@vitejs/plugin-legacy": "^5.2.0",
"@wecom/jssdk": "^1.4.3",
"axios": "^1.5.1",
"vant": "^4.7.2",
"vue": "^3.3.4"
},
"devDependencies": {
"@types/node": "^20.8.7",
"@vitejs/plugin-vue": "^4.2.3",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vue-tsc": "^1.8.5"
}
}

2220
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

281
src/App.vue Normal file
View File

@ -0,0 +1,281 @@
<template>
<div style="height:45px" v-if="list.length">
<span>已选择的客户</span>
<van-button
size="small"
type="warning"
@click="delUser"
style="float:right"
>
清空选择
</van-button>
</div>
<van-list
class="listCss"
v-model:loading="loading"
:finished="finished"
finished-text="到底啦~~~"
v-if="list.length"
>
<van-radio-group inset>
<van-cell
v-for="(item, index) in list"
:title="item.value"
:label="item.corpFullName"
>
<template #icon>
<img
:src="item.avatar"
style="
width: 25px;
height: 25px;
border-radius: 50%;
margin-right: 10px;
"
/>
</template>
</van-cell>
</van-radio-group>
</van-list>
<van-empty description="暂未选择客户" v-else />
<van-button
style="width: 100%; position: fixed; bottom: 0"
type="primary"
v-if="showBn"
@click="setValueUSer"
>
{{ list.length ? "重新选择(" + list.length + ")" : "客户选择" }}
</van-button>
</template>
<script setup>
import { ref, watch } from "vue";
import { showConfirmDialog } from 'vant';
import { getJsConf, getAgentConf, getUserData } from "@/api/wx";
// import * as ww from '@wecom/jssdk'
let url = window.location.href.split("#")[0];
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const key = getQueryString("key");
const selectorType = getQueryString("selectorType");
let agentId = "1000164";
let code = getQueryString("code");
let showBn = ref(false)
let userid=localStorage.getItem('userid')
if (code||userid) {
JSsdkPromis().then(res=>{
showBn.value =true
initSelctData()
})
} else {
url = encodeURIComponent(url + "&");
window.open(
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=ww70d3bce9fa48251f&redirect_uri=" +
url +
"response_type=code&scope=snsapi_base&state=STATE&agentid=" +
agentId +
"#wechat_redirect",
"_self"
);
}
//
function delUser(){
showConfirmDialog({
title: '清空',
message:
'是否清空当前选中的客户',
})
.then(() => {
list.value = []
wx.invoke(
"saveApprovalSelectedItems",
{
key: key, // URL key
selectedData:JSON.stringify(list.value),
},
(res) => {
if (res.err_msg === "saveApprovalSelectedItems:ok") {
}
}
);
// on confirm
})
.catch(() => {
// on cancel
});
}
//jsSdk
function JSsdkPromis() {
return new Promise((resolve, reject) => {
getJsConf({ url: url, agentId }).then((res) => {
let config = res.data;
wx.config({
beta: true, // wx.invokejsapi
debug: false, // ,apialertpclogpc
appId: "ww70d3bce9fa48251f", // corpIDcorpID使
timestamp: config.timestamp, //
nonceStr: config.nonceStr, //
signature: config.signature, // -JS-SDK使
jsApiList: [
"saveApprovalSelectedItems",
"getApprovalSelectedItems",
"selectExternalContact",
], // 使JS
});
wx.ready(function () {
getAgentConf({ url: url, agentId }).then((res) => {
let config = res.data;
wx.agentConfig({
corpid: "ww70d3bce9fa48251f", // corpid
agentid: agentId, // id e.g. 1000247
timestamp: config.timestamp, //
nonceStr: config.nonceStr, //
signature: config.signature, // -JS-SDK使
jsApiList: [
"saveApprovalSelectedItems",
"getApprovalSelectedItems",
"selectExternalContact",
], //使
success: function (res) {
resolve()
},
fail: function (res) {
if (res.errMsg.indexOf("function not exist") > -1) {
alert("版本过低请升级");
}
reject()
},
});
});
});
});
});
}
//
function initSelctData() {
wx.invoke(
"getApprovalSelectedItems",
{
key: key, // URL key
},
(res) => {
if (res.err_msg === "getApprovalSelectedItems:ok") {
if(res.selectedData){
let selectId = JSON.parse(res.selectedData);
let data = selectId.map((item) => {
return JSON.parse(item.key) ;
});
list.value = data
finished.value = true;
}
// res.selectedData JSON
}
}
);
}
//id
function getUSerDataList(data) {
let useridList = data;
finished.value = false;
getUserData({
useridList,
agentId,
userid,
code
}).then((res) => {
userid = res.data.userid;
localStorage.setItem('userid', res.data.userid)
list.value = res.data.contacts.map((item) => {
return {
key: item.externalUserId,
value: item.name,
corpFullName: item.corpName,
avatar: item.avatar,
};
});
finished.value = true;
let data = JSON.parse(JSON.stringify(list.value))
data.forEach(item=>{
let corpName = item.corpFullName?'('+item.corpFullName+')':''
item.key = JSON.stringify(item)
item.value = item.value+corpName
})
wx.invoke(
"saveApprovalSelectedItems",
{
key: key, // URL key
selectedData: JSON.stringify(data),
},
(res) => {
if (res.err_msg === "saveApprovalSelectedItems:ok") {
}
}
);
});
}
//
function getQueryString(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
//
function setValueUSer() {
wx.invoke(
"selectExternalContact",
{
filterType: 0, //010012.4.22
},
function (res) {
if (res.err_msg == "selectExternalContact:ok") {
getUSerDataList(res.userIds);
} else {
//
}
}
);
}
</script>
<style scoped>
.flexdCss {
position: fixed;
top: 0;
z-index: 1;
width: 100%;
}
.tagCss {
margin-top: 55px;
}
.listCss {
height: calc(100vh - 90px);
overflow-y: scroll;
}
:deep(.van-tag) {
padding: 5px;
margin: 5px;
}
</style>

22
src/api/wx.js Normal file
View File

@ -0,0 +1,22 @@
import request from '@/utils/request'
export function getJsConf(data){
return request({
url:'/wxcp/getJsConf',
method: 'post',
data:data
})
}
export function getAgentConf(data){
return request({
url:'/wxcp/getAgentConf',
method: 'post',
data:data
})
}
export function getUserData(data){
return request({
url:'/wxcp/getExternalcontact',
method: 'post',
data:data
})
}

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,39 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

16
src/main.js Normal file
View File

@ -0,0 +1,16 @@
import { createApp } from 'vue'
import { Search,List, Cell, CellGroup,Radio ,RadioGroup ,Tag,Button,Empty } from 'vant';
import './style.css'
import App from './App.vue'
import 'vant/lib/index.css';
const app = createApp(App);
app.use(Search)
app.use(List )
app.use(Cell);
app.use(Radio);
app.use(CellGroup);
app.use(RadioGroup);
app.use(Tag);
app.use(Empty);
app.use(Button);
app.mount('#app')

39
src/style.css Normal file
View File

@ -0,0 +1,39 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

78
src/utils/request.js Normal file
View File

@ -0,0 +1,78 @@
import axios from 'axios'
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 对应国际化资源文件后缀
axios.defaults.headers['Content-Language'] = 'zh_CN'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
}
console.log(config)
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
if (code == 200){
return Promise.resolve(res.data)
}else{
alert(res.data.msg)
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
alert(message)
return Promise.reject(error)
}
)
export default service

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

25
tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/main.js"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

39
vite.config.ts Normal file
View File

@ -0,0 +1,39 @@
import { defineConfig } from 'vite'
import path from 'path'
import vue from '@vitejs/plugin-vue'
import legacy from "@vitejs/plugin-legacy";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
legacy({
targets: [
"Safari >= 10.1",
"iOS >= 8",
],
}),
],
resolve: {
alias: {
// 设置别名
'@': path.resolve(__dirname, './src'),
}
},
server: {
port: 80,
host: true,
open: true,
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/dev-api': {
target: 'http://water.lidinghb.com:9090/',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}
}
}
})