提交后台前端基础框架

This commit is contained in:
wanghao
2025-03-12 12:48:13 +08:00
parent 67a3fc68c9
commit 8e19156555
174 changed files with 36652 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
<template>
<div>
<el-container id="auth-container">
<el-main>
<div id="logo-name" class="animated slideInLeft" style="width: 600px; display: flex; align-items: center;">
<img src="@/icons/logo.png" style="width: 56px; height: 56px;">
<div style="margin-left: 25px;">{{ $store.state.website_name }}</div>
</div>
<router-view />
<div class="copyright" v-html="$store.state.copyright"></div>
</el-main>
</el-container>
<div class="fly-box">
<div class="fly bg-fly-circle1"></div>
<div class="fly bg-fly-circle2"></div>
<div class="fly bg-fly-circle3"></div>
<div class="fly bg-fly-circle4"></div>
</div>
</div>
</template>
<script>
export default {
data() {
return {}
},
}
</script>
<style lang="less" scoped>
@import '~@/assets/css/page/login-auth.less';
</style>

117
Backend/src/views/auth/login.vue Executable file
View File

@@ -0,0 +1,117 @@
<template>
<div id="login-box">
<div class="header">管理员登录</div>
<div class="main">
<el-form ref="form" :model="form" :rules="rules">
<el-form-item prop="username">
<el-input
v-model="form.username"
placeholder="账号"
class="cuborder-radius"
maxlength="32"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="密码"
class="cuborder-radius"
@keyup.enter.native="onSubmit('form')"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="submit-btn"
:loading="loginLoading"
@click="onSubmit('form')"
>立即登录
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { setToken } from '@/utils/auth'
import { ServeLogin } from '@/api/user'
export default {
data() {
return {
loginLoading: false,
form: {
username: '',
password: '',
},
rules: {
username: [
{
required: true,
message: '账号不能为空',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '密码不能为空!',
trigger: 'blur',
},
],
},
}
},
methods: {
onSubmit(formName) {
if (this.loginLoading) return false
this.$refs[formName].validate(valid => {
if (!valid) return false
this.loginLoading = true
this.login()
})
},
login() {
ServeLogin({
username: this.form.username,
password: this.form.password,
})
.then(res => {
if (res.code == 200) {
let result = res.data
// 保存授权信息到本地缓存
setToken(result.token, result.token_expired)
this.$store.commit('UPDATE_USER_INFO', result.member)
this.$store.commit('UPDATE_LOGIN_STATUS')
// 登录成功后连接 WebSocket 服务器
this.toLink('/')
} else {
this.$notify.info({
title: '提示',
message: res.msg,
})
}
})
.finally(() => {
this.loginLoading = false
})
},
toLink(url) {
this.$router.push({
path: url,
})
},
},
}
</script>
<style lang="less" scoped>
@import '~@/assets/css/page/login-auth.less';
</style>

View File

@@ -0,0 +1,218 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-header style="display: flex; align-items: center;justify-content: space-between; padding: 0;">
<div style="display: flex; align-items: center;"></div>
<div style="padding-right: 30px;">
<el-form :inline="true" :model="search" size="small">
<el-form-item>
<el-select v-model="search.is_online" placeholder="是否在线" clearable style="width: 120px;">
<el-option
v-for="item in onlines"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="search.status" placeholder="状态" clearable style="width: 120px;">
<el-option
v-for="item in statuses"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="search.keywords" placeholder="设备编号" clearable></el-input>
</el-form-item>
<el-button @click="loadData(1)" type="primary" icon="el-icon-search" size="small">搜索</el-button>
</el-form>
</div>
</el-header>
<el-main style="padding: 0; position: relative;">
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;">
<el-table
:data="tableData"
height="100%"
stripe
style="width: 100%;"
size="small">
<el-table-column
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="number"
label="设备编号"
min-width="120"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="name"
label="设备型号"
min-width="130"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.name ? scope.row.name : '-' }}
</template>
</el-table-column>
<el-table-column
prop="ip"
label="IP"
min-width="120"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.ip ? scope.row.ip : '-' }}
</template>
</el-table-column>
<el-table-column
prop="app_version"
label="APP版本"
min-width="88"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.app_version ? scope.row.app_version : '-' }}
</template>
</el-table-column>
<el-table-column
prop="is_online"
label="是否在线"
min-width="88"
show-overflow-tooltip>
<template slot-scope="scope">
<el-tag v-if="scope.row.is_online == 1" type="success" effect="dark" size="small">在线</el-tag>
<el-tag v-else type="danger" effect="dark" size="small">离线</el-tag>
</template>
</el-table-column>
<el-table-column
prop="active_time"
label="活动时间"
width="150">
</el-table-column>
<el-table-column
prop="create_time"
label="添加时间"
width="150">
</el-table-column>
</el-table>
</div>
</el-main>
<el-footer style="display: flex; align-items: center; padding: 0;">
<el-pagination
@current-change="setPage"
background
layout="prev, pager, next"
:current-page="page"
:page-count="pageCount">
</el-pagination>
</el-footer>
</el-container>
</template>
<script>
import { DeviceIndex } from "@/api/device";
export default {
name: 'DeviceIndexPage',
data() {
return {
tableData: [],
search: {},
loading: false,
page: 1,
pageCount: 1,
statuses: [],
onlines: [],
}
},
created() {
this.loadData(1)
},
methods: {
setPage(page) {
this.loadData(page);
},
loadData(page) {
if (this.loading) {
return;
}
this.loading = true
DeviceIndex(Object.assign(this.search, { page })).then(ret => {
this.loading = false
if (ret.code == 200) {
this.page = ret.data.page;
this.pageCount = ret.data.pageCount;
this.tableData = ret.data.list;
this.statuses = ret.data.statuses;
this.onlines = ret.data.onlines;
}
});
},
}
}
</script>
<style lang="less" scoped>
.container h4 {
color: rgba(0, 0, 0, 0.85);
font-size: 20px;
font-weight: 500;
line-height: 28px;
margin-bottom: 12px;
margin-bottom: 30px;
}
.container .list-item {
height: 70px;
margin: 5px 25px 5px 0px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.left-col {
display: flex;
flex-direction: row;
.avatar {
flex-basis: 50px;
flex-shrink: 0;
}
.conent {
flex: 1 1;
margin-left: 30px;
}
h4 {
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5715;
}
p {
margin-top: 5px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
}
}
.right-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
.action {
color: rgb(24, 144, 255);
font-size: 14px;
font-weight: 300;
cursor: pointer;
user-select: none;
}
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div>
<MainLayout :idx="2">
<el-container slot="container" class="container">
<el-header height="60px" class="header no-select" style="font-weight: 500;">{{ titles[$route.path] }}</el-header>
<el-container class="full-height">
<el-main style="padding-right: 0;">
<router-view />
</el-main>
</el-container>
</el-container>
</MainLayout>
</div>
</template>
<script>
import MainLayout from '@/views/layout/MainLayout'
export default {
name: 'DevicePage',
components: {
MainLayout,
},
computed: {
},
data() {
return {
titles: {
'/device/index': '设备列表',
},
}
},
mounted() {
},
created() {
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
width: 100%;
overflow: hidden;
.header {
line-height: 60px;
border-bottom: 1px solid #f5f5f5;
}
.aside {
display: flex;
flex-direction: column;
padding: 8px;
background-color: #f0f2f5;
a {
text-decoration: none;
color: rgba(0, 0, 0, 0.65);
}
.menu-list {
height: 35px;
line-height: 35px;
padding-left: 25px;
font-weight: 400;
font-size: 13px;
background-color: white;
border-left: 3px solid white;
border-radius: 5px;
margin: 4px 2px;
transition: ease 0.5s;
&:hover {
color: rgb(24, 144, 255);
}
&.selectd {
border-left-color: #2196f3;
color: #2196f3;
}
}
}
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-main style="padding: 0; position: relative;">
dddd
</el-main>
</el-container>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'IndexPage',
components: {
},
computed: {
},
data() {
return {
}
},
created() {
},
methods: {
}
}
</script>
<style lang="less" scoped>
.container h4 {
color: rgba(0, 0, 0, 0.85);
font-size: 20px;
font-weight: 500;
line-height: 28px;
margin-bottom: 12px;
margin-bottom: 30px;
}
.container .list-item {
height: 70px;
margin: 5px 25px 5px 0px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.left-col {
display: flex;
flex-direction: row;
.avatar {
flex-basis: 50px;
flex-shrink: 0;
}
.conent {
flex: 1 1;
margin-left: 30px;
}
h4 {
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5715;
}
p {
margin-top: 5px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
}
}
.right-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
.action {
color: rgb(24, 144, 255);
font-size: 14px;
font-weight: 300;
cursor: pointer;
user-select: none;
}
}
}
</style>

417
Backend/src/views/home/layout.vue Executable file
View File

@@ -0,0 +1,417 @@
<template>
<div>
<MainLayout :idx="0">
<el-container slot="container" class="container">
<el-header height="60px" class="header no-select" style="font-weight: 500;">首页</el-header>
<el-container class="full-height" style="background-color: #f4f4f4; overflow-x: hidden; overflow-y: auto;">
<el-main style="padding-right: 0;">
<div style="padding-right: 15px;">
<div style="float: left; width: calc(100% - 335px); padding: 10px 15px 15px 15px; box-shadow: 0 2px 12px 0 rgba(153,153,153, 0.1); background-color: #fff;">
<el-tabs v-model="active">
<el-tab-pane label="数据总览" name="first">
<div v-if="false" style="display: flex; justify-content: space-between; margin-top: 12px;">
<div class="block-item">
<div class="block-item-head" style="height: 40px;">
<div>
<div style="font-size: 14px;">数据总览</div>
</div>
</div>
<div style="padding: 12px 0 0 12px; display: flex; flex-wrap: wrap;">
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_total ? chatStat.friend_total : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">客户总数</div>
</div>
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_yes_total ? chatStat.friend_yes_total : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">现存客户</div>
</div>
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_del_total ? chatStat.friend_del_total : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">被删总数</div>
</div>
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.bind_total ? chatStat.bind_total : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">注册总数</div>
</div>
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.account_total ? chatStat.account_total : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">账号总数</div>
</div>
</div>
</div>
<div class="block-item" style="width: calc(66.66% - 8px)">
<div class="block-item-head" style="height: 40px;">
<div>
<div style="font-size: 14px;">今日数据<span style="font-size: 12px; color: #888; margin-left: 10px;">(每小时更新一次)</span></div>
</div>
</div>
<div style="padding: 12px 0 0 12px; display: flex; flex-wrap: wrap;">
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_today ? chatStat.friend_today : 0 }}/{{ chatStat.friend_yes_today ? chatStat.friend_yes_today : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日新客数/现存新客数</div>
</div>
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.bind_today ? chatStat.bind_today : 0 }}/{{ chatStat.bind_total ? chatStat.bind_total : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日注册数/总注册数</div>
</div>
<div class="data-item">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_del_today ? chatStat.friend_del_today : 0 }}/{{ chatStat.friend_del_total ? chatStat.friend_del_total : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日被删除数/总被删除数</div>
</div>
<div class="data-item" style="width: calc(20% - 12px);">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.plus_today ? chatStat.plus_today : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日添加好友数</div>
</div>
<div class="data-item" style="width: calc(20% - 12px);">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.plus_pass_today ? chatStat.plus_pass_today : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日通过好友数</div>
</div>
<div class="data-item" style="width: calc(20% - 12px);">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_send_today ? chatStat.friend_send_today : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日发送消息人数</div>
</div>
<div class="data-item" style="width: calc(20% - 12px);">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_reply_today ? chatStat.friend_reply_today : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日回复消息人数</div>
</div>
<div class="data-item" style="width: calc(20% - 12px);">
<div style="font-size: 18px; font-weight: bold;">{{ chatStat.friend_reply_rate ? chatStat.friend_reply_rate : 0 }}</div>
<div style="font-size: 12px; margin-top: 8px;">今日回复率(%)</div>
</div>
</div>
</div>
</div>
<div v-if="false" style="display: flex; justify-content: space-between; margin-top: 12px;">
<div class="block-item">
<div class="block-item-head" style="height: 40px;">
<div style="font-size: 14px;">业绩指标</div>
<div style="display: flex; align-items: center;">
<div style="line-height: 40px; font-size: 12px; padding-right: 8px;">注册目标: {{ performanceNum }}</div>
<el-radio-group v-model="performanceType" @change="loadPerformanceStatIndex" size="mini">
<el-radio-button label="day"></el-radio-button>
<el-radio-button label="week"></el-radio-button>
<el-radio-button label="month"></el-radio-button>
</el-radio-group>
</div>
</div>
<div style="height: 300px; overflow-x: hidden; overflow-y: auto;">
<div style="width: 100%; height: auto;">
<div style="padding: 12px; font-size: 13px;">
<div v-for="(item, i) in performanceList" :key="i" style="height: 30px; display: flex; align-items: center;">
<div style="width: 10%;">{{ i + 1 }}</div>
<div style="width: 50%;">{{ item.name }}</div>
<div style="width: 40%;"><el-progress :percentage="item.rate"></el-progress></div>
</div>
</div>
</div>
</div>
</div>
<div class="block-item" style="width: calc(66.66% - 8px)">
<div class="block-item-head" style="height: 40px;">
<div style="font-size: 14px;">加人增长</div>
<div style="display: flex; align-items: center;">
<el-radio-group v-model="plusIncType" @change="setPlusInc" size="mini">
<el-radio-button label="day"></el-radio-button>
<el-radio-button label="week"></el-radio-button>
<el-radio-button label="month"></el-radio-button>
</el-radio-group>
</div>
</div>
<div style="height: 300px;">
<iframe :src="plusIncUrl" style="width: 100%; height: 100%; border: 0;"></iframe>
</div>
</div>
</div>
<div v-if="false" style="margin-top: 20px; border-top: 1px solid #e8e8e8;">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="padding: 15px 0; font-size: 14px; font-weight: bold;">员工账号<span style="font-size: 12px; color: #888; margin-left: 10px; font-weight: normal;">(每小时更新一次)</span></div>
<div>
<el-date-picker
v-model="userDays"
type="date"
format="yyyy年MM月dd日"
value-format="yyyyMMdd"
placeholder="选择日期"
@change="loadChatStatUserIndex"
size="mini"
style="width: 158px;">
</el-date-picker>
</div>
</div>
<div>
<el-table
:data="userList"
height="300px"
stripe
style="width: 100%;"
size="small">
<el-table-column
prop="days"
label="日期"
min-width="120"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="username"
label="账号"
min-width="120"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="name"
label="姓名"
min-width="120"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="bind_total"
label="总注册数"
min-width="100"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="bind_today"
label="今日注册数"
min-width="100"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="friend_send_today"
label="发送消息人数"
min-width="100"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="friend_reply_today"
label="回复消息人数"
min-width="100"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="friend_reply_rate"
label="回复消息率"
min-width="100"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="group_send_today"
label="群发消息数"
width="100">
</el-table-column>
<el-table-column
prop="group_send_reply_rate_today"
label="群发回复率"
width="100">
</el-table-column>
<el-table-column
prop="update_time"
label="更新时间"
width="150">
</el-table-column>
</el-table>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<div style="float: right; width: 288px;">
<div style="padding: 10px 15px; box-shadow: 0 2px 12px 0 rgba(153,153,153, 0.1); background-color: #fff; display: none;">
<div style="font-size: 14px; font-weight: bold; line-height: 30px;">企微号</div>
<div style="margin-top: 10px; display: flex; justify-content: space-between; font-size: 14px;">
<div style="background-color: #ffede3; width: 123px; padding-bottom: 10px; border-radius: 5px;">
<div style="padding: 10px 15px;">席位数</div>
<div style="font-size: 18px; font-weight: bold; color: #f90; padding-left: 15px; line-height: 18px;">-</div>
</div>
<div style="background-color: #ffede3; width: 123px; padding-bottom: 10px; border-radius: 5px;">
<div style="padding: 10px 15px;">当前在线数</div>
<div style="font-size: 18px; font-weight: bold; color: #f90; padding-left: 15px; line-height: 18px;">-</div>
</div>
</div>
<div style="padding-bottom: 10px;">
<el-button type="primary" size="medium" plain style="width: 100%; margin-top: 15px;">企微号管理</el-button>
</div>
</div>
<div style="padding: 10px 15px; box-shadow: 0 2px 12px 0 rgba(153,153,153, 0.1); background-color: #fff;">
<div style="font-size: 14px; font-weight: bold; line-height: 30px;">帮助文档</div>
<div style="margin-top: 10px;">
<!--<div class="help-item">
<i class="el-icon-question"></i>
<span>天眼AI操作指南</span>
</div>
<div class="help-item">
<i class="el-icon-question"></i>
<span>账号登录注意事项</span>
</div>
<div class="help-item">
<i class="el-icon-question"></i>
<span>如何配置新客户自动化接待</span>
</div>-->
</div>
</div>
</div>
<div style="clear: both;"></div>
</div>
</el-main>
</el-container>
</el-container>
</MainLayout>
</div>
</template>
<script>
import MainLayout from '@/views/layout/MainLayout'
import config from '@/config/config'
import { getToken } from "@/utils/auth";
export default {
name: 'WorkPage',
components: {
MainLayout,
},
data() {
return {
type: 2,
active: 'first',
tableData: [],
userSmall: {},
payMoney: {},
sendNum: {},
chatStat: {},
userDays: '',
userList: [],
performanceType: 'day',
performanceList: [],
performanceNum: 0,
plusIncType: 'day',
plusIncUrl: '',
}
},
created() {
//this.loadData()
//this.setPlusInc()
},
methods: {
setPlusInc() {
//this.plusIncUrl = config.BASE_API_URL + '/manage/plus_stat/chartIndex?type=' + this.plusIncType + '&token=' + getToken()
},
loadData() {
},
timeFormat(timestamp, fmt) {
var t = new Date(timestamp);
var o = {
"M+" : t.getMonth() + 1, //月份
"d+" : t.getDate(), //日
"h+" : t.getHours(), //小时
"m+" : t.getMinutes(), //分
"s+" : t.getSeconds(), //秒
"q+" : Math.floor((t.getMonth() + 3) / 3), //季度
"S" : t.getMilliseconds() //毫秒
};
if(/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (t.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if(new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return fmt;
},
clientScreenChange(e) {
var t = new Date().getTime()
var n = this.timeFormat(t, 'yyyy-MM-dd')
var l = this.timeFormat(t - e * 24 * 3600 * 1000 + 24 * 3600 * 1000, 'yyyy-MM-dd')
this.clientDate = [
l, n
]
},
}
}
</script>
<style lang="less">
.block-item .data-item {
width: calc(33.33% - 12px);
background: #f4f4f4;
border-radius: 3px;
text-align: center;
padding: 12px 0;
margin-right: 12px;
margin-bottom: 12px;
}
.block-item {
width: calc(33% - 8px);
border: 1px solid #e8e8e8;
border-radius: 3px;
}
.block-item-head {
padding: 0 15px;
height: 80px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e8e8e8;
}
.help-item {
display: flex;
align-items: center;
background: #f5f5f5;
height: 38px;
font-size: 14px;
color: #333;
padding: 0 10px;
margin-bottom: 5px;
cursor: pointer;
}
.help-item span {
padding-left: 5px;
}
.help-item :hover {
background: rgb(236, 245, 255);
}
.container {
height: 100%;
width: 100%;
overflow: hidden;
.header {
line-height: 60px;
border-bottom: 1px solid #f5f5f5;
}
.aside {
display: flex;
flex-direction: column;
padding: 8px;
background-color: #f0f2f5;
a {
text-decoration: none;
color: rgba(0, 0, 0, 0.65);
}
.menu-list {
height: 35px;
line-height: 35px;
padding-left: 25px;
font-weight: 400;
font-size: 13px;
background-color: white;
border-left: 3px solid white;
border-radius: 5px;
margin: 4px 2px;
transition: ease 0.5s;
&:hover {
color: rgb(24, 144, 255);
}
&.selectd {
border-left-color: #2196f3;
color: #2196f3;
}
}
}
}
</style>

View File

@@ -0,0 +1,433 @@
<template>
<div v-if="reload" class="body-bag" :class="themeBagImg">
<el-container class="main-layout" :class="{ 'full-mode': themeMode }">
<el-aside width="228px" class="side-edge" style="background-color: #fff; border-right: 2px solid #DCDFE6;">
<el-container class="full-height">
<el-header height="70px" class="logo-header" style="display: flex; align-items: center; border-bottom: 1px solid #DCDFE6; position: relative;">
<div class="userlogo" style="margin: 0 15px 0 20px;" v-popover:usercard>
<img :src="userAvatar" :onerror="detaultAvatar" />
</div>
<p class="user-status" style="text-align: left; display: block; margin-top: 0;">
<span style="display: block; font-size: 14px; font-weight: 500; color: #333;">{{ userName }}</span>
<span style="display: block; margin-top: 3px;" class="online">在线</span>
</p>
<div @click="logout" class="t-logout" style="font-size: 13px; color: #333; cursor: pointer; position: absolute; right: 10px; bottom: 14px;">
<i class="el-icon-switch-button"></i>
<span style="margin-left: 3px;">退出</span>
</div>
</el-header>
<el-main class="sidebar-menu" style="width: auto; margin: 0; position: relative;">
<div class="thin-scrollbar" ref="thinScrollbar" @scroll="handleScroll" style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow-x: hidden; overflow-y: auto;">
<div style="height: 8px;"></div>
<div v-for="(menu, i) in menus" :key="i">
<router-link :to="{ path: menu.path, query: { _: top > 0 ? top : undefined } }">
<div class="left-menu-title" :class="{ active: idx == i }">
<i :class="menu.icon" />
<span>{{ menu.title }}</span>
</div>
</router-link>
<div v-if="menu.items && menu.items.length > 0" class="left-menu-menus">
<router-link v-for="(item, j) in menu.items" :key="j" :to="{ path: item.path, query: { _: top > 0 ? top : undefined } }">
<el-button v-if="$route.path == item.path" size="small" type="primary">{{ item.title }}</el-button>
<el-button v-else size="small">{{ item.title }}</el-button>
</router-link>
</div>
</div>
<div style="height: 20px;"></div>
</div>
</el-main>
<!--
<el-footer height="60px" class="fixed-sidebar">
<div class="menu-items" @click="logout">
<span class="logout">退出</span>
</div>
</el-footer>
-->
</el-container>
</el-aside>
<el-main class="no-padding" style="background: white">
<slot name="container"></slot>
</el-main>
</el-container>
<!-- 语音消息提示 -->
<audio id="audio" preload="auto">
<source src="~@/assets/image/1701.mp3" type="audio/mp3" />
</audio>
</div>
</template>
<script>
import Vue from 'vue'
import { mapState } from 'vuex'
import { ServeUserRoleGet } from '@/api/user'
export default {
name: 'MainLayout',
components: {
},
props: {
idx: {
type: Number,
default: 0,
},
},
computed: {
...mapState({
userName: state => state.user.name ? state.user.name : state.user.username,
userAvatar: state => state.user.avatar,
detaultAvatar: state => state.detaultAvatar,
themeMode: state => state.settings.themeMode,
themeBagImg: state => state.settings.themeBagImg,
}),
},
data() {
return {
reload: true,
top: 0,
menus: [
{
title: '首页',
icon: 'el-icon-s-home', // el-icon-house
path: '/home',
},
{
title: '任务队列',
icon: 'el-icon-s-operation',
path: '/task',
items: [],
},
{
title: '设备管理',
icon: 'el-icon-mobile', // el-icon-chat-line-round
path: '/device',
items: [
{ title: '设备列表', path: '/device/index' },
],
},
{
title: '商品管理',
icon: 'el-icon-present', // el-icon-chat-line-round
path: '/product',
items: [
{ title: '商品列表', path: '/product/index' },
{ title: '商品分组', path: '/product/group' },
],
},
{
title: '设置',
icon: 'el-icon-setting', // el-icon-setting
path: '/system',
items: [
{ title: '个人信息', path: '/system/index' },
//{ title: '服务器管理', path: '/system/server' },
//{ title: '员工管理', path: '/system/user' },
//{ title: '角色管理', path: '/system/role' },
],
}
]
}
},
watch: {
},
mounted() {
if (this.$route.query && this.$route.query._) {
this.top = this.$route.query._
this.$refs.thinScrollbar.scrollTop = this.top
}
},
created() {
},
methods: {
handleScroll(e) {
this.top = e.target.scrollTop
},
logout() {
this.$store.dispatch('ACT_USER_LOGOUT')
},
},
}
</script>
<style lang="less">
/* 设置细滚动条 */
.thin-scrollbar::-webkit-scrollbar {
width: 2px; /* 对于水平滚动条的高度 */
height: 2px; /* 对于垂直滚动条的宽度 */
}
.thin-scrollbar::-webkit-scrollbar-track {
background: #fff; /* 滚动条轨道的背景色 */
}
.thin-scrollbar::-webkit-scrollbar-thumb {
background: #888; /* 滚动条实际可拖动部分的背景色 */
border-radius: 2px; /* 滚动条圆角 */
}
.thin-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555; /* 鼠标悬浮时滚动条的背景色 */
}
.left-menu-menus {
display: flex;
flex-wrap: wrap;
padding-left: 25px;
padding-bottom: 10px;
}
.left-menu-menus .el-button {
margin-left: 0;
margin-right: 8px;
margin-top: 8px;
}
.left-menu-title {
display: flex;
align-items: center;
cursor: pointer;
color: #706d6d;
position: relative;
margin-left: 0;
padding-left: 15px;
font-size: 16px;
font-weight: 500;
height: 38px;
}
.left-menu-title i {
font-size: 20px !important;
}
.left-menu-title span {
margin-left: 5px;
}
.left-menu-title.active {
color: #222 !important;
}
.form-notice {
font-size: 12px;
color: #999;
line-height: 12px;
margin-top: 8px;
}
.main-layout {
position: fixed;
width: 75%;
height: 80%;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
overflow: hidden;
transition: ease 0.5s;
border-radius: 5px;
&.full-mode {
width: 100%;
height: 100%;
border-radius: 0;
}
.side-edge {
user-select: none;
background-color: #202225;
.logo-header {
padding: 0;
.userlogo {
width: 50px;
height: 50px;
margin: 10px auto 0;
border-radius: 50%;
position: relative;
cursor: pointer;
overflow: hidden;
transition: ease-in 3s;
img {
width: 100%;
height: 100%;
}
&:hover {
transform: rotate(360deg);
}
}
.user-status {
text-align: center;
margin-top: 10px;
color: #ccc9c9;
font-size: 13px;
font-weight: 300;
.online {
color: #0d710d;
}
}
}
}
.sidebar-menu {
width: 60px;
margin: 0 auto;
text-align: center;
padding: 0;
overflow: hidden;
a {
text-decoration: none;
}
.menu-items {
cursor: pointer;
color: #706d6d;
position: relative;
width: 45px;
height: 45px;
margin: 6px auto 0;
line-height: 45px;
text-align: center;
i {
font-size: 20px;
&:hover {
transform: scale(1.3);
}
}
.notify {
width: 5px;
height: 5px;
background: #ff1e1e;
display: inline-block;
border-radius: 5px;
position: absolute;
right: 5px;
top: 9px;
animation: notifymove 3s infinite;
animation-direction: alternate;
-webkit-animation: notifymove 3s infinite;
}
&.active {
color: #416641 !important;
}
}
}
}
.fixed-sidebar {
padding: 0;
.menu-items {
height: 25px;
line-height: 25px;
padding: 10px 5px;
cursor: pointer;
box-shadow: 0 0 0 0 #ccc9c9;
text-align: center;
color: #afabab;
i {
font-size: 20px;
}
.logout {
font-weight: 300;
font-size: 15px;
color: #9e9e9e;
transition: ease 0.5;
&:hover {
font-size: 16px;
}
}
}
}
/* 主题背景图片 */
.body-bag {
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
top: 0;
left: 0;
background-color: #121212;
transition: ease-in 0.5s;
&.bag001 {
background: url(~@/assets/image/background/001.jpg);
background-repeat: no-repeat;
background-size: 100% 100%;
}
&.bag002 {
background: url(~@/assets/image/background/002.jpg);
background-repeat: no-repeat;
background-size: 100% 100%;
}
&.bag003 {
background: url(~@/assets/image/background/003.jpg);
background-repeat: no-repeat;
background-size: 100% 100%;
}
&.bag004 {
background: url(~@/assets/image/background/005.png);
background-repeat: no-repeat;
background-size: 100% 100%;
}
}
@keyframes notifymove {
0% {
background: #ff1e1e;
}
25% {
background: #2e3238;
}
50% {
background: #ff1e1e;
}
75% {
background: #2e3238;
}
100% {
background: #ff1e1e;
}
}
@-webkit-keyframes notifymove {
0% {
background: #ff1e1e;
}
25% {
background: #2e3238;
}
50% {
background: #ff1e1e;
}
75% {
background: #2e3238;
}
100% {
background: #ff1e1e;
}
}
@media screen and (max-width: 1000px) {
.main-layout {
width: 100%;
height: 100%;
border-radius: 0;
}
}
</style>

View File

@@ -0,0 +1,415 @@
<template>
<div v-if="reload" class="body-bag" :class="themeBagImg">
<el-container class="main-layout" :class="{ 'full-mode': themeMode }">
<el-aside width="70px" class="side-edge">
<el-container class="full-height">
<el-header height="100px" class="logo-header">
<div class="userlogo" v-popover:usercard>
<img :src="userAvatar" :onerror="detaultAvatar" />
</div>
<p class="user-status">
<span class="online">在线</span>
</p>
</el-header>
<el-main class="sidebar-menu">
<el-tooltip
content="首页"
placement="right"
:visible-arrow="false"
>
<router-link to="/home">
<div class="menu-items" :class="{ active: idx == 0 }">
<i class="el-icon-house" />
</div>
</router-link>
</el-tooltip>
<el-tooltip
v-if="$userRoles.indexOf('/chat') >= 0"
content="账号运营"
placement="right"
:visible-arrow="false"
>
<router-link to="/chat">
<div class="menu-items" :class="{ active: idx == 1 }">
<i class="el-icon-chat-line-round" />
</div>
</router-link>
</el-tooltip>
<el-tooltip
v-if="$userRoles.indexOf('/rise') >= 0"
content="增长管理"
placement="right"
:visible-arrow="false"
>
<router-link to="/rise">
<div class="menu-items" :class="{ active: idx == 2 }">
<i class="el-icon-data-line" />
</div>
</router-link>
</el-tooltip>
<el-tooltip
v-if="$userRoles.indexOf('/service') >= 0"
content="客服管理"
placement="right"
:visible-arrow="false"
>
<router-link to="/service">
<div class="menu-items" :class="{ active: idx == 3 }">
<i class="el-icon-service" />
</div>
</router-link>
</el-tooltip>
<el-tooltip
v-if="$userRoles.indexOf('/clients') >= 0"
content="客户管理"
placement="right"
:visible-arrow="false"
>
<router-link to="/clients">
<div class="menu-items" :class="{ active: idx == 4 }">
<i class="el-icon-user-solid" />
</div>
</router-link>
</el-tooltip>
<!--<el-tooltip
v-if="$userRoles.indexOf('/game') >= 0"
content="游戏管理"
placement="right"
:visible-arrow="false"
>
<router-link to="/game">
<div class="menu-items" :class="{ active: idx == 5 }">
<i class="el-icon-coordinate" />
</div>
</router-link>
</el-tooltip>-->
<el-tooltip
v-if="$userRoles.indexOf('/statistics') >= 0"
content="数据统计"
placement="right"
:visible-arrow="false"
>
<router-link to="/statistics">
<div class="menu-items" :class="{ active: idx == 6 }">
<i class="el-icon-s-data" />
</div>
</router-link>
</el-tooltip>
<el-tooltip
content="设置"
placement="right"
:visible-arrow="false"
>
<router-link to="/system">
<div class="menu-items" :class="{ active: idx == 7 }">
<i class="el-icon-setting" />
</div>
</router-link>
</el-tooltip>
</el-main>
<el-footer height="60px" class="fixed-sidebar">
<div class="menu-items" @click="logout">
<span class="logout">退出</span>
</div>
</el-footer>
</el-container>
</el-aside>
<el-main class="no-padding" style="background: white">
<slot name="container"></slot>
</el-main>
</el-container>
<!-- 语音消息提示 -->
<audio id="audio" preload="auto">
<source src="~@/assets/image/1701.mp3" type="audio/mp3" />
</audio>
</div>
</template>
<script>
import Vue from 'vue'
import { mapState } from 'vuex'
import { ServeUserRoleGet } from '@/api/user'
export default {
name: 'MainLayout',
components: {
},
props: {
idx: {
type: Number,
default: 0,
},
},
computed: {
...mapState({
userAvatar: state => state.user.avatar,
detaultAvatar: state => state.detaultAvatar,
themeMode: state => state.settings.themeMode,
themeBagImg: state => state.settings.themeBagImg,
}),
},
data() {
return {
reload: true
}
},
watch: {
},
mounted() {
},
created() {
ServeUserRoleGet().then(ret => {
if (ret && ret.code == 200) {
Vue.prototype.$userRoles = ret.data
this.$forceUpdate()
}
})
},
methods: {
logout() {
this.$store.dispatch('ACT_USER_LOGOUT')
},
},
}
</script>
<style lang="less">
.form-notice {
font-size: 12px;
color: #999;
line-height: 12px;
margin-top: 8px;
}
.main-layout {
position: fixed;
width: 75%;
height: 80%;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
overflow: hidden;
transition: ease 0.5s;
border-radius: 5px;
&.full-mode {
width: 100%;
height: 100%;
border-radius: 0;
}
.side-edge {
user-select: none;
background-color: #202225;
.logo-header {
padding: 0;
.userlogo {
width: 50px;
height: 50px;
margin: 10px auto 0;
border-radius: 50%;
position: relative;
cursor: pointer;
overflow: hidden;
transition: ease-in 3s;
img {
width: 100%;
height: 100%;
}
&:hover {
transform: rotate(360deg);
}
}
.user-status {
text-align: center;
margin-top: 10px;
color: #ccc9c9;
font-size: 13px;
font-weight: 300;
.online {
color: #0d710d;
}
}
}
}
.sidebar-menu {
width: 60px;
margin: 0 auto;
text-align: center;
padding: 0;
overflow: hidden;
a {
text-decoration: none;
}
.menu-items {
cursor: pointer;
color: #706d6d;
position: relative;
width: 45px;
height: 45px;
margin: 6px auto 0;
line-height: 45px;
text-align: center;
i {
font-size: 20px;
&:hover {
transform: scale(1.3);
}
}
.notify {
width: 5px;
height: 5px;
background: #ff1e1e;
display: inline-block;
border-radius: 5px;
position: absolute;
right: 5px;
top: 9px;
animation: notifymove 3s infinite;
animation-direction: alternate;
-webkit-animation: notifymove 3s infinite;
}
&.active {
color: #416641 !important;
}
}
}
}
.fixed-sidebar {
padding: 0;
.menu-items {
height: 25px;
line-height: 25px;
padding: 10px 5px;
cursor: pointer;
box-shadow: 0 0 0 0 #ccc9c9;
text-align: center;
color: #afabab;
i {
font-size: 20px;
}
.logout {
font-weight: 300;
font-size: 15px;
color: #9e9e9e;
transition: ease 0.5;
&:hover {
font-size: 16px;
}
}
}
}
/* 主题背景图片 */
.body-bag {
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
top: 0;
left: 0;
background-color: #121212;
transition: ease-in 0.5s;
&.bag001 {
background: url(~@/assets/image/background/001.jpg);
background-repeat: no-repeat;
background-size: 100% 100%;
}
&.bag002 {
background: url(~@/assets/image/background/002.jpg);
background-repeat: no-repeat;
background-size: 100% 100%;
}
&.bag003 {
background: url(~@/assets/image/background/003.jpg);
background-repeat: no-repeat;
background-size: 100% 100%;
}
&.bag004 {
background: url(~@/assets/image/background/005.png);
background-repeat: no-repeat;
background-size: 100% 100%;
}
}
@keyframes notifymove {
0% {
background: #ff1e1e;
}
25% {
background: #2e3238;
}
50% {
background: #ff1e1e;
}
75% {
background: #2e3238;
}
100% {
background: #ff1e1e;
}
}
@-webkit-keyframes notifymove {
0% {
background: #ff1e1e;
}
25% {
background: #2e3238;
}
50% {
background: #ff1e1e;
}
75% {
background: #2e3238;
}
100% {
background: #ff1e1e;
}
}
@media screen and (max-width: 1000px) {
.main-layout {
width: 100%;
height: 100%;
border-radius: 0;
}
}
</style>

103
Backend/src/views/other/404.vue Executable file
View File

@@ -0,0 +1,103 @@
<template>
<div>
<div class="not-found">
<div class="not-found-left">
<SvgNotFount class="svg-notfount" />
</div>
<div class="not-found-right">
<h1>404</h1>
<p>抱歉你访问的页面不存在或仍在开发中...</p>
<div>
<el-button type="primary" size="small" @click="toHome">
返回首页 ({{ second }}S)
</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import { SvgNotFount } from '@/core/icons'
export default {
name: 'NotFoundPage',
components: {
SvgNotFount,
},
data() {
return {
second: 10,
setInterval: null,
}
},
created() {
this.setInterval = setInterval(() => {
this.second--
if (this.second == 0) {
clearInterval(this.setInterval)
this.toHome()
}
}, 1000)
},
destroyed() {
clearInterval(this.setInterval)
},
methods: {
toHome() {
this.$router.push({
path: '/',
})
},
},
}
</script>
<style lang="less" scoped>
.not-found {
position: fixed;
width: 600px;
height: 300px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
.not-found-left {
width: 300px;
height: 300px;
float: left;
.svg-notfount {
width: 300px;
height: 300px;
}
}
.not-found-right {
width: 300px;
height: 300px;
float: left;
h1 {
margin-bottom: 24px;
color: #434e59;
font-weight: 600;
font-size: 72px;
line-height: 72px;
padding-left: 30px;
margin-top: 50px;
}
p {
padding-left: 30px;
margin-bottom: 16px;
color: rgba(0, 0, 0, 0.45);
font-size: 20px;
line-height: 28px;
}
div {
padding-left: 30px;
margin-top: 20px;
}
}
}
</style>

View File

@@ -0,0 +1,235 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogShow"
v-loading="loading"
width="868px"
modal-append-to-body
append-to-body
destroy-on-close
:close-on-press-escape="false">
<div>
<el-form :model="form" :rules="rules" ref="form" size="small" label-width="88px">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入商品标题"></el-input>
</el-form-item>
<el-form-item label="描述" prop="content">
<el-input v-model="form.content" type="textarea" :rows="5" placeholder="请输入商品描述"></el-input>
</el-form-item>
<el-form-item label="宝贝图片" prop="images">
<div>
<el-upload
:action="uploadUrl"
multiple
list-type="picture-card"
:file-list="images"
:on-success="handleSuccess"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
accept="image/png, image/jpeg">
<i class="el-icon-plus"></i>
</el-upload>
<div class="form-notice">只能上传jpg/png文件且不超过1M</div>
</div>
</el-form-item>
<el-form-item label="图片标签" prop="labels">
<el-select
v-model="form.labels"
multiple
filterable
allow-create
default-first-option
placeholder="请设置"
style="width: 100%;">
</el-select>
</el-form-item>
<el-form-item label="视频" prop="video">
<div><el-button type="primary" icon="el-icon-upload2">上传</el-button></div>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="价格" prop="price">
<el-input v-model="form.price" placeholder="¥0.00" style="width: 80%;"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="库存" prop="stock">
<el-input v-model="form.stock" placeholder="9999" style="width: 80%;"></el-input>
</el-form-item>
</el-col>
</el-row>
<!--<el-row>
<el-col :span="12">
<el-form-item label="运费" prop="shipping_fee">
<el-input v-model="form.shipping_fee" placeholder="¥0.00, 不填写则为包邮" style="width: 80%;"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="地点" prop="address">
<el-input v-model="form.address" placeholder="" style="width: 80%;"></el-input>
</el-form-item>
</el-col>
</el-row>-->
<!--<el-form-item label="其他选项" prop="opts">
<el-checkbox-group v-model="form.opts">
<el-checkbox label="全新宝贝"></el-checkbox>
<el-checkbox label="自提"></el-checkbox>
<el-checkbox label="同城面交"></el-checkbox>
<el-checkbox label="邮寄"></el-checkbox>
</el-checkbox-group>
</el-form-item>-->
<el-form-item label="宝贝主题" prop="themes">
<el-select
v-model="form.themes"
multiple
filterable
allow-create
placeholder="请设置"
style="width: 100%;">
</el-select>
</el-form-item>
<!--<el-form-item label="分类/品牌" prop="cb">
<el-select
v-model="form.cb"
multiple
filterable
allow-create
placeholder="请设置"
style="width: 100%;">
</el-select>
</el-form-item>-->
<el-form-item label="宝贝分组" prop="group_id">
<el-select
v-model="form.group_id"
filterable
placeholder="默认分组"
style="width: 260px;">
<el-option
v-for="item in groups"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</span>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</el-dialog>
</template>
<script>
import config from '@/config/config'
import { ProductSave } from "@/api/product";
import { getToken } from '@/utils/auth'
export default {
name: 'ProductForm',
data() {
return {
loading: false,
dialogShow: false,
dialogVisible: false,
dialogImageUrl: '',
uploadUrl: '',
title: '',
form: { opts: [], labels: ['点头像+'], cb: [], themes: [], images: [] },
rules: {
title: [
{ required: true, message: '请输入商品标题', trigger: 'blur' },
],
content: [
{ required: true, message: '请输入商品描述', trigger: 'blur' },
],
images: [
{ required: true, message: '请上传商品图片', trigger: 'blur' },
],
price: [
{ required: true, message: '请输入商品价格', trigger: 'blur' },
],
stock: [
{ required: true, message: '请输入商品库存', trigger: 'blur' },
],
},
images: [],
groups: [],
};
},
created() {
this.uploadUrl = config.BASE_API_URL + '/backend/upload?token=' + getToken()
},
methods: {
handleSuccess(res, file, fileList) {
if (res && res.name && res.url) {
this.form.images.push(res)
}
},
handleRemove(file, fileList) {
var images = []
for (var i = 0; i < this.form.images.length; i ++) {
if (this.form.images[i].name != file.name) {
images.push(this.form.images[i])
}
}
this.form.images = images
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
submit() {
this.$refs.form.validate(valid => {
if (valid) {
this.loading = true
ProductSave(this.form).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.$emit('fetch-data')
this.close()
} else {
this.$message.error(ret.msg);
}
}).catch(e => {
this.loading = false
this.$message.error(e.message);
})
} else {
return false;
}
})
},
show(row, groups) {
this.groups = groups
if (row) {
this.title = '修改商品'
this.dialogShow = true
this.form = Object.assign({}, row, { group_id: row.group_id ? row.group_id : '' })
this.images = [].concat(this.form.images)
} else {
this.title = '添加商品'
this.dialogShow = true
this.form = { opts: [], labels: ['点头像+'], cb: [], themes: [], images: [] }
this.images = []
}
},
close() {
this.dialogShow = false
this.$refs['form'].resetFields()
this.form = this.$options.data().form
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,89 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogShow"
v-loading="loading"
width="368px"
modal-append-to-body
append-to-body
destroy-on-close
:close-on-press-escape="false">
<div>
<el-form :model="form" :rules="rules" ref="form" size="small" label-width="0">
<el-form-item label="" prop="name">
<el-input v-model="form.name" placeholder="请输入分组名称"></el-input>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</span>
</el-dialog>
</template>
<script>
import { ProductGroupSave } from "@/api/product";
export default {
name: 'ProductGroupForm',
data() {
return {
loading: false,
dialogShow: false,
title: '',
form: { },
rules: {
name: [
{ required: true, message: '请输入分组名称', trigger: 'blur' },
],
},
};
},
methods: {
submit() {
this.$refs.form.validate(valid => {
if (valid) {
this.loading = true
ProductGroupSave(this.form).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.$emit('fetch-data')
this.close()
} else {
this.$message.error(ret.msg);
}
}).catch(e => {
this.loading = false
this.$message.error(e.message);
})
} else {
return false;
}
})
},
show(row) {
if (row) {
this.title = '修改分组'
this.dialogShow = true
this.form = Object.assign({}, row)
} else {
this.title = '添加分组'
this.dialogShow = true
this.form = { }
}
},
close() {
this.dialogShow = false
this.$refs['form'].resetFields()
this.form = this.$options.data().form
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,201 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-header style="display: flex; align-items: center;justify-content: space-between; padding: 0;">
<div style="display: flex; align-items: center;">
<el-button @click="handleCreate()" type="primary" size="small" icon="el-icon-plus">添加分组</el-button>
</div>
<div style="padding-right: 30px;">
<el-form :inline="true" :model="search" size="small">
<el-form-item>
<el-input v-model="search.keywords" placeholder="分组名称" clearable></el-input>
</el-form-item>
<el-button @click="loadData(1)" type="primary" icon="el-icon-search" size="small">搜索</el-button>
</el-form>
</div>
</el-header>
<el-main style="padding: 0; position: relative;">
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;">
<el-table
:data="tableData"
height="100%"
stripe
style="width: 100%;"
size="small">
<el-table-column
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="name"
label="分组名称"
min-width="130"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="product_num"
label="商品数量"
min-width="100"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="create_time"
label="添加时间"
width="150">
</el-table-column>
<el-table-column
label="操作"
width="120"
fixed="right">
<template slot-scope="scope">
<el-button @click="handleUpdate(scope.row)" type="text" size="small">修改</el-button>
<el-button @click="handleDelete(scope.row)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-main>
<el-footer style="display: flex; align-items: center; padding: 0;">
<el-pagination
@current-change="setPage"
background
layout="prev, pager, next"
:current-page="page"
:page-count="pageCount">
</el-pagination>
</el-footer>
<product-group-form ref="groupForm" @fetch-data="() => loadData(1)"></product-group-form>
</el-container>
</template>
<script>
import ProductGroupForm from "@/views/product/components/ProductGroupForm";
import { ProductGroupIndex, ProductGroupDelete } from "@/api/product";
export default {
name: 'ProductGroupPage',
components: {
ProductGroupForm,
},
data() {
return {
tableData: [],
search: {},
loading: false,
page: 1,
pageCount: 1,
}
},
created() {
this.loadData(1)
},
methods: {
handleCreate() {
this.$refs.groupForm.show(null)
},
handleUpdate(row) {
this.$refs.groupForm.show(row)
},
handleDelete(row) {
this.$confirm('此操作将删除该分组, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true
ProductGroupDelete(row.id).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.loadData(1)
} else {
this.$message.error(ret.msg)
}
}).catch(e => {
this.$message.error(e.message)
})
})
},
setPage(page) {
this.loadData(page);
},
loadData(page) {
if (this.loading) {
return;
}
this.loading = true
ProductGroupIndex(Object.assign(this.search, { page })).then(ret => {
this.loading = false
if (ret.code == 200) {
this.page = ret.data.page;
this.pageCount = ret.data.pageCount;
this.tableData = ret.data.list;
}
});
},
}
}
</script>
<style lang="less" scoped>
.container h4 {
color: rgba(0, 0, 0, 0.85);
font-size: 20px;
font-weight: 500;
line-height: 28px;
margin-bottom: 12px;
margin-bottom: 30px;
}
.container .list-item {
height: 70px;
margin: 5px 25px 5px 0px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.left-col {
display: flex;
flex-direction: row;
.avatar {
flex-basis: 50px;
flex-shrink: 0;
}
.conent {
flex: 1 1;
margin-left: 30px;
}
h4 {
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5715;
}
p {
margin-top: 5px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
}
}
.right-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
.action {
color: rgb(24, 144, 255);
font-size: 14px;
font-weight: 300;
cursor: pointer;
user-select: none;
}
}
}
</style>

View File

@@ -0,0 +1,310 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-header style="display: flex; align-items: center;justify-content: space-between; padding: 0;">
<div style="display: flex; align-items: center;">
<el-button @click="handleCreate()" type="primary" size="small" icon="el-icon-plus">添加商品</el-button>
</div>
<div style="padding-right: 30px;">
<el-form :inline="true" :model="search" size="small">
<el-form-item>
<el-select v-model="search.group_id" placeholder="商品分组" clearable style="width: 120px;">
<el-option label="默认分组" :value="0"></el-option>
<el-option
v-for="item in groups"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="search.is_used" placeholder="是否使用" clearable style="width: 120px;">
<el-option
v-for="item in isUseds"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="search.keywords" placeholder="商品名称" clearable></el-input>
</el-form-item>
<el-button @click="loadData(1)" type="primary" icon="el-icon-search" size="small">搜索</el-button>
</el-form>
</div>
</el-header>
<el-main style="padding: 0; position: relative;">
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;">
<el-table
:data="tableData"
height="100%"
stripe
style="width: 100%;"
size="small"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55"
fixed="left">
</el-table-column>
<el-table-column
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="group_name"
label="商品分组"
min-width="100"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.group_name ? scope.row.group_name : '默认分组' }}
</template>
</el-table-column>
<el-table-column
prop="images"
label="商品图片"
min-width="168"
show-overflow-tooltip>
<template slot-scope="scope">
<div style="display: flex;">
<el-image v-for="(item, i) in scope.row.images" v-if="i < 3" :key="i" :src="item.url" style="width: 42px; height: 42px; display: block; margin-right: 6px;"></el-image>
</div>
</template>
</el-table-column>
<el-table-column
prop="title"
label="商品标题"
min-width="130"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="content"
label="描述"
min-width="130"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="labels"
label="图片标签"
min-width="130"
show-overflow-tooltip>
<template slot-scope="scope">
{{ (scope.row.labels && scope.row.labels.length > 0) ? scope.row.labels.join(',') : '-' }}
</template>
</el-table-column>
<el-table-column
prop="price"
label="价格(¥)"
min-width="80"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="stock"
label="库存"
min-width="80"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="themes"
label="宝贝主题"
min-width="130"
show-overflow-tooltip>
<template slot-scope="scope">
{{ (scope.row.themes && scope.row.themes.length > 0) ? scope.row.themes.join(',') : '-' }}
</template>
</el-table-column>
<!--<el-table-column
prop="shipping_fee"
label="运费(¥)"
min-width="80"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.shipping_fee > 0 ? scope.row.shipping_fee : '包邮' }}
</template>
</el-table-column>-->
<el-table-column
prop="is_used_name"
label="是否使用"
min-width="80"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="create_time"
label="添加时间"
width="150">
</el-table-column>
<el-table-column
label="操作"
width="120"
fixed="right">
<template slot-scope="scope">
<el-button @click="handleUpdate(scope.row)" type="text" size="small">修改</el-button>
<el-button @click="handleDelete(scope.row)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-main>
<el-footer style="display: flex; align-items: center; padding: 0;">
<el-pagination
@current-change="setPage"
background
layout="prev, pager, next"
:current-page="page"
:page-count="pageCount">
</el-pagination>
</el-footer>
</el-container>
</template>
<script>
import ProductForm from "@/views/product/components/ProductForm";
import { ProductIndex, ProductDelete } from "@/api/product";
export default {
name: 'ProductPage',
components: {
ProductForm,
},
data() {
return {
tableData: [],
search: {},
loading: false,
page: 1,
pageCount: 1,
groups: [],
isUseds: [],
checked: [],
}
},
created() {
this.loadData(1)
},
methods: {
handleMenu(e) {
if (this.checked.length <= 0) {
return this.$message.error('请选择商品')
}
this.$refs['' + e + 'Form'].show(this.checked)
},
handleSelectionChange(rows) {
var checked = []
for (var i = 0; i < rows.length; i ++) {
checked.push(rows[i].id)
}
this.checked = checked
},
handleCreate() {
this.$refs.productForm.show(null, this.groups)
},
handleUpdate(row) {
this.$refs.productForm.show(row, this.groups)
},
handleDelete(row) {
this.$confirm('此操作将删除该商品, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true
ProductDelete(row.id).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.loadData(1)
} else {
this.$message.error(ret.msg)
}
}).catch(e => {
this.$message.error(e.message)
})
})
},
setPage(page) {
this.loadData(page);
},
loadData(page) {
if (this.loading) {
return;
}
this.loading = true
ProductIndex(Object.assign(this.search, { page })).then(ret => {
this.loading = false
if (ret.code == 200) {
this.page = ret.data.page;
this.pageCount = ret.data.pageCount;
this.tableData = ret.data.list;
this.groups = ret.data.groups;
this.isUseds = ret.data.isUseds;
}
});
},
}
}
</script>
<style lang="less" scoped>
.container h4 {
color: rgba(0, 0, 0, 0.85);
font-size: 20px;
font-weight: 500;
line-height: 28px;
margin-bottom: 12px;
margin-bottom: 30px;
}
.container .list-item {
height: 70px;
margin: 5px 25px 5px 0px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.left-col {
display: flex;
flex-direction: row;
.avatar {
flex-basis: 50px;
flex-shrink: 0;
}
.conent {
flex: 1 1;
margin-left: 30px;
}
h4 {
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5715;
}
p {
margin-top: 5px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
}
}
.right-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
.action {
color: rgb(24, 144, 255);
font-size: 14px;
font-weight: 300;
cursor: pointer;
user-select: none;
}
}
}
</style>

View File

@@ -0,0 +1,87 @@
<template>
<div>
<MainLayout :idx="3">
<el-container slot="container" class="container">
<el-header height="60px" class="header no-select" style="font-weight: 500;">{{ titles[$route.path] }}</el-header>
<el-container class="full-height">
<el-main style="padding-right: 0;">
<router-view />
</el-main>
</el-container>
</el-container>
</MainLayout>
</div>
</template>
<script>
import MainLayout from '@/views/layout/MainLayout'
export default {
name: 'ProductPage',
components: {
MainLayout,
},
computed: {
},
data() {
return {
titles: {
'/product/index': '商品列表',
'/product/group': '商品分组',
},
}
},
mounted() {
},
created() {
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
width: 100%;
overflow: hidden;
.header {
line-height: 60px;
border-bottom: 1px solid #f5f5f5;
}
.aside {
display: flex;
flex-direction: column;
padding: 8px;
background-color: #f0f2f5;
a {
text-decoration: none;
color: rgba(0, 0, 0, 0.65);
}
.menu-list {
height: 35px;
line-height: 35px;
padding-left: 25px;
font-weight: 400;
font-size: 13px;
background-color: white;
border-left: 3px solid white;
border-radius: 5px;
margin: 4px 2px;
transition: ease 0.5s;
&:hover {
color: rgb(24, 144, 255);
}
&.selectd {
border-left-color: #2196f3;
color: #2196f3;
}
}
}
}
</style>

View File

@@ -0,0 +1,147 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogShow"
v-loading="loading"
width="760px"
modal-append-to-body
append-to-body
destroy-on-close
:close-on-press-escape="false">
<div>
<el-form :model="form" :rules="rules" ref="form" size="small" label-width="68px">
<el-form-item label="角色名" prop="name">
<el-input v-model="form.name" placeholder="请输入角色名"></el-input>
</el-form-item>
<el-form-item label="权限" prop="privileges">
<el-checkbox-group v-model="form.privileges" @change="setChange">
<div v-for="(item, i) in privileges" :key="i">
<div v-if="parseInt(i) == i">
<div style="font-weight: bold;"><el-checkbox :label="item.value">{{ item.title }}</el-checkbox></div>
<div v-for="(item1, j) in item.items" :key="j">
<div style="padding-left: 25px;"><el-checkbox :disabled="getDisabled(item1.value)" :label="item1.value">{{ item1.title }}</el-checkbox></div>
<div style="padding-left: 50px;">
<el-checkbox v-for="(item2, n) in item1.items" :key="n" :disabled="getDisabled(item2.value)" :label="item2.value">{{ item2.title }}</el-checkbox>
</div>
</div>
</div>
</div>
</el-checkbox-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注"></el-input>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</span>
</el-dialog>
</template>
<script>
import { RoleSave, RolePrivileges } from "@/api/role";
export default {
name: 'SystemRoleForm',
data() {
return {
loading: false,
dialogShow: false,
title: '',
form: { privileges: [] },
rules: {
name: [
{ required: true, message: '请输入角色名', trigger: 'blur' },
],
privileges: [
{ required: true, message: '请设置权限', trigger: 'blur' },
],
},
privileges: [],
};
},
methods: {
setChange(e) {
for (var i = 0; i < this.form.privileges.length; i ++) {
var s = this.form.privileges[i].replace(/\/[\d\w]+$/, '')
if (s.length > 0) {
if (this.form.privileges.indexOf(s) < 0) {
this.form.privileges[i] = ''
}
}
}
var privileges = []
for (var i = 0; i < this.form.privileges.length; i ++) {
if (this.form.privileges[i].length > 0) {
privileges.push(this.form.privileges[i])
}
}
this.form.privileges = privileges
this.form = Object.assign({}, this.form)
},
getDisabled(value) {
if (this.form.privileges.indexOf(value) >= 0) {
return false
}
var prev = value.replace(/\/[\d\w]+$/, '')
if (this.form.privileges.indexOf(prev) >= 0) {
return false
}
return true
},
submit() {
this.$refs.form.validate(valid => {
if (valid) {
this.loading = true
RoleSave(this.form).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.$emit('fetch-data')
this.close()
} else {
this.$message.error(ret.msg);
}
}).catch(e => {
this.loading = false
this.$message.error(e.message);
})
} else {
return false;
}
})
},
show(row) {
if (row) {
this.title = '修改角色'
this.dialogShow = true
this.form = Object.assign({}, row)
} else {
this.title = '添加角色'
this.dialogShow = true
this.form = { privileges: [] }
}
RolePrivileges().then(ret => {
if (ret && ret.code == 200) {
this.privileges = ret.data
}
})
},
close() {
this.dialogShow = false
this.$refs['form'].resetFields()
this.form = this.$options.data().form
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,143 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogShow"
v-loading="loading"
width="580px"
modal-append-to-body
append-to-body
destroy-on-close
:close-on-press-escape="false">
<div>
<el-form :model="form" :rules="rules" ref="form" size="small" label-width="78px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item v-if="!form.id" label="密码" prop="password">
<el-input v-model="form.password" placeholder="请输入密码"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" maxlength="11" placeholder="请输入手机号"></el-input>
</el-form-item>
<el-form-item label="角色" prop="roles">
<el-checkbox-group v-model="form.roles">
<el-checkbox v-for="(role, i) in roles" :key="i" :label="role.value">{{ role.label }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注"></el-input>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择">
<el-option
v-for="item in statuses"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</span>
</el-dialog>
</template>
<script>
import { UserSave, UserGetUsername } from "@/api/user";
export default {
name: 'SystemUserForm',
data() {
return {
loading: false,
dialogShow: false,
title: '',
form: { roles: [], status: 0 },
rules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
],
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
],
roles: [
{ required: true, message: '请设置角色', trigger: 'blur' },
],
resource_key: [
{ required: true, message: '请输入资源KEY', trigger: 'blur' },
],
},
roles: [],
statuses: [],
};
},
methods: {
submit() {
this.$refs.form.validate(valid => {
if (valid) {
this.loading = true
UserSave(this.form).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.$emit('fetch-data')
this.close()
} else {
this.$message.error(ret.msg);
}
}).catch(e => {
this.loading = false
this.$message.error(e.message);
})
} else {
return false;
}
})
},
show(row, roles, statuses) {
this.roles = roles
this.statuses = statuses
if (row) {
this.title = '修改员工'
this.dialogShow = true
this.form = Object.assign({}, row)
} else {
this.title = '添加员工'
this.dialogShow = true
this.form = { roles: [], status: 0 }
UserGetUsername().then(ret => {
if (ret && ret.code == 200) {
this.form = Object.assign({}, this.form, {
username: ret.data.username
})
}
})
}
},
close() {
this.dialogShow = false
this.$refs['form'].resetFields()
this.form = this.$options.data().form
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,83 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogShow"
v-loading="loading"
width="420px"
modal-append-to-body
append-to-body
destroy-on-close
:close-on-press-escape="false">
<div>
<el-form :model="form" :rules="rules" ref="form" size="small" label-width="68px">
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" placeholder="请输入密码"></el-input>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</span>
</el-dialog>
</template>
<script>
import { UserPassword } from "@/api/user";
export default {
name: 'SystemUserPasswordForm',
data() {
return {
loading: false,
dialogShow: false,
title: '',
form: { roles: [], status: 0 },
rules: {
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
],
},
};
},
methods: {
submit() {
this.$refs.form.validate(valid => {
if (valid) {
this.loading = true
UserPassword(this.form).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.$emit('fetch-data')
this.close()
} else {
this.$message.error(ret.msg);
}
}).catch(e => {
this.loading = false
this.$message.error(e.message);
})
} else {
return false;
}
})
},
show(row) {
this.title = '修改密码'
this.dialogShow = true
this.form = Object.assign({}, row)
},
close() {
this.dialogShow = false
this.$refs['form'].resetFields()
this.form = this.$options.data().form
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,119 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-main style="padding: 0; position: relative;">
<el-tabs v-model="active">
<el-tab-pane label="个人信息" name="first">
<div style="height: 10px;"></div>
<el-form size="small" label-width="88px">
<el-form-item label="用户名">
{{ user.username }}
</el-form-item>
<el-form-item label="名称">
{{ user.name ? user.name : '-' }}
</el-form-item>
<el-form-item label="登录IP">
{{ user.login_ip ? user.login_ip: '-' }}
</el-form-item>
<el-form-item label="登录时间">
{{ user.login_time ? user.login_time: '-' }}
</el-form-item>
<el-form-item label="注册时间">
{{ user.create_time ? user.create_time: '-' }}
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="修改密码" name="second">
<div style="height: 10px;"></div>
<el-form :model="passwordForm" :rules="passwordRules" ref="passwordForm" size="small" label-width="88px">
<el-form-item label="原密码" prop="oldPassword">
<el-input v-model="passwordForm.oldPassword" type="password" style="width: 280px;" />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="passwordForm.newPassword" type="password" style="width: 280px;" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="passwordForm.confirmPassword" type="password" style="width: 280px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="passwordSubmit"> </el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-main>
</el-container>
</template>
<script>
import { ServeGetUser, ServeSetUserPassword } from "@/api/user";
export default {
name: 'IndexPage',
components: {},
computed: {},
data() {
return {
loading: false,
active: 'first',
user: {},
passwordForm: {},
passwordRules: {
oldPassword: [
{ required: true, message: '请输入原密码', trigger: 'blur' },
{ min: 6, max: 16, message: '不可超过16个字符', trigger: 'blur' }
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 16, message: '密码长度在6-16个字符之间', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
],
},
}
},
created() {
this.loadData()
},
methods: {
loadData() {
ServeGetUser().then(ret => {
if (ret.code == 200) {
if (ret.data.user) {
this.user = ret.data.user
}
}
})
},
passwordSubmit() {
this.$refs.passwordForm.validate(valid => {
if (valid) {
if (this.passwordForm.newPassword != this.passwordForm.confirmPassword) {
return this.$message.error('两次输入的密码不一致');
}
this.loading = true
ServeSetUserPassword(this.passwordForm).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.$refs['passwordForm'].resetFields()
this.passwordForm = this.$options.data().passwordForm
} else {
this.$message.error(ret.msg);
}
}).catch(e => {
this.loading = false
this.$message.error(e.message);
})
} else {
return false;
}
})
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div>
<MainLayout :idx="8">
<el-container slot="container" class="container">
<el-header height="60px" class="header no-select" style="font-weight: 500;">{{ titles[$route.path] }}</el-header>
<el-container class="full-height">
<el-main style="padding-right: 0;">
<router-view />
</el-main>
</el-container>
</el-container>
</MainLayout>
</div>
</template>
<script>
import MainLayout from '@/views/layout/MainLayout'
import { ServeUserRoleGet } from "@/api/user";
export default {
name: 'SystemPage',
components: {
MainLayout,
},
data() {
return {
titles: {
'/system/index': '个人信息',
'/system/user': '员工管理',
'/system/role': '角色管理',
},
}
},
created() {
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
width: 100%;
overflow: hidden;
.header {
line-height: 60px;
border-bottom: 1px solid #f5f5f5;
}
.aside {
display: flex;
flex-direction: column;
padding: 8px;
background-color: #f0f2f5;
a {
text-decoration: none;
color: rgba(0, 0, 0, 0.65);
}
.menu-list {
height: 35px;
line-height: 35px;
padding-left: 25px;
font-weight: 400;
font-size: 13px;
background-color: white;
border-left: 3px solid white;
border-radius: 5px;
margin: 4px 2px;
transition: ease 0.5s;
&:hover {
color: rgb(24, 144, 255);
}
&.selectd {
border-left-color: #2196f3;
color: #2196f3;
}
}
}
}
</style>

View File

@@ -0,0 +1,188 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-header style="display: flex; align-items: center;justify-content: space-between; padding: 0;">
<div style="display: flex; align-items: center;">
<el-button :disabled="$userRoles.indexOf('/system/role/save') < 0" @click="handleCreate" type="primary" size="small" icon="el-icon-plus">添加角色</el-button>
</div>
<div style="padding-right: 30px;">
<el-form :inline="true" :model="search" size="small">
<el-form-item>
<el-input v-model="search.keywords" placeholder="角色名" clearable style="width: 200px;"></el-input>
</el-form-item>
<el-button @click="loadData(1)" type="primary" icon="el-icon-search" size="small">搜索</el-button>
</el-form>
</div>
</el-header>
<el-main style="padding: 0; position: relative;">
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;">
<el-table
:data="tableData"
height="100%"
stripe
style="width: 100%;"
size="small">
<el-table-column
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="name"
label="角色名"
min-width="150"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="user_count"
label="员工数"
min-width="100"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="remark"
label="备注"
min-width="200"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.remark ? scope.row.remark : '-' }}
</template>
</el-table-column>
<el-table-column
prop="create_time"
label="添加时间"
min-width="150">
</el-table-column>
<el-table-column
label="操作"
width="88"
fixed="right">
<template slot-scope="scope">
<el-button :disabled="scope.row.privileges.indexOf('*') >= 0 || $userRoles.indexOf('/system/role/save') < 0" @click="handleUpdate(scope.row)" type="text" size="small">修改</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-main>
<el-footer style="display: flex; align-items: center; padding: 0;">
<el-pagination
@current-change="setPage"
background
layout="prev, pager, next"
:current-page="page"
:page-count="pageCount">
</el-pagination>
</el-footer>
<role-form ref="roleForm" @fetch-data="() => loadData(page)"></role-form>
</el-container>
</template>
<script>
import RoleForm from "@/views/system/components/RoleForm";
import { RoleIndex } from "@/api/role";
export default {
name: 'SystemRolePage',
components: {
RoleForm,
},
data() {
return {
tableData: [],
search: { },
loading: false,
page: 1,
pageCount: 1,
}
},
created() {
this.loadData(1)
},
methods: {
handleUpdate(row) {
this.$refs.roleForm.show(row)
},
handleCreate() {
this.$refs.roleForm.show(null)
},
setPage(page) {
this.loadData(page);
},
loadData(page) {
if (this.loading) {
return;
}
this.loading = true
RoleIndex(Object.assign(this.search, { page })).then(ret => {
this.loading = false
if (ret.code == 200) {
this.page = ret.data.page;
this.pageCount = ret.data.pageCount;
this.tableData = ret.data.list;
}
});
},
}
}
</script>
<style lang="less" scoped>
.container h4 {
color: rgba(0, 0, 0, 0.85);
font-size: 20px;
font-weight: 500;
line-height: 28px;
margin-bottom: 12px;
margin-bottom: 30px;
}
.container .list-item {
height: 70px;
margin: 5px 25px 5px 0px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.left-col {
display: flex;
flex-direction: row;
.avatar {
flex-basis: 50px;
flex-shrink: 0;
}
.conent {
flex: 1 1;
margin-left: 30px;
}
h4 {
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5715;
}
p {
margin-top: 5px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
}
}
.right-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
.action {
color: rgb(24, 144, 255);
font-size: 14px;
font-weight: 300;
cursor: pointer;
user-select: none;
}
}
}
</style>

View File

@@ -0,0 +1,279 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-header style="display: flex; align-items: center;justify-content: space-between; padding: 0;">
<div style="display: flex; align-items: center;">
<el-button :disabled="$userRoles.indexOf('/system/user/save') < 0" @click="handleCreate" type="primary" size="small" icon="el-icon-plus">添加员工</el-button>
</div>
<div style="padding-right: 30px;">
<el-form :inline="true" :model="search" size="small">
<el-form-item>
<el-select v-model="search.role_id" placeholder="角色" clearable style="width: 128px;">
<el-option
v-for="item in roles"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="search.status" placeholder="状态" clearable style="width: 100px;">
<el-option
v-for="item in statuses"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="search.keywords" placeholder="用户名、姓名、手机号" clearable style="width: 200px;"></el-input>
</el-form-item>
<el-button @click="loadData(1)" type="primary" icon="el-icon-search" size="small">搜索</el-button>
</el-form>
</div>
</el-header>
<el-main style="padding: 0; position: relative;">
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;">
<el-table
:data="tableData"
height="100%"
stripe
style="width: 100%;"
size="small">
<el-table-column
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="username"
label="用户名"
min-width="120"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="name"
label="姓名"
min-width="100"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.name ? scope.row.name : '-' }}
</template>
</el-table-column>
<el-table-column
prop="mobile"
label="手机号"
min-width="120"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.mobile ? scope.row.mobile : '-' }}
</template>
</el-table-column>
<el-table-column
prop="role_names"
label="角色"
min-width="150"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.role_names.length > 0 ? scope.row.role_names.join('、') : '-' }}
</template>
</el-table-column>
<el-table-column
prop="remark"
label="备注"
min-width="150"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.remark ? scope.row.remark : '-' }}
</template>
</el-table-column>
<el-table-column
prop="login_ip"
label="登录IP"
min-width="120"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.login_ip ? scope.row.login_ip : '-' }}
</template>
</el-table-column>
<el-table-column
prop="login_count"
label="登录次数"
min-width="100"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.login_count ? scope.row.login_count : '-' }}
</template>
</el-table-column>
<el-table-column
prop="login_time"
label="登录时间"
min-width="150"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.login_time ? scope.row.login_time : '-' }}
</template>
</el-table-column>
<el-table-column
prop="status_name"
label="状态"
width="88">
<template slot-scope="scope">
<el-tag :type="statusColors[scope.row.status]" size="small" effect="dark">{{ scope.row.status_name }}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="create_time"
label="添加时间"
min-width="150">
</el-table-column>
<el-table-column
label="操作"
width="128"
fixed="right">
<template slot-scope="scope">
<el-button :disabled="$userRoles.indexOf('/system/user/password') < 0" @click="handlePassword(scope.row)" type="text" size="small">修改密码</el-button>
<el-button :disabled="$userRoles.indexOf('/system/user/save') < 0" @click="handleUpdate(scope.row)" type="text" size="small">修改</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-main>
<el-footer style="display: flex; align-items: center; padding: 0;">
<el-pagination
@current-change="setPage"
background
layout="prev, pager, next"
:current-page="page"
:page-count="pageCount">
</el-pagination>
</el-footer>
<user-form ref="userForm" @fetch-data="() => loadData(page)"></user-form>
<user-password-form ref="userPasswordForm"></user-password-form>
</el-container>
</template>
<script>
import UserForm from "@/views/system/components/UserForm";
import UserPasswordForm from "@/views/system/components/UserPasswordForm";
import { UserIndex } from "@/api/user";
export default {
name: 'SystemUserPage',
components: {
UserForm,
UserPasswordForm,
},
data() {
return {
tableData: [],
search: { },
loading: false,
page: 1,
pageCount: 1,
roles: [],
statuses: [],
statusColors: {
0: 'success',
99: 'danger',
},
}
},
created() {
this.loadData(1)
},
methods: {
handlePassword(row) {
this.$refs.userPasswordForm.show(row)
},
handleUpdate(row) {
this.$refs.userForm.show(row, this.roles, this.statuses)
},
handleCreate() {
this.$refs.userForm.show(null, this.roles, this.statuses)
},
setPage(page) {
this.loadData(page);
},
loadData(page) {
if (this.loading) {
return;
}
this.loading = true
UserIndex(Object.assign(this.search, { page })).then(ret => {
this.loading = false
if (ret.code == 200) {
this.page = ret.data.page;
this.pageCount = ret.data.pageCount;
this.tableData = ret.data.list;
this.roles = ret.data.roles;
this.statuses = ret.data.statuses;
}
});
},
}
}
</script>
<style lang="less" scoped>
.container h4 {
color: rgba(0, 0, 0, 0.85);
font-size: 20px;
font-weight: 500;
line-height: 28px;
margin-bottom: 12px;
margin-bottom: 30px;
}
.container .list-item {
height: 70px;
margin: 5px 25px 5px 0px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.left-col {
display: flex;
flex-direction: row;
.avatar {
flex-basis: 50px;
flex-shrink: 0;
}
.conent {
flex: 1 1;
margin-left: 30px;
}
h4 {
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5715;
}
p {
margin-top: 5px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
}
}
.right-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
.action {
color: rgb(24, 144, 255);
font-size: 14px;
font-weight: 300;
cursor: pointer;
user-select: none;
}
}
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogShow"
v-loading="loading"
width="760px"
modal-append-to-body
append-to-body
destroy-on-close
:close-on-press-escape="false">
<div style="max-height: 300px; overflow-x: hidden; overflow-y: auto;">
<div v-for="(item, i) in list" :key="item.id" :style="{color: item.type === 'error' ? '#F56C6C' : '#67C23A', paddingBottom: '5px'}">
[{{ item.create_time }}] {{ item.message }}
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
</span>
</el-dialog>
</template>
<script>
import { TaskLog } from "@/api/task";
export default {
name: 'TaskLog',
data() {
return {
loading: false,
dialogShow: false,
title: '任务日志',
list: [],
};
},
methods: {
show(row) {
this.dialogShow = true
this.list = []
TaskLog({id: row.id}).then(ret => {
if (ret && ret.code == 200) {
this.list = ret.data
}
})
},
close() {
this.dialogShow = false
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,329 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-header style="display: flex; align-items: center;justify-content: space-between; padding: 0;">
<div style="display: flex; align-items: center;">
<el-button @click="handleBatchDelete()" type="warning" size="small" icon="el-icon-circle-close">批量删除</el-button>
</div>
<div style="padding-right: 30px;">
<el-form :inline="true" :model="search" size="small">
<el-form-item>
<el-select v-model="search.type" placeholder="任务类型" clearable style="width: 148px;">
<el-option
v-for="item in types"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="search.run_type" placeholder="执行方式" clearable style="width: 120px;">
<el-option
v-for="item in runTypes"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="search.status" placeholder="执行状态" clearable style="width: 120px;">
<el-option
v-for="item in statuses"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="search.keywords" placeholder="设备编号" clearable style="width: 156px;"></el-input>
</el-form-item>
<el-button @click="loadData(1)" type="primary" icon="el-icon-search" size="small">搜索</el-button>
</el-form>
</div>
</el-header>
<el-main style="padding: 0; position: relative;">
<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;">
<el-table
:data="tableData"
height="100%"
stripe
style="width: 100%;"
size="small"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55"
fixed="left">
</el-table-column>
<el-table-column
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="device_number"
label="设备编号"
min-width="98"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.device_number ? scope.row.device_number : '-' }}
</template>
</el-table-column>
<el-table-column
prop="device_name"
label="设备型号"
min-width="120"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.device_name ? scope.row.device_name : '-' }}
</template>
</el-table-column>
<el-table-column
prop="device_online"
label="设备状态"
min-width="80"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="type_name"
label="任务类型"
min-width="140"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="remark"
label="任务备注"
min-width="150"
show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.remark ? scope.row.remark : '-' }}
</template>
</el-table-column>
<el-table-column
prop="status_name"
label="运行状态"
min-width="88"
show-overflow-tooltip>
<template slot-scope="scope">
<el-tag :type="statusColors[scope.row.status]" size="small" effect="dark">{{ scope.row.status_name }}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="run_type_name"
label="执行方式"
min-width="88"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="run_time"
label="执行时间"
width="150">
<template slot-scope="scope">
{{ scope.row.run_time ? scope.row.run_time : '-' }}
</template>
</el-table-column>
<el-table-column
prop="create_time"
label="添加时间"
width="150">
</el-table-column>
<el-table-column
label="操作"
width="88"
fixed="right">
<template slot-scope="scope">
<el-button @click="handleLog(scope.row)" type="text" size="small">日志</el-button>
<el-button @click="handleDelete(scope.row)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-main>
<el-footer style="display: flex; align-items: center; padding: 0;">
<el-pagination
@current-change="setPage"
background
layout="prev, pager, next"
:current-page="page"
:page-count="pageCount">
</el-pagination>
</el-footer>
<task-log ref="taskLog"></task-log>
</el-container>
</template>
<script>
import {TaskIndex, TaskDelete, TaskBatchDelete, TaskSave} from "@/api/task";
import TaskLog from "@/views/task/components/TaskLog";
export default {
name: 'TaskIndexPage',
components: {
TaskLog
},
data() {
return {
tableData: [],
search: {},
loading: false,
page: 1,
pageCount: 1,
platforms: [],
types: [],
runTypes: [],
statuses: [],
statusColors: {
0: 'info',
10: 'warning',
20: 'success',
},
checkeds: [],
}
},
created() {
this.loadData(1)
},
methods: {
handleLog(row) {
this.$refs.taskLog.show(row)
},
handleBatchDelete() {
if (this.checkeds.length <= 0) {
return this.$message.error('请选择要删除的任务')
}
this.$confirm('此操作将删除选中的任务, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true
TaskBatchDelete(this.checkeds).then(ret => {
this.loading = false
if (ret && ret.code == 200) {
this.$message.success('删除成功')
this.loadData(this.page)
}
})
})
},
handleSelectionChange(rows) {
var checkeds = []
for (var i = 0; i < rows.length; i ++) {
checkeds.push(rows[i].id)
}
this.checkeds = checkeds
},
handleCreate() {
this.$refs.productForm.show(null, this.groups)
},
handleUpdate(row) {
this.$refs.productForm.show(row, this.groups)
},
handleDelete(row) {
this.$confirm('此操作将删除该任务, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true
TaskDelete(row.id).then(ret => {
this.loading = false
if (ret.code == 200) {
this.$message.success(ret.msg)
this.loadData(1)
} else {
this.$message.error(ret.msg)
}
}).catch(e => {
this.$message.error(e.message)
})
})
},
setPage(page) {
this.loadData(page);
},
loadData(page) {
if (this.loading) {
return;
}
this.loading = true
TaskIndex(Object.assign(this.search, { page })).then(ret => {
this.loading = false
if (ret.code == 200) {
this.page = ret.data.page;
this.pageCount = ret.data.pageCount;
this.tableData = ret.data.list;
this.types = ret.data.types;
this.statuses = ret.data.statuses;
this.runTypes = ret.data.runTypes;
}
});
},
}
}
</script>
<style lang="less" scoped>
.container h4 {
color: rgba(0, 0, 0, 0.85);
font-size: 20px;
font-weight: 500;
line-height: 28px;
margin-bottom: 12px;
margin-bottom: 30px;
}
.container .list-item {
height: 70px;
margin: 5px 25px 5px 0px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
.left-col {
display: flex;
flex-direction: row;
.avatar {
flex-basis: 50px;
flex-shrink: 0;
}
.conent {
flex: 1 1;
margin-left: 30px;
}
h4 {
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 1.5715;
}
p {
margin-top: 5px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 1.5715;
}
}
.right-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
.action {
color: rgb(24, 144, 255);
font-size: 14px;
font-weight: 300;
cursor: pointer;
user-select: none;
}
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div>
<MainLayout :idx="1">
<el-container slot="container" class="container">
<el-header height="60px" class="header no-select" style="font-weight: 500;">{{ titles[$route.path] }}</el-header>
<el-container class="full-height">
<el-main style="padding-right: 0;">
<router-view />
</el-main>
</el-container>
</el-container>
</MainLayout>
</div>
</template>
<script>
import MainLayout from '@/views/layout/MainLayout'
export default {
name: 'TaskXyPage',
components: {
MainLayout,
},
computed: {
},
data() {
return {
titles: {
'/task/index': '任务队列',
},
}
},
mounted() {
},
created() {
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
width: 100%;
overflow: hidden;
.header {
line-height: 60px;
border-bottom: 1px solid #f5f5f5;
}
.aside {
display: flex;
flex-direction: column;
padding: 8px;
background-color: #f0f2f5;
a {
text-decoration: none;
color: rgba(0, 0, 0, 0.65);
}
.menu-list {
height: 35px;
line-height: 35px;
padding-left: 25px;
font-weight: 400;
font-size: 13px;
background-color: white;
border-left: 3px solid white;
border-radius: 5px;
margin: 4px 2px;
transition: ease 0.5s;
&:hover {
color: rgb(24, 144, 255);
}
&.selectd {
border-left-color: #2196f3;
color: #2196f3;
}
}
}
}
</style>