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路径
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||