This commit is contained in:
Oceania2018 2018-08-01 07:17:13 -05:00
parent 51113f98b2
commit 69c437224c
42 changed files with 11697 additions and 0 deletions

5
BotSharp.UI/.babelrc Normal file
View file

@ -0,0 +1,5 @@
{
"presets": [
["es2015", { "modules": false }]
]
}

11
BotSharp.UI/.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
.idea
.idea/
.DS_Store
node_modules/
.project
dist
dist/*
src/config/*.tmp
src/config/env.js
/index.html
/index_prod.html

21
BotSharp.UI/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 iView
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
BotSharp.UI/README.md Normal file
View file

@ -0,0 +1,18 @@
Voiceweb AI Chatbot Web Console
===
## How to install
#### Download source code
````sh
git clone https://github.com/voicecoin/voiceweb-chatbot-vue
````
#### Run node.js server
````sh
npm install
npm run init
npm run dev
````
#### Deploy web portal
````sh
npm run build
````

10122
BotSharp.UI/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

59
BotSharp.UI/package.json Normal file
View file

@ -0,0 +1,59 @@
{
"name": "iview-project",
"version": "2.0.0",
"description": "A base project with Vue.js2、Vue-Router、webpack2 and with iView2.",
"main": "index.js",
"scripts": {
"init": "webpack --progress --config webpack.dev.config.js",
"dev": "webpack-dev-server --content-base ./ --open --inline --hot --compress --history-api-fallback --config webpack.dev.config.js",
"build": "webpack --progress --hide-modules --config webpack.prod.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/iview/iview-project.git"
},
"author": "Aresn",
"license": "MIT",
"dependencies": {
"axios": "^0.16.2",
"iview": "^2.14.3",
"localStorage": "^1.0.3",
"vue": "^2.5.16",
"vue-avatar-editor": "^1.0.4",
"vue-input-tag": "0.0.16",
"vue-localstorage": "^0.3.0",
"vue-router": "^2.8.1",
"vue-typer": "^1.2.0",
"vuex": "^2.5.0"
},
"devDependencies": {
"autoprefixer-loader": "^2.0.0",
"babel": "^6.23.0",
"babel-core": "^6.26.3",
"babel-loader": "^6.2.4",
"babel-plugin-transform-runtime": "^6.12.0",
"babel-preset-es2015": "^6.24.1",
"babel-runtime": "^6.26.0",
"css-loader": "^0.23.1",
"extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.8.5",
"html-loader": "^0.3.0",
"html-webpack-plugin": "^2.30.1",
"less": "^2.7.3",
"less-loader": "^2.2.3",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"vue-hot-reload-api": "^1.3.3",
"vue-html-loader": "^1.2.3",
"vue-loader": "^11.0.0",
"vue-style-loader": "^1.0.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^2.2.1",
"webpack-dev-server": "^2.11.2",
"webpack-merge": "^3.0.0"
},
"bugs": {
"url": "https://github.com/iview/iview-project/issues"
},
"homepage": "https://www.iviewui.com"
}

22
BotSharp.UI/src/app.vue Normal file
View file

@ -0,0 +1,22 @@
<style scoped>
@import 'styles/common.css';
</style>
<template>
<router-view></router-view>
</template>
<script>
export default {
data () {
return {}
},
mounted () {
},
beforeDestroy () {
},
methods: {
}
}
</script>

View file

@ -0,0 +1,48 @@
<template>
<input
v-model="scale"
type="range"
:min="min"
:max="max"
:step="step"
:style="{ width:width +'px'}"
/>
</template>
<script >
export default{
props:{
width:{
type:Number,
default:200
},
min:{
type:Number,
default:1
},
max:{
type:Number,
default:2
},
step:{
type:Number,
default:0.01
},
},
methods:{
setScale:function(scale){
this.scale = scale
}
},
data:function(){
return {
scale:1
}
},
watch:{
scale:function(){
this.$emit('vue-avatar-editor-scale:change-scale', this.scale)
}
}
}
</script>

View file

@ -0,0 +1,428 @@
<template>
<div>
<canvas :width="canvasWidth" :height="canvasHeight" ref="canvas" @dragover.prevent @drop="onDrop" @mousedown="onDragStart" @mouseup="onDragEnd" @mousemove="onMouseMove" @click="clicked" v-bind:class="cursor"/>
<input type="file" id='ab-1' @change="fileSelected" style="display:none;">
</div>
</template>
<style type="text/css">
.cursorPointer{
cursor: pointer;
}
.cursorGrab{
cursor: grab;
cursor: -webkit-grab;
cursor: -moz-grab;
}
.cursorGrabbing{
cursor: grabbing;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
</style>
<script>
const drawRoundedRect = (context, x, y, width, height, borderRadius) => {
if (borderRadius === 0) {
context.rect(x, y, width, height)
} else {
const widthMinusRad = width - borderRadius
const heightMinusRad = height - borderRadius
context.translate(x, y)
context.arc(borderRadius, borderRadius, borderRadius, Math.PI, Math.PI * 1.5)
context.lineTo(widthMinusRad, 0)
context.arc(widthMinusRad, borderRadius, borderRadius, Math.PI * 1.5, Math.PI * 2)
context.lineTo(width, heightMinusRad)
context.arc(widthMinusRad, heightMinusRad, borderRadius, Math.PI * 2, Math.PI * 0.5)
context.lineTo(borderRadius, height)
context.arc(borderRadius, heightMinusRad, borderRadius, Math.PI * 0.5, Math.PI)
context.translate(-x, -y)
}
}
export default {
props:{
image:{
type:String,
default:''
},
border:{
type:Number,
default:0
},
borderRadius:{
type:Number,
default:0
},
width:{
type:Number,
default:200
},
height:{
type:Number,
default:200
},
color:{
type:Array,
default:function(){return [0, 0, 0, 0.5]}
}
},
data:function(){
return {
cursor:'cursorPointer',
scale:1,
canvas:null,
context:null,
dragged:false,
imageLoaded:false,
changed:false,
state: {
drag: false,
my: null,
mx: null,
xxx: "ab",
image: {
x: 0,
y: 0,
resource:null
}
}
}
},
computed:{
canvasWidth:function(){
return this.getDimensions().canvas.width
},
canvasHeight:function(){
return this.getDimensions().canvas.height
}
},
mounted: function(){
this.canvas = this.$refs.canvas
this.context = this.canvas.getContext('2d')
this.paint()
if(!this.image)
{
var placeHolder = this.svgToImage(this.context,'<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 65 65"><defs><style>.cls-1{fill:#999;}</style></defs><title>Upload_Upload</title><path class="cls-1" d="M32.5,1A31.5,31.5,0,1,1,1,32.5,31.54,31.54,0,0,1,32.5,1m0-1A32.5,32.5,0,1,0,65,32.5,32.5,32.5,0,0,0,32.5,0h0Z"/><polygon class="cls-1" points="41.91 28.2 32.59 18.65 23.09 28.39 24.17 29.44 31.87 21.54 31.87 40.05 33.37 40.05 33.37 21.59 40.83 29.25 41.91 28.2"/><polygon class="cls-1" points="40.66 40.35 40.66 44.35 24.34 44.35 24.34 40.35 22.34 40.35 22.34 44.35 22.34 46.35 24.34 46.35 40.66 46.35 42.66 46.35 42.66 44.35 42.66 40.35 40.66 40.35"/></svg>');
var self = this;
placeHolder.onload = function(){
var dim = self.getDimensions()
var x = self.canvasWidth / 2 - this.width / 2
var y = self.canvasHeight / 2 - this.height / 2
self.context.drawImage(placeHolder,x,y,this.width,this.height);
}
}
else
{
this.loadImage(this.image)
}
},
methods:{
svgToImage:function(ctx, rawSVG) {
var svg = new Blob([rawSVG], {type:"image/svg+xml;charset=utf-8"}),
domURL = self.URL || self.webkitURL || self,
url = domURL.createObjectURL(svg),
img = new Image;
img.src = url;
return img;
},
setState:function(state1){
var min = Math.ceil(1);
var max = Math.floor(10000);
this.state = state1;
this.state.cnt = 'HELLO' + Math.floor(Math.random() * (max - min)) + min
},
paint:function(){
this.context.save()
this.context.translate(0, 0)
this.context.fillStyle = 'rgba(' + this.color.slice(0, 4).join(',') + ')'
let borderRadius = this.borderRadius
const dimensions = this.getDimensions()
const borderSize = dimensions.border
const height = dimensions.canvas.height
const width = dimensions.canvas.width
// clamp border radius between zero (perfect rectangle) and half the size without borders (perfect circle or "pill")
borderRadius = Math.max(borderRadius, 0)
borderRadius = Math.min(borderRadius, width / 2 - borderSize, height / 2 - borderSize)
this.context.beginPath()
// inner rect, possibly rounded
drawRoundedRect(this.context, borderSize, borderSize, width - borderSize * 2, height - borderSize * 2, borderRadius)
this.context.rect(width, 0, -width, height) // outer rect, drawn "counterclockwise"
this.context.fill('evenodd')
this.context.restore()
},
getDimensions:function(){
return {
width: this.width,
height: this.height,
border: this.border,
canvas: {
width: this.width + (this.border * 2),
height: this.height + (this.border * 2)
}
}
},
onDrop:function(e){
e = e || window.event
e.stopPropagation()
e.preventDefault()
if (e.dataTransfer && e.dataTransfer.files.length)
{
//this.props.onDropFile(e)
const reader = new FileReader()
const file = e.dataTransfer.files[0]
this.changed = true
reader.onload = (e) => this.loadImage(e.target.result)
reader.readAsDataURL(file)
}
},
onDragStart:function(e){
e = e || window.event
e.preventDefault()
this.state.drag = true
this.state.mx = null
this.state.my = null
this.cursor = 'cursorGrabbing'
},
onDragEnd: function(e){
if (this.state.drag) {
this.state.drag = false
this.cursor = 'cursorPointer'
}
},
onMouseMove:function(e){
e = e || window.event
if (this.state.drag === false) {
return
}
this.dragged = true
this.changed = true
let imageState = this.state.image
const lastX = imageState.x
const lastY = imageState.y
const mousePositionX = e.targetTouches ? e.targetTouches[0].pageX : e.clientX
const mousePositionY = e.targetTouches ? e.targetTouches[0].pageY : e.clientY
const newState = { mx: mousePositionX, my: mousePositionY, image: imageState }
if (this.state.mx && this.state.my) {
const xDiff = (this.state.mx - mousePositionX) / this.scale
const yDiff = (this.state.my - mousePositionY) / this.scale
imageState.y = this.getBoundedY(lastY - yDiff, this.scale)
imageState.x = this.getBoundedX(lastX - xDiff, this.scale)
}
this.state.mx = newState.mx
this.state.my = newState.my
this.state.image = imageState
//this.setState(newState)
},
loadImage: function (imageURL) {
var imageObj = new Image()
var self = this
imageObj.onload = function(){
self.handleImageReady(imageObj)
}
//imageObj.onerror = this.props.onLoadFailure
if (!this.isDataURL(imageURL)) imageObj.crossOrigin = 'anonymous'
imageObj.src = imageURL
},
handleImageReady: function (image) {
var imageState = this.getInitialSize(image.width, image.height)
imageState.resource = image
imageState.x = 0
imageState.y = 0
var oldState = this.state
oldState.drag = false
oldState.image = imageState
this.state.image.x = 0
this.state.image.y = 0
this.state.image.resource = image
this.state.image.width = imageState.width
this.state.image.height= imageState.height
this.state.drag = false
this.scale = 1
this.$emit('vue-avatar-editor:image-ready', this.scale)
this.imageLoaded = true
this.cursor = 'cursorGrab'
},
getInitialSize: function (width, height) {
let newHeight
let newWidth
const dimensions = this.getDimensions()
const canvasRatio = dimensions.height / dimensions.width
const imageRatio = height / width
if (canvasRatio > imageRatio)
{
newHeight = (this.getDimensions().height)
newWidth = (width * (newHeight / height))
}
else
{
newWidth = (this.getDimensions().width)
newHeight = (height * (newWidth / width))
}
return {
height: newHeight,
width: newWidth
}
},
isDataURL (str) {
if (str === null) {
return false
}
const regex = /^\s*data:([a-z]+\/[a-z]+(;[a-z\-]+=[a-z\-]+)?)?(;base64)?,[a-z0-9!$&',()*+;=\-._~:@\/?%\s]*\s*$/i
return !!str.match(regex)
},
getBoundedX:function (x, scale) {
var image = this.state.image
var dimensions = this.getDimensions()
let widthDiff = Math.floor((image.width - dimensions.width / scale) / 2)
widthDiff = Math.max(0, widthDiff)
return Math.max(-widthDiff, Math.min(x, widthDiff))
},
getBoundedY:function (y, scale) {
var image = this.state.image
var dimensions = this.getDimensions()
let heightDiff = Math.floor((image.height - dimensions.height / scale) / 2)
heightDiff = Math.max(0, heightDiff)
return Math.max(-heightDiff, Math.min(y, heightDiff))
},
paintImage: function (context, image, border) {
if (image.resource) {
var position = this.calculatePosition(image, border)
context.save()
context.globalCompositeOperation = 'destination-over'
context.drawImage(image.resource, position.x, position.y, position.width, position.height)
context.restore()
}
},
calculatePosition:function (image, border) {
var image = image || this.state.image
var dimensions = this.getDimensions()
var width = image.width * this.scale
var height = image.height * this.scale
var widthDiff = (width - dimensions.width) / 2
var heightDiff = (height - dimensions.height) / 2
var x = image.x * this.scale - widthDiff + border
var y = image.y * this.scale - heightDiff + border
return {
x,
y,
height,
width
}
},
changeScale:function(sc){
this.changed = true
this.scale = sc
},
redraw:function(){
this.context.clearRect(0, 0, this.getDimensions().canvas.width, this.getDimensions().canvas.height)
this.paint()
this.paintImage(this.context, this.state.image, this.border)
},
getImage:function(){
const cropRect = this.getCroppingRect()
const image = this.state.image
// get actual pixel coordinates
cropRect.x *= image.resource.width
cropRect.y *= image.resource.height
cropRect.width *= image.resource.width
cropRect.height *= image.resource.height
// create a canvas with the correct dimensions
const canvas = document.createElement('canvas')
canvas.width = cropRect.width
canvas.height = cropRect.height
// draw the full-size image at the correct position,
// the image gets truncated to the size of the canvas.
canvas.getContext('2d').drawImage(image.resource, -cropRect.x, -cropRect.y)
return canvas
},
getImageScaled: function(){
const { width, height } = this.getDimensions()
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
// don't paint a border here, as it is the resulting image
this.paintImage(canvas.getContext('2d'), this.state.image, 0)
return canvas
},
imageChanged:function(){
return this.changed
},
getCroppingRect: function () {
const dim = this.getDimensions()
const frameRect = { x: dim.border, y: dim.border, width: dim.width, height: dim.height }
const imageRect = this.calculatePosition(this.state.image, dim.border)
return {
x: (frameRect.x - imageRect.x) / imageRect.width,
y: (frameRect.y - imageRect.y) / imageRect.height,
width: frameRect.width / imageRect.width,
height: frameRect.height / imageRect.height
}
},
clicked:function(e){
if(this.dragged == true)
{
this.dragged = false
}
else
{
document.getElementById('ab-1').click()
}
},
fileSelected:function(e){
var files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
var image = new Image();
var reader = new FileReader();
this.changed = true
reader.onload = (e) => {
this.loadImage(e.target.result)
};
reader.readAsDataURL(files[0]);
}
},
watch:{
state:{
handler:function(val,oldval){
if(this.imageLoaded == true)
this.redraw();
},
deep:true
},
scale:function(){
if(this.imageLoaded == true)
this.redraw();
}
}
}
</script>

View file

@ -0,0 +1,9 @@
import Env from './env';
let config = {
env: Env,
authURL: 'http://0.0.0.0:3112',
baseURL: (Env == 'development' ? `http://0.0.0.0:3112` : `http://0.0.0.0:3112`),
testAccount: {username: `botsharp@gmail.com`, password: (Env == 'development' ? `botsharp` : `botsharp`)}
};
export default config;

BIN
BotSharp.UI/src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="iso-8859-1"?><!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" id="Facebook_Messenger" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="322px" height="324px" viewBox="96 93 322 324" style="enable-background:new 96 93 322 324;" xml:space="preserve" ><g id="Facebook_Messenger_1_"><g><path style="fill:#0084FF;" d="M257,93c-88.918,0-161,67.157-161,150c0,47.205,23.412,89.311,60,116.807V417l54.819-30.273 C225.449,390.801,240.948,393,257,393c88.918,0,161-67.157,161-150S345.918,93,257,93z M273,295l-41-44l-80,44l88-94l42,44l79-44 L273,295z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,42 @@
import axios from 'axios';
import storage from 'localStorage';
import config from '../config/config';
import iView from 'iview';
export const HTTP = axios.create({
baseURL: config.baseURL,
withCredentials: true
})
HTTP.interceptors.request.use(function (config) {
// Do something before request is sent
config.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token');
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
HTTP.interceptors.response.use(null, function(error) {
let response = error.response;
let status = response.status;
if (status === 400) {
iView.Message.error(response.data);
/*Object.keys(response.data).forEach(function(key) {
iView.Message.error(response.data[key][0]);
});*/
} else if (status === 401) {
iView.Message.error("Unauthorized user");
} else if (status === 404) {
if(response.request.responseURL.indexOf('login?ReturnUrl=') > 0) {
status = iView.Message.error("Login again");
return;
}
iView.Message.error("Not found.");
} else {
iView.Message.error(response.statusText);
}
return Promise.reject(response.statusText);
});

View file

@ -0,0 +1,32 @@
let util = {
};
util.title = function (title) {
title = title ? title + ' - Home' : 'iView project';
window.document.title = title;
};
util.getSelectionText = function () {
var text = "";
var activeEl = document.activeElement;
var activeElTagName = activeEl ? activeEl.tagName.toLowerCase() : null;
if (
(activeElTagName == "textarea" || activeElTagName == "input") &&
/^(?:text|search|password|tel|url)$/i.test(activeEl.type) &&
(typeof activeEl.selectionStart == "number")
) {
text = activeEl.value.slice(activeEl.selectionStart, activeEl.selectionEnd);
} else if (window.getSelection) {
text = window.getSelection().toString();
}
return text;
}
//This prototype function allows you to remove even array from array
Array.prototype.remove = function (val) {
var i = this.indexOf(val);
return i > -1 ? this.splice(i, 1) : [];
};
export default util;

107
BotSharp.UI/src/main.js Normal file
View file

@ -0,0 +1,107 @@
import Vue from 'vue';
import Vuex from 'vuex'
import iView from 'iview';
import VueRouter from 'vue-router';
import Routers from './router';
import config from './config/config';
import VueLocalStorage from 'vue-localstorage';
import {HTTP} from './libs/http-common';
import Util from './libs/util';
import App from './app.vue';
import 'iview/dist/styles/iview.css';
Vue.use(Vuex)
Vue.use(VueRouter);
Vue.use(iView);
// use it via this.$ls
Vue.use(VueLocalStorage, {name: 'ls'});
Vue.prototype.$ajax = HTTP;
Vue.prototype.$config = config;
Vue.prototype.$util = Util;
// router config
const RouterConfig = {
mode: 'history',
routes: Routers
};
const router = new VueRouter(RouterConfig);
router.beforeEach((to, from, next) => {
iView.LoadingBar.start();
Util.title(to.meta.title);
next();
});
router.afterEach((to, from, next) => {
iView.LoadingBar.finish();
window.scrollTo(0, 0);
});
// Global status management
const store = new Vuex.Store({
state: {
// current agent
agentId: null,
agent: {id: null, name: null, avatar: null},
agents: [],
// user status
user: {id: null, name: null, avatar: null},
conversation: {id: null}
},
getters: {
user: state => {
if(!state.user.name){
state.user=JSON.parse(localStorage.getItem('user'));
};
return state.user;
}
},
mutations: {
setAgentId(state, agentId) {
state.agentId = agentId;
HTTP.get('/v1/agents/' + agentId)
.then(response => {
state.agent = response.data;
});
},
refreshAgents(state, agents) {
if(!agents){
HTTP.get('/v1/Agents/MyAgents')
.then(response => {
if(response.data.length > 0) {
state.agents = response.data;
state.agentId = response.data[0].id;
HTTP.get('/v1/agents/' + state.agentId)
.then(response => {
state.agent = response.data;
});
}
});
} else {
state.agents = agents;
setAgentId(agents[0].id);
}
},
authenticated(state, token) {
localStorage.setItem('token', token);
router.push('/agent/agents');
HTTP.get('/account', { baseURL: config.authURL })
.then(response => {
state.user = response.data;
localStorage.setItem('user', JSON.stringify(response.data));
router.push('/agent/agents');
});
}
}
})
new Vue({
el: '#app',
router: router,
// inject global status to vue instance
store: store,
render: h => h(App)
});

17
BotSharp.UI/src/router.js Normal file
View file

@ -0,0 +1,17 @@
const routers = [
{
path: '/',
meta: {
title: 'BotSharp'
},
component: (resolve) => require(['./views/index.vue'], resolve)
},
{
path: '/login',
meta: {
title: 'Login'
},
component: (resolve) => require(['./views/index.vue'], resolve)
}
];
export default routers;

View file

@ -0,0 +1,3 @@
body, html {
height: 100%;
}

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[0] %>"></script>
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[1] %>"></script>
</body>
</html>

View file

@ -0,0 +1,3 @@
import Vue from 'vue';
import iView from 'iview';
import VueRouter from 'vue-router';

View file

@ -0,0 +1,66 @@
<style scoped lang="less">
</style>
<template>
<Row>
<Form ref="formInline" :model="formInline" :rules="ruleInline">
<Form-item prop="username">
<Input type="text" v-model="formInline.username" placeholder="Email">
<Icon type="ios-person-outline" slot="prepend"></Icon>
</Input>
</Form-item>
<Form-item prop="password">
<Input type="password" v-model="formInline.password" placeholder="Password">
<Icon type="ios-locked-outline" slot="prepend"></Icon>
</Input>
</Form-item>
</Form>
<div>
<Button @click="handleSubmit('formInline')" style="margin-left:10px;">Login</Button>
<p style="margin-top:30px;">Automate conversationon and enterprise processing through intelligent bot</p>
</div>
</Row>
</template>
<script>
export default {
data () {
return {
formInline: {
username: this.$config.testAccount.username,
password: this.$config.testAccount.password
},
ruleInline: {
username: [
{ required: true, message: '请填写用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请填写密码', trigger: 'blur' },
{ type: 'string', min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
]
}
}
},
methods: {
goto(path){
this.$router.push(path);
},
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.getToken();
} else {
this.$Message.error('表单验证失败!');
}
})
},
getToken() {
this.$ajax.post(`/token`, this.formInline, { baseURL: this.$config.authURL })
.then(response => {
this.$store.commit('authenticated', response.data)
});
}
}
}
</script>

View file

@ -0,0 +1,69 @@
<template>
<Tabs :animated="false">
<Tab-pane label="基本资料" name="general">
<Row>
<Col span="1">&nbsp;</Col>
<Col span="3" style="text-align:center;">
<img v-if="user.avatar" :src="user.avatar" class="user-status-img"/>
<img v-else src="../../images/user.png" class="user-status-img"/>
<!--<Button type="ghost">上传头像</Button>-->
</Col>
<Col span="18">
<Form :model="user" :label-width="80">
<Form-item label="注册邮箱">
{{user.email}}
</Form-item>
<Form-item label="昵称">
<Input v-model="user.firstName" placeholder="用户昵称"></Input>
</Form-item>
<Form-item label="账户描述">
<Input v-model="user.description" type="textarea" :autosize="{minRows: 2,maxRows: 5}" placeholder="账户描述..."></Input>
</Form-item>
<Form-item label="创建时间">
{{user.createdDate}}
</Form-item>
</Form>
</Col>
<Col span="2">&nbsp;</Col>
</Row>
<br/>
<Row>
<Col span="4">
&nbsp;
</Col>
<Col span="20" style="text-align:center;">
<Button type="ghost">删除</Button>
<Button type="primary" @click="updateAgent(agent.id)">保存</Button>
</Col>
</Row>
</Tab-pane>
<Tab-pane label="安全设置" name="advance"></Tab-pane>
</Tabs>
</template>
<script>
export default {
data(){
return {
user: {
name: "Blank"
}
}
},
created() {
let agentId = this.$route.query.agentId;
this.$ajax.get(`/Account`, { baseURL: this.$config.authURL })
.then(response => {
this.user = response.data;
});
},
methods:{
updateAgent(agentId){
this.$ajax.put('/v1/Agents/' + agentId, this.agent)
.then(response => {
this.$Message.info("保存成功");
});
}
}
}
</script>

View file

@ -0,0 +1,43 @@
<template>
<div>
<img v-if="user.avatar" :src="user.avatar" class="user-status-img"/>
<img v-else src="../../images/user.png" class="user-status-img"/>
<div style="display:inline-block;float:right;"><Button type="text" @click="handleUserSettings">{{user.fullName}}</Button>
<Button v-if="user.id" type="text" icon="android-exit" size="large" @click="handleLogout">退出</Button>
</div>
</div>
</template>
<script>
export default {
data(){
return {
}
},
computed: {
user () {
return this.$store.getters.user;
}
},
mounted() {
//let userId = this.$store.getters.user.id;//this.$route.query.agentId;
this.$store.commit('refreshAgents');
},
methods: {
handleLogout() {
this.$ls.remove('token');
this.$router.push('/');
},
handleUserSettings(){
this.$router.push('/account/settings');
}
}
}
</script>
<style scoped lang="less">
.user-status-img{
height: 32px;
}
</style>

View file

@ -0,0 +1,31 @@
<template>
<Row>
<Col span="24">
<Row>
</Row>
<Row>
</Row>
</Col>
</Row>
</template>
<script>
import agents from './agent/agents.vue';
export default {
data () {
return {
}
},
methods: {
},
created() {
},
components: {
agents: agents
}
}
</script>

View file

@ -0,0 +1,90 @@
<template>
<div class="index">
<Row type="flex" justify="center" align="middle">
<Col span="24">
<Row type="flex" justify="center">
<Col span="2"></Col>
<Col span="20">
<div></div>
<h1>
<img src="../images/logo.png">
</h1>
<div class="title">The Chatbot Platform Builder for Enterprise</div>
<div class="slogan"><vue-typer text='Hello' :type-delay='500' :repeat='0'></vue-typer></div>
</Col>
<Col span="2" style="height: 95%">
</Col>
</Row>
<Row type="flex" justify="center" align="middle">
<Col>
<login></login>
</Col>
</Row>
<Row style="height:100px;" type="flex" justify="center" align="bottom">
<Col>
<span>© 2018 The BotSharp</span>
</Col>
</Row>
</Col>
</Row>
</div>
</template>
<script>
import {VueTyper} from 'vue-typer';
import login from './account/login.vue';
export default {
data () {
return {
}
},
methods: {
goto(path){
this.$router.push(path);
}
},
components: {
VueTyper,
login
}
}
</script>
<style scoped lang="less">
.index{
width: 100%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
text-align: center;
h1{
height: 200px;
img{
height: 100%;
}
}
h2{
color: #666;
margin-bottom: 200px;
p{
margin: 0 0 50px;
}
}
.ivu-row-flex{
height: 100%;
}
}
.title{
font-size:2em;
margin-top:1.5em;
}
.slogan{
font-size:1.2em;
margin-bottom:3em;
}
</style>

View file

@ -0,0 +1,190 @@
<template>
<div class="layout">
<Row class="layout-header" type="flex" align="middle">
<Col :xs="0" :sm="3">
<div class="layout-logo-left">
<img src="../images/logo.png" style="height:100%;"/>
</div>
</Col>
<Col span="1">
<Button type="text" @click="handleMenuDisplayStatus">
<Icon type="navicon" size="32"></Icon>
</Button>
</Col>
<Col :xs="23" :sm="16" class="layout-ceiling-main">
<Icon type="home" size="16"></Icon><a href="/">首页</a>
<a href="http://forum.yaya.ai" target="_blank">聊天机器人论坛</a>
<a href="/">开发文档</a>
</Col>
<Col :xs="0" :sm="4" style="text-align:right;">
<userStatus></userStatus>
</Col>
</Row>
<Row style="height:100%;">
<Col :xs="0" :sm="5" :md="4" :lg="3" class="layout-menu-left">
<Menu theme="dark" width="auto" active-name="/agent/agents" @on-select="redirect">
<!--<Menu-item name="dashboard">
<Icon type="ios-keypad" :size="iconSize"></Icon>
<div class="layout-text" @click="openMenu('dashboard', '/dashboard')">仪表盘</div>
</Menu-item>-->
<Menu-item name="/agent/agents">
<Icon type="ios-body-outline" size="24"></Icon>
<span class="layout-text">机器人&nbsp;&nbsp;</span>
</Menu-item>
<Menu-item v-if="agentId" Menu-item name="/faq/corpus">
<Icon type="chatbubble-working" size="24"></Icon>
<span class="layout-text">快速问答</span>
</Menu-item>
<Menu-item v-if="agentId" Menu-item name="/intent/intents">
<Icon type="pull-request" size="24"></Icon>
<span class="layout-text">任务意图</span>
</Menu-item>
<Menu-item v-if="agentId" Menu-item name="/entity/entity-types">
<Icon type="ios-book" size="24"></Icon>
<span class="layout-text">词库管理</span>
</Menu-item>
<!--<Menu-item name="/hotword/training">
<Icon type="ios-pulse" size="24"></Icon>
<span class="layout-text">热词训练</span>
</Menu-item>
<Menu-item name="/chat/window">
<Icon type="ios-chatboxes" size="28"></Icon>
<span class="layout-text">会话管理</span>
</Menu-item>-->
<Menu-item v-if="agentId" name="/integration/third-parts">
<Icon type="usb" size="28"></Icon>
<span class="layout-text">系统集成</span>
</Menu-item>
<Menu-item name="/account/settings">
<Icon type="person" size="24"></Icon>
<span class="layout-text">账户设置</span>
</Menu-item>
</Menu>
<activeAgent v-if="agentId"></activeAgent>
</Col>
<Col :xs="24" :sm="19" :md="20" :lg="21" style="height:100%;overflow:scroll">
<div class="layout-breadcrumb">
<Breadcrumb>
<Breadcrumb-item href="/">首页</Breadcrumb-item>
<Breadcrumb-item>我的机器人</Breadcrumb-item>
</Breadcrumb>
</div>
<div class="layout-content">
<router-view class="layout-content-main"></router-view>
</div>
<div class="layout-copy">
© 2012-2018 Voicebot 深圳爱用科技有限公司
</div>
</Col>
</Row>
</div>
</template>
<script>
import active from './agent/active.vue';
import userStatus from './account/user-status.vue';
export default {
data () {
return {
}
},
computed: {
agent(){
return this.$store.state.agent;
},
agentId(){
return this.$store.state.agent.id;
}
},
methods: {
handleMenuDisplayStatus(){
},
redirect(path){
this.$router.push({path: path});
}
},
components: {
activeAgent: active,
userStatus
}
}
</script>
<style scoped>
.layout{
border: 1px solid #d7dde4;
background: #f5f7f9;
position: relative;
border-radius: 4px;
overflow: hidden;
height:100%;
}
.layout-breadcrumb{
padding: 10px 15px 0;
}
.layout-content{
min-height: 500px;
margin: 15px;
overflow: auto;
background: #fff;
border-radius: 4px;
}
.layout-content-main{
padding: 10px;
height:100%;
}
.layout-copy{
text-align: center;
height:10%;
color: #9ea7b4;
}
.layout-menu-left{
background: #464c5b;
text-align: center;
padding-top: 50px;
height: 100%;
}
.layout-header{
height: 60px;
background: #fff;
box-shadow: 0 1px 1px rgba(0,0,0,.1);
}
.layout-logo-left{
text-align: center;
height: 3rem;
}
.layout-ceiling-main{
text-align: center;
}
.layout-ceiling-main a{
color: black;
font-size: 16px;
margin-left: 10px;
}
.layout-right{
background: #464c5b;
height: 100%;
}
.layout-hide-text .layout-text{
display: inline-block;
}
.layout-text{
font-size: 15px;
margin-right: 20%;
float: right;
}
.ivu-col{
transition: width .2s ease-in-out;
}
</style>

View file

@ -0,0 +1,70 @@
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: {
main: './src/main',
vendors: './src/vendors'
},
output: {
path: path.join(__dirname, './dist')
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
less: ExtractTextPlugin.extract({
use: ['css-loader?minimize', 'autoprefixer-loader', 'less-loader'],
fallback: 'vue-style-loader'
}),
css: ExtractTextPlugin.extract({
use: ['css-loader', 'autoprefixer-loader', 'less-loader'],
fallback: 'vue-style-loader'
})
}
}
},
{
test: /iview\/.*?js$/,
loader: 'babel-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize', 'autoprefixer-loader'],
fallback: 'style-loader'
})
},
{
test: /\.less/,
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize', 'autoprefixer-loader', 'less-loader'],
fallback: 'style-loader'
})
},
{
test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
loader: 'url-loader?limit=1024'
},
{
test: /\.(html|tpl)$/,
loader: 'html-loader'
}
]
},
resolve: {
extensions: ['.js', '.vue'],
alias: {
'vue': 'vue/dist/vue.esm.js'
}
}
};

View file

@ -0,0 +1,39 @@
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const merge = require('webpack-merge');
const webpackBaseConfig = require('./webpack.base.config.js');
const fs = require('fs');
fs.open('./src/config/env.js', 'w', function (err, fd) {
const buf = 'export default "development";';
fs.write(fd, buf, 0, buf.length, 0, function (err, written, buffer){});
});
module.exports = merge(webpackBaseConfig, {
devtool: '#source-map',
output: {
publicPath: '/dist/',
filename: '[name].js',
chunkFilename: '[name].chunk.js'
},
devServer: {
inline:true,
port: 3110
},
plugins: [
new ExtractTextPlugin({
filename: '[name].css',
allChunks: true
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'vendors.js'
}),
new HtmlWebpackPlugin({
filename: '../index.html',
template: './src/template/index.ejs',
inject: false
})
]
});

View file

@ -0,0 +1,44 @@
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const merge = require('webpack-merge');
const webpackBaseConfig = require('./webpack.base.config.js');
const fs = require('fs');
fs.open('./src/config/env.js', 'w', function (err, fd) {
const buf = 'export default "production";';
fs.write(fd, buf, 0, buf.length, 0, function (err, written, buffer){});
});
module.exports = merge(webpackBaseConfig, {
output: {
publicPath: '/dist/',
filename: '[name].[hash].js',
chunkFilename: '[name].[hash].chunk.js'
},
plugins: [
new ExtractTextPlugin({
filename: '[name].[hash].css',
allChunks: true
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'vendors.[hash].js'
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new HtmlWebpackPlugin({
filename: '../index_prod.html',
template: './src/template/index.ejs',
inject: false
})
]
});

View file

@ -0,0 +1,91 @@
var webpack = require("webpack");
var path = require('path');
var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ROOT_PATH = path.resolve(__dirname); // 项目跟路径
var APP_PATH = path.resolve(ROOT_PATH, 'Views'); // 项目开发目录src
var APP_FILE = path.resolve(APP_PATH, 'index.js'); // 项目入口的index.js
var DIST_PATH = path.resolve(ROOT_PATH, 'dist'); // 项目打包输出路径
module.exports = {
entry: {
app: APP_FILE,
venders: [
'react',
'react-dom',
'react-router',
// 'antd',这里直接会把所有antd打包
'mockjs',
'babel-polyfill'
]
},
output: {
path: DIST_PATH,
filename: '[name].js',
chunkFilename: 'js/[name].[chunkhash:5].min.js'
},
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer'])
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer', 'less'])
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['react-hot', 'babel?presets[]=es2015,presets[]=react,presets[]=stage-0']
},
{
test: /\.(png|jpg)$/,
loader: 'file-loader?limit=8192&name=images/[hash:8].[name].[ext]'
},
{
test: /\.(woff|woff2|eot|ttf|svg)(\?.*$|$)/,
loader: 'url'
}
]
},
resolve: {
extensions: ['', '.js', '.jsx', '.json', '.less']
},
plugins: [
// new webpack.ProvidePlugin({ $: "jquery" }), // 这是将jquery变成全局变量不用在自己文件require('jquery')了
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production') //定义生产环境
}
}),
new webpack.optimize.CommonsChunkPlugin('venders', 'venders.js'), // 这是第三方库打包生成的文件
new uglifyJsPlugin({
output: {
comments: false, // remove all comments
},
compress: {
warnings: false
}
}),
new ExtractTextPlugin("[name].css"),
new HtmlWebpackPlugin({
template: './Views/template/template.html', // html模板路径
htmlWebpackPlugin: {
files: {
css: ["app.css"],
js: ["bundle.js", "venders.js"]
}
},
minify: {
removeComments: true,
collapseWhitespace: true, // 删除空白符与换行符
removeAttributeQuotes: true // 移除HTML中的属性引号
},
filename: 'index.html',
favicon:'./Views/favicon.ico', //favicon路径
})
]
};