admin base

This commit is contained in:
xiaomlove
2021-04-22 03:24:59 +08:00
parent b822536283
commit 92f22c9560
26 changed files with 891 additions and 75 deletions

View File

@@ -7,8 +7,10 @@
"": {
"version": "0.0.0",
"dependencies": {
"axios": "^0.21.1",
"element-plus": "^1.0.2-beta.40",
"vue": "^3.0.5"
"vue": "^3.0.5",
"vue-router": "^4.0.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.2.1",
@@ -158,6 +160,14 @@
"resolved": "https://registry.npm.taobao.org/async-validator/download/async-validator-3.5.1.tgz",
"integrity": "sha1-zWK5aIskZfSEIOJ620d2CrG1VZ8="
},
"node_modules/axios": {
"version": "0.21.1",
"resolved": "https://registry.npm.taobao.org/axios/download/axios-0.21.1.tgz",
"integrity": "sha1-IlY0gZYvTWvemnbVFu8OXTwJsrg=",
"dependencies": {
"follow-redirects": "^1.10.0"
}
},
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npm.taobao.org/big.js/download/big.js-5.2.2.tgz",
@@ -254,6 +264,14 @@
"resolved": "https://registry.npm.taobao.org/estree-walker/download/estree-walker-2.0.2.tgz?cache=0&sync_timestamp=1611956983677&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Festree-walker%2Fdownload%2Festree-walker-2.0.2.tgz",
"integrity": "sha1-UvAQF4wqTBF6d1fP6UKtt9LaTKw="
},
"node_modules/follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.13.3.tgz",
"integrity": "sha1-5VmK1QF0wbxOhyMB6CrCzZf5Amc=",
"engines": {
"node": ">=4.0"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.3.2.tgz",
@@ -628,6 +646,14 @@
"@vue/shared": "3.0.11"
}
},
"node_modules/vue-router": {
"version": "4.0.6",
"resolved": "https://registry.nlark.com/vue-router/download/vue-router-4.0.6.tgz",
"integrity": "sha1-kXUNtQfSZkLyJbDsYGRWjl/kSNY=",
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npm.taobao.org/yallist/download/yallist-3.1.1.tgz",
@@ -764,6 +790,14 @@
"resolved": "https://registry.npm.taobao.org/async-validator/download/async-validator-3.5.1.tgz",
"integrity": "sha1-zWK5aIskZfSEIOJ620d2CrG1VZ8="
},
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npm.taobao.org/axios/download/axios-0.21.1.tgz",
"integrity": "sha1-IlY0gZYvTWvemnbVFu8OXTwJsrg=",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npm.taobao.org/big.js/download/big.js-5.2.2.tgz",
@@ -839,6 +873,11 @@
"resolved": "https://registry.npm.taobao.org/estree-walker/download/estree-walker-2.0.2.tgz?cache=0&sync_timestamp=1611956983677&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Festree-walker%2Fdownload%2Festree-walker-2.0.2.tgz",
"integrity": "sha1-UvAQF4wqTBF6d1fP6UKtt9LaTKw="
},
"follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.13.3.tgz",
"integrity": "sha1-5VmK1QF0wbxOhyMB6CrCzZf5Amc="
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.3.2.tgz",
@@ -1138,6 +1177,12 @@
"@vue/shared": "3.0.11"
}
},
"vue-router": {
"version": "4.0.6",
"resolved": "https://registry.nlark.com/vue-router/download/vue-router-4.0.6.tgz",
"integrity": "sha1-kXUNtQfSZkLyJbDsYGRWjl/kSNY=",
"requires": {}
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npm.taobao.org/yallist/download/yallist-3.1.1.tgz",

View File

@@ -7,8 +7,10 @@
"serve": "vite preview"
},
"dependencies": {
"axios": "^0.21.1",
"element-plus": "^1.0.2-beta.40",
"vue": "^3.0.5"
"vue": "^3.0.5",
"vue-router": "^4.0.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.2.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,56 +1,188 @@
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + Vite" />
<el-button type="primary"> Element UI </el-button>
<div>
<el-row>
<el-col :span="12">Left</el-col>
<el-col :span="12">Right</el-col>
</el-row>
<el-row>
<el-form>
<el-form-item label="测试" label-width="120px">
<el-checkbox-group v-model="checkList">
<el-checkbox label="复选框 A"></el-checkbox>
<el-checkbox label="复选框 B"></el-checkbox>
<el-checkbox label="复选框 C"></el-checkbox>
<el-checkbox label="禁用" disabled></el-checkbox>
<el-checkbox label="选中且禁用" disabled></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</el-row>
<div class="layout">
<el-container v-if="state.showMenu" class="container">
<el-aside class="aside">
<div class="head">
<div>
<!-- <img src="http://demo.nexusphp.org/favicon.ico" alt="logo">-->
<span>NexusPHP</span>
</div>
</div>
<div class="line" />
<el-menu
:default-openeds="state.defaultOpen"
background-color="#222832"
text-color="#fff"
:router="true"
:default-active='state.currentPath'
>
<el-menu-item index="/"><i class="el-icon-odometer" />Dashboard</el-menu-item>
<el-submenu index="2">
<template #title>
<span>User</span>
</template>
<el-menu-item-group>
<el-menu-item index="/user"><i class="el-icon-user" />User list</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="3">
<template #title>
<span>System</span>
</template>
<el-menu-item-group>
<el-menu-item index="/agent-allow"><i class="el-icon-menu" />Agent allow</el-menu-item>
</el-menu-item-group>
<el-menu-item-group>
<el-menu-item index="/exam"><i class="el-icon-menu" />Exam</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container class="content">
<Header />
<div class="main">
<router-view />
</div>
<Footer />
</el-container>
</el-container>
<el-container v-else class="container">
<router-view />
</el-container>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import { reactive } from 'vue'
import Header from './components/Header.vue'
import Footer from './components/Footer.vue'
import { useRouter } from 'vue-router'
import { pathMap, localGet } from './utils'
export default {
name: 'App',
components: {
HelloWorld
Header,
Footer
},
setup(props, context) {
console.log(props, context)
const checkList = [];
setup() {
const noMenu = ['/login']
const router = useRouter()
const state = reactive({
defaultOpen: ['1', '2', '3', '4'],
showMenu: true,
currentPath: '/dashboard',
count: {
number: 1
}
})
router.beforeEach((to, from, next) => {
if (to.path == '/login') {
// 如果路径是 /login 则正常执行
next()
} else {
// 如果不是 /login判断是否有 token
if (!localGet('token')) {
// 如果没有,则跳至登录页面
next({ path: '/login' })
} else {
// 否则继续执行
next()
}
}
state.showMenu = !noMenu.includes(to.path)
state.currentPath = to.path
document.title = pathMap[to.name]
})
return {
checkList
state
}
}
}
// This starter template is using Vue 3 experimental <script setup> SFCs
// Check out https://github.com/vuejs/rfcs/blob/script-setup-2/active-rfcs/0000-script-setup.md
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
<style scoped>
.layout {
min-height: 100vh;
background-color: #ffffff;
}
.container {
height: 100vh;
}
.aside {
width: 200px!important;
background-color: #222832;
overflow: hidden;
overflow-y: auto;
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
}
.aside::-webkit-scrollbar {
display: none;
}
.head {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
}
.head > div {
display: flex;
align-items: center;
}
.head img {
width: 50px;
height: 50px;
margin-right: 10px;
}
.head span {
font-size: 20px;
color: #ffffff;
}
.line {
border-top: 1px solid hsla(0,0%,100%,.05);
border-bottom: 1px solid rgba(0,0,0,.2);
}
.content {
display: flex;
flex-direction: column;
max-height: 100vh;
overflow: hidden;
}
.main {
height: calc(100vh - 100px);
overflow: auto;
padding: 10px;
}
</style>
<style>
body {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.el-menu {
border-right: none!important;
}
.el-submenu {
border-top: 1px solid hsla(0, 0%, 100%, .05);
border-bottom: 1px solid rgba(0, 0, 0, .2);
}
.el-submenu:first-child {
border-top: none;
}
.el-submenu [class^="el-icon-"] {
vertical-align: -1px!important;
}
a {
color: #409eff;
text-decoration: none;
}
.el-pagination {
text-align: center;
margin-top: 20px;
}
.el-popper__arrow {
display: none;
}
</style>

View File

@@ -0,0 +1,25 @@
<template>
<div class="footer">
<div class="left">Designed By Xiaomlove</div>
<div class="right">
<a target="_blank" href="https://nexusphp.org/">nexusphp.org</a>
</div>
</div>
</template>
<script>
export default {
name: 'Footer'
}
</script>
<style scoped>
.footer {
height: 50px;
border-top: 1px solid #e9e9e9;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<div class="header">
<div class="left">
<i v-if="hasBack" class="el-icon-back" @click="back"></i>
<span style="font-size: 20px">{{ name }}</span>
</div>
<div class="right">
<el-popover
placement="bottom"
:width="320"
trigger="click"
popper-class="popper-user-box"
>
<template #reference>
<div class="author">
<i class="icon el-icon-s-custom" />
{{ userInfo && userInfo.nickName || '' }}
<i class="el-icon-caret-bottom" />
</div>
</template>
<div class="nickname">
<p>登录名{{ userInfo && userInfo.loginUserName || '' }}</p>
<p>昵称{{ userInfo && userInfo.nickName || '' }}</p>
<el-tag size="small" effect="dark" class="logout" @click="logout">退出</el-tag>
</div>
</el-popover>
</div>
</div>
</template>
<script>
import { onMounted, reactive, toRefs } from 'vue'
import { useRouter } from 'vue-router'
import axios from '../utils/axios'
import { localRemove, pathMap } from '../utils'
export default {
name: 'Header',
setup() {
const router = useRouter()
const state = reactive({
name: 'dashboard',
userInfo: null,
hasBack: false
})
onMounted(() => {
const pathname = window.location.hash.split('/')[1] || ''
if (!['login'].includes(pathname)) {
// getUserInfo()
}
})
const getUserInfo = async () => {
const userInfo = await axios.get('/adminUser/profile')
state.userInfo = userInfo
}
const logout = () => {
axios.delete('/logout').then(() => {
localRemove('token')
router.push({ path: '/login' })
})
}
const back = () => {
router.back()
}
router.afterEach((to) => {
console.log('to', to)
const { id } = to.query
state.name = pathMap[to.name]
if (id && to.name == 'add') {
state.name = '编辑商品'
}
state.hasBack = ['level2', 'level3', 'order_detail'].includes(to.name)
})
return {
...toRefs(state),
logout,
back
}
}
}
</script>
<style scoped>
.header {
height: 50px;
border-bottom: 1px solid #e9e9e9;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.el-icon-back {
border: 1px solid #e9e9e9;
padding: 4px;
border-radius: 50px;
margin-right: 10px;
}
.right > div > .icon{
font-size: 18px;
margin-right: 6px;
}
.author {
margin-left: 10px;
cursor: pointer;
}
</style>
<style>
.popper-user-box {
background: url('https://s.yezgea02.com/lingling-h5/static/account-banner-bg.png') 50% 50% no-repeat!important;
background-size: cover!important;
border-radius: 0!important;
}
.popper-user-box .nickname {
position: relative;
color: #ffffff;
}
.popper-user-box .nickname .logout {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
</style>

3
admin/src/main.js vendored
View File

@@ -1,5 +1,6 @@
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import router from './router/index'
import 'element-plus/lib/theme-chalk/index.css'
createApp(App).use(ElementPlus).mount('#app')
createApp(App).use(ElementPlus).use(router).mount('#app')

50
admin/src/router/index.js vendored Normal file
View File

@@ -0,0 +1,50 @@
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(), // hash模式createWebHashHistoryhistory模式createWebHistory
routes: [
{
path: '/',
name: 'dashboard',
component: () => import('../views/dashboard/index.vue')
},
{
path: '/login',
name: 'login',
component: () => import('../views/login.vue')
},
{
path: '/user',
name: 'user',
component: () => import('../views/user/index.vue')
},
{
path: '/user-form',
name: 'user-form',
component: () => import('../views/user/form.vue')
},
{
path: '/exam',
name: 'exam',
component: () => import('../views/exam/index.vue')
},
{
path: '/exam-form',
name: 'exam-form',
component: () => import('../views/exam/form.vue')
},
{
path: '/agent-allow',
name: 'agent-allow',
component: () => import('../views/agent-allow/index.vue')
},
{
path: '/agent-allow-form',
name: 'agent-allow-form',
component: () => import('../views/agent-allow/form.vue')
},
]
})
export default router

47
admin/src/utils/api.js vendored Normal file
View File

@@ -0,0 +1,47 @@
import axios from "./axios";
const api = {
listAllowAgent: (params = {}) => {
return axios.get('agent-allow', {params: params});
},
storeAllowAgent: (params = {}) => {
return axios.post('agent-allow', params);
},
updateAllowAgent: (id, params = {}) => {
return axios.put('agent-allow/' + id, params);
},
getAllowAgent: (id) => {
return axios.get('agent-allow/' + id);
},
deleteAllowAgent: (id) => {
return axios.delete('agent-allow/' + id);
},
listUser: (params = {}) => {
return axios.get('user', {params: params});
},
storeUser: (params = {}) => {
return axios.post('user', params);
},
listExam: (params = {}) => {
return axios.get('exam', {params: params});
},
storeExam: (params = {}) => {
return axios.post('exam', params);
},
updateExam: (id, params = {}) => {
return axios.put('exam/' + id, params);
},
getExam: (id) => {
return axios.get('exam/' + id);
},
deleteExam: (id) => {
return axios.delete('exam/' + id);
},
listClass: (params = {}) => {
return axios.get('class', {params: params});
},
}
export default api

28
admin/src/utils/axios.js vendored Normal file
View File

@@ -0,0 +1,28 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
axios.defaults.baseURL = 'http://nexus-php8.tinyhd.net'
axios.defaults.withCredentials = true
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
axios.defaults.headers['Content-Type'] = 'application/json'
axios.defaults.headers['Accept'] = 'application/json'
// 请求拦截器,内部根据返回值,重新组装,统一管理。
axios.interceptors.response.use(res => {
console.log(res)
if (typeof res.data !== 'object') {
ElMessage.error('Server Error 1')
return Promise.reject(res)
}
if (res.data.ret && res.data.ret != 0) {
ElMessage.error(res.data.msg)
return Promise.reject(res.data)
}
return res.data
}, error => {
console.log(error.response)
ElMessage.error(error.response.data.msg || 'Server Error 2')
return Promise.reject(error)
})
export default axios

50
admin/src/utils/index.js vendored Normal file
View File

@@ -0,0 +1,50 @@
export function localGet (key) {
const value = window.localStorage.getItem(key)
try {
return JSON.parse(window.localStorage.getItem(key))
} catch (error) {
return value
}
}
export function localSet (key, value) {
window.localStorage.setItem(key, JSON.stringify(value))
}
export function localRemove (key) {
window.localStorage.removeItem(key)
}
// 判断内容是否含有表情字符,现有数据库不支持。
export function hasEmoji (str = '') {
const reg = /[^\u0020-\u007E\u00A0-\u00BE\u2E80-\uA4CF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF\u0080-\u009F\u2000-\u201f\u2026\u2022\u20ac\r\n]/g;
return str.match(reg) && str.match(reg).length
}
// 单张图片上传
export const uploadImgServer = 'http://backend-api-02.newbee.ltd/manage-api/v1/upload/file'
// 多张图片上传
export const uploadImgsServer = 'http://backend-api-02.newbee.ltd/manage-api/v1/upload/files'
export const pathMap = {
login: '登录',
introduce: '系统介绍',
dashboard: 'Dashboard',
add: '添加商品',
swiper: '轮播图配置',
hot: '热销商品配置',
new: '新品上线配置',
recommend: '为你推荐配置',
category: '分类管理',
level2: '分类二级管理',
level3: '分类三级管理',
good: '商品管理',
guest: '会员管理',
order: '订单管理',
order_detail: '订单详情',
account: '修改账户',
'agent-allow': 'Agent allow',
'agent-allow-form': 'Agent allow form',
'user': 'User',
'user-form': 'User form',
}

View File

@@ -0,0 +1,3 @@
<template>
<div>Agent Allow Form</div>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<div>Agent Allow Index</div>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<div>Dashboard</div>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<div>Exam Form</div>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<div>Exam Index</div>
</template>

128
admin/src/views/login.vue Normal file
View File

@@ -0,0 +1,128 @@
<template>
<div class="login-body">
<div class="login-container">
<div class="head">
<img class="logo" src="http://demo.nexusphp.org/favicon.ico" />
<div class="name">
<div class="title">NexusPHP</div>
<div class="tips">Management system - v1.6</div>
</div>
</div>
<el-form label-position="top" :rules="rules" :model="ruleForm" ref="loginForm" class="login-form">
<el-form-item label="Username" prop="username">
<el-input type="text" v-model.trim="ruleForm.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="Password" prop="password">
<el-input type="password" v-model.trim="ruleForm.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item style="margin-top: 50px">
<!-- <div style="color: #333">登录表示您已同意<a>服务条款</a></div>-->
<el-button style="width: 100%" type="primary" @click="submitForm">Submit</el-button>
<!-- <el-checkbox v-model="checked" @change="!checked">下次自动登录</el-checkbox>-->
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import axios from '../utils/axios'
import { reactive, ref, toRefs } from 'vue'
import { localSet } from '../utils'
export default {
name: 'Login',
setup() {
const loginForm = ref(null)
const state = reactive({
ruleForm: {
username: '',
password: ''
},
checked: true,
rules: {
username: [
{ required: 'true', }
],
password: [
{ required: 'true', }
]
}
})
const submitForm = async () => {
loginForm.value.validate((valid) => {
if (valid) {
axios.post('/adminUser/login', {
userName: state.ruleForm.username || '',
passwordMd5: md5(state.ruleForm.password)
}).then(res => {
localSet('token', res)
window.location.href = '/'
})
} else {
console.log('error submit!!')
return false;
}
})
}
const resetForm = () => {
loginForm.value.resetFields();
}
return {
...toRefs(state),
loginForm,
submitForm,
resetForm
}
}
}
</script>
<style scoped>
.login-body {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
background-color: #fff;
/* background-image: linear-gradient(25deg, #077f7c, #3aa693, #5ecfaa, #7ffac2); */
}
.login-container {
width: 420px;
height: 500px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0px 21px 41px 0px rgba(0, 0, 0, 0.2);
}
.head {
display: flex;
justify-content: center;
align-items: center;
padding: 40px 0 20px 0;
}
.head img {
width: 100px;
height: 100px;
margin-right: 20px;
}
.head .title {
font-size: 28px;
color: #1BAEAE;
font-weight: bold;
}
.head .tips {
font-size: 12px;
color: #999;
}
.login-form {
width: 70%;
margin: 0 auto;
}
</style>
<style>
.el-form--label-top .el-form-item__label {
padding: 0;
}
.login-form .el-form-item {
margin-bottom: 12px;
}
</style>

View File

@@ -0,0 +1,3 @@
<template>
<div>User Form</div>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<div>User Index</div>
</template>