online-chat

This commit is contained in:
Shao Huan Qing
2021-06-02 20:44:54 +08:00
parent d567aa9e0e
commit 0fd417ce1a
17 changed files with 648 additions and 8 deletions

View File

@ -13,7 +13,7 @@
<!-- <span>微信扫码</span> -->
<p style="margin-top: 10px; margin-bottom: 0; text-align: center;">
<!-- <img src="/images/dz.png" style="width: 150px;"></img> -->
<img src="./JoinCKACommunity.jpeg" style="width: 150px; padding: 10px;"></img>
<img src="./JoinCKACommunity.jpeg" style="width: 150px; padding: 10px;"/>
</p>
</div>
</div>

View File

@ -31,6 +31,7 @@
<main class="page" style="padding-top: 2rem;">
<Content class="theme-default-content" style="padding-top: 0; margin-top: 0; padding-bottom: 1rem;"/>
<PageVssue style="max-width: 1000px; margin: auto; padding: 0 2.5rem;"></PageVssue>
<OnlineChat></OnlineChat>
</main>
</div>
</template>
@ -41,9 +42,10 @@ import Navbar from '@theme/components/Navbar.vue'
import Page from '@theme/components/Page.vue'
import Sidebar from '@theme/components/Sidebar.vue'
import { resolveSidebarItems } from '../theme/util'
import OnlineChat from '../theme/components/OnlineChat.vue'
export default {
components: { Home, Page, Sidebar, Navbar },
components: { Home, Page, Sidebar, Navbar, OnlineChat },
data () {
return {

View File

@ -11,15 +11,14 @@
<div style="text-align: center; font-size: 18px; weight: 500;">
<div style="background-color: rgb(236, 245, 255); padding: 10px 10px 10px 10px; margin-bottom: 10px; border: solid 1px #007af5;">
<a href="https://github.com/eip-work/kuboard-press" target="_blank" @click="linkToStar">
一个 Github Star
一个 Github Star
<OutboundLink/>
</a>
就可以鼓励作者尽快完成
<span style="color: red; font-weight: 500;">剩下的 {{$themeConfig.incompleteRatio}}% </span>
是您对 Kuboard 最大的鼓励
</div>
<a href="https://github.com/eip-work/kuboard-press" target="_blank" @click="linkToStar">
<div style="border: solid 1px #ddd;">
<img src="./star.png" style="max-width: 100%; opacity: 0.6;">
<img src="./star.png" style="max-width: 100%; opacity: 0.8;">
</div>
</a>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

@ -4,7 +4,26 @@ module.exports = {
// configureWebpack: () => ({
// devtool: 'source-map'
// }),
host: 'kuboard-develop',
port: 8000,
devServer: {
proxy: {
'/uc-api/': {
target: 'http://kuboard-develop:8080',
changeOrigin: true,
pathRewrite: {
'^/uc-api': '/'
}
},
// '/uc-api/': {
// target: 'https://uc-v3.kuboard.cn',
// changeOrigin: true,
// pathRewrite: {
// '^/uc-api': '/api'
// }
// },
},
},
modules: ['bootstrap-vue/nuxt'],
title: 'Kuboard',
description: '一款Kubernetes_Dashboard_简化Kubernetes的学习和使用_帮助您快速落地Kubernetes_提供_Kubernetes_免费中文教程_国内安装文档',

View File

@ -13,6 +13,7 @@ import Container from './grid/Container'
import Grid from './grid/Grid'
import GridItem from './grid/GridItem'
import defaults from './grid/utils/defaults'
import './login-manager.js'
import Comp from './comp/index'

View File

@ -0,0 +1,23 @@
import Vue from 'vue'
import Cookies from 'js-cookie'
let TOKEN_KEY = 'kb-user-center-token'
window.KbUcloginStatus = {
status: Cookies.get(TOKEN_KEY) !== null && Cookies.get(TOKEN_KEY) !== undefined,
user: undefined
}
Vue.prototype.$login = function (token) {
// axios.defaults.headers.common['Authorization'] = token;
// localStorage.setItem(TOKEN_KEY, token)
Cookies.set(TOKEN_KEY, token, { path: '/' })
window.KbUcloginStatus.status = true
}
Vue.prototype.$logout = function () {
window.KbUcloginStatus.status = false
// localStorage.removeItem(TOKEN_KEY)
Cookies.remove(TOKEN_KEY, { path: '/' })
// delete (axios.defaults.headers.common['Authorization'])
}

3
.vuepress/public/chat/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,192 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible " content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Kuboard 在线支持</title>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0
}
</style>
</head>
<body>
<script src="./axios.min.js"></script>
<script type="text/javascript">
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
}
Date.prototype.format = function(fmt) {
var o = {
"M+" : this.getMonth()+1, //月份
"d+" : this.getDate(), //日
"h+" : this.getHours(), //小时
"m+" : this.getMinutes(), //分
"s+" : this.getSeconds(), //秒
"q+" : Math.floor((this.getMonth()+3)/3), //季度
"S" : this.getMilliseconds() //毫秒
};
if(/(y+)/.test(fmt)) {
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
}
for(var k in o) {
if(new RegExp("("+ k +")").test(fmt)){
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
}
}
return fmt;
}
let TOKEN_KEY = 'kb-user-center-token'
function parse(query) {
var qs = {};
var i = query.indexOf('?');
if (i < 0 && query.indexOf('=') < 0) {
return qs;
} else if (i >= 0) {
query = query.substring(i + 1);
}
var parts = query.split('&');
for (var n = 0; n < parts.length; n++) {
var part = parts[n];
var key = part.split('=')[0];
var val = part.split('=')[1];
key = key.toLowerCase();
if (typeof qs[key] === 'undefined') {
qs[key] = decodeURIComponent(val);
} else if (typeof qs[key] === 'string') {
var arr = [qs[key], decodeURIComponent(val)];
qs[key] = arr;
} else {
qs[key].push(decodeURIComponent(val));
}
}
return qs;
}
async function init() {
(function (m, ei, q, i, a, j, s) {
m[i] =
m[i] ||
function () {
(m[i].a = m[i].a || []).push(arguments);
};
(j = ei.createElement(q)), (s = ei.getElementsByTagName(q)[0]);
j.async = true;
j.charset = 'UTF-8';
j.src = 'https://static.meiqia.com/widget/loader.js';
s.parentNode.insertBefore(j, s);
})(window, document, 'script', '_MEIQIA');
var data = parse(window.location.search);
var entId = data.entid || data.eid;
if (Object.prototype.toString.call(entId) === '[object Array]') {
entId = +entId[0];
} else {
entId = +entId;
}
_MEIQIA('entId', '961dff7f89ddc015ac1f8e193bf774d0' || entId);
_MEIQIA('standalone', function (config) {
if (config.color) {
document.body.style['background-color'] = '#' + config.color;
}
if (config.url) {
document.body.style['background-image'] = 'url(' + config.url + ')';
document.body.style['background-repeat'] = 'no-repeat';
document.body.style['background-size'] = '100% 100%';
}
});
_MEIQIA('withoutBtn');
if (!getCookie(TOKEN_KEY)) {
window.alert('请先登录')
window.location.href = '/support/?showLogin=true'
}
// Kuboard Logic
let ax = axios.create({
baseURL: '/uc-api',
})
let orders = []
let user = undefined
await ax.get(`/orders/list`, {params: {
status: 'PAID',
pageNum: 1,
pageSize: 1000
}}).then(resp => {
orders = resp.data.items
}).catch(e => {
console.log(e)
})
await ax.get('/users/selfInfo').then(resp => {
user = resp.data.data
}).catch(e => {
console.log(e)
})
data.clientid = user.id
let totalAmount = 0
for (let order of orders) {
totalAmount += order.totalPrice
}
totalAmount = Math.round(totalAmount)
data.metadata = {
name: user.name, // 美洽默认字段
email: user.email,
tel: user.mobile,
Count: orders.length,
Amount: totalAmount,
uc_id: user.id,
}
for (let i in orders) {
let order = orders[i]
let prefix = 'r' + (parseInt(i) + 1)
if (i >= 5) {
break
}
data.metadata[prefix + '_id'] = order.id //`${order.id} -- ${order.description} -- ${format(order.createTime, 'YYYY-MM-DD HH:mm')}`
data.metadata[prefix + '_desc'] = order.description
data.metadata[prefix + '_time'] = new Date(order.createTime).format("yyyy-MM-dd hh:mm") //format(order.createTime, 'YYYY-MM-DD HH:mm')
}
// Kuboard Logic
if (data.metadata) {
try {
_MEIQIA('metadata', metadata);
} catch (e) { }
}
if (data.language) {
_MEIQIA('language', data.language);
}
if (data.fallback) {
_MEIQIA('fallback', +data.fallback);
}
if (data.clientid) {
_MEIQIA('clientId', data.clientid);
}
if (data.agentid || data.groupid) {
_MEIQIA('assign', { agentToken: data.agentid || null, groupToken: data.groupid || null });
}
_MEIQIA('showPanel', {
greeting: data.greeting || '',
agentToken: data.agentid || null,
groupToken: data.groupid || null
});
}
init();
</script>
</body >
</html >

View File

@ -0,0 +1,168 @@
<template>
<div v-if="isdebugging">
<OnlineChatLogin ref="loginWindow" @loginSuccess="loginSuccess"></OnlineChatLogin>
<b-button variant="outline-primary" size="sm" class="logout" v-if="showLogout" @click="logout">退出登录</b-button>
<b-button variant="primary" size="sm" class="openChatWindow" v-if="showLogout" @click="openChatWindow">打开单独的聊天窗口</b-button>
<div class="frameButton" @click="showChat">
<span class="ecMDHC"></span>
</div>
</div>
</template>
<script>
import OnlineChatLogin from './OnlineChatLogin.vue'
import axios from 'axios'
import {format} from 'date-fns'
import Cookies from 'js-cookie'
(function(a, b, c, d, e, j, s) {
a[d] = a[d] || function() {
(a[d].a = a[d].a || []).push(arguments)
};
j = b.createElement(c),
s = b.getElementsByTagName(c)[0];
j.async = true;
j.charset = 'UTF-8';
j.src = 'https://static.meiqia.com/widget/loader.js';
s.parentNode.insertBefore(j, s);
})(window, document, 'script', '_MEIQIA');
_MEIQIA('entId', '961dff7f89ddc015ac1f8e193bf774d0');
_MEIQIA('manualInit');
_MEIQIA('withoutBtn');
let TOKEN_KEY = 'kb-user-center-token'
export default {
props: {
},
data() {
return {
showLogout: false,
isdebugging: location.search === '?showLogin=true'
}
},
computed: {
},
components: { OnlineChatLogin },
mounted () {
},
methods: {
showChat () {
console.log(Cookies.get(TOKEN_KEY))
if (Cookies.get(TOKEN_KEY)) {
this.loginSuccess()
// _MEIQIA('showPanel');
} else {
this.$refs.loginWindow.show()
}
},
async loginSuccess () {
this.showLogout = true
let ax = axios.create({
baseURL: '/uc-api',
// headers: {
// common: {
// Authorization: localStorage.getItem(TOKEN_KEY)
// }
// }
})
let orders = []
let user = undefined
await ax.get(`/orders/list`, {params: {
status: 'PAID',
pageNum: 1,
pageSize: 1000
}}).then(resp => {
orders = resp.data.items
}).catch(e => {
console.log(e)
})
await ax.get('/users/selfInfo').then(resp => {
user = resp.data.data
}).catch(e => {
console.log(e)
})
_MEIQIA('clientId', user.id)
let totalAmount = 0
for (let order of orders) {
totalAmount += order.totalPrice
}
totalAmount = Math.round(totalAmount)
let metadata = {
name: user.name, // 美洽默认字段
email: user.email,
tel: user.mobile,
Count: orders.length,
Amount: totalAmount,
uc_id: user.id,
}
for (let i in orders) {
let order = orders[i]
let prefix = 'r' + (parseInt(i) + 1)
if (i >= 5) {
break
}
metadata[prefix + '_id'] = order.id //`${order.id} -- ${order.description} -- ${format(order.createTime, 'YYYY-MM-DD HH:mm')}`
metadata[prefix + '_desc'] = order.description
metadata[prefix + '_time'] = format(order.createTime, 'YYYY-MM-DD HH:mm')
}
_MEIQIA('metadata', metadata);
_MEIQIA('init');
_MEIQIA('showPanel')
},
openChatWindow () {
let url = '/chat/index.html'
let a = document.createElement('a')
document.body.appendChild(a)
a.setAttribute('href', url)
a.setAttribute('target', '_blank')
a.click()
document.body.removeChild(a)
_MEIQIA('hidePanel')
this.showLogout = false
},
logout () {
this.showLogout = false
_MEIQIA('hidePanel')
localStorage.removeItem(TOKEN_KEY)
}
},
}
</script>
<style scoped lang="stylus">
.ecMDHC {
vertical-align: middle;
padding-right: 32px;
margin-top: 18px;
height: 32px;
display: inline-block;
background: url(/chat/icon-mq-round@2x.png) 0px -355px / 64px no-repeat;
}
.frameButton {
height: 60px;
width: 60px;
background-color: rgb(0, 122, 255);
border-radius: 50%;
box-shadow: rgb(0 0 0 / 16%) 0px 5px 14px;
position: fixed;
bottom: 100px;
right: 25px;
text-align: center;
cursor: pointer;
}
.logout {
position: fixed;
bottom: 0;
right: 492px;
}
.openChatWindow {
position: fixed;
bottom: 0;
right: 340px
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<b-modal ref="loginModel" id="login-model" title="付费用户专属答疑/技术支持通道" centered>
<div shadow="none" class="login" style="font-size: 14px;">
<div class="thanks">
感谢大家的支持和认可Kuboard 已经在超过三千家企业的生产环境中投产随着用户量的扩大微信群QQ群的免费答疑已经不能很好的满足许多用户的诉求为了更好的为付费用户提供支持开通了专属于付费用户的在线答疑技术支持通道
</div>
<div class="thanks2">
如果您已付费请继续登录如果您尚未付费也可以登录后咨询付费方式
<router-link to="/support/#订阅">查看定价</router-link>
</div>
<b-form label-position="left" label-width="100px" :model="form" :rules="rules" size="large">
<b-form-group label="手机号" prop="mobile">
<b-form-input placeholder="请输入您的手机号" v-model="form.mobile"></b-form-input>
</b-form-group>
<b-form-group label="验证码" prop="capture">
<div style="display: flex;">
<b-form-input placeholder="请输入图片验证码" v-model="form.capture" style="width: calc(100% - 190px)"></b-form-input>
<img v-if="showCapture" class="capture" :src="captureUrl" @click="loadCapture"/>
</div>
</b-form-group>
<b-form-group label="短信验证码" prop="smsCode" required>
<div style="display: flex;">
<b-form-input placeholder="请输入短信验证码" v-model="form.smsCode" style="width: calc(100% - 190px)"></b-form-input>
<b-button variant="primary" style="margin-left: 10px; width: 180px;" @click="getSmsCode"
:loading="smsLoading"
:disabled="countDown > 0 || !form.capture || !form.mobile">
<span v-if="countDown" style="color: red;">{{countDown}}</span> 获取短信验证码
</b-button>
</div>
</b-form-group>
<b-alert :closable="false" show>如果您的手机号未曾注册过将直接为您创建账号</b-alert>
</b-form>
</div>
<div slot="modal-footer">
<div style="text-align: center; width: 100%; display: flex">
<div style="margin-top: 8px; margin-right: 20px;">
<b-form-checkbox v-model="goStandAlone">
进入单独的聊天窗口
</b-form-checkbox>
</div>
<b-button variant="primary" :disabled="!form.smsCode || !smsCodeId" size="large" :loading="loginLoading"
@click="login" icon="el-icon-thumb" style="width: 180px;">
</b-button>
</div>
</div>
</b-modal>
</template>
<script>
import ax from 'axios'
let axios = ax.create({ baseURL: '/uc-api' })
function guid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
export default {
props: {
},
data () {
let _this = this
return {
code: undefined,
form: {
mobile: undefined,
capture: undefined,
smsCode: undefined,
},
goStandAlone: false,
smsCodeId: undefined,
loginLoading: false,
smsLoading: false,
captureId: undefined,
showCapture: false,
countDown: 0,
rules: {
mobile: [
{ required: true, message: '请输入手机号码', trigger: 'blur'},
{ validator: (rule, value, callback) => {
let pattern = /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/;
console.log(value, pattern.test(value))
if (pattern.test(value)) {
callback()
} else {
callback(new Error('手机号码格式不正确'))
}
},
trigger: 'blur'
}
],
capture: [
{ required: true, message: '请输入图片验证码', trigger: 'blur' },
{ validator: (rule, value, callback) => {
axios.get(`/capture/validate/${_this.captureId}/${value}`).then(resp => {
if (resp.data.data === 'OK') {
callback()
} else {
callback(new Error(resp.data.message))
}
}).catch(e => {
callback(new Error('验证出错: ' + e))
})
}, trigger: 'blur'}
],
smsCode: [
{ required: true, message: '请输入短信验证码', trigger: 'blur' },
]
}
}
},
computed: {
captureUrl () {
return `/uc-api/capture/genVerifyCode?id=${this.captureId}`
}
},
mounted () {
// console.log(location)
if (location.search === '?showLogin=true') {
this.goStandAlone = true
this.show()
}
},
methods: {
show () {
this.loadCapture()
this.$refs.loginModel.show()
},
loadCapture() {
this.showCapture = false
setTimeout(_ => {
this.captureId = guid()
this.showCapture = true
}, 100)
},
getSmsCode () {
this.smsLoading = true
axios.post('/smscodes', {
mobile: this.form.mobile,
type: 'REGISTER/LOGIN',
captureId: this.captureId,
captureCode: this.form.capture,
}).then(resp => {
console.log(resp.data)
this.$bvToast.toast('短信验证码已经发送到您的手机,请查收。', {title: '发送成功', variant: 'success', solid: true})
this.smsLoading = false
this.smsCodeId = resp.data.data
this.countDown = 60
let interval = setInterval(_ => {
this.countDown = this.countDown - 1
if (this.countDown === 0) {
clearInterval(interval)
}
}, 1000)
}).catch(e => {
this.smsLoading = false
this.$bvToast.toast('发送短信验证码失败: ' + (e.response ? e.response.data.message : e.message), {title: '发送失败', variant: 'danger', solid: true})
})
},
login () {
this.loginLoading = true
axios.post(`/users/loginBySmsCode`, {
smsCodeId: this.smsCodeId,
smsCode: this.form.smsCode,
}).then(resp => {
this.$refs.loginModel.hide()
this.$login(resp.data.data)
this.loginLoading = false
console.log(this.goStandAlone)
if (this.goStandAlone) {
window.location.href = '/chat/'
} else {
this.$emit('loginSuccess')
}
}).catch(e => {
this.$bvToast.toast('登录失败: ' + e, {variant: 'danger', solid: true})
this.loginLoading = false
})
}
}
}
</script>
<style lang="stylus" scoped>
.login {
width: 450px;
margin: auto;
}
.capture {
width: 178px;
height: 38px;
vertical-align: bottom;
border: 1px solid rgb(206, 212, 218);
margin-left: 10px;
border-radius: 4px;
padding: 0 10px;
}
.thanks {
font-size: 13px;
margin-bottom: 10px;
color: #333;
}
.thanks2 {
font-size: 13px;
margin-bottom: 20px;
color: red;
}
.thanks3 {
color: green;
margin-left: 20px;
font-size: 13px;
font-weight: bold;
margin-bottom: 10px;
}
</style>

View File

@ -58,6 +58,7 @@
<LazyLoad :noAdsOnSharing="true">
<AdSenseRightSide v-show="!$isSharing"/>
</LazyLoad>
<OnlineChat></OnlineChat>
</main>
</template>
@ -65,9 +66,10 @@
import PageEdit from '@theme/components/PageEdit.vue'
import PageNav from '@theme/components/PageNav.vue'
import JoinCommunity from './JoinCommunity'
import OnlineChat from './OnlineChat.vue'
export default {
components: { PageEdit, PageNav, JoinCommunity },
components: { PageEdit, PageNav, JoinCommunity, OnlineChat },
props: ['sidebarItems'],
data () {
return {

View File

@ -7,6 +7,14 @@ server {
expires 1d;
}
location /uc-api/ {
proxy_pass http://svc-user-center-v3:8080/;
proxy_http_version 1.1;
proxy_pass_header Authorization;
gzip on;
expires -1;
}
error_page 404 500 502 503 504 /404.html;
location /50x.html {
root /usr/share/nginx/html;

View File

@ -101,7 +101,6 @@ Kuboard是一款免费的 Kubernetes 图形化管理工具Kuboard 力图
<b-card style="height: 100%; color: #2c3e50; line-height: 1.7;" shadow="hover">
<p>
<KuboardDemo suffix="install" label="在线体验 Kuboard" color="#007af5"/>
</p>
<p>
为保证环境的稳定性,在线 Demo 中只提供只读权限。<span style="color: #F56C6C; font-weight: 500;">请在PC浏览器中打开</span>

View File

@ -14,6 +14,7 @@
"animated-number-vue": "^1.0.0",
"aos": "^2.3.4",
"axios": "^0.19.2",
"js-cookie": "^2.2.1",
"babel-plugin-component": "^1.1.1",
"bootstrap": "^4.6.0",
"bootstrap-vue": "^2.21.2",

View File

@ -4475,6 +4475,11 @@ javascript-stringify@^2.0.1:
resolved "https://registry.npm.taobao.org/javascript-stringify/download/javascript-stringify-2.0.1.tgz#6ef358035310e35d667c675ed63d3eb7c1aa19e5"
integrity sha1-bvNYA1MQ411mfGde1j0+t8GqGeU=
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npm.taobao.org/js-tokens/download/js-tokens-4.0.0.tgz?cache=0&sync_timestamp=1586796305651&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjs-tokens%2Fdownload%2Fjs-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"