未验证 提交 be1998e5 编写于 作者: 麦壳饼's avatar 麦壳饼 提交者: GitHub

Merge pull request #7 from masterchen/master

ui changes
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
"codemirror": "5.45.0", "codemirror": "5.45.0",
"driver.js": "0.9.5", "driver.js": "0.9.5",
"dropzone": "5.5.1", "dropzone": "5.5.1",
"echarts": "4.2.1", "echarts": "^4.2.1",
"element-ui": "2.7.0", "element-ui": "2.7.0",
"file-saver": "2.0.1", "file-saver": "2.0.1",
"fuse.js": "3.4.4", "fuse.js": "3.4.4",
...@@ -66,6 +66,7 @@ ...@@ -66,6 +66,7 @@
"sortablejs": "1.8.4", "sortablejs": "1.8.4",
"tasksfile": "^5.1.0", "tasksfile": "^5.1.0",
"tui-editor": "1.3.3", "tui-editor": "1.3.3",
"v-charts": "^1.19.0",
"vue": "2.6.10", "vue": "2.6.10",
"vue-count-to": "1.0.13", "vue-count-to": "1.0.13",
"vue-i18n": "^8.11.2", "vue-i18n": "^8.11.2",
......
import request from '@/utils/request' import request from '@/utils/request'
// Get all of the customer's devices.
export function getDevices(id) { export function getDevices(id) {
return request({ return request({
url: '/Devices/Customers/' + id, url: '/Devices/Customers/' + id,
...@@ -7,35 +8,67 @@ export function getDevices(id) { ...@@ -7,35 +8,67 @@ export function getDevices(id) {
}) })
} }
export function postDevice(data) { // Get a device's detail
export function getDevice(id) {
return request({ return request({
url: '/Devices', url: '/Devices/' + id,
method: 'get'
})
}
// Create a new device
export function creatDevice(data) {
return request({
url: '/Devices/',
method: 'post', method: 'post',
data data
}) })
} }
export function fetchArticle(id) { // DELETE: api/Devices/5
export function deleteDevice(data) {
return request({ return request({
url: '/article/detail', url: '/Devices',
method: 'get', method: 'delete',
params: { id } data
}) })
} }
// for pagin usage,current not support
export function fetchPv(pv) { export function getDevicePv(pv) {
return request({ return request({
url: '/article/pv', url: '/Devices/pv',
method: 'get', method: 'get',
params: { pv } params: { pv }
}) })
} }
// modify device
export function updateDevice(id, data) {
export function updateArticle(data) {
return request({ return request({
url: '/article/update', url: `/Devices/${id}`,
method: 'post', method: 'put',
data data
}) })
} }
// Get a device's credentials
export function getDeviceAccessToken(id) {
return request({
url: `/Devices/${id}/Identity`,
method: 'get'
})
}
// Request attribute values by device access token from the server
export function getDeviceAttributes(id) {
return request({
url: `/Devices/${id}/AttributeLatest`,
method: 'get'
})
}
// Request telemetry values by device access token from the server
export function getDeviceTelemetryLatest(id) {
return request({
url: `/Devices/${id}/TelemetryLatest`,
method: 'get'
})
}
...@@ -7,10 +7,10 @@ Vue.use(Router) ...@@ -7,10 +7,10 @@ Vue.use(Router)
import Layout from '@/layout' import Layout from '@/layout'
/* Router Modules */ /* Router Modules */
import componentsRouter from './modules/components' // import componentsRouter from './modules/components'
import chartsRouter from './modules/charts' // import chartsRouter from './modules/charts'
import tableRouter from './modules/table' // import tableRouter from './modules/table'
import nestedRouter from './modules/nested' // import nestedRouter from './modules/nested'
/** /**
* Note: sub-menu only appear when route children.length >= 1 * Note: sub-menu only appear when route children.length >= 1
...@@ -110,12 +110,27 @@ export const constantRoutes = [ ...@@ -110,12 +110,27 @@ export const constantRoutes = [
} }
}, },
{ {
path: 'add', path: 'board',
component: () => import('@/views/device/add'), component: () => import('@/views/device/device-board'),
name: 'DeviceAdd', name: 'DeviceBoard',
meta: { meta: {
title: '添加设备' title: '设备详情'
// if do not set roles, means: this page does not require permission }
},
{
path: 'compomentlib',
component: () => import('@/views/device/component-lib'),
name: 'ComponentLib',
meta: {
title: '部件库'
}
},
{
path: 'DeviceVisionBoardboard',
component: () => import('@/views/device/device-visionboard'),
name: 'DeviceVisionBoardboard',
meta: {
title: '仪表盘'
} }
}, },
{ {
...@@ -204,8 +219,8 @@ export const asyncRoutes = [ ...@@ -204,8 +219,8 @@ export const asyncRoutes = [
} }
] ]
}, },
/** when your routing map is too long, you can split it into small modules **/ /** when your routing map is too long, you can split it into small modules **/
/*
componentsRouter, componentsRouter,
chartsRouter, chartsRouter,
nestedRouter, nestedRouter,
...@@ -414,7 +429,7 @@ export const asyncRoutes = [ ...@@ -414,7 +429,7 @@ export const asyncRoutes = [
} }
] ]
}, },
*/
// 404 page must be placed at the end !!! // 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true } { path: '*', redirect: '/404', hidden: true }
] ]
......
...@@ -6,6 +6,7 @@ import { getToken } from '@/utils/auth' ...@@ -6,6 +6,7 @@ import { getToken } from '@/utils/auth'
// create an axios instance // create an axios instance
const service = axios.create({ const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// baseURL: process.env.BASE_API,
// withCredentials: true, // send cookies when cross-domain requests // withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout timeout: 5000 // request timeout
}) })
...@@ -44,9 +45,6 @@ service.interceptors.response.use( ...@@ -44,9 +45,6 @@ service.interceptors.response.use(
*/ */
response => { response => {
const res = response.data const res = response.data
console.log('response = ')
console.log(response)
console.log('response token = ')
// if the custom code is not 20000, it is judged as an error. // if the custom code is not 20000, it is judged as an error.
if (res.code !== 10000) { if (res.code !== 10000) {
Message({ Message({
......
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.title" placeholder="Title" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.importance" placeholder="Imp" clearable style="width: 90px" class="filter-item">
<el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-select v-model="listQuery.type" placeholder="Type" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key" />
</el-select>
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
Add
</el-button>
<el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload">
Export
</el-button>
<el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
reviewer
</el-checkbox>
</div>
<el-table
:key="tableKey"
v-loading="listLoading"
:data="list"
border
fit
highlight-current-row
style="width: 100%;"
@sort-change="sortChange"
>
<el-table-column label="ID" prop="id" sortable="custom" align="center" width="80">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column label="Date" width="150px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="Title" min-width="150px">
<template slot-scope="{row}">
<span class="link-type" @click="handleUpdate(row)">{{ row.title }}</span>
<el-tag>{{ row.type | typeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column label="Author" width="110px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column v-if="showReviewer" label="Reviewer" width="110px" align="center">
<template slot-scope="scope">
<span style="color:red;">{{ scope.row.reviewer }}</span>
</template>
</el-table-column>
<el-table-column label="Imp" width="80px">
<template slot-scope="scope">
<svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star" class="meta-item__icon" />
</template>
</el-table-column>
<el-table-column label="Readings" align="center" width="95">
<template slot-scope="{row}">
<span v-if="row.pageviews" class="link-type" @click="handleFetchPv(row.pageviews)">{{ row.pageviews }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column label="Status" class-name="status-col" width="100">
<template slot-scope="{row}">
<el-tag :type="row.status | statusFilter">
{{ row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="Actions" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="handleUpdate(row)">
Edit
</el-button>
<el-button v-if="row.status!='published'" size="mini" type="success" @click="handleModifyStatus(row,'published')">
Publish
</el-button>
<el-button v-if="row.status!='draft'" size="mini" @click="handleModifyStatus(row,'draft')">
Draft
</el-button>
<el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleModifyStatus(row,'deleted')">
Delete
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;">
<el-form-item label="Type" prop="type">
<el-select v-model="temp.type" class="filter-item" placeholder="Please select">
<el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
</el-form-item>
<el-form-item label="Date" prop="timestamp">
<el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date" />
</el-form-item>
<el-form-item label="Title" prop="title">
<el-input v-model="temp.title" />
</el-form-item>
<el-form-item label="Status">
<el-select v-model="temp.status" class="filter-item" placeholder="Please select">
<el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="Imp">
<el-rate v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" style="margin-top:8px;" />
</el-form-item>
<el-form-item label="Remark">
<el-input v-model="temp.remark" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="Please input" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
Cancel
</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
Confirm
</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="dialogPvVisible" title="Reading statistics">
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel" />
<el-table-column prop="pv" label="Pv" />
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">Confirm</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article'
import waves from '@/directive/waves' // waves directive
import { parseTime } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
{ key: 'US', display_name: 'USA' },
{ key: 'JP', display_name: 'Japan' },
{ key: 'EU', display_name: 'Eurozone' }
]
// arr to obj, such as { CN : "China", US : "USA" }
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
acc[cur.key] = cur.display_name
return acc
}, {})
export default {
name: 'DeviceAdd',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
data() {
return {
tableKey: 0,
list: null,
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 20,
importance: undefined,
title: undefined,
type: undefined,
sort: '+id'
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
showReviewer: false,
temp: {
id: undefined,
importance: 1,
remark: '',
timestamp: new Date(),
title: '',
type: '',
status: 'published'
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
dialogPvVisible: false,
pvData: [],
rules: {
type: [{ required: true, message: 'type is required', trigger: 'change' }],
timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }],
title: [{ required: true, message: 'title is required', trigger: 'blur' }]
},
downloadLoading: false
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.items
this.total = response.data.total
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
handleModifyStatus(row, status) {
this.$message({
message: '操作Success',
type: 'success'
})
row.status = status
},
sortChange(data) {
const { prop, order } = data
if (prop === 'id') {
this.sortByID(order)
}
},
sortByID(order) {
if (order === 'ascending') {
this.listQuery.sort = '+id'
} else {
this.listQuery.sort = '-id'
}
this.handleFilter()
},
resetTemp() {
this.temp = {
id: undefined,
importance: 1,
remark: '',
timestamp: new Date(),
title: '',
status: 'published',
type: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id
this.temp.author = 'vue-element-admin'
createArticle(this.temp).then(() => {
this.list.unshift(this.temp)
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Created Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464
updateArticle(tempData).then(() => {
for (const v of this.list) {
if (v.id === this.temp.id) {
const index = this.list.indexOf(v)
this.list.splice(index, 1, this.temp)
break
}
}
this.dialogFormVisible = false
this.$notify({
title: 'Success',
message: 'Update Successfully',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
this.$notify({
title: 'Success',
message: 'Delete Successfully',
type: 'success',
duration: 2000
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
},
handleFetchPv(pv) {
fetchPv(pv).then(response => {
this.pvData = response.data.pvData
this.dialogPvVisible = true
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel({
header: tHeader,
data,
filename: 'table-list'
})
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
<template>
<div>
<div class="mixin-components-container">
<el-row v-for="(page, index) of pages" :key="index" :gutter="8" style="margin-top:10px;">
<el-col v-for="(item, innerindex) of page" :key="item.id" :offset="innerindex > 0 ? 2 : 1" :span="6">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>{{ item.name }}</span>
</div>
<div v-if="item.vChartType === 've-line'">
<ve-line :data="item.chartData" />
</div>
<div v-if="item.vChartType === 've-gauge'">
<ve-gauge :data="item.chartData" :settings="item.settings" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
// import PanThumb from '@/components/PanThumb'
// import MdInput from '@/components/MDinput'
import VeLine from 'v-charts/lib/line.common'
import VeGauge from 'v-charts/lib/gauge.common'
import { deepClone } from '@/utils'
// https://codesandbox.io/s/z69myovqzx
// https://segmentfault.com/q/1010000012529833
const DEV_COMPONENT_STORAGE_KEY = 'devcomponents'
const cardDefaultInfo = {
id: '123',
name: 'Testname',
title: 'chart',
vChartType: 'type',
chartData: null,
settings: null
}
export default {
name: 'ComponentLib',
components: {
VeLine,
VeGauge
},
data() {
const validate = (rule, value, callback) => {
if (value.length !== 6) {
callback(new Error('请输入六个字符'))
} else {
callback()
}
}
this.chartGaugeSettings = {
dimension: 'type',
metrics: 'value'
}
return {
demo: {
title: ''
},
demoRules: {
title: [{ required: true, trigger: 'change', validator: validate }]
},
cardInfo: Object.assign({}, cardDefaultInfo),
allChartList: [],
chartLineData: {
columns: ['日期', 'Meter1', 'Meter2', 'Meter3'],
rows: [
{ 日期: '1/1', Meter1: 1393, Meter2: 1093, Meter3: 0.32 },
{ 日期: '1/2', Meter1: 3530, Meter2: 3230, Meter3: 0.26 },
{ 日期: '1/3', Meter1: 2923, Meter2: 2623, Meter3: 0.76 },
{ 日期: '1/4', Meter1: 1723, Meter2: 1423, Meter3: 0.49 },
{ 日期: '1/5', Meter1: 3792, Meter2: 3492, Meter3: 0.323 },
{ 日期: '1/6', Meter1: 4593, Meter2: 4293, Meter3: 0.78 }
]
},
chartDefaultGaugeData: {
columns: ['type', 'a', 'b', 'value'],
rows: [
{ type: '温度', value: 20, a: 1, b: 2 }
]
},
chartGaugeData: {
columns: ['type', 'a', 'b', 'value'],
rows: [
{ type: '温度', value: 80, a: 1, b: 2 }
]
}
}
},
computed: {
pages() {
const pages = []
this.allChartList.forEach((item, index) => {
const page = Math.floor(index / 4) // 4代表4条为一行,随意更改
if (!pages[page]) {
pages[page] = []
}
pages[page].push(item)
})
return pages
}
},
created() {
const tempcardInfo = deepClone(this.cardInfo)
tempcardInfo.chartData = this.chartLineData
tempcardInfo.name = 'threeMeterLineChart'
tempcardInfo.vChartType = 've-line'
this.allChartList.push(tempcardInfo)
const tempcardInfo2 = deepClone(this.cardInfo)
tempcardInfo2.id = '456'
tempcardInfo2.name = 'oneValueGaugeChart'
tempcardInfo2.vChartType = 've-gauge'
tempcardInfo2.chartData = this.chartGaugeData
tempcardInfo2.settings = this.chartGaugeSettings
this.allChartList.push(tempcardInfo2)
window.localStorage.setItem(DEV_COMPONENT_STORAGE_KEY, JSON.stringify(this.allChartList))
console.log('all chart list:')
console.log(this.allChartList)
},
methods: { // 这里用于定义方法
change() {
console.log('change gauge data:')
this.chartGaugeData = this.chartDefaultGaugeData
}
}
}
</script>
<style scoped>
.mixin-components-container {
background-color: #f0f2f5;
padding: 10px;
min-height: calc(100vh - 84px);
}
.component-item{
min-height: 50px;
}
</style>
此差异已折叠。
<template>
<div>
<div class="mixin-components-container">
<el-row v-for="(page, index) in pages" :key="index" :gutter="8" style="margin-top:10px;">
<el-col v-for="(item, innerindex) of page" :key="item.id" :offset="innerindex > 0 ? 2 : 1" :span="6">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>{{ item.name }}</span>
</div>
<div v-if="item.vChartType === 've-line'">
<ve-line :data="item.chartData" />
</div>
<div v-if="item.vChartType === 've-gauge'">
<ve-gauge :data="item.chartData" :settings="item.settings" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
// import PanThumb from '@/components/PanThumb'
// import MdInput from '@/components/MDinput'
import VeLine from 'v-charts/lib/line.common'
import VeGauge from 'v-charts/lib/gauge.common'
import { deepClone } from '@/utils'
import { bus } from './utils.js'
// https://codesandbox.io/s/z69myovqzx
// https://segmentfault.com/q/1010000012529833
const cardDefaultInfo = {
id: '123',
name: 'Testname',
title: 'chart',
vChartType: 'type',
chartData: null,
settings: null
}
/*
const devChartBindingData = {
id: '',
deviceId: '',
devName: '',
devBindedAttrId: '',
devBindedAttr: '',
devBingedChart: ''
}
*/
export default {
name: 'ComponentLib',
components: {
VeLine,
VeGauge
},
data() {
const validate = (rule, value, callback) => {
if (value.length !== 6) {
callback(new Error('请输入六个字符'))
} else {
callback()
}
}
this.chartGaugeSettings = {
dimension: 'type',
metrics: 'value',
dataName: {
'温度': '温度'
}
}
return {
demo: {
title: ''
},
demoRules: {
title: [{ required: true, trigger: 'change', validator: validate }]
},
cardInfo: Object.assign({}, cardDefaultInfo),
allChartList: [],
pages: [],
refreshTimer: null,
chartLineData: {
columns: ['日期', 'Meter1', 'Meter2', 'Meter3'],
rows: [
{ 日期: '1/1', Meter1: 1393, Meter2: 1093, Meter3: 0.32 },
{ 日期: '1/2', Meter1: 3530, Meter2: 3230, Meter3: 0.26 },
{ 日期: '1/3', Meter1: 2923, Meter2: 2623, Meter3: 0.76 },
{ 日期: '1/4', Meter1: 1723, Meter2: 1423, Meter3: 0.49 },
{ 日期: '1/5', Meter1: 3792, Meter2: 3492, Meter3: 0.323 },
{ 日期: '1/6', Meter1: 4593, Meter2: 4293, Meter3: 0.78 }
]
},
chartDefaultGaugeData: {
title: '温度',
columns: ['type', 'a', 'b', 'value'],
rows: [
{ type: '温度', value: 20, a: 1, b: 2 }
]
},
chartGaugeData: {
title: '温度',
columns: ['type', 'a', 'b', 'value'],
rows: [
{ type: '温度', value: 80, a: 1, b: 2 }
]
},
devChartBindingDataReceived: null
}
},
computed() {
// this.refreshPages()
/*
pages() {
const pages = []
this.allChartList.forEach((item, index) => {
const page = Math.floor(index / 4) // 4代表4条为一行,随意更改
if (!pages[page]) {
pages[page] = []
}
pages[page].push(item)
})
return pages
}
*/
},
created() {
var chartDataInfo = JSON.parse(window.localStorage.getItem('datachartbinginfo'))
console.log('from local storage:')
console.log(chartDataInfo)
const tempcardInfo = deepClone(this.cardInfo)
tempcardInfo.vChartType = chartDataInfo.devBingedChart
tempcardInfo.name = chartDataInfo.devName
if (tempcardInfo.vChartType === 've-gauge') {
tempcardInfo.chartData = this.chartGaugeData
tempcardInfo.settings = this.chartGaugeSettings
}
if (tempcardInfo.vChartType === 've-line') {
tempcardInfo.chartData = this.chartLineData
}
this.allChartList.push(tempcardInfo)
// start
this.refreshTimer = setInterval(() => {
for (let i = 0; i < this.allChartList.length; i++) {
// todo, only show how to
if (this.allChartList[i].vChartType === 've-gauge') {
var rdmValue = Math.floor(Math.random() * 10) + 25
this.allChartList[i].chartData.rows = [{ type: '温度', value: rdmValue, a: 1, b: 2 }]
}
}
}, 2000)
console.log('created called all chart list:')
console.log(this.allChartList)
this.refreshPages()
// this.$forceUpdate()
bus.$on('devbindingdata', (data) => {
console.log('Received data:')
// this.refreshPages()
// this.$forceUpdate()
})
},
mounted() {
},
beforeDestroy() {
clearInterval(this.refreshTimer)
},
methods: { // 这里用于定义方法
change() {
console.log('change gauge data:')
this.chartGaugeData = this.chartDefaultGaugeData
},
refreshPages() {
console.log('calculate paegs!')
this.allChartList.forEach((item, index) => {
const page = Math.floor(index / 4) // 4代表4条为一行,随意更改
if (!this.pages[page]) {
this.pages[page] = []
}
this.pages[page].push(item)
})
console.log(this.pages)
}
}
}
</script>
<style scoped>
.mixin-components-container {
background-color: #f0f2f5;
padding: 10px;
min-height: calc(100vh - 84px);
}
.component-item{
min-height: 50px;
}
</style>
import Vue from 'vue'
export const bus = new Vue()
...@@ -46,17 +46,18 @@ ...@@ -46,17 +46,18 @@
<div class="block"> <div class="block">
<el-button :loading="loading" type="primary" class="item-btn" size="medium" style="width:25%" @click.native.prevent="handleLogin">登录</el-button> <el-button :loading="loading" type="primary" class="item-btn" size="medium" style="width:25%" @click.native.prevent="handleLogin">登录</el-button>
<el-button type="primary" class="item-btn" size="medium" @click.native.prevent="gotoRegisterPage">我要注册</el-button> <el-button type="primary" class="item-btn" size="medium" @click.native.prevent="gotoRegisterPage">我要注册</el-button>
</div> </div>
</div> </div>
<div style="position:relative;margin-top:30px"> <div style="position:relative;margin-top:30px">
<div class="tips"> <div class="tips">
<span>Please register the user first.the username must be email.</span> <span style="color:gray">Please register first.The username must be email.</span>
</div> </div>
<div class="tips"> <div class="tips">
<span style="margin-right:18px;">The password must contain upper and lower case letters, numbers, and symbols.</span> <span style="margin-right:18px;color:gray">The password must contain upper and lower case letters, numbers, and symbols.</span>
</div>
<div style="position:relative" align="right">
<el-button class="item-btn" type="primary" @click="showDialog=true">Getting a tenant?</el-button>
</div> </div>
<el-button class="thirdparty-button" type="primary" @click="showDialog=true">Getting a tenant?</el-button>
</div> </div>
</el-form> </el-form>
...@@ -74,11 +75,11 @@ ...@@ -74,11 +75,11 @@
<script> <script>
import { validEmail } from '@/utils/validate' import { validEmail } from '@/utils/validate'
import SocialSign from './components/SocialSignin' // import SocialSign from './components/SocialSignin'
export default { export default {
name: 'Login', name: 'Login',
components: { SocialSign }, // components: { SocialSign },
data() { data() {
const validateUsername = (rule, value, callback) => { const validateUsername = (rule, value, callback) => {
if (!validEmail(value)) { if (!validEmail(value)) {
......
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="title-container">
<h3 class="title">IoTSharp登录入口</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="Password"
name="password"
tabindex="2"
auto-complete="on"
@keyup.native="checkCapslock"
@blur="capsTooltip = false"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
</el-tooltip>
<div style="position:relative" align="right">
<div class="block">
<el-button :loading="loading" type="primary" class="item-btn" size="medium" style="width:25%" @click.native.prevent="handleLogin">登陆</el-button>
<el-button type="primary" class="item-btn" size="medium" @click.native.prevent="handleRegister">我要注册</el-button>
</div>
</div>
<div style="position:relative;margin-top:30px">
<div class="tips">
<span>Username : admin</span>
<span>Password : any</span>
</div>
<div class="tips">
<span style="margin-right:18px;">Username : editor</span>
<span>Password : any</span>
</div>
<el-button class="thirdparty-button" type="primary" @click="showDialog=true">
Or connect with
</el-button>
</div>
</el-form>
<el-dialog title="Or connect with" :visible.sync="showDialog">
Can not be simulated on local, so please combine you own business simulation! ! !
<br>
<br>
<br>
<social-sign />
</el-dialog>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
import SocialSign from './components/SocialSignin'
export default {
name: 'Login',
components: { SocialSign },
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
return {
loginForm: {
username: 'iotmaster@iotsharp.net',
password: 'Admin_123'
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
passwordType: 'password',
capsTooltip: false,
loading: false,
showDialog: false,
redirect: undefined,
otherQuery: {}
}
},
watch: {
$route: {
handler: function(route) {
const query = route.query
if (query) {
this.redirect = query.redirect
this.otherQuery = this.getOtherQuery(query)
}
},
immediate: true
}
},
created() {
// window.addEventListener('storage', this.afterQRScan)
},
mounted() {
if (this.loginForm.username === '') {
this.$refs.username.focus()
} else if (this.loginForm.password === '') {
this.$refs.password.focus()
}
},
destroyed() {
// window.removeEventListener('storage', this.afterQRScan)
},
methods: {
checkCapslock({ shiftKey, key } = {}) {
if (key && key.length === 1) {
if (shiftKey && (key >= 'a' && key <= 'z') || !shiftKey && (key >= 'A' && key <= 'Z')) {
this.capsTooltip = true
} else {
this.capsTooltip = false
}
}
if (key === 'CapsLock' && this.capsTooltip === true) {
this.capsTooltip = false
}
},
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
console.log('value = ' + valid)
this.$store.dispatch('user/login', this.loginForm)
.then(() => {
console.log('push to home')
this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
this.loading = false
})
.catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
},
handleRegister() {
this.$router.push({ path: this.redirect || '/login/register', query: this.otherQuery })
},
getOtherQuery(query) {
return Object.keys(query).reduce((acc, cur) => {
if (cur !== 'redirect') {
acc[cur] = query[cur]
}
return acc
}, {})
}
// afterQRScan() {
// if (e.key === 'x-admin-oauth-code') {
// const code = getQueryObject(e.newValue)
// const codeMap = {
// wechat: 'code',
// tencent: 'code'
// }
// const type = codeMap[this.auth_type]
// const codeName = code[type]
// if (codeName) {
// this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
// this.$router.push({ path: this.redirect || '/' })
// })
// } else {
// alert('第三方登录失败')
// }
// }
// }
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
.thirdparty-button {
position: absolute;
right: 0;
bottom: 6px;
}
@media only screen and (max-width: 470px) {
.thirdparty-button {
display: none;
}
}
}
</style>
...@@ -70,9 +70,10 @@ ...@@ -70,9 +70,10 @@
</span> </span>
</el-form-item> </el-form-item>
</el-tooltip> </el-tooltip>
<div style="position:relative" align="center"> <div style="position:relative" align="right">
<div class="block"> <div class="block">
<el-button :loading="loading" type="primary" class="item-btn" size="medium" @click.native.prevent="handleRegister">Register</el-button> <el-button :loading="loading" type="primary" class="item-btn" size="medium" @click.native.prevent="handleRegister">Register</el-button>
<el-button class="item-btn" type="primary" @click="showDialog=true">Getting a tenant?</el-button>
</div> </div>
<div class="block"> <div class="block">
<el-button class="thirdparty-button" type="primary" @click="showDialog=true">Getting a tenant?</el-button> <el-button class="thirdparty-button" type="primary" @click="showDialog=true">Getting a tenant?</el-button>
...@@ -99,11 +100,9 @@ ...@@ -99,11 +100,9 @@
<script> <script>
// import { validUsername } from '@/utils/validate' // import { validUsername } from '@/utils/validate'
// import SocialSign from './components/SocialSignin'
import customerId from '@/utils/test-customer-id' import customerId from '@/utils/test-customer-id'
export default { export default {
name: 'Register', name: 'Register',
// components: { SocialSign },
data() { data() {
const validatePassword = (rule, value, callback) => { const validatePassword = (rule, value, callback) => {
if (value == null || value.length < 6 || value.trim().length === '') { if (value == null || value.length < 6 || value.trim().length === '') {
......
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="title-container">
<h3 class="title">IoTSharp注册</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="message" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="phone" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="Password"
name="password"
tabindex="2"
auto-complete="on"
@keyup.native="checkCapslock"
@blur="capsTooltip = false"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
</el-tooltip>
<div style="position:relative" align="center">
<div class="block">
<el-button type="primary" class="item-btn" size="medium" @click.native.prevent="handleLogin">注册</el-button>
</div>
</div>
<div style="position:relative;margin-top:30px">
<div class="tips">
<span>Username : admin</span>
<span>Password : any</span>
</div>
<div class="tips">
<span style="margin-right:18px;">Username : editor</span>
<span>Password : any</span>
</div>
<el-button class="thirdparty-button" type="primary" @click="showDialog=true">
Or connect with
</el-button>
</div>
</el-form>
<el-dialog title="Or connect with" :visible.sync="showDialog">
Can not be simulated on local, so please combine you own business simulation! ! !
<br>
<br>
<br>
<social-sign />
</el-dialog>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
import SocialSign from './components/SocialSignin'
export default {
name: 'Register',
components: { SocialSign },
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
return {
loginForm: {
username: 'iotmaster@iotsharp.net',
password: 'Admin_123'
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
passwordType: 'password',
capsTooltip: false,
loading: false,
showDialog: false,
redirect: undefined,
otherQuery: {}
}
},
watch: {
$route: {
handler: function(route) {
const query = route.query
if (query) {
this.redirect = query.redirect
this.otherQuery = this.getOtherQuery(query)
}
},
immediate: true
}
},
created() {
// window.addEventListener('storage', this.afterQRScan)
},
mounted() {
if (this.loginForm.username === '') {
this.$refs.username.focus()
} else if (this.loginForm.password === '') {
this.$refs.password.focus()
}
},
destroyed() {
// window.removeEventListener('storage', this.afterQRScan)
},
methods: {
checkCapslock({ shiftKey, key } = {}) {
if (key && key.length === 1) {
if (shiftKey && (key >= 'a' && key <= 'z') || !shiftKey && (key >= 'A' && key <= 'Z')) {
this.capsTooltip = true
} else {
this.capsTooltip = false
}
}
if (key === 'CapsLock' && this.capsTooltip === true) {
this.capsTooltip = false
}
},
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
console.log('value = ' + valid)
this.$store.dispatch('user/login', this.loginForm)
.then(() => {
console.log('push to home')
this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
this.loading = false
})
.catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
},
getOtherQuery(query) {
return Object.keys(query).reduce((acc, cur) => {
if (cur !== 'redirect') {
acc[cur] = query[cur]
}
return acc
}, {})
}
// afterQRScan() {
// if (e.key === 'x-admin-oauth-code') {
// const code = getQueryObject(e.newValue)
// const codeMap = {
// wechat: 'code',
// tencent: 'code'
// }
// const type = codeMap[this.auth_type]
// const codeName = code[type]
// if (codeName) {
// this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
// this.$router.push({ path: this.redirect || '/' })
// })
// } else {
// alert('第三方登录失败')
// }
// }
// }
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
.thirdparty-button {
position: absolute;
right: 0;
bottom: 6px;
}
@media only screen and (max-width: 470px) {
.thirdparty-button {
display: none;
}
}
}
</style>
...@@ -205,6 +205,7 @@ export default { ...@@ -205,6 +205,7 @@ export default {
const isEdit = this.dialogType === 'edit' const isEdit = this.dialogType === 'edit'
const checkedKeys = this.$refs.tree.getCheckedKeys() const checkedKeys = this.$refs.tree.getCheckedKeys()
const checkednodes = this.$refs.tree.getCheckedNodes()
this.role.routes = this.generateTree(deepClone(this.serviceRoutes), '/', checkedKeys) this.role.routes = this.generateTree(deepClone(this.serviceRoutes), '/', checkedKeys)
if (isEdit) { if (isEdit) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册