Add UI
5
BotSharp.UI/.babelrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"presets": [
|
||||
["es2015", { "modules": false }]
|
||||
]
|
||||
}
|
||||
11
BotSharp.UI/.gitignore
vendored
Normal 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
|
|
@ -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
|
|
@ -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
59
BotSharp.UI/package.json
Normal 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
|
|
@ -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>
|
||||
48
BotSharp.UI/src/components/vue-avatar-scale.vue
Normal 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>
|
||||
428
BotSharp.UI/src/components/vue-avatar.vue
Normal 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>
|
||||
9
BotSharp.UI/src/config/config.js
Normal 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
|
After Width: | Height: | Size: 4.2 KiB |
BIN
BotSharp.UI/src/images/bot.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
BotSharp.UI/src/images/icons/SMS.png
Normal file
|
After Width: | Height: | Size: 7 KiB |
BIN
BotSharp.UI/src/images/icons/Skype_t.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
BotSharp.UI/src/images/icons/Slack_Icon.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
BotSharp.UI/src/images/icons/Twitter.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |
1
BotSharp.UI/src/images/icons/facebook-messenger.svg
Normal 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 |
BIN
BotSharp.UI/src/images/icons/logo-kik.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
BotSharp.UI/src/images/icons/logo-twilio.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
BotSharp.UI/src/images/icons/user.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
BotSharp.UI/src/images/icons/website.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
BotSharp.UI/src/images/icons/wechat.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
BotSharp.UI/src/images/logo.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
BotSharp.UI/src/images/user.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
BotSharp.UI/src/images/wakeup.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
42
BotSharp.UI/src/libs/http-common.js
Normal 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);
|
||||
});
|
||||
32
BotSharp.UI/src/libs/util.js
Normal 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
|
|
@ -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
|
|
@ -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;
|
||||
3
BotSharp.UI/src/styles/common.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
body, html {
|
||||
height: 100%;
|
||||
}
|
||||
16
BotSharp.UI/src/template/index.ejs
Normal 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>
|
||||
3
BotSharp.UI/src/vendors.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Vue from 'vue';
|
||||
import iView from 'iview';
|
||||
import VueRouter from 'vue-router';
|
||||
66
BotSharp.UI/src/views/account/login.vue
Normal 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>
|
||||
69
BotSharp.UI/src/views/account/settings.vue
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<Tabs :animated="false">
|
||||
<Tab-pane label="基本资料" name="general">
|
||||
<Row>
|
||||
<Col span="1"> </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"> </Col>
|
||||
</Row>
|
||||
<br/>
|
||||
<Row>
|
||||
<Col span="4">
|
||||
|
||||
</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>
|
||||
43
BotSharp.UI/src/views/account/user-status.vue
Normal 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>
|
||||
31
BotSharp.UI/src/views/dashboard.vue
Normal 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>
|
||||
90
BotSharp.UI/src/views/index.vue
Normal 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>
|
||||
190
BotSharp.UI/src/views/master.vue
Normal 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">机器人 </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>
|
||||
70
BotSharp.UI/webpack.base.config.js
Normal 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'
|
||||
}
|
||||
}
|
||||
};
|
||||
39
BotSharp.UI/webpack.dev.config.js
Normal 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
|
||||
})
|
||||
]
|
||||
});
|
||||
44
BotSharp.UI/webpack.prod.config.js
Normal 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
|
||||
})
|
||||
]
|
||||
});
|
||||
91
BotSharp.UI/webpack.pub.config.js
Normal 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路径
|
||||
})
|
||||
]
|
||||
};
|
||||