admin-import

This commit is contained in:
xiaomlove
2021-04-12 20:31:02 +08:00
parent be9b8634c4
commit 3f13ec875f
131 changed files with 125950 additions and 10676 deletions
+170 -117
View File
@@ -1,156 +1,209 @@
<template>
<el-container>
<el-header>
<el-row class="top-menu">
<div class="part-left">
<div class="layout">
<el-container v-if="state.showMenu" class="container">
<el-aside class="aside">
<div class="head">
<div>
<a href="#">主站</a>
<img src="https://s.weituibao.com/1582958061265/mlogo.png" alt="logo">
<span>vue3 admin</span>
</div>
</div>
<div class="part-center">
<el-menu
:default-active="activeIndex2"
class="el-menu-demo"
mode="horizontal"
@select="handleSelect"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b">
<el-menu-item index="1">处理中心</el-menu-item>
<el-submenu index="2">
<template #title>我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
<el-submenu index="2-4">
<template #title>选项4</template>
<el-menu-item index="2-4-1">选项1</el-menu-item>
<el-menu-item index="2-4-2">选项2</el-menu-item>
<el-menu-item index="2-4-3">选项3</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item index="3" disabled>消息中心</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
</el-menu>
</div>
<div class="part-right">
<div>个人信息</div>
</div>
</el-row>
</el-header>
<el-container>
<el-aside width="200px">
<div class="line" />
<el-menu
:uniqueOpened="true"
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b">
:default-openeds="state.defaultOpen"
background-color="#222832"
text-color="#fff"
:router="true"
:default-active='state.currentPath'
>
<el-submenu index="1">
<template #title>
<i class="el-icon-location"></i>
<span>导航一</span>
<span>Dashboard</span>
</template>
<el-menu-item-group>
<template #title>分组一</template>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
<el-menu-item index="/introduce"><i class="el-icon-data-line" />系统介绍</el-menu-item>
<el-menu-item index="/dashboard"><i class="el-icon-odometer" />Dashboard</el-menu-item>
<el-menu-item index="/add"><i class="el-icon-plus" />添加商品</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="1-3">选项3</el-menu-item>
</el-menu-item-group>
<el-submenu index="1-4">
<template #title>选项4</template>
<el-menu-item index="1-4-1">选项1</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item index="2">
<i class="el-icon-menu"></i>
<template #title>导航二</template>
</el-menu-item>
<el-menu-item index="3" disabled>
<i class="el-icon-document"></i>
<template #title>导航三</template>
</el-menu-item>
<el-menu-item index="4">
<i class="el-icon-setting"></i>
<template #title>导航四</template>
</el-menu-item>
<el-submenu index="5">
<el-submenu index="2">
<template #title>
<i class="el-icon-location"></i>
<span>导航一</span>
<span>首页配置</span>
</template>
<el-menu-item-group>
<template #title>分组一</template>
<el-menu-item index="5-1">选项1</el-menu-item>
<el-menu-item index="5-2">选项2</el-menu-item>
<el-menu-item index="/swiper"><i class="el-icon-picture" />轮播图配置</el-menu-item>
<el-menu-item index="/hot"><i class="el-icon-star-on" />热销商品配置</el-menu-item>
<el-menu-item index="/new"><i class="el-icon-sell" />新品上线配置</el-menu-item>
<el-menu-item index="/recommend"><i class="el-icon-thumb" />为你推荐配置</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="5-3">选项3</el-menu-item>
</el-submenu>
<el-submenu index="3">
<template #title>
<span>模块管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="/category"><i class="el-icon-menu" />分类管理</el-menu-item>
<el-menu-item index="/good"><i class="el-icon-s-goods" />商品管理</el-menu-item>
<el-menu-item index="/guest"><i class="el-icon-user-solid" />会员管理</el-menu-item>
<el-menu-item index="/order"><i class="el-icon-s-order" />订单管理</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="4">
<template #title>
<span>系统管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="/account"><i class="el-icon-lock" />修改密码</el-menu-item>
<!-- <el-menu-item><i class="el-icon-upload2" />安全退出</el-menu-item> -->
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-main>Main</el-main>
<el-footer>Footer</el-footer>
<el-container class="content">
<Header />
<div class="main">
<router-view />
</div>
<Footer />
</el-container>
</el-container>
</el-container>
<el-container v-else class="container">
<router-view />
</el-container>
</div>
</template>
<script>
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 {
data() {
return {
activeIndex: '1',
activeIndex2: '1'
};
name: 'App',
components: {
Header,
Footer
},
methods: {
handleSelect(key, keyPath) {
console.log(key, keyPath);
console.log("select", this)
},
handleOpen(key, keyPath) {
console.log(key, keyPath);
console.log("open", this)
},
handleClose(key, keyPath) {
console.log(key, keyPath);
console.log("close", this)
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 {
state
}
}
}
</script>
<style lang="scss">
.el-header {
padding: 0 !important;
}
.top-menu {
background-color: rgb(84, 92, 100);
padding: 0 30px;
.part-left {
<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;
a {
color: white;
}
}
.part-center {
display: flex;
flex-grow: 1;
justify-content: center;
height: 50px;
}
.part-right {
.head > div {
display: flex;
align-items: center;
color: white;
}
}
.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>
+135
View File
@@ -0,0 +1,135 @@
<template>
<el-dialog
:title="type == 'add' ? '添加分类' : '修改分类'"
v-model="visible"
width="400px"
>
<el-form :model="ruleForm" :rules="rules" ref="formRef" label-width="100px" class="good-form">
<el-form-item label="商品名称" prop="name">
<el-input type="text" v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="排序值" prop="rank">
<el-input type="number" v-model="ruleForm.rank"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref, toRefs } from 'vue'
import { useRoute } from 'vue-router'
import axios from '@/utils/axios'
import { hasEmoji } from '@/utils/index'
import { ElMessage } from 'element-plus'
export default {
name: 'DialogAddCategory',
props: {
type: String,
reload: Function
},
setup(props) {
const formRef = ref(null)
const route = useRoute()
const state = reactive({
visible: false,
categoryLevel: 1,
parentId: 0,
ruleForm: {
name: '',
rank: ''
},
rules: {
name: [
{ required: 'true', message: '名称不能为空', trigger: ['change'] }
],
rank: [
{ required: 'true', message: '编号不能为空', trigger: ['change'] }
]
},
id: ''
})
// 获取详情
const getDetail = (id) => {
axios.get(`/categories/${id}`).then(res => {
state.ruleForm = {
name: res.categoryName,
rank: res.categoryRank
}
state.parentId = res.parentId
state.categoryLevel = res.categoryLevel
})
}
// 开启弹窗
const open = (id) => {
state.visible = true
if (id) {
state.id = id
getDetail(id)
} else {
// 新增类目,从路由获取父分类id 和 分类级别
const { level, parent_id } = route.query
state.ruleForm = {
name: '',
rank: ''
}
state.parentId = parent_id
state.categoryLevel = level
}
}
// 关闭弹窗
const close = () => {
state.visible = false
}
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
if (hasEmoji(state.ruleForm.name)) {
ElMessage.error('不要输入表情包,再输入就打死你个龟孙儿~')
return
}
if (props.type == 'add') {
axios.post('/categories', {
categoryLevel: state.categoryLevel,
parentId: state.parentId,
categoryName: state.ruleForm.name,
categoryRank: state.ruleForm.rank
}).then(() => {
ElMessage.success('添加成功')
state.visible = false
if (props.reload) props.reload()
})
} else {
axios.put('/categories', {
categoryId: state.id,
categoryLevel: state.categoryLevel,
parentId: state.categoryLevel,
categoryName: state.ruleForm.name,
categoryRank: state.ruleForm.rank
}).then(() => {
ElMessage.success('修改成功')
state.visible = false
if (props.reload) props.reload()
})
}
}
})
}
return {
...toRefs(state),
open,
close,
formRef,
submitForm
}
}
}
</script>
<style scoped>
</style>
+147
View File
@@ -0,0 +1,147 @@
<template>
<el-dialog
:title="type == 'add' ? '添加商品' : '修改商品'"
v-model="visible"
width="400px"
>
<el-form :model="ruleForm" :rules="rules" ref="formRef" label-width="100px" class="good-form">
<el-form-item label="商品名称" prop="name">
<el-input type="text" v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="跳转链接" prop="link">
<el-input type="text" v-model="ruleForm.link"></el-input>
</el-form-item>
<el-form-item label="商品编号" prop="id">
<el-input type="number" min="0" v-model="ruleForm.id"></el-input>
</el-form-item>
<el-form-item label="排序值" prop="sort">
<el-input type="number" v-model="ruleForm.sort"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref, toRefs } from 'vue'
import axios from '@/utils/axios'
import { hasEmoji } from '@/utils/index'
import { ElMessage } from 'element-plus'
export default {
name: 'DialogAddHotGood',
props: {
type: String,
configType: Number,
reload: Function
},
setup(props) {
const formRef = ref(null)
const state = reactive({
visible: false,
ruleForm: {
name: '',
link: '',
id: '',
sort: ''
},
rules: {
name: [
{ required: 'true', message: '名称不能为空', trigger: ['change'] }
],
id: [
{ required: 'true', message: '编号不能为空', trigger: ['change'] }
],
sort: [
{ required: 'true', message: '排序不能为空', trigger: ['change'] }
]
},
id: ''
})
// 获取详情
const getDetail = (id) => {
axios.get(`/indexConfigs/${id}`).then(res => {
state.ruleForm = {
name: res.configName,
id: res.goodsId,
link: res.redirectUrl,
sort: res.configRank
}
})
}
// 开启弹窗
const open = (id) => {
state.visible = true
if (id) {
state.id = id
getDetail(id)
} else {
state.ruleForm = {
name: '',
id: '',
link: '',
sort: ''
}
}
}
// 关闭弹窗
const close = () => {
state.visible = false
}
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
if (hasEmoji(state.ruleForm.name) || hasEmoji(state.ruleForm.link)) {
ElMessage.error('不要输入表情包,再输入就打死你个龟孙儿~')
return
}
if (state.ruleForm.id < 0 || state.ruleForm.id > 200) {
ElMessage.error('商品编号不能小于 0 或大于 200')
return
}
if (props.type == 'add') {
axios.post('/indexConfigs', {
configType: props.configType || 3,
configName: state.ruleForm.name,
redirectUrl: state.ruleForm.link,
goodsId: state.ruleForm.id,
configRank: state.ruleForm.sort
}).then(() => {
ElMessage.success('添加成功')
state.visible = false
if (props.reload) props.reload()
})
} else {
axios.put('/indexConfigs', {
configId: state.id,
configType: props.configType || 3,
configName: state.ruleForm.name,
redirectUrl: state.ruleForm.link,
goodsId: state.ruleForm.id,
configRank: state.ruleForm.sort
}).then(() => {
ElMessage.success('修改成功')
state.visible = false
if (props.reload) props.reload()
})
}
}
})
}
return {
...toRefs(state),
open,
close,
formRef,
submitForm
}
}
}
</script>
<style scoped>
</style>
+172
View File
@@ -0,0 +1,172 @@
<template>
<el-dialog
:title="type == 'add' ? '添加轮播图' : '修改轮播图'"
v-model="visible"
width="400px"
>
<el-form :model="ruleForm" :rules="rules" ref="formRef" label-width="100px" class="good-form">
<el-form-item label="图片" prop="url">
<el-upload
class="avatar-uploader"
:action="uploadImgServer"
accept="jpg,jpeg,png"
:headers="{
token: token
}"
:show-file-list="false"
:before-upload="handleBeforeUpload"
:on-success="handleUrlSuccess"
>
<img style="width: 200px; height: 100px; border: 1px solid #e9e9e9;" v-if="ruleForm.url" :src="ruleForm.url" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="跳转链接" prop="link">
<el-input type="text" v-model="ruleForm.link"></el-input>
</el-form-item>
<el-form-item label="排序值" prop="sort">
<el-input type="number" v-model="ruleForm.sort"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref, toRefs } from 'vue'
import axios from '@/utils/axios'
import { localGet, uploadImgServer, hasEmoji } from '@/utils'
import { ElMessage } from 'element-plus'
export default {
name: 'DialogAddSwiper',
props: {
type: String,
reload: Function
},
setup(props) {
const formRef = ref(null)
const state = reactive({
uploadImgServer,
token: localGet('token') || '',
visible: false,
ruleForm: {
url: '',
link: '',
sort: ''
},
rules: {
url: [
{ required: 'true', message: '图片不能为空', trigger: ['change'] }
],
sort: [
{ required: 'true', message: '排序不能为空', trigger: ['change'] }
]
},
id: ''
})
// 获取详情
const getDetail = (id) => {
axios.get(`/carousels/${id}`).then(res => {
state.ruleForm = {
url: res.carouselUrl,
link: res.redirectUrl,
sort: res.carouselRank
}
})
}
const handleBeforeUpload = (file) => {
const sufix = file.name.split('.')[1] || ''
if (!['jpg', 'jpeg', 'png'].includes(sufix)) {
ElMessage.error('请上传 jpg、jpeg、png 格式的图片')
return false
}
}
// 上传图片
const handleUrlSuccess = (val) => {
state.ruleForm.url = val.data || ''
}
// 开启弹窗
const open = (id) => {
state.visible = true
if (id) {
state.id = id
getDetail(id)
} else {
state.ruleForm = {
url: '',
link: '',
sort: ''
}
}
}
// 关闭弹窗
const close = () => {
state.visible = false
}
const submitForm = () => {
console.log(formRef.value.validate)
formRef.value.validate((valid) => {
if (valid) {
if (hasEmoji(state.ruleForm.link)) {
ElMessage.error('不要输入表情包,再输入就打死你个龟孙儿~')
return
}
if (props.type == 'add') {
axios.post('/carousels', {
carouselUrl: state.ruleForm.url,
redirectUrl: state.ruleForm.link,
carouselRank: state.ruleForm.sort
}).then(() => {
ElMessage.success('添加成功')
state.visible = false
if (props.reload) props.reload()
})
} else {
axios.put('/carousels', {
carouselId: state.id,
carouselUrl: state.ruleForm.url,
redirectUrl: state.ruleForm.link,
carouselRank: state.ruleForm.sort
}).then(() => {
ElMessage.success('修改成功')
state.visible = false
if (props.reload) props.reload()
})
}
}
})
}
return {
...toRefs(state),
open,
close,
formRef,
handleBeforeUpload,
handleUrlSuccess,
submitForm
}
}
}
</script>
<style scoped>
.avatar-uploader {
width: 100px;
height: 100px;
color: #ddd;
font-size: 30px;
}
.avatar-uploader-icon {
display: block;
width: 100%;
height: 100%;
border: 1px solid #e9e9e9;
padding: 32px 17px;
}
</style>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class="footer">
<div class="left">Copyright © 2019-2021 十三. All rights reserved.</div>
<div class="right">
<a target="_blank" href="https://github.com/newbee-ltd/vue3-admin">vue3-admin Version 3.0.0</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>
+122
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>
-58
View File
@@ -1,58 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
+48 -4
View File
@@ -1,8 +1,52 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import App from './App.vue'
import installElementPlus from './plugins/element'
import router from './router'
import router from './router/index'
const app = createApp(App).use(router)
installElementPlus(app)
import 'element-plus/lib/theme-chalk/index.css'
// 修改后的主题样式必须放在最后面
import '../theme/index.css'
const orderStatus = {
0: '待支付',
1: '已支付',
2: '配货完成',
3: '出库成功',
4: '交易成功',
'-1': '手动关闭',
'-2': '超时关闭',
'-3': '商家关闭'
}
const app = createApp(App)
// 全局过滤器
app.config.globalProperties.$filters = {
orderMap(status) {
return orderStatus[status] || '未知状态'
},
prefix(url) {
if (url && url.startsWith('http')) {
return url
} else {
url = `http://backend-api-02.newbee.ltd${url}`
return url
}
},
resetImgUrl(imgObj, imgSrc, maxErrorNum) {
if (maxErrorNum > 0) {
imgObj.onerror = function() {
resetImgUrl(imgObj, imgSrc, maxErrorNum - 1)
}
setTimeout(function() {
imgObj.src = imgSrc
}, 500)
} else {
imgObj.onerror = null
imgObj.src = imgSrc
}
}
}
app.use(router).use(ElementPlus)
app.mount('#app')
-3
View File
@@ -1,3 +0,0 @@
<template>
<h1>Agent Allow Page</h1>
</template>
-3
View File
@@ -1,3 +0,0 @@
<template>
<h1>Home Page</h1>
</template>
+1 -3
View File
@@ -1,7 +1,5 @@
import ElementPlus from 'element-plus'
import '../element-variables.scss'
import locale from 'element-plus/lib/locale/lang/zh-cn'
export default (app) => {
app.use(ElementPlus, { locale })
app.use(ElementPlus, { locale })
}
+92 -20
View File
@@ -1,23 +1,95 @@
import { createWebHistory, createRouter } from "vue-router";
import Home from "../pages/index";
import AgentAllow from "../pages/agent-allow";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/agent-allow",
name: "Agent-allow",
component: AgentAllow,
},
];
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes,
});
history: createWebHashHistory(), // hash模式:createWebHashHistoryhistory模式:createWebHistory
routes: [
{
path: '/',
redirect: '/introduce'
},
{
path: '/introduce',
name: 'introduce',
component: () => import(/* webpackChunkName: "introduce" */ '../views/Introduce.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '../views/Index.vue')
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue')
},
{
path: '/add',
name: 'add',
component: () => import(/* webpackChunkName: "add" */ '../views/AddGood.vue')
},
{
path: '/swiper',
name: 'swiper',
component: () => import(/* webpackChunkName: "swiper" */ '../views/Swiper.vue')
},
{
path: '/hot',
name: 'hot',
component: () => import(/* webpackChunkName: "hot" */ '../views/IndexConfig.vue')
},
{
path: '/new',
name: 'new',
component: () => import(/* webpackChunkName: "new" */ '../views/IndexConfig.vue')
},
{
path: '/recommend',
name: 'recommend',
component: () => import(/* webpackChunkName: "recommend" */ '../views/IndexConfig.vue')
},
{
path: '/category',
name: 'category',
component: () => import(/* webpackChunkName: "category" */ '../views/Category.vue'),
children: [
{
path: '/category/level2',
name: 'level2',
component: () => import(/* webpackChunkName: "level2" */ '../views/Category.vue'),
},
{
path: '/category/level3',
name: 'level3',
component: () => import(/* webpackChunkName: "level3" */ '../views/Category.vue'),
}
]
},
{
path: '/good',
name: 'good',
component: () => import(/* webpackChunkName: "new" */ '../views/Good.vue')
},
{
path: '/guest',
name: 'guest',
component: () => import(/* webpackChunkName: "guest" */ '../views/Guest.vue')
},
{
path: '/order',
name: 'order',
component: () => import(/* webpackChunkName: "order" */ '../views/Order.vue')
},
{
path: '/order_detail',
name: 'order_detail',
component: () => import(/* webpackChunkName: "order_detail" */ '../views/OrderDetail.vue')
},
{
path: '/account',
name: 'account',
component: () => import(/* webpackChunkName: "account" */ '../views/Account.vue')
}
]
})
export default router;
export default router
+35
View File
@@ -0,0 +1,35 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router/index'
import { localGet } from './index'
import config from '~/config'
// 这边由于后端没有区分测试和正式,姑且都写成一个接口。
axios.defaults.baseURL = config[import.meta.env.MODE].baseUrl
// 携带 cookie,对目前的项目没有什么作用,因为我们是 token 鉴权
axios.defaults.withCredentials = true
// 请求头,headers 信息
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
axios.defaults.headers['token'] = localGet('token') || ''
// 默认 post 请求,使用 application/json 形式
axios.defaults.headers.post['Content-Type'] = 'application/json'
// 请求拦截器,内部根据返回值,重新组装,统一管理。
axios.interceptors.response.use(res => {
if (typeof res.data !== 'object') {
ElMessage.error('服务端异常!')
return Promise.reject(res)
}
if (res.data.resultCode != 200) {
if (res.data.message) ElMessage.error(res.data.message)
if (res.data.resultCode == 419) {
router.push({ path: '/login' })
}
return Promise.reject(res.data)
}
return res.data.data
})
export default axios
+46
View File
@@ -0,0 +1,46 @@
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: '大盘数据',
add: '添加商品',
swiper: '轮播图配置',
hot: '热销商品配置',
new: '新品上线配置',
recommend: '为你推荐配置',
category: '分类管理',
level2: '分类二级管理',
level3: '分类三级管理',
good: '商品管理',
guest: '会员管理',
order: '订单管理',
order_detail: '订单详情',
account: '修改账户'
}
+113
View File
@@ -0,0 +1,113 @@
<template>
<el-card class="account-container">
<el-form :model="nameForm" :rules="rules" ref="nameRef" label-width="80px" label-position="right" class="demo-ruleForm">
<el-form-item required label="登录名:" prop="loginName">
<el-input style="width: 200px" v-model="nameForm.loginName"></el-input>
</el-form-item>
<el-form-item required label="昵称:" prop="nickName">
<el-input style="width: 200px" v-model="nameForm.nickName"></el-input>
</el-form-item>
<el-form-item>
<el-button type="danger" @click="submitName">确认修改</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="account-container">
<el-form :model="passForm" :rules="rules" ref="passRef" label-width="80px" label-position="right" class="demo-ruleForm">
<el-form-item required label="原密码:" prop="oldpass">
<el-input style="width: 200px" v-model="passForm.oldpass"></el-input>
</el-form-item>
<el-form-item required label="新密码:" prop="newpass">
<el-input style="width: 200px" v-model="passForm.newpass"></el-input>
</el-form-item>
<el-form-item>
<el-button type="danger" @click="submitPass">确认修改</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import { onMounted, reactive, ref, toRefs } from 'vue'
import axios from '@/utils/axios'
import { ElMessage } from 'element-plus'
import md5 from 'js-md5'
export default {
name: 'Account',
setup() {
const nameRef = ref(null)
const passRef = ref(null)
const state = reactive({
user: null,
nameForm: {
loginName: '',
nickName: ''
},
passForm: {
oldpass: '',
newpass: ''
},
rules: {
loginName: [
{ required: 'true', message: '登录名不能为空', trigger: ['change'] }
],
nickName: [
{ required: 'true', message: '昵称不能为空', trigger: ['change'] }
],
oldpass: [
{ required: 'true', message: '原密码不能为空', trigger: ['change'] }
],
newpass: [
{ required: 'true', message: '新密码不能为空', trigger: ['change'] }
]
},
})
onMounted(() => {
axios.get('/adminUser/profile').then(res => {
state.user = res
state.nameForm.loginName = res.loginUserName
state.nameForm.nickName = res.nickName
})
})
const submitName = () => {
nameRef.value.validate((vaild) => {
if (vaild) {
axios.put('/adminUser/name', {
loginUserName: state.nameForm.loginName,
nickName: state.nameForm.nickName
}).then(() => {
ElMessage.success('修改成功')
window.location.reload()
})
}
})
}
const submitPass = () => {
passRef.value.validate((vaild) => {
if (vaild) {
axios.put('/adminUser/password', {
originalPassword: md5(state.passForm.oldpass),
newPassword: md5(state.passForm.newpass)
}).then(() => {
ElMessage.success('修改成功')
window.location.reload()
})
}
})
}
return {
...toRefs(state),
nameRef,
passRef,
submitName,
submitPass
}
}
}
</script>
<style>
.account-container {
margin-bottom: 20px;
}
</style>
+267
View File
@@ -0,0 +1,267 @@
<template>
<div class="add">
<el-card class="add-container">
<el-form :model="goodForm" :rules="rules" ref="goodRef" label-width="100px" class="goodForm">
<el-form-item required label="商品分类">
<el-cascader :placeholder="defaultCate" style="width: 300px" :props="category" @change="handleChangeCate"></el-cascader>
</el-form-item>
<el-form-item label="商品名称" prop="goodsName">
<el-input style="width: 300px" v-model="goodForm.goodsName" placeholder="请输入商品名称"></el-input>
</el-form-item>
<el-form-item label="商品简介" prop="goodsIntro">
<el-input style="width: 300px" type="textarea" v-model="goodForm.goodsIntro" placeholder="请输入商品简介(100字)"></el-input>
</el-form-item>
<el-form-item label="商品价格" prop="originalPrice">
<el-input type="number" min="0" style="width: 300px" v-model="goodForm.originalPrice" placeholder="请输入商品价格"></el-input>
</el-form-item>
<el-form-item label="商品售卖价" prop="sellingPrice">
<el-input type="number" min="0" style="width: 300px" v-model="goodForm.sellingPrice" placeholder="请输入商品售价"></el-input>
</el-form-item>
<el-form-item label="商品库存" prop="stockNum">
<el-input type="number" min="0" style="width: 300px" v-model="goodForm.stockNum" placeholder="请输入商品库存"></el-input>
</el-form-item>
<el-form-item label="商品标签" prop="tag">
<el-input style="width: 300px" v-model="goodForm.tag" placeholder="请输入商品小标签"></el-input>
</el-form-item>
<el-form-item label="上架状态" prop="goodsSellStatus">
<el-radio-group v-model="goodForm.goodsSellStatus">
<el-radio label="0">上架</el-radio>
<el-radio label="1">下架</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item required label="商品主图" prop="goodsCoverImg">
<el-upload
class="avatar-uploader"
:action="uploadImgServer"
accept="jpg,jpeg,png"
:headers="{
token: token
}"
:show-file-list="false"
:before-upload="handleBeforeUpload"
:on-success="handleUrlSuccess"
>
<img style="width: 100px; height: 100px; border: 1px solid #e9e9e9;" v-if="goodForm.goodsCoverImg" :src="goodForm.goodsCoverImg" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="详情内容">
<div ref='editor'></div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitAdd()">{{ id ? '立即修改' : '立即创建' }}</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import { reactive, ref, toRefs, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'
import WangEditor from 'wangeditor'
import axios from '@/utils/axios'
import { ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { localGet, uploadImgServer, uploadImgsServer, hasEmoji } from '@/utils'
export default {
name: 'AddGood',
setup() {
const { proxy } = getCurrentInstance()
console.log('proxy', proxy)
const editor = ref(null)
const goodRef = ref(null)
const route = useRoute()
const router = useRouter()
const { id } = route.query
const state = reactive({
uploadImgServer,
token: localGet('token') || '',
id: id,
defaultCate: '',
goodForm: {
goodsName: '',
goodsIntro: '',
originalPrice: '',
sellingPrice: '',
stockNum: '',
goodsSellStatus: '0',
goodsCoverImg: '',
tag: ''
},
rules: {
goodsName: [
{ required: 'true', message: '请填写商品名称', trigger: ['change'] }
],
originalPrice: [
{ required: 'true', message: '请填写商品价格', trigger: ['change'] }
],
sellingPrice: [
{ required: 'true', message: '请填写商品售价', trigger: ['change'] }
],
stockNum: [
{ required: 'true', message: '请填写商品库存', trigger: ['change'] }
],
},
categoryId: '',
category: {
lazy: true,
lazyLoad(node, resolve) {
const { level = 0, value } = node
axios.get('/categories', {
params: {
pageNumber: 1,
pageSize: 1000,
categoryLevel: level + 1,
parentId: value || 0
}
}).then(res => {
const list = res.list
const nodes = list.map(item => ({
value: item.categoryId,
label: item.categoryName,
leaf: level > 1
}))
resolve(nodes)
})
}
}
})
let instance
onMounted(() => {
instance = new WangEditor(editor.value)
instance.config.showLinkImg = false
instance.config.showLinkImgAlt = false
instance.config.showLinkImgHref = false
instance.config.uploadImgMaxSize = 2 * 1024 * 1024 // 2M
instance.config.uploadFileName = 'file'
instance.config.uploadImgHeaders = {
token: state.token
}
// 图片返回格式不同,需要自定义返回格式
instance.config.uploadImgHooks = {
// 图片上传并返回了结果,想要自己把图片插入到编辑器中
// 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
customInsert: function(insertImgFn, result) {
console.log('result', result)
// result 即服务端返回的接口
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
if (result.data && result.data.length) {
result.data.forEach(item => insertImgFn(item))
}
}
}
instance.config.uploadImgServer = uploadImgsServer
Object.assign(instance.config, {
onchange() {
console.log('change')
},
})
instance.create()
if (id) {
axios.get(`/goods/${id}`).then(res => {
const { goods, firstCategory, secondCategory, thirdCategory } = res
state.goodForm = {
goodsName: goods.goodsName,
goodsIntro: goods.goodsIntro,
originalPrice: goods.originalPrice,
sellingPrice: goods.sellingPrice,
stockNum: goods.stockNum,
goodsSellStatus: String(goods.goodsSellStatus),
goodsCoverImg: proxy.$filters.prefix(goods.goodsCoverImg),
tag: goods.tag
}
state.categoryId = goods.goodsCategoryId
state.defaultCate = `${firstCategory.categoryName}/${secondCategory.categoryName}/${thirdCategory.categoryName}`
if (instance) {
// 初始化商品详情 html
instance.txt.html(goods.goodsDetailContent)
}
})
}
})
onBeforeUnmount(() => {
instance.destroy()
instance = null
})
const submitAdd = () => {
goodRef.value.validate((vaild) => {
if (vaild) {
// 默认新增用 post 方法
let httpOption = axios.post
let params = {
goodsCategoryId: state.categoryId,
goodsCoverImg: state.goodForm.goodsCoverImg,
goodsDetailContent: instance.txt.html(),
goodsIntro: state.goodForm.goodsIntro,
goodsName: state.goodForm.goodsName,
goodsSellStatus: state.goodForm.goodsSellStatus,
originalPrice: state.goodForm.originalPrice,
sellingPrice: state.goodForm.sellingPrice,
stockNum: state.goodForm.stockNum,
tag: state.goodForm.tag
}
if (hasEmoji(params.goodsIntro) || hasEmoji(params.goodsName) || hasEmoji(params.tag) || hasEmoji(params.goodsDetailContent)) {
ElMessage.error('不要输入表情包,再输入就打死你个龟孙儿~')
return
}
console.log('params', params)
if (id) {
params.goodsId = id
// 修改商品使用 put 方法
httpOption = axios.put
}
httpOption('/goods', params).then(() => {
ElMessage.success(id ? '修改成功' : '添加成功')
router.push({ path: '/good' })
})
}
})
}
const handleBeforeUpload = (file) => {
const sufix = file.name.split('.')[1] || ''
if (!['jpg', 'jpeg', 'png'].includes(sufix)) {
ElMessage.error('请上传 jpg、jpeg、png 格式的图片')
return false
}
}
const handleUrlSuccess = (val) => {
state.goodForm.goodsCoverImg = val.data || ''
}
const handleChangeCate = (val) => {
state.categoryId = val[2] || 0
}
return {
...toRefs(state),
goodRef,
submitAdd,
handleBeforeUpload,
handleUrlSuccess,
editor,
handleChangeCate
}
}
}
</script>
<style scoped>
.add {
display: flex;
}
.add-container {
flex: 1;
height: 100%;
}
.avatar-uploader {
width: 100px;
height: 100px;
color: #ddd;
font-size: 30px;
}
.avatar-uploader-icon {
display: block;
width: 100%;
height: 100%;
border: 1px solid #e9e9e9;
padding: 32px 17px;
}
</style>
+213
View File
@@ -0,0 +1,213 @@
<template>
<el-card class="category-container">
<template #header>
<div class="header">
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">增加</el-button>
<el-popconfirm
title="确定删除吗?"
@confirm="handleDelete"
>
<template #reference>
<el-button type="danger" size="small" icon="el-icon-delete">批量删除</el-button>
</template>
</el-popconfirm>
</div>
</template>
<el-table
v-loading="loading"
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55"
>
</el-table-column>
<el-table-column
prop="categoryName"
label="分类名称"
>
</el-table-column>
<el-table-column
prop="categoryRank"
label="排序值"
width="120"
>
</el-table-column>
<el-table-column
prop="createTime"
label="添加时间"
width="200"
>
</el-table-column>
<el-table-column
label="操作"
width="220"
>
<template #default="scope">
<a style="cursor: pointer; margin-right: 10px" @click="handleEdit(scope.row.categoryId)">修改</a>
<a style="cursor: pointer; margin-right: 10px" @click="handleNext(scope.row)">下级分类</a>
<el-popconfirm
title="确定删除吗?"
@confirm="handleDeleteOne(scope.row.categoryId)"
>
<template #reference>
<a style="cursor: pointer">删除</a>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!--总数超过一页再展示分页器-->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@current-change="changePage"
/>
</el-card>
<DialogAddCategory ref='addGood' :reload="getCategory" :type="type" :level="level" :parent_id="parent_id" />
</template>
<script>
import { onMounted, reactive, ref, toRefs } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import DialogAddCategory from '@/components/DialogAddCategory.vue'
import axios from '@/utils/axios'
export default {
name: 'Category',
components: {
DialogAddCategory
},
setup() {
const multipleTable = ref(null)
const addGood = ref(null)
const router = useRouter()
const route = useRoute()
const state = reactive({
loading: false,
tableData: [], // 数据列表
multipleSelection: [], // 选中项
total: 0, // 总条数
currentPage: 1, // 当前页
pageSize: 10, // 分页大小
type: 'add', // 操作类型
level: 1,
parent_id: 0
})
onMounted(() => {
getCategory()
})
router.afterEach((to) => {
if (['category', 'level2', 'level3'].includes(to.name)) {
getCategory()
}
})
// 获取分类列表
const getCategory = () => {
const { level = 1, parent_id = 0 } = route.query
state.loading = true
axios.get('/categories', {
params: {
pageNumber: state.currentPage,
pageSize: state.pageSize,
categoryLevel: level,
parentId: parent_id
}
}).then(res => {
state.tableData = res.list
state.total = res.totalCount
state.currentPage = res.currPage
state.loading = false
state.level = level
state.parentId = parent_id
})
}
// 添加分类
const handleAdd = () => {
state.type = 'add'
addGood.value.open()
}
// 修改分类
const handleEdit = (id) => {
state.type = 'edit'
addGood.value.open(id)
}
// 选择项
const handleSelectionChange = (val) => {
state.multipleSelection = val
}
// 批量删除
const handleDelete = () => {
if (!state.multipleSelection.length) {
ElMessage.error('请选择项')
return
}
axios.delete('/categories', {
data: {
ids: state.multipleSelection.map(i => i.categoryId)
}
}).then(() => {
ElMessage.success('删除成功')
getCategory()
})
}
// 单个删除
const handleDeleteOne = (id) => {
axios.delete('/categories', {
data: {
ids: [id]
}
}).then(() => {
ElMessage.success('删除成功')
getCategory()
})
}
const changePage = (val) => {
state.currentPage = val
getCategory()
}
const handleNext = (item) => {
const levelNumber = item.categoryLevel + 1
if (levelNumber == 4) {
ElMessage.error('没有下一级')
return
}
router.push({
name: `level${levelNumber}`,
query: {
level: levelNumber,
parent_id: item.categoryId
}
})
}
return {
...toRefs(state),
multipleTable,
handleSelectionChange,
addGood,
handleAdd,
handleEdit,
handleDelete,
handleDeleteOne,
getCategory,
changePage,
handleNext
}
}
}
</script>
<style scoped>
.category-container {
min-height: 100%;
}
.el-card.is-always-shadow {
min-height: 100%!important;
}
</style>
+163
View File
@@ -0,0 +1,163 @@
<template>
<el-card class="swiper-container">
<template #header>
<div class="header">
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增商品</el-button>
</div>
</template>
<el-table
v-loading="loading"
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
prop="goodsId"
label="商品编号"
>
</el-table-column>
<el-table-column
prop="goodsName"
label="商品名"
>
</el-table-column>
<el-table-column
prop="goodsIntro"
label="商品简介"
>
</el-table-column>
<el-table-column
label="商品图片"
width="150px"
>
<template #default="scope">
<img style="width: 100px; height: 100px;" :key="scope.row.goodsId" :src="$filters.prefix(scope.row.goodsCoverImg)" alt="商品主图">
</template>
</el-table-column>
<el-table-column
prop="stockNum"
label="商品库存"
>
</el-table-column>
<el-table-column
prop="sellingPrice"
label="商品售价"
>
</el-table-column>
<el-table-column
label="上架状态"
>
<template #default="scope">
<span style="color: green;" v-if="scope.row.goodsSellStatus == 0">销售中</span>
<span style="color: red;" v-else>已下架</span>
</template>
</el-table-column>
<el-table-column
label="操作"
width="100"
>
<template #default="scope">
<a style="cursor: pointer; margin-right: 10px" @click="handleEdit(scope.row.goodsId)">修改</a>
<a style="cursor: pointer; margin-right: 10px" v-if="scope.row.goodsSellStatus == 0" @click="handleStatus(scope.row.goodsId, 1)">下架</a>
<a style="cursor: pointer; margin-right: 10px" v-else @click="handleStatus(scope.row.goodsId, 0)">上架</a>
</template>
</el-table-column>
</el-table>
<!--总数超过一页再展示分页器-->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@current-change="changePage"
/>
</el-card>
</template>
<script>
import { onMounted, reactive, ref, toRefs } from 'vue'
import axios from '@/utils/axios'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
export default {
name: 'Good',
setup() {
const multipleTable = ref(null)
const router = useRouter()
const state = reactive({
loading: false,
tableData: [], // 数据列表
multipleSelection: [], // 选中项
total: 0, // 总条数
currentPage: 1, // 当前页
pageSize: 10 // 分页大小
})
onMounted(() => {
getGoodList()
})
// 获取轮播图列表
const getGoodList = () => {
state.loading = true
axios.get('/goods/list', {
params: {
pageNumber: state.currentPage,
pageSize: state.pageSize
}
}).then(res => {
state.tableData = res.list
state.total = res.totalCount
state.currentPage = res.currPage
state.loading = false
})
}
const handleAdd = () => {
router.push({ path: '/add' })
}
const handleEdit = (id) => {
router.push({ path: '/add', query: { id } })
}
// 选择项
const handleSelectionChange = (val) => {
state.multipleSelection = val
}
const changePage = (val) => {
state.currentPage = val
getGoodList()
}
const handleStatus = (id, status) => {
axios.put(`/goods/status/${status}`, {
ids: id ? [id] : []
}).then(() => {
ElMessage.success('修改成功')
getGoodList()
})
}
return {
...toRefs(state),
multipleTable,
handleSelectionChange,
handleAdd,
handleEdit,
getGoodList,
changePage,
handleStatus
}
}
}
</script>
<style scoped>
.swiper-container {
min-height: 100%;
}
.el-card.is-always-shadow {
min-height: 100%!important;
}
</style>
+168
View File
@@ -0,0 +1,168 @@
<template>
<el-card class="guest-container">
<template #header>
<div class="header">
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleSolve">解除禁用</el-button>
<el-button type="danger" size="small" icon="el-icon-delete" @click="handleForbid">禁用账户</el-button>
</div>
</template>
<el-table
v-loading="loading"
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
prop="nickName"
label="昵称"
>
</el-table-column>
<el-table-column
prop="loginName"
label="登录名"
>
</el-table-column>
<el-table-column
label="身份状态"
>
<template #default="scope">
<span :style="scope.row.lockedFlag == 0 ? 'color: green;' : 'color: red;'">
{{ scope.row.lockedFlag == 0 ? '正常' : '禁用' }}
</span>
</template>
</el-table-column>
<el-table-column
label="是否注销"
>
<template #default="scope">
<span :style="scope.row.lockedFlag == 0 ? 'color: green;' : 'color: red;'">
{{ scope.row.isDeleted == 0 ? '正常' : '注销' }}
</span>
</template>
</el-table-column>
<el-table-column
prop="createTime"
label="注册时间"
>
</el-table-column>
<!-- <el-table-column
label="操作"
width="100"
>
<template #default="scope">
<a style="cursor: pointer; margin-right: 10px" @confirm="handleSolve(scope.row)">解除禁用</a>
<el-popconfirm
title="确定禁用吗?"
@confirm="handleForbid(scope.row)"
>
<template #reference>
<a style="cursor: pointer">禁用账户</a>
</template>
</el-popconfirm>
</template>
</el-table-column> -->
</el-table>
<!--总数超过一页再展示分页器-->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@current-change="changePage"
/>
</el-card>
</template>
<script>
import { onMounted, reactive, ref, toRefs } from 'vue'
import axios from '@/utils/axios'
import { ElMessage } from 'element-plus'
export default {
name: 'Guest',
setup() {
const multipleTable = ref(null)
const state = reactive({
loading: false,
tableData: [], // 数据列表
multipleSelection: [], // 选中项
total: 0, // 总条数
currentPage: 1, // 当前页
pageSize: 10 // 分页大小
})
onMounted(() => {
getGuestList()
})
// 获取轮播图列表
const getGuestList = () => {
state.loading = true
axios.get('/users', {
params: {
pageNumber: state.currentPage,
pageSize: state.pageSize
}
}).then(res => {
state.tableData = res.list
state.total = res.totalCount
state.currentPage = res.currPage
state.loading = false
})
}
// 选择项
const handleSelectionChange = (val) => {
state.multipleSelection = val
}
const changePage = (val) => {
state.currentPage = val
getGuestList()
}
const handleSolve = () => {
if (!state.multipleSelection.length) {
ElMessage.error('请选择项')
return
}
axios.put(`/users/0`, {
ids: state.multipleSelection.map(item => item.userId)
}).then(() => {
ElMessage.success('解除成功')
getGuestList()
})
}
const handleForbid = () => {
if (!state.multipleSelection.length) {
ElMessage.error('请选择项')
return
}
axios.put(`/users/1`, {
ids: state.multipleSelection.map(item => item.userId)
}).then(() => {
ElMessage.success('禁用成功')
getGuestList()
})
}
return {
...toRefs(state),
multipleTable,
handleSelectionChange,
getGuestList,
changePage,
handleSolve,
handleForbid
}
}
}
</script>
<style scoped>
.guest-container {
min-height: 100%;
}
.el-card.is-always-shadow {
min-height: 100%!important;
}
</style>
+169
View File
@@ -0,0 +1,169 @@
<template>
<el-card class="introduce">
<div class="order">
<el-card class="order-item">
<template #header>
<div class="card-header">
<span>今日订单数</span>
</div>
</template>
<div class="item">1888</div>
</el-card>
<el-card class="order-item">
<template #header>
<div class="card-header">
<span>今日日活</span>
</div>
</template>
<div class="item">36271</div>
</el-card>
<el-card class="order-item">
<template #header>
<div class="card-header">
<span>转化率</span>
</div>
</template>
<div class="item">20%</div>
</el-card>
</div>
<div id="zoom"></div>
</el-card>
</template>
<script>
import { onMounted, onUnmounted } from 'vue'
let myChart = null
export default {
name: 'Index',
setup() {
onMounted(() => {
if (window.echarts) {
// 基于准备好的dom,初始化echarts实例
myChart = window.echarts.init(document.getElementById('zoom'))
// 指定图表的配置项和数据
const option = {
title: {
text: '系统折线图'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['新增注册', '付费用户', '活跃用户', '订单数', '当日总收入']
},
toolbox: {
feature: {
saveAsImage: {}
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['2021-03-11', '2021-03-12', '2021-03-13', '2021-03-14', '2021-03-15', '2021-03-16', '2021-03-17']
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '新增注册',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '付费用户',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '活跃用户',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '订单数',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '当日总收入',
type: 'line',
stack: '总量',
label: {
show: true,
position: 'top'
},
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
})
onUnmounted(() => {
myChart.dispose()
})
}
}
</script>
<style scoped>
.introduce .order {
display: flex;
margin-bottom: 50px;
}
.introduce .order .order-item {
flex: 1;
margin-right: 20px;
}
.introduce .order .order-item:last-child{
margin-right: 0;
}
#zoom {
min-height: 300px;
}
</style>
+211
View File
@@ -0,0 +1,211 @@
<template>
<el-card class="index-container">
<template #header>
<div class="header">
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">增加</el-button>
<el-popconfirm
title="确定删除吗?"
@confirm="handleDelete"
>
<template #reference>
<el-button type="danger" size="small" icon="el-icon-delete">批量删除</el-button>
</template>
</el-popconfirm>
</div>
</template>
<el-table
v-loading="loading"
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
prop="configName"
label="商品名称"
>
</el-table-column>
<el-table-column
label="跳转链接"
>
<template #default="scope">
<a target="_blank" :href="scope.row.redirectUrl">{{ scope.row.redirectUrl }}</a>
</template>
</el-table-column>
<el-table-column
prop="configRank"
label="排序值"
width="120"
>
</el-table-column>
<el-table-column
prop="goodsId"
label="商品编号"
width="200"
>
</el-table-column>
<el-table-column
prop="createTime"
label="添加时间"
width="200"
>
</el-table-column>
<el-table-column
label="操作"
width="100"
>
<template #default="scope">
<a style="cursor: pointer; margin-right: 10px" @click="handleEdit(scope.row.configId)">修改</a>
<el-popconfirm
title="确定删除吗?"
@confirm="handleDeleteOne(scope.row.configId)"
>
<template #reference>
<a style="cursor: pointer">删除</a>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!--总数超过一页再展示分页器-->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@current-change="changePage"
/>
</el-card>
<DialogAddGood ref='addGood' :reload="getIndexConfig" :type="type" :configType="configType" />
</template>
<script>
import { onMounted, reactive, ref, toRefs } from 'vue'
import { ElMessage } from 'element-plus'
import DialogAddGood from '@/components/DialogAddGood.vue'
import { useRoute, useRouter } from 'vue-router'
import axios from '@/utils/axios'
// 首页配置类型参数
const configTypeMap = {
hot: 3,
new: 4,
recommend: 5
}
export default {
name: 'Hot',
components: {
DialogAddGood
},
setup() {
const router = useRouter()
const route = useRoute()
const multipleTable = ref(null)
const addGood = ref(null)
const state = reactive({
loading: false,
tableData: [], // 数据列表
multipleSelection: [], // 选中项
total: 0, // 总条数
currentPage: 1, // 当前页
pageSize: 10, // 分页大小
type: 'add', // 操作类型
configType: 3 // 3-(首页)热销商品 4-(首页)新品上线 5-(首页)为你推荐
})
// 监听路由变化
router.beforeEach((to) => {
if (['hot', 'new', 'recommend'].includes(to.name)) {
state.configType = configTypeMap[to.name]
state.currentPage = 1
getIndexConfig()
}
})
// 初始化
onMounted(() => {
state.configType = configTypeMap[route.name]
getIndexConfig()
})
// 首页热销商品列表
const getIndexConfig = () => {
state.loading = true
axios.get('/indexConfigs', {
params: {
pageNumber: state.currentPage,
pageSize: state.pageSize,
configType: state.configType
}
}).then(res => {
state.tableData = res.list
state.total = res.totalCount
state.currentPage = res.currPage
state.loading = false
})
}
// 添加商品
const handleAdd = () => {
state.type = 'add'
addGood.value.open()
}
// 修改商品
const handleEdit = (id) => {
state.type = 'edit'
addGood.value.open(id)
}
// 选择项
const handleSelectionChange = (val) => {
state.multipleSelection = val
}
// 删除
const handleDelete = () => {
if (!state.multipleSelection.length) {
ElMessage.error('请选择项')
return
}
axios.post('/indexConfigs/delete', {
ids: state.multipleSelection.map(i => i.configId)
}).then(() => {
ElMessage.success('删除成功')
getIndexConfig()
})
}
// 单个删除
const handleDeleteOne = (id) => {
axios.post('/indexConfigs/delete', {
ids: [id]
}).then(() => {
ElMessage.success('删除成功')
getIndexConfig()
})
}
const changePage = (val) => {
state.currentPage = val
getIndexConfig()
}
return {
...toRefs(state),
multipleTable,
handleSelectionChange,
addGood,
handleAdd,
handleEdit,
handleDelete,
handleDeleteOne,
getIndexConfig,
changePage
}
}
}
</script>
<style scoped>
.index-container {
min-height: 100%;
}
.el-card.is-always-shadow {
min-height: 100%!important;
}
</style>
+101
View File
@@ -0,0 +1,101 @@
<template>
<el-card class="account-container">
<h1>系统简介</h1>
<div style="line-height: 30px">
vue3-admin 是一套企业级后台管理系统基于 Spring Boot Vue 3.0 相关技术栈开发供各位开发者们体验和学习一定给你最好的学习体验
</div>
<img style="width: 70%;
display: block;
margin: 0 auto;" src="https://s.yezgea02.com/1616331765416/WechatIMG40038.png" alt="">
<h1>开发及部署文档</h1>
<img style="width: 70%;
display: block;
margin: 0 auto;" src="https://s.yezgea02.com/1616938239101/419241616938196_.pic.jpg" />
<ul class="course" @click="goJuejin">
<li>开篇词通关Vue3.0 企业级项目开发升职加薪快人一步</li>
<li>项目须知与课程约定</li>
<li>大势所趋前后端分离开发模式</li>
<li>Vue 3.0 简介及开发环境搭建</li>
<li>Vue 3.0 组合 API 入口 Setup 浅析</li>
<li>Vue 3.0 之响应式系统 API</li>
<li>Vue 3.0 之生命周期钩子函数提供注入</li>
<li>Vue 3.0 性能和业务层面上的提升</li>
<li>Vite 2.0 原理分析及简单插件编写</li>
<li>Vue-Router 4.x 使用方法及路由原理</li>
<li>Vue 3.0 实战项目启动篇</li>
<li>技术选型之 Spring Boot</li>
<li>后端开发环境搭建</li>
<li>快速搭建一个 Spring Boot 项目</li>
<li>Spring Boot 实践之 Web 功能开发</li>
<li>Spring Boot 实践之文件上传处理</li>
<li>Spring Boot 实践之整合 MyBatis 操作数据库</li>
<li>Spring Boot 实践之整合 Lombok</li>
<li>Spring Boot 实践之整合 Swagger 生成接口文档</li>
<li>后端 API 项目启动和运行注意事项</li>
</ul>
<ul class="course" @click="goJuejin">
<li>接口参数处理和统一响应结果处理</li>
<li>API 接口开发实战之用户登录接口开发</li>
<li>API 接口开发实战之用户身份认证详解</li>
<li>API 接口开发实战之轮播图管理模块接口开发</li>
<li>API 接口开发实战之商品分类管理模块接口开发</li>
<li>API 接口开发实战之商品管理模块接口开发</li>
<li>API 接口开发实战之商品配置管理模块接口开发</li>
<li>API 接口开发实战之订单管理模块接口开发</li>
<li>成为一名有独立开发能力的前端工程师</li>
<li>Vite 2.0 + Vue 3.0 + Element-plus 搭建管理后台项目</li>
<li>Vue 3.0 实战之管理后台左右栏目布局Menu 菜单组件</li>
<li>Vue 3.0 实战之登录鉴权Form 表单组件</li>
<li>Vue 3.0 实战之首页大盘数据Echart 5.x</li>
<li>Vue 3.0 实战之首页配置 Table</li>
<li>Vue 3.0 实战之分类管理多级共用 Table</li>
<li>Vue 3.0 实战之商品管理</li>
<li>Vue 3.0 实战之订单管理操作多级判断</li>
<li>Vue 3.0 实战之会员管理账户修改</li>
<li>pm2 实现一键部署云端服务器</li>
<li>常见问题汇总讲解</li>
</ul>
<h1>技术选型</h1>
<ul style="font-weight: bold;">
<li>Vue 3.x</li>
<li>Vite 2.x</li>
<li>Vue-Router 4.x</li>
<li>Element-Plus适配全新 Vue 3.x PC 端组件库</li>
<li>Echarts 5.0</li>
<li>axios</li>
<li>wangEditor</li>
</ul>
<h1>联系作者</h1>
<ul>
<li>我的邮箱2449207463@qq.com</li>
<li>QQ技术交流群932227898707779034</li>
</ul>
</el-card>
</template>
<script>
export default {
name: 'Introduce',
setup() {
const goJuejin = () => {
console.log('goJuejin')
window.open('https://juejin.cn/book/6933939264455442444', 'target')
}
return {
goJuejin
}
}
}
</script>
<style scoped>
.course {
font-weight: bold;
display: inline-block;
}
.course li {
line-height: 36px;
color: #409eff;
cursor: pointer;
}
</style>
+129
View File
@@ -0,0 +1,129 @@
<template>
<div class="login-body">
<div class="login-container">
<div class="head">
<img class="logo" src="https://s.weituibao.com/1582958061265/mlogo.png" />
<div class="name">
<div class="title">新蜂商城</div>
<div class="tips">Vue3.0 后台管理系统</div>
</div>
</div>
<el-form label-position="top" :rules="rules" :model="ruleForm" ref="loginForm" class="login-form">
<el-form-item label="账号" prop="username">
<el-input type="text" v-model.trim="ruleForm.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model.trim="ruleForm.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<div style="color: #333">登录表示您已同意<a>服务条款</a></div>
<el-button style="width: 100%" type="primary" @click="submitForm">立即登录</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 md5 from 'js-md5'
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', message: '账户不能为空', trigger: 'blur' }
],
password: [
{ required: 'true', message: '密码不能为空', trigger: 'blur' }
]
}
})
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>
+276
View File
@@ -0,0 +1,276 @@
<template>
<el-card class="order-container">
<template #header>
<div class="header">
<el-input
style="width: 200px; margin-right: 10px"
placeholder="请输入订单号"
v-model="orderNo"
@change="handleOption"
size="small"
clearable
/>
<el-select @change="handleOption" v-model="orderStatus" size="small" style="width: 200px; margin-right: 10px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- <el-button type="primary" size="small" icon="el-icon-edit">修改订单</el-button> -->
<el-button type="primary" size="small" icon="el-icon-s-home" @click="handleConfig()">配货完成</el-button>
<el-button type="primary" size="small" icon="el-icon-s-home" @click="handleSend()">出库</el-button>
<el-button type="danger" size="small" icon="el-icon-delete" @click="handleClose()">关闭订单</el-button>
</div>
</template>
<el-table
v-loading="loading"
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
prop="orderNo"
label="订单号"
>
</el-table-column>
<el-table-column
prop="totalPrice"
label="订单总价"
>
</el-table-column>
<el-table-column
prop="orderStatus"
label="订单状态"
>
<template #default="scope">
<span>{{ $filters.orderMap(scope.row.orderStatus) }}</span>
</template>
</el-table-column>
<el-table-column
prop="payType"
label="支付方式"
>
<template #default='scope'>
<span v-if="scope.row.payType == 1">微信支付</span>
<span v-else-if="scope.row.payType == 2">支付宝支付</span>
<span v-else>未知</span>
</template>
</el-table-column>
<el-table-column
prop="createTime"
label="创建时间"
>
</el-table-column>
<el-table-column
label="操作"
>
<template #default="scope">
<el-popconfirm
v-if="scope.row.orderStatus == 1"
title="确定配货完成吗?"
@confirm="handleConfig(scope.row.orderId)"
>
<template #reference>
<a style="cursor: pointer; margin-right: 10px">配货完成</a>
</template>
</el-popconfirm>
<el-popconfirm
v-if="scope.row.orderStatus == 2"
title="确定出库吗?"
@confirm="handleSend(scope.row.orderId)"
>
<template #reference>
<a style="cursor: pointer; margin-right: 10px">出库</a>
</template>
</el-popconfirm>
<el-popconfirm
v-if="!(scope.row.orderStatus == 4 || scope.row.orderStatus < 0)"
title="确定关闭订单吗?"
@confirm="handleClose(scope.row.orderId)"
>
<template #reference>
<a style="cursor: pointer; margin-right: 10px">关闭订单</a>
</template>
</el-popconfirm>
<router-link :to="{ path: '/order_detail', query: { id: scope.row.orderId }}">订单详情</router-link>
</template>
</el-table-column>
</el-table>
<!--总数超过一页再展示分页器-->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@current-change="changePage"
/>
</el-card>
</template>
<script>
import { onMounted, reactive, ref, toRefs } from 'vue'
import { ElMessage } from 'element-plus'
import axios from '@/utils/axios'
export default {
name: 'Order',
setup() {
const multipleTable = ref(null)
const addGood = ref(null)
const state = reactive({
loading: false,
tableData: [], // 数据列表
multipleSelection: [], // 选中项
total: 0, // 总条数
currentPage: 1, // 当前页
pageSize: 10, // 分页大小
orderNo: '', // 订单号
orderStatus: '', // 订单状态
options: [{
value: '',
label: '全部'
}, {
value: 0,
label: '待支付'
}, {
value: 1,
label: '已支付'
}, {
value: 2,
label: '配货完成'
}, {
value: 3,
label: '出库成功'
}, {
value: 4,
label: '交易成功'
}, {
value: -1,
label: '手动关闭'
}, {
value: -2,
label: '超时关闭'
}, {
value: -3,
label: '商家关闭'
}]
})
onMounted(() => {
getOrderList()
})
// 获取轮播图列表
const getOrderList = () => {
state.loading = true
axios.get('/orders', {
params: {
pageNumber: state.currentPage,
pageSize: state.pageSize,
orderNo: state.orderNo,
orderStatus: state.orderStatus
}
}).then(res => {
state.tableData = res.list
state.total = res.totalCount
state.currentPage = res.currPage
state.loading = false
})
}
const handleOption = () => {
state.currentPage = 1
getOrderList()
}
// 选择项
const handleSelectionChange = (val) => {
state.multipleSelection = val
}
const changePage = (val) => {
state.currentPage = val
getOrderList()
}
const handleConfig = (id) => {
console.log('id', id)
let params
if (id) {
params = [id]
} else {
if (!state.multipleSelection.length) {
console.log('state.multipleSelection', state.multipleSelection.length)
ElMessage.error('请选择项')
return
}
params = state.multipleSelection.map(i => i.orderId)
}
axios.put('/orders/checkDone', {
ids: params
}).then(() => {
ElMessage.success('配货成功')
getOrderList()
})
}
const handleSend = (id) => {
let params
if (id) {
params = [id]
} else {
if (!state.multipleSelection.length) {
ElMessage.error('请选择项')
return
}
params = state.multipleSelection.map(i => i.orderId)
}
axios.put('/orders/checkOut', {
ids: params
}).then(() => {
ElMessage.success('出库成功')
getOrderList()
})
}
const handleClose = (id) => {
let params
if (id) {
params = [id]
} else {
if (!state.multipleSelection.length) {
ElMessage.error('请选择项')
return
}
params = state.multipleSelection.map(i => i.orderId)
}
axios.put('/orders/close', {
ids: params
}).then(() => {
ElMessage.success('关闭成功')
getOrderList()
})
}
return {
...toRefs(state),
multipleTable,
handleSelectionChange,
addGood,
getOrderList,
changePage,
handleOption,
handleConfig,
handleSend,
handleClose
}
}
}
</script>
<style scoped>
.order-container {
min-height: 100%;
}
.el-card.is-always-shadow {
min-height: 100%!important;
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<el-card class="order-container">
<div class="data">
<el-card class="data-item" shadow="hover">
<template #header>
<div class="card-header">
<span>订单状态</span>
</div>
</template>
<div>
{{ data.orderStatusString }}
</div>
</el-card>
<el-card class="data-item" shadow="hover">
<template #header>
<div class="card-header">
<span>创建时间</span>
</div>
</template>
<div>
{{ data.createTime }}
</div>
</el-card>
<el-card class="data-item" shadow="hover">
<template #header>
<div class="card-header">
<span>订单号</span>
</div>
</template>
<div>
{{ data.orderNo }}
</div>
</el-card>
</div>
<el-table
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
>
<el-table-column
label="商品图片"
>
<template #default="scope">
<img style="width: 100px" :key="scope.row.goodsId" :src="$filters.prefix(scope.row.goodsCoverImg)" alt="商品主图">
</template>
</el-table-column>
<el-table-column
prop="goodsId"
label="商品编号"
>
</el-table-column>
<el-table-column
prop="goodsName"
label="商品名"
></el-table-column>
<el-table-column
prop="goodsCount"
label="商品数量"
>
</el-table-column>
<el-table-column
prop="sellingPrice"
label="价格"
>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
import { onMounted, reactive, toRefs } from 'vue'
import { useRoute } from 'vue-router'
import axios from '@/utils/axios'
export default {
name: 'OrderDetail',
setup() {
const route = useRoute()
const { id } = route.query
const state = reactive({
data: {},
tableData: []
})
onMounted(() => {
axios.get(`/orders/${id}`).then(res => {
console.log(res)
state.data = res
state.tableData = res.newBeeMallOrderItemVOS
})
})
return {
...toRefs(state)
}
}
}
</script>
<style scoped>
.data {
display: flex;
margin-bottom: 50px;
}
.data .data-item {
flex: 1;
margin: 0 10px;
}
.el-table {
border: 1px solid #EBEEF5;
border-bottom: none;
}
.has-gutter th {
border-right: 1px solid #EBEEF5;
}
.has-gutter th:last-child {
border-right: none;
}
.el-table__row td {
border-right: 1px solid #EBEEF5;
}
.el-table__row td:last-child {
border-right: none;
}
</style>
+190
View File
@@ -0,0 +1,190 @@
<template>
<el-card class="swiper-container">
<template #header>
<div class="header">
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">增加</el-button>
<el-popconfirm
title="确定删除吗?"
@confirm="handleDelete"
>
<template #reference>
<el-button type="danger" size="small" icon="el-icon-delete">批量删除</el-button>
</template>
</el-popconfirm>
</div>
</template>
<el-table
v-loading="loading"
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
label="轮播图"
width="200">
<template #default="scope">
<img style="width: 150px;height: 150px" :src="scope.row.carouselUrl" alt="轮播图">
</template>
</el-table-column>
<el-table-column
label="跳转链接"
>
<template #default="scope">
<a target="_blank" :href="scope.row.redirectUrl">{{ scope.row.redirectUrl }}</a>
</template>
</el-table-column>
<el-table-column
prop="carouselRank"
label="排序值"
width="120"
>
</el-table-column>
<el-table-column
prop="createTime"
label="添加时间"
width="200"
>
</el-table-column>
<el-table-column
label="操作"
width="100"
>
<template #default="scope">
<a style="cursor: pointer; margin-right: 10px" @click="handleEdit(scope.row.carouselId)">修改</a>
<el-popconfirm
title="确定删除吗?"
@confirm="handleDeleteOne(scope.row.carouselId)"
>
<template #reference>
<a style="cursor: pointer">删除</a>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!--总数超过一页再展示分页器-->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@current-change="changePage"
/>
</el-card>
<DialogAddSwiper ref='addGood' :reload="getCarousels" :type="type" />
</template>
<script>
import { onMounted, reactive, ref, toRefs } from 'vue'
import { ElMessage } from 'element-plus'
import DialogAddSwiper from '@/components/DialogAddSwiper.vue'
import axios from '@/utils/axios'
export default {
name: 'Swiper',
components: {
DialogAddSwiper
},
setup() {
const multipleTable = ref(null)
const addGood = ref(null)
const state = reactive({
loading: false,
tableData: [], // 数据列表
multipleSelection: [], // 选中项
total: 0, // 总条数
currentPage: 1, // 当前页
pageSize: 10, // 分页大小
type: 'add', // 操作类型
})
onMounted(() => {
getCarousels()
})
// 获取轮播图列表
const getCarousels = () => {
state.loading = true
axios.get('/carousels', {
params: {
pageNumber: state.currentPage,
pageSize: state.pageSize
}
}).then(res => {
state.tableData = res.list
state.total = res.totalCount
state.currentPage = res.currPage
state.loading = false
})
}
// 添加轮播项
const handleAdd = () => {
state.type = 'add'
addGood.value.open()
}
// 修改轮播图
const handleEdit = (id) => {
state.type = 'edit'
addGood.value.open(id)
}
// 选择项
const handleSelectionChange = (val) => {
state.multipleSelection = val
}
// 批量删除
const handleDelete = () => {
if (!state.multipleSelection.length) {
ElMessage.error('请选择项')
return
}
axios.delete('/carousels', {
data: {
ids: state.multipleSelection.map(i => i.carouselId)
}
}).then(() => {
ElMessage.success('删除成功')
getCarousels()
})
}
// 单个删除
const handleDeleteOne = (id) => {
axios.delete('/carousels', {
data: {
ids: [id]
}
}).then(() => {
ElMessage.success('删除成功')
getCarousels()
})
}
const changePage = (val) => {
state.currentPage = val
getCarousels()
}
return {
...toRefs(state),
multipleTable,
handleSelectionChange,
addGood,
handleAdd,
handleEdit,
handleDelete,
handleDeleteOne,
getCarousels,
changePage
}
}
}
</script>
<style scoped>
.swiper-container {
min-height: 100%;
}
.el-card.is-always-shadow {
min-height: 100%!important;
}
</style>