提交后台前端基础框架
This commit is contained in:
33
Backend/src/views/auth/layout.vue
Executable file
33
Backend/src/views/auth/layout.vue
Executable 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
117
Backend/src/views/auth/login.vue
Executable 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>
|
||||
218
Backend/src/views/device/index.vue
Normal file
218
Backend/src/views/device/index.vue
Normal 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>
|
||||
86
Backend/src/views/device/layout.vue
Executable file
86
Backend/src/views/device/layout.vue
Executable 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>
|
||||
92
Backend/src/views/home/index.vue
Executable file
92
Backend/src/views/home/index.vue
Executable 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
417
Backend/src/views/home/layout.vue
Executable 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>
|
||||
433
Backend/src/views/layout/MainLayout.vue
Executable file
433
Backend/src/views/layout/MainLayout.vue
Executable 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>
|
||||
415
Backend/src/views/layout/MainLayout_back.vue
Executable file
415
Backend/src/views/layout/MainLayout_back.vue
Executable 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
103
Backend/src/views/other/404.vue
Executable 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>
|
||||
235
Backend/src/views/product/components/ProductForm.vue
Normal file
235
Backend/src/views/product/components/ProductForm.vue
Normal 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>
|
||||
89
Backend/src/views/product/components/ProductGroupForm.vue
Normal file
89
Backend/src/views/product/components/ProductGroupForm.vue
Normal 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>
|
||||
201
Backend/src/views/product/group.vue
Normal file
201
Backend/src/views/product/group.vue
Normal 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>
|
||||
310
Backend/src/views/product/index.vue
Normal file
310
Backend/src/views/product/index.vue
Normal 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>
|
||||
87
Backend/src/views/product/layout.vue
Executable file
87
Backend/src/views/product/layout.vue
Executable 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>
|
||||
147
Backend/src/views/system/components/RoleForm.vue
Normal file
147
Backend/src/views/system/components/RoleForm.vue
Normal 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>
|
||||
143
Backend/src/views/system/components/UserForm.vue
Normal file
143
Backend/src/views/system/components/UserForm.vue
Normal 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>
|
||||
83
Backend/src/views/system/components/UserPasswordForm.vue
Normal file
83
Backend/src/views/system/components/UserPasswordForm.vue
Normal 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>
|
||||
119
Backend/src/views/system/index.vue
Executable file
119
Backend/src/views/system/index.vue
Executable 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>
|
||||
83
Backend/src/views/system/layout.vue
Executable file
83
Backend/src/views/system/layout.vue
Executable 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>
|
||||
188
Backend/src/views/system/role.vue
Normal file
188
Backend/src/views/system/role.vue
Normal 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>
|
||||
279
Backend/src/views/system/user.vue
Normal file
279
Backend/src/views/system/user.vue
Normal 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>
|
||||
55
Backend/src/views/task/components/TaskLog.vue
Normal file
55
Backend/src/views/task/components/TaskLog.vue
Normal 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>
|
||||
329
Backend/src/views/task/index.vue
Normal file
329
Backend/src/views/task/index.vue
Normal 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>
|
||||
86
Backend/src/views/task/layout.vue
Executable file
86
Backend/src/views/task/layout.vue
Executable 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>
|
||||
Reference in New Issue
Block a user