修正项目

This commit is contained in:
2024-01-07 01:10:08 +08:00
parent 2f29241806
commit b22014e976
943 changed files with 27699 additions and 28227 deletions

11
xlcs-admin/src/App.vue Normal file
View File

@@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>

View File

@@ -0,0 +1,72 @@
import request from '@/utils/request'
/*
权限管理相关的API请求函数
*/
const api_name = '/admin/acl/permission'
export default {
/*
获取权限(菜单/功能)列表
*/
getPermissionList() {
return request({
url: `${api_name}`,
method: 'get'
})
},
/*
删除一个权限项
*/
removePermission(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
/*
保存一个权限项
*/
addPermission(permission) {
return request({
url: `${api_name}/save`,
method: 'post',
data: permission
})
},
/*
更新一个权限项
*/
updatePermission(permission) {
return request({
url: `${api_name}/update`,
method: 'put',
data: permission
})
},
/*
查看某个角色的权限列表
*/
toAssign(roleId) {
return request({
url: `${api_name}/toAssign/${roleId}`,
method: 'get'
})
},
/*
给某个角色授权
*/
doAssign(roleId, permissionId) {
return request({
url: `${api_name}/doAssign`,
method: 'post',
params: { roleId, permissionId }
})
}
}

View File

@@ -0,0 +1,83 @@
/*
角色管理相关的API请求函数
*/
import request from '@/utils/request'
const api_name = '/admin/acl/role'
export default {
/*
获取角色分页列表(带搜索)
*/
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj // url查询字符串或表单键值对
})
},
/*
保存一个新角色
*/
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
/*
获取某个角色
*/
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
/*
更新一个角色
*/
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
/*
获取一个角色的所有权限列表
*/
getAssign(roleId) {
return request({
url: `${api_name}/toAssign/${roleId}`,
method: 'get'
})
},
/*
删除某个角色
*/
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
/*
批量删除多个角色
*/
removeRoles(ids) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: ids
})
}
}

View File

@@ -0,0 +1,131 @@
import request from '@/utils/request'
const api_name = '/admin/acl/user'
/*
登陆
*/
export function login({ username, password }) {
return request({
url: '/admin/acl/index/login',
method: 'post',
data: { username, password }
})
}
/*
获取用户信息(根据token)
*/
export function getInfo() {
return request({
url: '/admin/acl/index/info',
method: 'get'
})
}
/*
登出
*/
export function logout() {
return request({
url: '/admin/acl/index/logout',
method: 'post'
})
}
/*
获取当前用户的菜单权限列表
*/
export function getMenu() {
return request('/admin/acl/index/menu')
}
/*
获取后台用户分页列表(带搜索)
*/
export function getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
}
/*
根据ID获取某个后台用户
*/
export function getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
}
/*
保存一个新的后台用户
*/
export function add(user) {
return request({
url: `${api_name}/save`,
method: 'post',
data: user
})
}
/*
更新一个后台用户
*/
export function update(user) {
return request({
url: `${api_name}/update`,
method: 'put',
data: user
})
}
/*
获取某个用户的所有角色
*/
export function getRoles(adminId) {
return request({
url: `${api_name}/toAssign/${adminId}`,
method: 'get'
})
}
/*
给某个用户分配角色
roleId的结构: 字符串, 'rId1,rId2,rId3'
*/
export function assignRoles(adminId, roleId) {
return request({
url: `${api_name}/doAssign`,
method: 'post',
params: {
adminId,
roleId
}
})
}
/*
删除某个用户
*/
export function removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
}
/*
批量删除多个用户
ids的结构: ids是包含n个id的数组
*/
export function removeUsers(ids) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: ids
})
}

View File

@@ -0,0 +1,69 @@
import request from '@/utils/request'
const api_name = '/admin/activity/activityInfo'
export default {
getPageList(page, limit) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get'
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
findActivityRuleList(id) {
return request({
url: `${api_name}/findActivityRuleList/${id}`,
method: 'get'
})
},
saveActivityRule(rule) {
return request({
url: `${api_name}/saveActivityRule`,
method: 'post',
data: rule
})
},
findSkuInfoByKeyword(keyword) {
return request({
url: `${api_name}/findSkuInfoByKeyword/${keyword}`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,69 @@
import request from '@/utils/request'
const api_name = '/admin/activity/couponInfo'
export default {
getPageList(page, limit) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get'
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
findCouponRuleList(id) {
return request({
url: `${api_name}/findCouponRuleList/${id}`,
method: 'get'
})
},
saveCouponRule(rule) {
return request({
url: `${api_name}/saveCouponRule`,
method: 'post',
data: rule
})
},
findCouponByKeyword(keyword) {
return request({
url: `${api_name}/findCouponByKeyword/${keyword}`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,55 @@
import request from '@/utils/request'
const api_name = '/admin/activity/seckill'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
updateStatus(id, status) {
return request({
url: `${api_name}/updateStatus/${id}/${status}`,
method: 'post'
})
}
}

View File

@@ -0,0 +1,49 @@
import request from '@/utils/request'
const api_name = '/admin/activity/seckillSku'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
}
}

View File

@@ -0,0 +1,54 @@
import request from '@/utils/request'
const api_name = '/admin/activity/seckillTime'
export default {
getList() {
return request({
url: `${api_name}`,
method: 'get'
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
updateStatus(id, status) {
return request({
url: `${api_name}/updateStatus/${id}/${status}`,
method: 'post'
})
}
}

View File

@@ -0,0 +1,3 @@
export * as user from './acl/user'
export { default as role } from './acl/role'
export { default as permission } from './acl/permission'

View File

@@ -0,0 +1,43 @@
import request from '@/utils/request'
const api_name = '/admin/order/orderInfo'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj // url查询字符串或表单键值对
})
},
show(orderId) {
return request({
url: `${api_name}/get/${orderId}`,
method: 'get'
})
},
deliver(orderDeliverVo) {
return request({
url: `${api_name}/deliver`,
method: 'post',
data: orderDeliverVo
})
},
findReceiveList(wareId, date) {
return request({
url: `${api_name}/findReceiveList/${wareId}/${date}`,
method: 'get'
})
},
findLeaderReceiveList(leaderId, date) {
return request({
url: `${api_name}/findLeaderReceiveList/${leaderId}/${date}`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,48 @@
import request from '@/utils/request'
const api_name = '/admin/product/attr'
export default {
getList(groupId) {
return request({
url: `${api_name}/${groupId}`,
method: 'get'
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
}
}

View File

@@ -0,0 +1,56 @@
import request from '@/utils/request'
const api_name = '/admin/product/attrGroup'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
findAllList() {
return request({
url: `${api_name}/findAllList`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,56 @@
import request from '@/utils/request'
const api_name = '/admin/product/category'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
findAllList() {
return request({
url: `${api_name}/findAllList`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,73 @@
import request from '@/utils/request'
const api_name = '/admin/product/skuInfo'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
// 商品上架
publish(id, status) {
return request({
url: `${api_name}/publish/${id}/${status}`,
method: 'get'
})
},
// 商品审核
check(id, status) {
return request({
url: `${api_name}/check/${id}/${status}`,
method: 'get'
})
},
// 新人专享
isNewPerson(id, status) {
return request({
url: `${api_name}/isNewPerson/${id}/${status}`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,20 @@
import request from '@/utils/request'
const api_name = '/admin/sys/region'
export default {
findRegionByKeyword(keyword) {
return request({
url: `${api_name}/findRegionByKeyword/${keyword}`,
method: 'get'
})
},
findByParentId(parentId) {
return request({
url: `${api_name}/findByParentId/${parentId}`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,49 @@
import request from '@/utils/request'
const api_name = '/admin/sys/regionWare'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateStatus(id, status) {
return request({
url: `${api_name}/updateStatus/${id}/${status}`,
method: 'post'
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
}
}

View File

@@ -0,0 +1,57 @@
import request from '@/utils/request'
const api_name = '/admin/sys/ware'
export default {
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
findAllList() {
return request({
url: `${api_name}/findAllList`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/vue-admin-template/table/list',
method: 'get',
params
})
}

View File

@@ -0,0 +1,24 @@
import request from '@/utils/request'
export function login(data) {
return request({
url: '/admin/acl/index/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/admin/acl/index/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/admin/acl/index/logout',
method: 'post'
})
}

View File

@@ -0,0 +1,12 @@
import request from '@/utils/request'
const api_name = '/admin/user/driver'
export default {
findDriver(wareId) {
return request({
url: `${api_name}/findDriver/${wareId}`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request'
const api_name = '/admin/user/leader'
export default {
getPageCheckList(page, limit) {
return request({
url: `${api_name}/checkList/${page}/${limit}`,
method: 'get'
})
},
getPageList(page, limit) {
return request({
url: `${api_name}/list/${page}/${limit}`,
method: 'get'
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
save(role) {
return request({
url: `${api_name}/save`,
method: 'post',
data: role
})
},
updateById(role) {
return request({
url: `${api_name}/update`,
method: 'put',
data: role
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
removeRows(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
},
check(id, status) {
return request({
url: `${api_name}/check/${id}/${status}`,
method: 'post'
})
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,80 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect==='noRedirect'||index===levelList.length-1" class="no-redirect">{{
item.meta.title
}}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
import pathToRegexp from 'path-to-regexp'
export default {
data() {
return {
levelList: null
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
// only show routes with meta.title
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
isDashboard(route) {
const name = route && route.name
if (!name) {
return false
}
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
},
pathCompile(path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route
var toPath = pathToRegexp.compile(path)
return toPath(params)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(this.pathCompile(path))
}
}
}
</script>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<div style="padding: 0 15px;" @click="toggleClick">
<svg
:class="{'is-active':isActive}"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
/>
</svg>
</div>
</template>
<script>
export default {
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick')
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.hamburger.is-active {
transform: rotate(180deg);
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<el-tooltip :content="title" placement="top-start">
<el-button v-bind="$attrs" v-on="$listeners" />
</el-tooltip>
</template>
<script type="text/ecmascript-6">
export default {
name: 'HintButton',
props: {
title: String
},
mounted() {
// console.log('mounted()', this.$attrs)
// console.log('mounted()', this.$listeners)
}
}
</script>

View File

@@ -0,0 +1,62 @@
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

View File

@@ -0,0 +1,4 @@
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg">
<path
d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,4 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/>
</svg>

After

Width:  |  Height:  |  Size: 506 B

View File

@@ -0,0 +1,7 @@
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128">
<defs>
<style/>
</defs>
<path
d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,4 @@
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg">
<path
d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/>
</svg>

After

Width:  |  Height:  |  Size: 953 B

View File

@@ -0,0 +1,4 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/>
<path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/>
<path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/>
</svg>

After

Width:  |  Height:  |  Size: 296 B

View File

@@ -0,0 +1,4 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/>
</svg>

After

Width:  |  Height:  |  Size: 830 B

View File

@@ -0,0 +1,4 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/>
</svg>

After

Width:  |  Height:  |  Size: 632 B

View File

@@ -0,0 +1,6 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/>
<path
d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/>
</svg>

After

Width:  |  Height:  |  Size: 613 B

View File

@@ -0,0 +1,4 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,5 @@
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg">
<path
d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z"
stroke="#979797"/>
</svg>

After

Width:  |  Height:  |  Size: 453 B

View File

@@ -0,0 +1,22 @@
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'

View File

@@ -0,0 +1,41 @@
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<router-view :key="key" />
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
key() {
return this.$route.path
}
}
}
</script>
<style scoped>
.app-main {
/*50 = navbar */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header + .app-main {
padding-top: 50px;
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>

View File

@@ -0,0 +1,139 @@
<template>
<div class="navbar">
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb class="breadcrumb-container" />
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/">
<el-dropdown-item>
Home
</el-dropdown-item>
</router-link>
<a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
<el-dropdown-item>Docs</el-dropdown-item>
</a>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">Log Out</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
export default {
components: {
Breadcrumb,
Hamburger
},
computed: {
...mapGetters([
'sidebar',
'avatar'
])
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
async logout() {
await this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}
}
}
</script>
<style lang="scss" scoped>
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background .3s;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,26 @@
export default {
computed: {
device() {
return this.$store.state.app.device
}
},
mounted() {
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
this.fixBugIniOS()
},
methods: {
fixBugIniOS() {
const $subMenu = this.$refs.subMenu
if ($subMenu) {
const handleMouseleave = $subMenu.handleMouseleave
$subMenu.handleMouseleave = (e) => {
if (this.device === 'mobile') {
return
}
handleMouseleave(e)
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']}/>)
} else {
vnodes.push(<svg-icon icon-class={icon}/>)
}
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: to
}
}
}
}
</script>

View File

@@ -0,0 +1,82 @@
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 v-else class="sidebar-title">{{ title }} </h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 class="sidebar-title">{{ title }} </h1>
</router-link>
</transition>
</div>
</template>
<script>
export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: true
}
},
data() {
return {
title: 'Vue Admin Template',
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
}
}
}
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 12px;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div v-if="!item.hidden">
<template
v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
export default {
name: 'SidebarItem',
components: { Item, AppLink },
mixins: [FixiOSBug],
props: {
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
},
data() {
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
// TODO: refactor with render function
this.onlyOneChild = null
return {}
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.basePath)) {
return this.basePath
}
return path.resolve(this.basePath, routePath)
}
}
}
</script>

View File

@@ -0,0 +1,56 @@
<template>
<div :class="{'has-logo':showLogo}">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters([
'sidebar'
]),
routes() {
return this.$router.options.routes
},
activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>

View File

@@ -0,0 +1,3 @@
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'

View File

@@ -0,0 +1,95 @@
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar />
</div>
<app-main />
</div>
</div>
</template>
<script>
import { Navbar, Sidebar, AppMain } from './components'
import ResizeMixin from './mixin/ResizeHandler'
export default {
name: 'Layout',
components: {
Navbar,
Sidebar,
AppMain
},
mixins: [ResizeMixin],
computed: {
sidebar() {
return this.$store.state.app.sidebar
},
device() {
return this.$store.state.app.device
},
fixedHeader() {
return this.$store.state.settings.fixedHeader
},
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
@import "~@/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px)
}
.mobile .fixed-header {
width: 100%;
}
</style>

View File

@@ -0,0 +1,45 @@
import store from '@/store'
const { body } = document
const WIDTH = 992 // refer to Bootstrap's responsive design
export default {
watch: {
$route(route) {
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
},
beforeMount() {
window.addEventListener('resize', this.$_resizeHandler)
},
beforeDestroy() {
window.removeEventListener('resize', this.$_resizeHandler)
},
mounted() {
const isMobile = this.$_isMobile()
if (isMobile) {
store.dispatch('app/toggleDevice', 'mobile')
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - 1 < WIDTH
},
$_resizeHandler() {
if (!document.hidden) {
const isMobile = this.$_isMobile()
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
}
}
}
}

51
xlcs-admin/src/main.js Normal file
View File

@@ -0,0 +1,51 @@
import Vue from 'vue'
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/en' // lang i18n
import '@/styles/index.scss' // global css
import App from './App'
import store from './store'
import router from './router'
import * as API from '@/api'
import HintButton from '@/components/HintButton'
import hasBtnPermission from '@/utils/btn-permission'
import '@/icons' // icon
import '@/permission' // permission control
Vue.prototype.$API = API
Vue.component(HintButton.name, HintButton)
Vue.prototype.$hasBP = hasBtnPermission
/**
* If you don't want to use mock-server
* you want to use MockJs for mock api
* you can execute: mockXHR()
*
* Currently MockJs will be used in the production environment,
* please remove it before going online ! ! !
*/
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
}
// set ElementUI lang to EN
Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui按如下方式声明
// Vue.use(ElementUI)
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})

View File

@@ -0,0 +1,64 @@
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
try {
// get user info
await store.dispatch('user/getInfo')
next()
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})

View File

@@ -0,0 +1,384 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/**
* Note: sub-menu only appear when route children.length >= 1
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
*
* hidden: true if set true, item will not show in the sidebar(default is false)
* alwaysShow: true if set true, will always show the root menu
* if not set alwaysShow, when item has more than one children route,
* it will becomes nested mode, otherwise not show the root menu
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
* name:'router-name' the name is used by <keep-alive> (must set!!!)
* meta : {
roles: ['admin','editor'] control the page roles (you can set multiple roles)
title: 'title' the name show in sidebar and breadcrumb (recommend set)
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
}
*/
/**
* constantRoutes
* a base page that does not have permission requirements
* all roles can be accessed
*/
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},
/* 权限管理 */
{
name: 'Acl',
path: '/acl',
component: Layout,
redirect: '/acl/user/list',
meta: {
title: '权限管理',
icon: 'el-icon-lock'
},
children: [
{
name: 'User',
path: 'user/list',
component: () => import('@/views/acl/user/list'),
meta: {
title: '用户管理'
}
},
{
name: 'Role',
path: 'role/list',
component: () => import('@/views/acl/role/list'),
meta: {
title: '角色管理'
}
},
{
name: 'RoleAuth',
path: 'role/auth/:id',
component: () => import('@/views/acl/role/roleAuth'),
meta: {
activeMenu: '/acl/role/list',
title: '角色授权'
},
hidden: true
},
{
name: 'Permission',
path: 'permission/list',
component: () => import('@/views/acl/permission/list'),
meta: {
title: '菜单管理'
}
}
]
},
{
name: 'Sys',
path: '/sys',
component: Layout,
redirect: '/sys/list',
meta: {
title: '系统管理',
icon: 'el-icon-lock'
},
alwaysShow: true,
children: [
{
name: 'RegionWare',
path: 'regionWare/list',
component: () => import('@/views/sys/regionWare/list'),
meta: {
title: '开通区域'
}
}
]
},
{
name: 'Product',
path: '/product',
component: Layout,
redirect: '/product/category/list',
meta: { title: '商品信息管理', icon: 'dashboard' },
alwaysShow: true,
children: [
{
path: 'category/list',
name: 'Category',
component: () => import('@/views/product/category/list'),
meta: { title: '商品分类' }
},
{
path: 'category/add',
name: 'CategoryAdd',
component: () => import('@/views/product/category/form'),
meta: { title: '添加' },
hidden: true
},
{
path: 'category/edit/:id',
name: 'CategoryEdit',
component: () => import('@/views/product/category/form'),
meta: { title: '编辑', noCache: true },
hidden: true
},
{
path: 'attrGroup/list',
name: 'AttrGroup',
component: () => import('@/views/product/attrGroup/list'),
meta: { title: '平台属性分组' }
},
{
path: 'attrGroup/add',
name: 'AttrGroupAdd',
component: () => import('@/views/product/attrGroup/form'),
meta: { title: '添加' },
hidden: true
},
{
path: 'attrGroup/edit/:id',
name: 'AttrGroupEdit',
component: () => import('@/views/product/attrGroup/form'),
meta: { title: '编辑', noCache: true },
hidden: true
},
{
path: 'attr/list/:id',
name: 'AttrList',
component: () => import('@/views/product/attr/list'),
meta: { title: '平台属性列表', noCache: true },
hidden: true
},
{
path: 'attr/add',
name: 'AttrAdd',
component: () => import('@/views/product/attr/form'),
meta: { title: '添加' },
hidden: true
},
{
path: 'attr/edit/:id',
name: 'AttrEdit',
component: () => import('@/views/product/attr/form'),
meta: { title: '编辑', noCache: true },
hidden: true
},
{
path: 'skuInfo/list',
name: 'SkuInfo',
component: () => import('@/views/product/skuInfo/list'),
meta: { title: 'SKU列表' }
},
{
path: 'skuInfo/add',
name: 'SkuInfoAdd',
component: () => import('@/views/product/skuInfo/form'),
meta: { title: '添加' },
hidden: true
},
{
path: 'skuInfo/edit/:id',
name: 'SkuInfoEdit',
component: () => import('@/views/product/skuInfo/form'),
meta: { title: '编辑', noCache: true },
hidden: true
}
]
},
{
path: '/activity',
component: Layout,
redirect: '/activity/seckill',
name: 'Activity',
meta: { title: '营销活动管理', icon: 'dashboard' },
alwaysShow: true,
children: [
// {
// path: 'seckill/list',
// name: 'Seckill',
// component: () => import('@/views/activity/seckill/list'),
// meta: { title: '秒杀活动列表' }
// },
// {
// path: 'seckill/timeList',
// name: 'SeckillTime',
// component: () => import('@/views/activity/seckill/timeList'),
// meta: { title: '秒杀时间段列表' },
// hidden: true
// },
// {
// path: 'seckill/selectTimeList',
// name: 'SelectSeckillTime',
// component: () => import('@/views/activity/seckill/selectTimeList'),
// meta: { title: '秒杀时间段选择' },
// hidden: true
// },
// {
// path: 'seckill/seckillSkuList',
// name: 'SeckillSku',
// component: () => import('@/views/activity/seckill/seckillSkuList'),
// meta: { title: '秒杀商品列表' },
// hidden: true
// },
{
path: 'activityInfo/list',
name: 'ActivityInfo',
component: () => import('@/views/activity/activityInfo/list'),
meta: { title: '活动列表' }
},
{
path: 'activityInfo/add',
name: 'ActivityInfoAdd',
component: () => import('@/views/activity/activityInfo/form'),
meta: { title: '添加' },
hidden: true
},
{
path: 'activityInfo/edit/:id',
name: 'ActivityInfoEdit',
component: () => import('@/views/activity/activityInfo/form'),
meta: { title: '编辑', noCache: true },
hidden: true
},
{
path: 'activityInfo/rule/:id',
name: 'ActivityInfoRule',
component: () => import('@/views/activity/activityInfo/rule'),
meta: { title: '规则', noCache: true },
hidden: true
},
{
path: 'couponInfo/list',
name: 'CouponInfo',
component: () => import('@/views/activity/couponInfo/list'),
meta: { title: '优惠券列表' }
},
{
path: 'couponInfo/add',
name: 'CouponInfoAdd',
component: () => import('@/views/activity/couponInfo/form'),
meta: { title: '添加' },
hidden: true
},
{
path: 'couponInfo/edit/:id',
name: 'CouponInfoEdit',
component: () => import('@/views/activity/couponInfo/form'),
meta: { title: '编辑', noCache: true },
hidden: true
},
{
path: 'couponInfo/rule/:id',
name: 'CouponInfoRule',
component: () => import('@/views/activity/couponInfo/rule'),
meta: { title: '规则', noCache: true },
hidden: true
}
]
},
{
path: '/user',
component: Layout,
redirect: '/user/leader/list',
name: 'Leader',
meta: { title: '团长管理', icon: 'table' },
alwaysShow: true,
children: [
{
path: 'leader/checkList',
name: 'LeaderCheck',
component: () => import('@/views/user/leader/checkList'),
meta: { title: '团长待审核' }
},
{
path: 'leader/list',
name: 'leader',
component: () => import('@/views/user/leader/list'),
meta: { title: '团长已审核' }
}
]
},
{
name: 'Order',
path: '/order',
component: Layout,
redirect: '/order/orderInfo/list',
meta: { title: '订单管理', icon: 'table' },
alwaysShow: true,
children: [
{
path: 'orderInfo/list',
name: 'OrderInfo',
component: () => import('@/views/order/orderInfo/list'),
meta: { title: '订单列表' }
},
{
path: 'orderInfo/show/:id',
name: 'OrderInfoShow',
component: () => import('@/views/order/orderInfo/show'),
meta: { title: '查看', noCache: true },
hidden: true
},
{
path: 'orderInfo/deliverList',
name: 'DetailList',
component: () => import('@/views/order/orderInfo/deliverList'),
meta: { title: '发货列表' }
}
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router

View File

@@ -0,0 +1,16 @@
module.exports = {
title: 'Vue Admin Template',
/**
* @type {boolean} true | false
* @description Whether fix the header
*/
fixedHeader: false,
/**
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: false
}

View File

@@ -0,0 +1,8 @@
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name
}
export default getters

View File

@@ -0,0 +1,19 @@
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
settings,
user
},
getters
})
export default store

View File

@@ -0,0 +1,48 @@
import Cookies from 'js-cookie'
const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
withoutAnimation: false
},
device: 'desktop'
}
const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1)
} else {
Cookies.set('sidebarStatus', 0)
}
},
CLOSE_SIDEBAR: (state, withoutAnimation) => {
Cookies.set('sidebarStatus', 0)
state.sidebar.opened = false
state.sidebar.withoutAnimation = withoutAnimation
},
TOGGLE_DEVICE: (state, device) => {
state.device = device
}
}
const actions = {
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
},
closeSideBar({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
toggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

View File

@@ -0,0 +1,32 @@
import defaultSettings from '@/settings'
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
const state = {
showSettings: showSettings,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo
}
const mutations = {
CHANGE_SETTING: (state, { key, value }) => {
// eslint-disable-next-line no-prototype-builtins
if (state.hasOwnProperty(key)) {
state[key] = value
}
}
}
const actions = {
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

View File

@@ -0,0 +1,97 @@
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: ''
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
}
}
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { name, avatar } = data
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')
resolve()
}).catch(error => {
reject(error)
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // must remove token first
commit('RESET_STATE')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

View File

@@ -0,0 +1,49 @@
// cover some element-ui styles
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
// refine element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
// dropdown
.el-dropdown-menu {
a {
display: block
}
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}

View File

@@ -0,0 +1,65 @@
@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
}
html {
height: 100%;
box-sizing: border-box;
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
// main-container global css
.app-container {
padding: 20px;
}

View File

@@ -0,0 +1,28 @@
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
@mixin relative {
position: relative;
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,227 @@
#app {
.main-container {
min-height: 100%;
transition: margin-left .28s;
margin-left: $sideBarWidth;
position: relative;
}
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
background-color: $menuBg;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
&:hover {
background-color: $menuHover !important;
}
}
.is-active > .el-submenu__title {
color: $subMenuActiveText !important;
}
& .nest-menu .el-submenu > .el-submenu__title,
& .el-submenu .el-menu-item {
min-width: $sideBarWidth !important;
background-color: $subMenuBg !important;
&:hover {
background-color: $subMenuHover !important;
}
}
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.el-submenu {
overflow: hidden;
& > .el-submenu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-submenu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-submenu {
& > .el-submenu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: $sideBarWidth !important;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform .28s;
width: $sideBarWidth !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
& > .el-menu {
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
}
.nest-menu .el-submenu > .el-submenu__title,
.el-menu-item {
&:hover {
// you can use $subMenuHover
background-color: $menuHover !important;
}
}
// the scroll bar appears when the subMenu is too long
> .el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@@ -0,0 +1,48 @@
// global transition css
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all .5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all .5s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-move {
transition: all .5s;
}
.breadcrumb-leave-active {
position: absolute;
}

View File

@@ -0,0 +1,25 @@
// sidebar
$menuText: #bfcbd9;
$menuActiveText: #409EFF;
$subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951
$menuBg: #304156;
$menuHover: #263445;
$subMenuBg: #1f2d3d;
$subMenuHover: #001528;
$sideBarWidth: 210px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuText: $menuText;
menuActiveText: $menuActiveText;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
}

View File

@@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const TokenKey = 'vue_admin_template_token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}

View File

@@ -0,0 +1,12 @@
import store from '@/store'
/**
* 判断当前用户是否有此按钮权限
* @param {按钮权限字符串} permission
*/
export default function hasBtnPermission(permission) {
// 得到当前用户的所有按钮权限
const myBtns = store.getters.buttons
// 如果指定的功能权限在myBtns中, 返回true ==> 这个按钮就会显示, 否则隐藏
return myBtns.indexOf(permission) !== -1
}

View File

@@ -0,0 +1,10 @@
import defaultSettings from '@/settings'
const title = defaultSettings.title || 'Vue Admin Template'
export default function getPageTitle(pageTitle) {
if (pageTitle) {
return `${pageTitle} - ${title}`
}
return `${title}`
}

View File

@@ -0,0 +1,119 @@
/**
* Created by PanJiaChen on 16/11/18.
*/
/**
* Parse the time to string
* @param {(Object|string|number)} time
* @param {string} cFormat
* @returns {string | null}
*/
export function parseTime(time, cFormat) {
if (arguments.length === 0 || !time) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string')) {
if ((/^[0-9]+$/.test(time))) {
// support "1548221490638"
time = parseInt(time)
} else {
// support safari
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
time = time.replace(new RegExp(/-/gm), '/')
}
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
const value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
return value.toString().padStart(2, '0')
})
return time_str
}
/**
* @param {number} time
* @param {string} option
* @returns {string}
*/
export function formatTime(time, option) {
if (('' + time).length === 10) {
time = parseInt(time) * 1000
} else {
time = +time
}
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
)
}
}
/**
* @param {string} url
* @returns {Object}
*/
export function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
}
const obj = {}
const searchArr = search.split('&')
searchArr.forEach(v => {
const index = v.indexOf('=')
if (index !== -1) {
const name = v.substring(0, index)
const val = v.substring(index + 1, v.length)
obj[name] = val
}
})
return obj
}

View File

@@ -0,0 +1,85 @@
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 200) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service

View File

@@ -0,0 +1,20 @@
/**
* Created by PanJiaChen on 16/11/18.
*/
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor']
return valid_map.indexOf(str.trim()) >= 0
}

View File

@@ -0,0 +1,244 @@
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__info">All rights reserved
<a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to
return to the homepage.
</div>
<a href="" class="bullshit__return-home">Back to home</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Page404',
computed: {
message() {
return 'The webmaster said that you can not enter this page...'
}
}
}
</script>
<style lang="scss" scoped>
.wscn-http404-container {
transform: translate(-50%, -50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

View File

@@ -0,0 +1,294 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<div class="tools-div">
<el-button type="success" icon="el-icon-plus" size="mini" @click="add()"> </el-button>
</div>
<el-table
:data="sysMenuList"
style="width: 100%;margin-bottom: 20px;margin-top: 10px;"
row-key="id"
border
:default-expand-all="false"
:tree-props="{children: 'children'}"
>
<el-table-column prop="name" label="菜单名称" width="200" />
<el-table-column prop="code" label="权限标识" width="180" />
<el-table-column prop="createTime" label="创建时间" width="200" />
<el-table-column prop="updateTime" label="修改时间" width="200" />
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button
v-if="scope.row.type !== 2"
type="success"
icon="el-icon-plus"
size="mini"
title="添加下级节点"
@click="add(scope.row)"
/>
<el-button type="primary" icon="el-icon-edit" size="mini" title="修改" @click="edit(scope.row)" />
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
title="删除"
:disabled="scope.row.children.length > 0"
@click="removeDataById(scope.row.id)"
/>
</template>
</el-table-column>
</el-table>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="40%">
<el-form ref="dataForm" :model="sysMenu" label-width="150px" size="small" style="padding-right: 40px;">
<el-form-item v-if="sysMenu.id === ''" label="上级部门">
<el-input v-model="sysMenu.parentName" disabled="true" />
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="sysMenu.type" :disabled="typeDisabled">
<el-radio :label="0" :disabled="type0Disabled">目录</el-radio>
<el-radio :label="1" :disabled="type1Disabled">菜单</el-radio>
<el-radio :label="2" :disabled="type2Disabled">按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单名称" prop="name">
<el-input v-model="sysMenu.name" />
</el-form-item>
<el-form-item v-if="sysMenu.type !== 0" prop="to_code">
<span slot="label">
<el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
<i class="el-icon-question" />
</el-tooltip>
组件路径
</span>
<el-input v-model="sysMenu.to_code" placeholder="请输入组件路径" />
</el-form-item>
<el-form-item v-if="sysMenu.type === 2">
<el-input v-model="sysMenu.code" placeholder="请输入权限标识" maxlength="100" />
<span slot="label">
<el-tooltip
content="控制器中定义的权限字符,如:@PreAuthorize(hasAuthority('bnt.sysRole.list'))"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
权限字符
</span>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="small" icon="el-icon-refresh-right" @click="dialogVisible = false"> </el-button>
<el-button type="primary" icon="el-icon-check" size="small" @click="saveOrUpdate()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/acl/permission'
const defaultForm = {
id: '',
parentId: '',
name: '',
type: 0,
path: '',
component: '',
perms: '',
icon: '',
sortValue: 1,
status: 1
}
export default {
// 定义数据
data() {
return {
sysMenuList: [],
expandKeys: [], // 需要自动展开的项
typeDisabled: false,
type0Disabled: false,
type1Disabled: false,
type2Disabled: false,
dialogTitle: '',
dialogVisible: false,
sysMenu: defaultForm,
saveBtnDisabled: false,
iconList: [
{
class: 'el-icon-s-tools'
},
{
class: 'el-icon-s-custom'
},
{
class: 'el-icon-setting'
},
{
class: 'el-icon-user-solid'
},
{
class: 'el-icon-s-help'
},
{
class: 'el-icon-phone'
},
{
class: 'el-icon-s-unfold'
},
{
class: 'el-icon-s-operation'
},
{
class: 'el-icon-more-outline'
},
{
class: 'el-icon-s-check'
},
{
class: 'el-icon-tickets'
},
{
class: 'el-icon-s-goods'
},
{
class: 'el-icon-document-remove'
},
{
class: 'el-icon-warning'
},
{
class: 'el-icon-warning-outline'
},
{
class: 'el-icon-question'
},
{
class: 'el-icon-info'
}
]
}
},
// 当页面加载时获取数据
created() {
this.fetchData()
},
methods: {
// 调用api层获取数据库中的数据
fetchData() {
console.log('加载列表')
api.getPermissionList().then(response => {
this.sysMenuList = response.data
console.log(this.sysMenuList)
})
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removePermission(id)
}).then((response) => {
this.fetchData()
this.$message({
type: 'success',
message: '删除成功!'
})
}).catch(() => {
this.$message.info('取消删除')
})
},
// -------------
add(row) {
debugger
this.typeDisabled = false
this.dialogTitle = '添加下级节点'
this.dialogVisible = true
this.sysMenu = Object.assign({}, defaultForm)
this.sysMenu.id = ''
if (row) {
this.sysMenu.parentId = row.id
this.sysMenu.parentName = row.name
// this.sysMenu.component = 'ParentView'
if (row.type === 0) {
this.sysMenu.type = 1
this.typeDisabled = false
this.type0Disabled = false
this.type1Disabled = false
this.type2Disabled = true
} else if (row.type === 1) {
this.sysMenu.type = 2
this.typeDisabled = true
}
} else {
this.dialogTitle = '添加目录节点'
this.sysMenu.type = 0
this.sysMenu.component = 'Layout'
this.sysMenu.parentId = 0
this.typeDisabled = true
}
},
edit(row) {
debugger
this.dialogTitle = '修改节点'
this.dialogVisible = true
this.sysMenu = Object.assign({}, row)
this.typeDisabled = true
},
saveOrUpdate() {
if (this.sysMenu.type === 0 && this.sysMenu.parentId !== 0) {
this.sysMenu.component = 'ParentView'
}
this.$refs.dataForm.validate(valid => {
if (valid) {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.sysMenu.id) {
this.saveData()
} else {
this.updateData()
}
}
})
},
// 新增
saveData() {
api.addPermission(this.sysMenu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
// 根据id更新记录
updateData() {
api.updatePermission(this.sysMenu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData()
})
}
}
}
</script>

View File

@@ -0,0 +1,299 @@
<template>
<div>
<el-card class="operate-container" shadow="never">
<div style="margin-top: 15px">
<el-form :inline="true" size="small" label-width="140px">
<el-form-item label="输入搜索:">
<el-input v-model="tempSearchObj.roleName" style="width: 203px" placeholder="角色名称" />
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
<el-button type="default" @click="resetSearch()">清空</el-button>
</el-form>
</div>
</el-card>
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="addRole">添加</el-button>
<el-button
class="btn-add"
size="mini"
:disabled="selectedRoles.length === 0"
style="margin: 0 10px;"
@click="removeRoles()"
>批量删除
</el-button>
</el-card>
<el-table
v-loading="listLoading"
border
stripe
:data="roles"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="75"
/>
<el-table-column
type="index"
label="序号"
width="100"
align="center"
/>
<el-table-column label="角色名称" width="500" align="center">
<template slot-scope="{row}">
<template v-if="row.edit">
<el-input v-model="row.roleName" class="edit-input" size="small" />
<el-button
class="cancel-btn"
size="small"
icon="el-icon-refresh"
type="warning"
@click="cancelEdit(row)"
>
取消
</el-button>
</template>
<span v-else>{{ row.roleName }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="{row}">
<HintButton
size="mini"
type="info"
icon="el-icon-info"
title="分配权限"
@click="$router.push(`/acl/role/auth/${row.id}?roleName=${row.roleName}`)"
/>
<HintButton
v-if="row.edit"
size="mini"
type="primary"
icon="el-icon-check"
title="确定"
@click="updateRole(row)"
/>
<HintButton
v-if="!row.edit"
size="mini"
type="primary"
icon="el-icon-edit"
title="修改角色"
@click="row.edit= true"
/>
<HintButton
size="mini"
type="danger"
icon="el-icon-delete"
title="删除角色"
@click="removeRole(row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20]"
style="padding: 10px;"
layout="prev, pager, next, jumper, ->, sizes, total"
@current-change="getRoles"
@size-change="handleSizeChange"
/>
</div>
</template>
<script>
export default {
name: 'RoleList',
data() {
return {
listLoading: true, // 数据是否正在加载
roles: [], // 角色列表
total: 0, // 总记录数
page: 1, // 当前页码
limit: 5, // 每页记录数
tempSearchObj: { // 收集搜索条件数据
roleName: ''
},
searchObj: { // 发送请求的条件参数数据
roleName: ''
},
selectedRoles: [] // 所有选中的角色列表
}
},
mounted() {
this.getRoles()
},
methods: {
/*
取消修改
*/
cancelEdit(role) {
role.roleName = role.originRoleName
role.edit = false
this.$message.warning('取消角色修改')
},
/*
更新角色
*/
updateRole(role) {
this.$API.role.updateById({ id: role.id, roleName: role.roleName })
.then(result => {
this.$message.success(result.message || '更新角色成功!')
this.getRoles(this.page)
})
},
/*
每页数量发生改变的监听
*/
handleSizeChange(pageSize) {
this.limit = pageSize
this.getRoles()
},
/*
添加角色
*/
addRole() {
// 显示添加界面
this.$prompt('请输入新名称', '添加角色', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(({ value }) => {
this.$API.role.save({ roleName: value }).then(result => {
this.$message.success(result.message || '添加角色成功')
this.getRoles()
})
}).catch(() => {
this.$message.warning('取消添加')
})
},
/*
异步获取角色分页列表
*/
getRoles(page = 1) {
this.page = page
this.listLoading = true
const { limit, searchObj } = this
this.$API.role.getPageList(page, limit, searchObj).then(
result => {
const { records, total } = result.data
this.roles = records.map(item => {
item.edit = false // 用于标识是否显示编辑输入框的属性
item.originRoleName = item.roleName // 缓存角色名称, 用于取消
return item
})
this.total = total
}
).finally(() => {
this.listLoading = false
})
},
/*
根据搜索条件进行搜索
*/
search() {
this.searchObj = { ...this.tempSearchObj }
this.getRoles()
},
/*
重置查询表单搜索列表
*/
resetSearch() {
this.tempSearchObj = {
roleName: ''
}
this.searchObj = {
roleName: ''
}
this.getRoles()
},
/*
删除指定的角色
*/
removeRole({ id, roleName }) {
this.$confirm(`确定删除 '${roleName}' 吗?`, '提示', {
type: 'warning'
}).then(async() => {
const result = await this.$API.role.removeById(id)
this.getRoles(this.roles.length === 1 ? this.page - 1 : this.page)
this.$message.success(result.message || '删除成功!')
}).catch(() => {
this.$message.info('已取消删除')
})
},
/*
当表格复选框选项发生变化的时候触发
*/
handleSelectionChange(selection) {
this.selectedRoles = selection
},
/*
批量删除
*/
removeRoles() {
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
type: 'warning'
}).then(async() => {
const ids = this.selectedRoles.map(role => role.id)
const result = await this.$API.role.removeRoles(ids)
this.getRoles()
this.$message({
type: 'success',
message: '批量删除成功!'
})
}).then((result) => {
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>
<style scoped>
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div>
<el-input disabled :value="$route.query.roleName" />
<el-tree
ref="tree"
style="margin: 20px 0"
:data="allPermissions"
node-key="id"
show-checkbox
default-expand-all
:props="defaultProps"
/>
<el-button :loading="loading" type="primary" @click="save">保存</el-button>
<el-button @click="$router.replace({name: 'Role'})">取消</el-button>
</div>
</template>
<script>
export default {
name: 'RoleAuth',
data() {
return {
loading: false, // 用来标识是否正在保存请求中的标识, 防止重复提交
allPermissions: [], // 所有
defaultProps: {
children: 'children',
label: 'name'
}
}
},
created() {
this.init()
},
methods: {
/*
初始化
*/
init() {
const roleId = this.$route.params.id
this.getPermissions(roleId)
},
/*
获取指定角色的权限列表
*/
getPermissions(roleId) {
this.$API.permission.toAssign(roleId).then(result => {
const allPermissions = result.data
this.allPermissions = allPermissions
const checkedIds = this.getCheckedIds(allPermissions)
// console.log('getPermissions() checkedIds', checkedIds)
this.$refs.tree.setCheckedKeys(checkedIds)
})
},
/*
得到所有选中的id列表
*/
getCheckedIds(auths, initArr = []) {
return auths.reduce((pre, item) => {
if (item.select && item.level === 4) {
pre.push(item.id)
} else if (item.children) {
this.getCheckedIds(item.children, initArr)
}
return pre
}, initArr)
},
/*
保存权限列表
*/
save() {
var ids = this.$refs.tree.getCheckedKeys().join(',')
/*
vue elementUI tree树形控件获取父节点ID的实例
修改源码:
情况1: element-ui没有实现按需引入打包
node_modules\element-ui\lib\element-ui.common.js 25382行修改源码 去掉 'includeHalfChecked &&'
// if ((child.checked || includeHalfChecked && child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
if ((child.checked || child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
情况2: element-ui实现了按需引入打包
node_modules\element-ui\lib\tree.js 1051行修改源码 去掉 'includeHalfChecked &&'
// if ((child.checked || includeHalfChecked && child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
if ((child.checked || child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
*/
this.loading = true
this.$API.permission.doAssign(this.$route.params.id, ids).then(result => {
this.loading = false
this.$message.success(result.$message || '分配权限成功')
// 必须在跳转前获取(跳转后通过this获取不到正确的数据了)
const roleName = this.$route.query.roleName
const roles = this.$store.getters.roles
this.$router.replace('/acl/role/list', () => {
console.log('replace onComplete')
// 跳转成功后, 判断如果更新的是当前用户对应角色的权限, 重新加载页面以获得最新的数据
if (roles.includes(roleName)) {
window.location.reload()
}
})
})
}
}
}
</script>

View File

@@ -0,0 +1,377 @@
<template>
<div class="app-container">
<el-card class="operate-container" shadow="never">
<div style="margin-top: 15px">
<el-form :inline="true" size="small" label-width="140px">
<el-form-item label="输入搜索:">
<el-input v-model="tempSearchObj.username" style="width: 203px" placeholder="用户名" />
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="getUsers()">查询</el-button>
<el-button type="default" @click="resetSearch()">清空</el-button>
</el-form>
</div>
</el-card>
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="showAddUser"> </el-button>
<el-button
class="btn-add"
size="mini"
:disabled="selectedIds.length===0"
style="margin: 0 10px;"
@click="revomveUsers"
>批量删除
</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="users"
stripe
border
style="width: 100%;margin-top: 10px;"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
type="index"
label="序号"
width="80"
align="center"
/>
<el-table-column prop="username" label="用户名" width="120" />
<el-table-column prop="name" label="用户名称" width="120" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column prop="updateTime" label="更新时间" width="180" />
<el-table-column label="操作" width="220" align="center">
<!-- <template slot-scope="{row}">-->
<!-- <HintButton type="info" size="mini" icon="el-icon-user-solid" title="分配角色"-->
<!-- @click="showAssignRole(row)" v-if="$hasBP('btn.User.assgin')" />-->
<!-- <HintButton type="primary" size="mini" icon="el-icon-edit" title="修改用户"-->
<!-- @click="showUpdateUser(row)" v-if="$hasBP('btn.User.update')"/>-->
<!-- <el-popconfirm :title="`确定删除 ${row.username} 吗?`" @onConfirm="removeUser(row.id)">-->
<!-- <HintButton style="margin-left:10px" slot="reference" type="danger" size="mini" icon="el-icon-delete"-->
<!-- title="删除用户" v-if="$hasBP('btn.User.remove')"/>-->
<!-- </el-popconfirm>-->
<!-- </template>-->
<template slot-scope="scope">
<el-button type="text" size="mini" @click="showAssignRole(scope.row)">分配角色</el-button>
<el-button type="text" size="mini" @click="showUpdateUser(scope.row)">修改</el-button>
<el-button type="text" size="mini" @click="removeUser(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 20px 0;"
layout="prev, pager, next, jumper, ->, sizes, total"
@current-change="getUsers"
@size-change="handleSizeChange"
/>
<el-dialog :title="user.id ? '修改用户' : '添加用户'" :visible.sync="dialogUserVisible">
<el-form ref="userForm" :model="user" :rules="userRules" label-width="120px">
<el-form-item label="用户名" prop="username">
<el-input v-model="user.username" />
</el-form-item>
<el-form-item label="用户名称">
<el-input v-model="user.name" />
</el-form-item>
<el-form-item v-if="!user.id" label="用户密码" prop="password">
<el-input v-model="user.password" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button :loading="loading" type="primary" @click="addOrUpdate"> </el-button>
</div>
</el-dialog>
<el-dialog title="设置角色" :visible.sync="dialogRoleVisible" :before-close="resetRoleData">
<el-form label-width="80px">
<el-form-item label="用户名">
<el-input disabled :value="user.username" />
</el-form-item>
<el-form-item label="角色列表">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全选
</el-checkbox>
<div style="margin: 15px 0;" />
<el-checkbox-group v-model="userRoleIds" @change="handleCheckedChange">
<el-checkbox v-for="role in allRoles" :key="role.id" :label="role.id">{{ role.roleName }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<div slot="footer">
<el-button :loading="loading" type="primary" @click="assignRole">保存</el-button>
<el-button @click="resetRoleData">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import cloneDeep from 'lodash/cloneDeep'
export default {
name: 'AclUserList',
data() {
return {
listLoading: false, // 是否显示列表加载的提示
searchObj: { // 包含请求搜索条件数据的对象
username: ''
},
tempSearchObj: { // 收集搜索条件输入的对象
username: ''
},
selectedIds: [], // 所有选择的user的id的数组
users: [], // 当前页的用户列表
page: 1, // 当前页码
limit: 5, // 每页数量
total: 0, // 总数量
user: {}, // 当前要操作的user
dialogUserVisible: false, // 是否显示用户添加/修改的dialog
userRules: { // 用户添加/修改表单的校验规则
username: [
{ required: true, message: '用户名必须输入' },
{ min: 4, message: '用户名不能小于4位' }
],
password: [
{ required: true, validator: this.validatePassword }
]
},
loading: false, // 是否正在提交请求中
dialogRoleVisible: false, // 是否显示角色Dialog
allRoles: [], // 所有角色列表
userRoleIds: [], // 用户的角色ID的列表
isIndeterminate: false, // 是否是不确定的
checkAll: false // 是否全选
}
},
created() {
this.getUsers()
},
methods: {
/*
显示指定角色的界面
*/
showAssignRole(user) {
this.user = user
this.dialogRoleVisible = true
this.getRoles()
},
/*
全选勾选状态发生改变的监听
*/
handleCheckAllChange(value) { // value 当前勾选状态true/false
// 如果当前全选, userRoleIds就是所有角色id的数组, 否则是空数组
this.userRoleIds = value ? this.allRoles.map(item => item.id) : []
// 如果当前不是全选也不全不选时, 指定为false
this.isIndeterminate = false
},
/*
异步获取用户的角色列表
*/
async getRoles() {
const result = await this.$API.user.getRoles(this.user.id)
const { allRolesList, assignRoles } = result.data
this.allRoles = allRolesList
this.userRoleIds = assignRoles.map(item => item.id)
this.checkAll = allRolesList.length === assignRoles.length
this.isIndeterminate = assignRoles.length > 0 && assignRoles.length < allRolesList.length
},
/*
角色列表选中项发生改变的监听
*/
handleCheckedChange(value) {
const { userRoleIds, allRoles } = this
this.checkAll = userRoleIds.length === allRoles.length && allRoles.length > 0
this.isIndeterminate = userRoleIds.length > 0 && userRoleIds.length < allRoles.length
},
/*
请求给用户进行角色授权
*/
async assignRole() {
const userId = this.user.id
const roleIds = this.userRoleIds.join(',')
this.loading = true
const result = await this.$API.user.assignRoles(userId, roleIds)
this.loading = false
this.$message.success(result.message || '分配角色成功')
this.resetRoleData()
// console.log(this.$store.getters.name, this.user)
if (this.$store.getters.name === this.user.username) {
window.location.reload()
}
},
/*
重置用户角色的数据
*/
resetRoleData() {
this.dialogRoleVisible = false
this.allRoles = []
this.userRoleIds = []
this.isIndeterminate = false
this.checkAll = false
},
/*
自定义密码校验
*/
validatePassword(rule, value, callback) {
if (!value) {
callback('密码必须输入')
} else if (!value || value.length < 6) {
callback('密码不能小于6位')
} else {
callback()
}
},
/*
根据输入进行搜索
*/
search() {
this.searchObj = { ...this.tempSearchObj }
this.getUsers()
},
/*
重置输入后搜索
*/
resetSearch() {
this.searchObj = {
username: ''
}
this.tempSearchObj = {
username: ''
}
this.getUsers()
},
/*
显示添加用户的界面
*/
showAddUser() {
this.user = {}
this.dialogUserVisible = true
this.$nextTick(() => this.$refs.userForm.clearValidate())
},
/*
删除所有选中的用户
*/
revomveUsers() {
this.$confirm('确定删除吗?').then(async() => {
await this.$API.user.removeUsers(this.selectedIds)
this.$message.success('删除成功')
this.getUsers()
}).catch(() => {
this.$message.info('取消删除')
})
},
/*
列表选中状态发生改变的监听回调
*/
handleSelectionChange(selection) {
this.selectedIds = selection.map(item => item.id)
},
/*
显示更新用户的界面
*/
showUpdateUser(user) {
this.user = cloneDeep(user)
this.dialogUserVisible = true
},
/*
删除某个用户
*/
async removeUser(id) {
await this.$API.user.removeById(id)
this.$message.success('删除成功')
this.getUsers(this.users.length === 1 ? this.page - 1 : this.page)
},
/*
获取分页列表
*/
async getUsers(page = 1) {
this.page = page
const { limit, tempSearchObj } = this
this.listLoading = true
const result = await this.$API.user.getPageList(page, limit, tempSearchObj)
this.listLoading = false
const { records, total } = result.data
this.users = records
this.total = total - 1
this.selectedIds = []
},
/*
处理pageSize发生改变的监听回调
*/
handleSizeChange(pageSize) {
this.limit = pageSize
this.getUsers()
},
/*
取消用户的保存或更新
*/
cancel() {
this.dialogUserVisible = false
this.user = {}
},
/*
保存或者更新用户
*/
addOrUpdate() {
this.$refs.userForm.validate(valid => {
if (valid) {
const { user } = this
this.loading = true
this.$API.user[user.id ? 'update' : 'add'](user).then((result) => {
this.loading = false
this.$message.success('保存成功!')
this.getUsers(user.id ? this.page : 1)
this.user = {}
this.dialogUserVisible = false
})
}
})
}
}
}
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="活动名称">
<el-input v-model="activityInfo.activityName" />
</el-form-item>
<el-form-item label="活动类型">
<el-radio-group v-model="activityInfo.activityType">
<el-radio label="FULL_REDUCTION">满减</el-radio>
<el-radio label="FULL_DISCOUNT">满量打折</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动时间">
<el-date-picker
v-model="activityInfo.startTime"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
/>
<el-date-picker
v-model="activityInfo.endTime"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="活动描述">
<el-input v-model="activityInfo.activityDesc" :rows="3" type="textarea" />
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import api from '@/api/activity/activityInfo'
const defaultForm = {
id: '',
activityName: '',
activityType: 'FULL_REDUCTION',
activityDesc: '',
startTime: '',
endTime: ''
}
export default {
data() {
return {
activityInfo: defaultForm,
saveBtnDisabled: false
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
// debugger
if (this.$route.params && this.$route.params.id) {
const id = this.$route.params.id
this.fetchDataById(id)
} else {
// 对象拓展运算符:拷贝对象,而不是赋值对象的引用
this.activityInfo = { ...defaultForm }
}
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.activityInfo.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
api.save(this.activityInfo).then(response => {
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/activity/activityInfo/list' })
}
})
},
// 根据id更新记录
updateData() {
api.updateById(this.activityInfo).then(response => {
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/activity/activityInfo/list' })
}
})
},
back() {
this.$router.push({ path: '/activity/activityInfo/list' })
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
this.activityInfo = response.data
})
}
}
}
</script>

View File

@@ -0,0 +1,157 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="add()">添加</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="数据正在加载......"
border
fit
highlight-current-row
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="activityName" label="活动名称" />
<el-table-column prop="activityTypeString" label="活动类型" />
<el-table-column prop="startTime" label="开始时间" />
<el-table-column prop="endTime" label="结束时间" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<router-link :to="'/activity/activityInfo/edit/'+scope.row.id">
<el-button size="mini" type="text">修改</el-button>
</router-link>
<router-link :to="'/activity/activityInfo/rule/'+scope.row.id">
<el-button size="mini" type="text">规则</el-button>
</router-link>
<el-button size="mini" type="text" @click="removeDataById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
</div>
</template>
<script>
import api from '@/api/activity/activityInfo'
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
multipleSelection: [] // 批量选择中选择的记录列表
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
add() {
this.$router.push({ path: '/activity/activityInfo/add' })
},
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.fetchData()
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>

View File

@@ -0,0 +1,319 @@
<template>
<div class="app-container">
<h4>活动信息</h4>
<table class="table table-striped table-condenseda table-bordered" width="100%">
<tbody>
<tr>
<th width="15%">活动标题</th>
<td width="35%"><b style="font-size: 14px">{{ activityInfo.activityName }}</b></td>
<th width="15%">活动时间</th>
<td width="35%">{{ activityInfo.startTime }}{{ activityInfo.endTime }}</td>
</tr>
<tr>
<th>活动类型</th>
<td>{{ activityInfo.activityTypeString }}</td>
<th>创建时间</th>
<td>{{ activityInfo.createTime }}</td>
</tr>
<tr>
<th>活动描述</th>
<td colspan="3">{{ activityInfo.activityDesc }}</td>
</tr>
</tbody>
</table>
<el-dialog title="添加规则" :visible.sync="dialogRuleVisible" width="490px">
<el-form label-width="120px">
<el-form-item v-if="activityInfo.activityType == 'FULL_REDUCTION'" label="满减金额">
<el-input v-model="activityRule.conditionAmount" />
</el-form-item>
<el-form-item v-if="activityInfo.activityType == 'FULL_REDUCTION'" label="优惠金额">
<el-input v-model="activityRule.benefitAmount" />
</el-form-item>
<el-form-item v-if="activityInfo.activityType == 'FULL_DISCOUNT'" label="满减件数">
<el-input v-model="activityRule.conditionNum" />
</el-form-item>
<el-form-item v-if="activityInfo.activityType == 'FULL_DISCOUNT'" label="优惠折扣">
<el-input v-model="activityRule.benefitDiscount" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="add">添加</el-button>
<el-button type="" @click="dialogRuleVisible = false">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
<h4>
规则列表&nbsp;&nbsp;&nbsp;
<el-button type="" size="mini" @click="dialogRuleVisible = true">添加规则</el-button>
</h4>
<el-table
v-loading="listLoading"
:data="activityRuleList"
border
fit
highlight-current-row
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="活动类型">
{{ activityInfo.activityType == 'FULL_REDUCTION' ? '满减' : '满量打折' }}
</el-table-column>
<el-table-column v-if="activityInfo.activityType == 'FULL_REDUCTION'" prop="conditionAmount" label="满减金额" />
<el-table-column v-if="activityInfo.activityType == 'FULL_REDUCTION'" prop="benefitAmount" label="优惠金额" />
<el-table-column v-if="activityInfo.activityType == 'FULL_DISCOUNT'" prop="conditionNum" label="满减量数" />
<el-table-column v-if="activityInfo.activityType == 'FULL_DISCOUNT'" prop="benefitDiscount" label="优惠折扣" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="removeDataById(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="添加活动范围" :visible.sync="dialogRangVisible" width="490px">
<div style="margin-top: 20px;">
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-autocomplete
v-model="keyword"
:fetch-suggestions="querySearch"
:trigger-on-focus="false"
class="inline-input"
placeholder="请输入关键字,选择活动商品"
value-key="skuName"
style="width: 400px;"
@select="selectData"
/>
</el-form-item>
<el-form-item>
<el-button type="" @click="dialogRangVisible = false">取消</el-button>
</el-form-item>
</el-form>
</div>
</el-dialog>
<h4>
活动范围列表&nbsp;&nbsp;&nbsp;
<el-button type="" size="mini" @click="dialogRangVisible = true">添加活动范围</el-button>
</h4>
<el-table
v-loading="listLoading"
:data="skuInfoList"
border
fit
highlight-current-row
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="id" label="SKU ID" width="100" />
<el-table-column label="图片" width="320" align="center">
<template slot-scope="scope">
<div class="info">
<div class="pic">
<img :src="scope.row.imgUrl" alt="scope.row.title" style="width: 50px;">
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="skuName" label="名称" />
<el-table-column prop="price" label="价格" width="70" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="removeSkuDataById(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 15px;">
<el-form label-width="0px">
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import api from '@/api/activity/activityInfo'
const defaultForm = {
activityId: '',
conditionAmount: '',
conditionNum: '',
benefitAmount: '',
benefitDiscount: '',
benefitLevel: ''
}
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
activityRule: defaultForm,
saveBtnDisabled: false,
dialogRuleVisible: false,
activityInfo: null,
activityRuleList: [],
dialogRangVisible: false,
keyword: '',
skuInfoList: [],
dialogCouponVisible: false,
couponInfoList: []
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
const activityId = this.$route.params.id
this.fetchDataById(activityId)
this.fetchRuleDataById(activityId)
},
// 新增
add() {
this.activityRuleList.push(
{
activityId: this.activityInfo.id,
conditionAmount: this.activityRule.conditionAmount,
conditionNum: this.activityRule.conditionNum,
benefitAmount: this.activityRule.benefitAmount,
benefitDiscount: this.activityRule.benefitDiscount
}
)
this.activityRule = defaultForm
this.dialogRuleVisible = false
},
save() {
const skuList = []
this.skuInfoList.forEach(function(item) {
skuList.push({
skuId: item.id
})
})
const couponIdList = []
this.couponInfoList.forEach(function(item) {
couponIdList.push(item.id)
})
const ruleData = {
activityId: this.activityInfo.id,
activityRuleList: this.activityRuleList,
activitySkuList: skuList,
couponIdList: couponIdList
}
debugger
api.saveActivityRule(ruleData).then(response => {
this.$message.success(response.message)
this.$router.push({ path: '/activity/activityInfo/list' })
})
},
removeDataById(index) {
this.activityRuleList.splice(index, 1)
},
back() {
this.$router.push({ path: '/activity/activityInfo/list' })
},
fetchRuleDataById(id) {
api.findActivityRuleList(id).then(response => {
// debugger
this.activityRuleList = response.data.activityRuleList || []
this.skuInfoList = response.data.skuInfoList || []
this.couponInfoList = response.data.couponInfoList || []
this.listLoading = false
})
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
// debugger
this.activityInfo = response.data
})
},
// sku
querySearch(queryString, cb) {
console.log(queryString)
console.log(cb)
api.findSkuInfoByKeyword(queryString).then(response => {
// 调用 callback 返回建议列表的数据
cb(response.data)
})
},
selectData(item) {
this.skuInfoList.push(item)
this.dialogRangVisible = false
},
removeSkuDataById(index) {
this.skuInfoList.splice(index, 1)
}
}
}
</script>
<style>
.app-container h4 {
color: #606266;
}
</style>

View File

@@ -0,0 +1,163 @@
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="优惠券名称">
<el-input v-model="couponInfo.couponName" />
</el-form-item>
<el-form-item label="优惠券类型">
<el-radio-group v-model="couponInfo.couponType">
<el-radio label="CASH">现金券</el-radio>
<el-radio label="FULL_REDUCTION">满减券</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="发行数量">
<el-input v-model="couponInfo.publishCount" />
</el-form-item>
<el-form-item label="领取时间">
<el-date-picker
v-model="couponInfo.startTime"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
/>
<el-date-picker
v-model="couponInfo.endTime"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="过期时间">
<el-date-picker
v-model="couponInfo.expireTime"
type="datetime"
placeholder="选择开始日期"
value-format="yyyy-MM-dd HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import api from '@/api/activity/couponInfo'
const defaultForm = {
id: '',
couponType: 'CASH',
couponName: '',
amount: '0',
conditionAmount: '0',
startTime: '',
endTime: '',
rangeType: 'ALL',
rangeDesc: '',
publishCount: '',
perLimit: '1',
useCount: '0',
receiveCount: '',
expireTime: '',
publishStatus: ''
}
export default {
data() {
return {
couponInfo: defaultForm,
saveBtnDisabled: false,
keyword: '',
skuInfoList: []
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
// debugger
if (this.$route.params && this.$route.params.id) {
const id = this.$route.params.id
this.fetchDataById(id)
} else {
// 对象拓展运算符:拷贝对象,而不是赋值对象的引用
this.couponInfo = { ...defaultForm }
}
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.couponInfo.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
api.save(this.couponInfo).then(response => {
// debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/activity/couponInfo/list' })
}
})
},
// 根据id更新记录
updateData() {
api.updateById(this.couponInfo).then(response => {
debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/activity/couponInfo/list' })
}
})
},
back() {
this.$router.push({ path: '/activity/couponInfo/list' })
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
// debugger
this.couponInfo = response.data
})
}
}
}
</script>

View File

@@ -0,0 +1,164 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="add()">添加</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="数据正在加载......"
border
fit
highlight-current-row
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="couponName" label="购物券名称" />
<el-table-column prop="couponTypeString" label="购物券类型" />
<el-table-column label="规则">
<template slot-scope="scope">
{{
scope.row.couponType == 'CASH' ? '减金额' + scope.row.amount + '元' : '满' + scope.row.conditionAmount + '元减' + scope.row.amount + '元'
}}
</template>
</el-table-column>
<el-table-column prop="rangeTypeString" label="范围类型 " />
<el-table-column prop="publishCount" label="发行数量" />
<el-table-column prop="expireTime" label="过期时间" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<router-link :to="'/activity/couponInfo/edit/'+scope.row.id">
<el-button size="mini" type="text">修改</el-button>
</router-link>
<router-link :to="'/activity/couponInfo/rule/'+scope.row.id">
<el-button size="mini" type="text">规则</el-button>
</router-link>
<el-button size="mini" type="text" @click="removeDataById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
</div>
</template>
<script>
import api from '@/api/activity/couponInfo'
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
multipleSelection: [] // 批量选择中选择的记录列表
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
add() {
this.$router.push({ path: '/activity/couponInfo/add' })
},
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
debugger
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.fetchData()
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>

View File

@@ -0,0 +1,355 @@
<template>
<div class="app-container">
<h4>优惠券信息</h4>
<table class="table table-striped table-condenseda table-bordered" width="100%">
<tbody>
<tr>
<th width="15%">优惠券名称</th>
<td width="35%"><b style="font-size: 14px">{{ couponInfo.couponName }}</b></td>
<th width="15%">优惠券类型</th>
<td width="35%">
{{ couponInfo.couponTypeString }}
</td>
</tr>
<tr>
<th>发行数量</th>
<td>{{ couponInfo.publishCount }}</td>
<th>每人限领次数</th>
<td>{{ couponInfo.perLimit }}</td>
</tr>
<tr>
<th>领取时间</th>
<td>{{ couponInfo.startTime }}{{ couponInfo.endTime }}</td>
<th>过期时间</th>
<td>{{ couponInfo.expireTime }}</td>
</tr>
</tbody>
</table>
<h4>添加规则</h4>
<el-form label-width="140px" style="background: #f9f9f9;padding-top: 15px; padding-bottom: 1px;">
<el-form-item v-if="couponInfo.couponType == 'CASH'" label="现金券金额(元)" style="width: 50%;">
<el-input v-model="couponInfo.amount" />
</el-form-item>
<el-form-item v-if="couponInfo.couponType == 'FULL_REDUCTION'" label="满减金额(元)" style="width: 50%;">
<el-input v-model="couponInfo.conditionAmount" />
</el-form-item>
<el-form-item v-if="couponInfo.couponType == 'FULL_REDUCTION'" label="优惠金额(元)" style="width: 50%;">
<el-input v-model="couponInfo.amount" />
</el-form-item>
</el-form>
<h4>优惠券范围选择</h4>
<el-form label-width="0px" style="background: #f9f9f9;padding-top: 15px;padding-left: 15px; padding-bottom: 1px;">
<el-form-item>
<el-radio-group v-model="couponInfo.rangeType">
<el-radio label="ALL">通用</el-radio>
<el-radio label="SKU">SKU</el-radio>
<el-radio label="CATEGORY">分类</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div v-if="couponInfo.rangeType == 'SKU'">
<el-dialog title="添加范围" :visible.sync="dialogSkduRangVisible" width="490px">
<div style="margin-top: 20px;">
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-autocomplete
v-model="keyword"
:fetch-suggestions="querySkuSearch"
:trigger-on-focus="false"
class="inline-input"
placeholder="请输入关键字,选择活动商品"
value-key="skuName"
style="width: 400px;"
@select="selectSkuData"
/>
</el-form-item>
<el-form-item>
<el-button type="" @click="dialogSkduRangVisible = false">取消</el-button>
</el-form-item>
</el-form>
</div>
</el-dialog>
<h4>
优惠券范围列表&nbsp;&nbsp;&nbsp;
<el-button type="" size="mini" @click="dialogSkduRangVisible = true">添加优惠券范围</el-button>
</h4>
<el-table
v-loading="listLoading"
:data="skuInfoList"
border
fit
highlight-current-row
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="id" label="sku ID" width="100" />
<el-table-column prop="skuName" label="sku名称" width="120" />
<el-table-column prop="skuCode" label="sku编号" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="removeSkuDataById(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="couponInfo.rangeType == 'CATEGORY'">
<el-dialog title="添加范围" :visible.sync="dialogCateRangVisible" width="490px">
<div style="margin-top: 20px;">
<el-form :inline="true" class="demo-form-inline">
<el-select placeholder="请选择" style="width: 300px" @change="selectCategory">
<el-option
v-for="category in selectCategoryList"
:key="category.id"
:label="category.name"
:value="category.id"
/>
</el-select>
<el-form-item>
<el-button type="" @click="dialogCateRangVisible = false">取消</el-button>
</el-form-item>
</el-form>
</div>
</el-dialog>
<h4>
优惠券范围列表&nbsp;&nbsp;&nbsp;
<el-button type="" size="mini" @click="dialogCateRangVisible = true">添加优惠券范围</el-button>
</h4>
<el-table
v-loading="listLoading"
:data="categoryList"
border
fit
highlight-current-row
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="id" label="分类ID" width="100" />
<el-table-column prop="name" label="名称" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="removeCateDataById(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="couponInfo.rangeType == 'ALL'">
<span>通用</span>
</div>
<div style="margin-top: 15px;">
<el-form label-width="0px">
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import api from '@/api/activity/couponInfo'
import activityInfoApi from '@/api/activity/activityInfo'
import categoryApi from '@/api/product/category'
export default {
data() {
return {
listLoading: false, // 数据是否正在加载
couponInfo: {},
saveBtnDisabled: false,
// 优惠券范围SPU
dialogSkduRangVisible: false,
keyword: '',
skuInfoList: [],
// 优惠券范围:三级分类
dialogCateRangVisible: false,
categoryList: [],
selectCategoryList: []
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
const couponId = this.$route.params.id
// 获取优惠券信息
this.fetchDataById(couponId)
// 获取优惠券规则与范围信息
this.fetchRuleDataById(couponId)
this.fetchCategoryList()
},
fetchCategoryList() {
categoryApi.findAllList().then(response => {
this.selectCategoryList = response.data
})
},
// 提交保存数据
save() {
let rangeDesc = '可购买'
const couponRangeList = []
if (this.couponInfo.rangeType === 'SKU') {
this.skuInfoList.forEach(function(item) {
rangeDesc += ''
couponRangeList.push({
rangeType: 'SKU',
rangeId: item.id
})
rangeDesc += item.spuName + ','
})
}
if (this.couponInfo.rangeType === 'CATEGORY') {
rangeDesc += '分类:'
this.categoryList.forEach(function(item) {
couponRangeList.push({
rangeType: 'CATEGORY',
rangeId: item.id
})
rangeDesc += item.name + ','
})
}
if (this.couponInfo.rangeType === 'ALL') {
rangeDesc += '全场通用,'
}
if (rangeDesc.length > 3) {
rangeDesc = rangeDesc.substring(0, rangeDesc.length - 1)
} else {
rangeDesc = this.couponInfo.couponName
}
const ruleData = {
couponId: this.couponInfo.id,
rangeType: this.couponInfo.rangeType,
amount: this.couponInfo.amount,
conditionAmount: this.couponInfo.conditionAmount,
couponRangeList: couponRangeList,
rangeDesc: rangeDesc
}
api.saveCouponRule(ruleData).then(response => {
this.$message.success(response.message)
this.$router.push({ path: '/activity/couponInfo/list' })
})
},
removeDataById(index) {
this.activityRuleList.splice(index, 1)
},
back() {
// this.$router.push({ path: '/activity/couponInfo/list' })
window.history.go(-1)
},
fetchRuleDataById(id) {
api.findCouponRuleList(id).then(response => {
this.skuInfoList = response.data.skuInfoList || []
this.categoryList = response.data.categoryList || []
this.trademarkList = response.data.trademarkList || []
})
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
//
this.couponInfo = response.data
})
},
querySkuSearch(queryString, cb) {
console.log(queryString)
console.log(cb)
activityInfoApi.findSkuInfoByKeyword(queryString).then(response => {
// 调用 callback 返回建议列表的数据
cb(response.data)
})
},
selectSkuData(item) {
this.skuInfoList.push(item)
this.dialogSkduRangVisible = false
},
removeSkuDataById(index) {
this.skuInfoList.splice(index, 1)
},
selectCategory(categoryId) {
console.log(categoryId)
categoryApi.getById(categoryId)
.then(response => {
this.categoryList.push({
id: categoryId,
name: response.data.name
})
})
this.dialogCateRangVisible = false
},
removeCateDataById(index) {
this.categoryList.splice(index, 1)
}
}
}
</script>
<style>
.app-container h4 {
color: #606266;
}
</style>

View File

@@ -0,0 +1,290 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" :disabled="$hasBP('btn.all') === false" @click="add()">添加</el-button>
<el-button size="mini" class="btn-add" style="margin: 0 10px;" @click="addTimeList()">秒杀时间段列表</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="title" label="活动标题" />
<el-table-column prop="startTime" label="开始日期" />
<el-table-column prop="endTime" label="结束日期" />
<el-table-column label="上线/下线" width="200" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="updateStatus(scope.row)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="selectTime(scope.row)">设置商品</el-button>
<el-button size="mini" type="text" :disabled="$hasBP('btn.all') === false" @click="edit(scope.row.id)">编辑
</el-button>
<el-button
size="mini"
type="text"
:disabled="$hasBP('btn.all') === false"
@click="removeDataById(scope.row.id)"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
<el-dialog title="添加活动" :visible.sync="dialogVisible" width="40%">
<el-form ref="flashPromotionForm" label-width="150px" size="small">
<el-form-item label="活动标题:">
<el-input v-model="seckill.title" style="width: 250px" />
</el-form-item>
<el-form-item label="开始时间:">
<el-date-picker
v-model="seckill.startTime"
type="date"
placeholder="请选择时间"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="结束时间:">
<el-date-picker
v-model="seckill.endTime"
type="date"
placeholder="请选择时间"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="上线/下线">
<el-radio-group v-model="seckill.status">
<el-radio :label="1">上线</el-radio>
<el-radio :label="0">下线</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button size="small" @click="dialogVisible = false"> </el-button>
<el-button type="primary" size="small" @click="saveOrUpdate()"> </el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/activity/seckill'
const defaultForm = {
id: '',
title: '',
startTime: '',
endTime: '',
status: 1
}
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
dialogVisible: false,
saveBtnDisabled: false,
seckill: defaultForm
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
addTimeList() {
this.$router.push({ path: '/activity/seckill/timeList' })
},
selectTime(row) {
this.$router.push({ path: '/activity/seckill/selectTimeList', query: { seckillId: row.id }})
},
add() {
this.dialogVisible = true
this.seckill = Object.assign({}, defaultForm)
},
edit(id) {
this.dialogVisible = true
this.fetchDataById(id)
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.seckill.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
api.save(this.seckill).then(response => {
// debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.dialogVisible = false
this.fetchData(1)
}
})
},
// 根据id更新记录
updateData() {
api.updateById(this.seckill).then(response => {
debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.dialogVisible = false
this.fetchData(1)
}
})
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
this.seckill = response.data
})
},
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
debugger
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
updateStatus(row) {
this.$confirm('是否要修改该状态?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
api.updateStatus(row.id, row.status).then(response => {
this.$message({
type: 'success',
message: '修改成功!'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: '取消修改'
})
this.getList()
})
}
}
}
</script>

View File

@@ -0,0 +1,344 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button size="mini" class="btn-add" :disabled="$hasBP('btn.all') === false" @click="addSku()">添加</el-button>
<el-button class="btn-add" size="mini" style="margin: 0 10px;" @click="$router.back()">返回</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="skuInfo.skuName" label="商品名称" />
<el-table-column prop="skuInfo.skuCode" label="商品编号" />
<el-table-column prop="skuInfo.price" label="商品价格" />
<el-table-column prop="skuInfo.stock" label="剩余数量" />
<el-table-column prop="seckillPrice" label="秒杀价格" />
<el-table-column prop="seckillStock" label="秒杀数量" />
<el-table-column prop="seckillLimit" label="每人限购数量" />
<el-table-column prop="seckillSort" label="排序" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="text" :disabled="$hasBP('btn.all') === false" @click="handleUpdate(scope.row)">修改
</el-button>
<el-button type="text" :disabled="$hasBP('btn.all') === false" @click="removeDataById(scope.row.id)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
<el-dialog title="添加商品" :visible.sync="selectDialogVisible" width="40%">
<!-- <el-input style="width: 250px;margin-bottom: 20px" size="small" placeholder="商品名称搜索">-->
<!-- <el-button slot="append" icon="el-icon-search" @click="handleSelectSearch()"></el-button>-->
<!-- </el-input>-->
<el-table :data="dialogData.list" border @selection-change="handleDialogSelectionChange">
<el-table-column type="selection" width="60" align="center" />
<el-table-column label="商品名称" align="center">
<template slot-scope="scope">{{ scope.row.skuName }}</template>
</el-table-column>
<el-table-column label="货号" width="160" align="center">
<template slot-scope="scope">{{ scope.row.skuCode }}</template>
</el-table-column>
<el-table-column label="价格" width="120" align="center">
<template slot-scope="scope">{{ scope.row.price }}</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
background
layout="prev, pager, next"
:current-page.sync="dialogData.page"
:page-size="dialogData.limit"
:page-sizes="[5,10,15]"
:total="dialogData.total"
@size-change="handleDialogSizeChange"
@current-change="handleDialogCurrentChange"
/>
</div>
<div style="clear: both;" />
<div slot="footer">
<el-button size="small" @click="selectDialogVisible = false"> </el-button>
<el-button size="small" type="primary" @click="handleSelectDialogConfirm()"> </el-button>
</div>
</el-dialog>
<el-dialog title="编辑秒杀商品信息" :visible.sync="editDialogVisible" width="40%">
<el-form ref="flashProductRelationForm" label-width="150px" size="small">
<el-form-item label="商品名称:">
<span>{{ seckillSku.skuInfo.skuName }}</span>
</el-form-item>
<el-form-item label="sku编号">
<span>{{ seckillSku.skuInfo.skuCode }}</span>
</el-form-item>
<el-form-item label="sku价格">
<span>{{ seckillSku.skuInfo.price }}</span>
</el-form-item>
<el-form-item label="秒杀价格:">
<el-input v-model="seckillSku.seckillPrice" class="input-width">
<template slot="prepend"></template>
</el-input>
</el-form-item>
<el-form-item label="剩余数量:">
<span>{{ seckillSku.skuInfo.stock }}</span>
</el-form-item>
<el-form-item label="秒杀数量:">
<el-input v-model="seckillSku.seckillStock" class="input-width" />
</el-form-item>
<el-form-item label="限购数量:">
<el-input v-model="seckillSku.seckillLimit" class="input-width" />
</el-form-item>
<el-form-item label="排序:">
<el-input v-model="seckillSku.seckillSort" class="input-width" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="editDialogVisible = false"> </el-button>
<el-button type="primary" size="small" @click="updateData()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/activity/seckillSku'
import skuApi from '@/api/product/skuInfo'
const defaultForm = {
id: '',
seckillId: '',
seckillTimeId: '',
skuId: '',
seckillPrice: '',
seckillStock: '',
seckillLimit: '',
seckillSort: '',
skuName: '',
imgUrl: '',
skuInfo: {
skuName: '',
skuCode: '',
price: '',
stock: ''
}
}
export default {
data() {
return {
seckillId: null,
seckillTimeId: null,
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
dialogVisible: false,
selectDialogVisible: false,
dialogData: {
list: null,
total: null,
page: 1,
limit: 5,
multipleSelection: []
},
editDialogVisible: false,
seckillSku: defaultForm
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
this.seckillId = this.$route.query.seckillId
this.seckillTimeId = this.$route.query.seckillTimeId
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
this.searchObj.seckillId = this.seckillId
this.searchObj.seckillTimeId = this.seckillTimeId
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
addSku() {
this.selectDialogVisible = true
this.getDialogList()
},
getDialogList() {
skuApi.getPageList(this.dialogData.page, this.dialogData.limit, []).then(
response => {
debugger
this.dialogData.list = response.data.records
this.dialogData.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
handleDialogSizeChange(val) {
debugger
this.dialogData.pageNum = 1
this.dialogData.pageSize = val
this.getDialogList()
},
handleDialogCurrentChange(val) {
debugger
this.dialogData.pageNum = val
this.getDialogList()
},
handleDialogSelectionChange(val) {
this.dialogData.multipleSelection = val
},
handleSelectDialogConfirm() {
if (this.dialogData.multipleSelection < 1) {
this.$message({
message: '请选择一条记录',
type: 'warning',
duration: 1000
})
return
}
const selectSkuInfos = []
for (let i = 0; i < this.dialogData.multipleSelection.length; i++) {
selectSkuInfos.push({
skuId: this.dialogData.multipleSelection[i].id,
seckillId: this.seckillId,
seckillTimeId: this.seckillTimeId,
skuName: this.dialogData.multipleSelection[i].skuName,
imgUrl: this.dialogData.multipleSelection[i].imgUrl
})
}
this.$confirm('使用要进行添加操作?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
api.save(selectSkuInfos).then(response => {
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.selectDialogVisible = false
this.fetchData(1)
}
})
})
},
handleUpdate(row) {
this.editDialogVisible = true
this.seckillSku = Object.assign({}, row)
},
updateData() {
api.updateById(this.seckillSku).then(response => {
debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.editDialogVisible = false
this.fetchData(1)
}
})
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>
<style scoped>
.operate-container {
margin-top: 0;
}
.input-width {
width: 200px;
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="$router.back()">返回</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="name" label="场次名称" />
<el-table-column prop="startTime" label="每日开始时间" />
<el-table-column prop="endTime" label="每日结束时间" />
<el-table-column label="启用状态" width="200" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="updateStatus(scope.row)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="showRelation(scope.row)">商品列表</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import api from '@/api/activity/seckillTime'
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null // banner列表
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 加载banner列表数据
fetchData() {
api.getList().then(
response => {
debugger
this.list = response.data
// 数据加载并绑定成功
this.listLoading = false
}
)
},
showRelation(row) {
this.$router.push({
path: '/activity/seckill/seckillSkuList', query: {
seckillId: this.$route.query.seckillId,
seckillTimeId: row.id
}
})
}
}
}
</script>

View File

@@ -0,0 +1,251 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" :disabled="$hasBP('btn.all') === false" @click="add()">添加</el-button>
<el-button class="btn-add" size="mini" style="margin: 0 10px;" @click="$router.back()">返回</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="name" label="场次名称" />
<el-table-column prop="startTime" label="每日开始时间" />
<el-table-column prop="endTime" label="每日结束时间" />
<el-table-column label="启用状态" width="200" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="updateStatus(scope.row)"
/>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" :disabled="$hasBP('btn.all') === false" @click="edit(scope.row.id)">编辑
</el-button>
<el-button
size="mini"
type="text"
:disabled="$hasBP('btn.all') === false"
@click="removeDataById(scope.row.id)"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="添加活动" :visible.sync="dialogVisible" width="40%">
<el-form ref="flashPromotionForm" label-width="150px" size="small">
<el-form-item label="秒杀时间段名称:">
<el-input v-model="seckillTime.name" style="width: 250px" />
</el-form-item>
<el-form-item label="每日开始时间:">
<el-time-picker
v-model="seckillTime.startTime"
placeholder="请选择时间"
value-format="HH:mm:ss"
format="HH:mm:ss"
/>
</el-form-item>
<el-form-item label="每日结束时间:">
<el-time-picker
v-model="seckillTime.endTime"
placeholder="请选择时间"
value-format="HH:mm:ss"
format="HH:mm:ss"
/>
</el-form-item>
<el-form-item label="是否启用">
<el-radio-group v-model="seckillTime.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">不启用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogVisible = false"> </el-button>
<el-button type="primary" size="small" @click="saveOrUpdate()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/activity/seckillTime'
const defaultForm = {
id: '',
name: '',
startTime: '',
endTime: '',
status: 1
}
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
dialogVisible: false,
saveBtnDisabled: false,
seckillTime: defaultForm
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
add() {
this.dialogVisible = true
this.seckill = Object.assign({}, defaultForm)
},
edit(id) {
this.dialogVisible = true
this.fetchDataById(id)
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.seckill.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
api.save(this.seckillTime).then(response => {
// debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.dialogVisible = false
this.fetchData(1)
}
})
},
// 根据id更新记录
updateData() {
api.updateById(this.seckillTime).then(response => {
debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.dialogVisible = false
this.fetchData(1)
}
})
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
this.seckillTime = response.data
})
},
// 加载banner列表数据
fetchData() {
api.getList().then(
response => {
debugger
this.list = response.data
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
updateStatus(row) {
this.$confirm('是否要修改该状态?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
api.updateStatus(row.id, row.status).then(response => {
this.$message({
type: 'success',
message: '修改成功!'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: '取消修改'
})
this.getList()
})
}
}
}
</script>

View File

@@ -0,0 +1,31 @@
<template>
<div class="dashboard-container">
<div class="dashboard-text">name: {{ name }}</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'Dashboard',
computed: {
...mapGetters([
'name'
])
}
}
</script>
<style lang="scss" scoped>
.dashboard {
&-container {
margin: 30px;
}
&-text {
font-size: 30px;
line-height: 46px;
}
}
</style>

View File

@@ -0,0 +1,250 @@
<template>
<div class="login-container">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
auto-complete="on"
label-position="left"
>
<div class="title-container">
<h3 class="title">购物商城管理端</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="Password"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<el-button
:loading="loading"
type="primary"
style="width:100%;margin-bottom:30px;"
@click.native.prevent="handleLogin"
>Login
</el-button>
<div class="tips">
<span style="margin-right:20px;">username: admin</span>
<span> password: any</span>
</div>
</el-form>
</div>
</template>
<script>
import {validUsername} from '@/utils/validate'
export default {
name: 'Login',
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
return {
loginForm: {
username: 'admin',
password: '111111'
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true
}
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg: #283443;
$light_gray: #fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
</style>

View File

@@ -0,0 +1,231 @@
<template>
<div class="app-container">
<el-card class="operate-container" shadow="never">
<el-form inline>
<el-form-item label="下单时间">
<el-date-picker
v-model="searchObj.date"
type="date"
placeholder="选择开始日期"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="仓库">
<el-select v-model="searchObj.wareId" placeholder="请选择" @change="fetchLeaderList">
<el-option
v-for="ware in wareList"
:key="ware.id"
:label="ware.name"
:value="ware.id"
/>
</el-select>
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
<el-button type="default" @click="resetSearch">清空</el-button>
</el-form>
</el-card>
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" style="margin: 0 10px;">打印</el-button>
</el-card>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="数据正在加载......"
border
fit
highlight-current-row
>
<el-table-column
type="index"
label="序号"
width="50"
align="center"
/>
<el-table-column prop="leaderName" label="团长" width="90" />
<el-table-column prop="leaderPhone" label="团长电话" width="100" />
<el-table-column prop="takeName" label="提货点" width="130" />
<el-table-column prop="skuNum" label="商品数量" width="80" />
<el-table-column prop="driverName" label="司机" width="90" />
<el-table-column prop="driverPhone" label="司机电话" width="100" />
<el-table-column prop="deliverDate" label="配送日期" width="100" />
<el-table-column prop="createTime" label="配送时间" />
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<p>{{ scope.row.deliverStatus == 0 ? '未发货' : scope.row.deliverStatus == 1 ? '已发货' : '已收货' }}</p>
</template>
</el-table-column>
<el-table-column label="操作" width="140" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="fetchReceiveList(scope.row.leaderId)">发货明细</el-button>
<el-button v-if="scope.row.deliverStatus == 0" size="mini" type="text" @click="send(scope.row.leaderId)">
发货
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="发货清单" :visible.sync="receiveDialogVisible" width="40%">
<el-table
v-loading="listLoading"
:data="receiveList"
element-loading-text="数据正在加载......"
border
fit
highlight-current-row
>
<el-table-column
type="index"
label="序号"
width="50"
align="center"
/>
<el-table-column prop="skuName" label="商品" />
<el-table-column prop="skuNum" label="商品数量" width="130" />
</el-table>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="receiveDialogVisible = false"> </el-button>
</span>
</el-dialog>
<el-dialog title="发货" :visible.sync="sendDialogVisible" width="40%">
<el-form ref="flashPromotionForm" label-width="150px" size="small">
<el-form-item label="司机:">
<el-select v-model="driver" placeholder="请选择">
<el-option
v-for="driver in driverList"
:key="driver.id"
:label="driver.name"
:value="driver"
/>
</el-select>
</el-form-item>
<el-form-item>
<template slot-scope="scope">
<el-button size="small" @click="sendDialogVisible = false"> </el-button>
<el-button type="primary" size="small" @click="sendSave()"> </el-button>
</template>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/order/orderInfo'
import driverApi from '@/api/user/driver'
import wareApi from '@/api/sys/ware'
export default {
data() {
return {
listLoading: false, // 数据是否正在加载
list: null, // banner列表
searchObj: {}, // 查询表单对象
wareList: [],
leaderList: [],
receiveDialogVisible: false,
receiveList: [],
sendDialogVisible: false,
leaderId: null,
driverList: [],
driver: null
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchWareList()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 加载banner列表数据
fetchData() {
api.findReceiveList(this.searchObj.wareId, this.searchObj.date).then(
response => {
this.list = response.data
// 数据加载并绑定成功
this.listLoading = false
}
)
},
fetchWareList() {
wareApi.findAllList().then(response => {
this.wareList = response.data
})
},
fetchReceiveList(leaderid) {
this.receiveDialogVisible = true
api.findLeaderReceiveList(leaderid, this.searchObj.date).then(
response => {
this.receiveList = response.data
// 数据加载并绑定成功
this.listLoading = false
}
)
},
send(leaderId) {
this.sendDialogVisible = true
this.leaderId = leaderId
driverApi.findDriver(this.searchObj.wareId).then(response => {
this.driverList = response.data
})
},
sendSave() {
var orderDeliverVo = {
deliverDate: this.searchObj.date,
leaderId: this.leaderId,
driverId: this.driver.id,
driverName: this.driver.name,
driverPhone: this.driver.phone
}
debugger
api.deliver(orderDeliverVo).then(response => {
this.$message({
type: 'success',
message: '发货成功!'
})
this.fetchData()
})
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.fetchData()
}
}
}
</script>

View File

@@ -0,0 +1,260 @@
<template>
<div class="app-container">
<el-card class="operate-container" shadow="never">
<el-form inline>
<el-form-item label="订单号">
<el-input v-model="searchObj.outTradeNo" type="text" width="100" placeholder="订单号" clearable />
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="searchObj.orderStatus" clearable placeholder="订单状态">
<el-option value="UNPAID" label="未支付" />
<el-option value="WAITING_DELEVER" label="已支付,待发货" />
<el-option value="DELEVERED" label="已发货" />
<el-option value="WAITING_SIGN" label="待签收" />
<el-option value="FINISHED" label="已完结" />
<el-option value="CANCEL" label="已取消" />
</el-select>
</el-form-item>
<el-form-item label="下单时间">
<el-date-picker
v-model="searchObj.times"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="yyyy-MM-dd HH:mm:ss"
/>
</el-form-item>
<el-form-item label="仓库">
<el-select v-model="searchObj.wareId" placeholder="请选择" @change="fetchLeaderList">
<el-option
v-for="ware in wareList"
:key="ware.id"
:label="ware.name"
:value="ware.id"
/>
</el-select>
</el-form-item>
<el-form-item label="收货人">
<el-input v-model="searchObj.receiver" type="text" width="100" placeholder="收货人姓名/手机" clearable />
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
<el-button type="default" @click="resetSearch">清空</el-button>
</el-form>
</el-card>
<!-- 工具条 -->
<!-- <el-card class="operate-container" shadow="never">-->
<!-- <i class="el-icon-tickets" style="margin-top: 5px"></i>-->
<!-- <span style="margin-top: 5px">数据列表</span>-->
<!-- <el-button class="btn-add" size="mini" @click="add()">添加</el-button>-->
<!-- <el-button class="btn-add" size="mini" @click="batchDeliver()" style="margin: 0 10px;">批量发货</el-button>-->
<!-- </el-card>-->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
>
<el-table-column
label="序号"
width="55"
align="center"
>
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="orderNo" label="订单号" width="130" />
<el-table-column label="订单金额" width="140px">
<template slot-scope="scope">
<p>总额{{ scope.row.totalAmount }}</p>
<p>促销{{ scope.row.activityAmount }}</p>
<p>优惠券{{ scope.row.couponAmount }}</p>
</template>
</el-table-column>
<el-table-column label="收货人" width="160px">
<template slot-scope="scope">
<p>姓名{{ scope.row.receiverName }}</p>
<p>电话{{ scope.row.receiverPhone }}</p>
</template>
</el-table-column>
<el-table-column label="团长信息">
<template slot-scope="scope">
<p>姓名{{ scope.row.leaderName }}</p>
<p>提货点{{ scope.row.takeName }}</p>
</template>
</el-table-column>
<el-table-column label="操作时间" width="200px">
<template slot-scope="scope">
<p>创建{{ scope.row.createTime }}</p>
<p>支付{{ scope.row.paymentTime }}</p>
</template>
</el-table-column>
<el-table-column prop="param.orderStatusName" label="订单状态" width="100" />
<el-table-column label="操作" width="130" align="center">
<template slot-scope="scope">
<router-link :to="'/order/orderInfo/show/'+scope.row.id">
<el-button type="primary" size="mini">查看</el-button>
</router-link>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
</div>
</template>
<script>
import api from '@/api/order/orderInfo'
import leaderApi from '@/api/user/leader'
import wareApi from '@/api/sys/ware'
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
multipleSelection: [], // 批量选择中选择的记录列表
wareList: [],
leaderList: []
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
this.fetchWareList()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
// 时间搜索处理
if (this.searchObj.times && this.searchObj.times.length == 2) {
this.searchObj.createTimeBegin = this.searchObj.times[0]
this.searchObj.createTimeEnd = this.searchObj.times[1]
delete this.searchObj.times
}
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
fetchWareList() {
wareApi.findAllList().then(response => {
this.wareList = response.data
})
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.fetchData()
}
// 当表格复选框选项发生变化的时候触发
// handleSelectionChange(selection) {
// console.log('handleSelectionChange......')
// console.log(selection)
// this.multipleSelection = selection
// },
// batchDeliver() {
// if (this.multipleSelection.length === 0) {
// this.$message({
// type: 'warning',
// message: '请选择要发货的记录!'
// })
// return
// }
//
// this.multipleSelection.forEach(item => {
// if(item.orderStatus != 0) {
// this.$message({
// type: 'warning',
// message: '请选择未发货的记录,里面包含已发货记录!'
// })
// return
// }
// })
//
// this.$confirm('确认该发货吗, 是否确认?', '提示', {
// confirmButtonText: '确定',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => { // promise
// // 点击确定远程调用ajax
// var idList = []
// this.multipleSelection.forEach(item => {
// idList.push(item.id)
// })
// return api.batchDeliver(idList)
// }).then((response) => {
// if (response.code) {
// this.$message({
// type: 'success',
// message: '发货成功!'
// })
// }
// this.fetchData(this.page)
// }).catch(() => {
// this.$message({
// type: 'info',
// message: '已取消发货'
// })
// })
// }
}
}
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="app-container">
<h4>订单信息</h4>
<table class="table table-striped table-condenseda table-bordered" width="100%">
<tbody>
<tr>
<th width="15%">订单单号</th>
<td width="35%"><b style="font-size: 14px">{{ orderInfo.orderNo }}</b></td>
<th width="15%">下单时间</th>
<td width="35%">{{ orderInfo.createTime }}</td>
</tr>
<tr>
<th>支付时间</th>
<td>{{ orderInfo.paymentTime }}</td>
<th>支付方式</th>
<td>微信</td>
</tr>
<tr>
<th>订单状态</th>
<td>{{ orderInfo.param.orderStatusName }}</td>
<th />
<td />
</tr>
</tbody>
</table>
<h4>收货人信息</h4>
<table class="table table-striped table-condenseda table-bordered" width="100%">
<tbody>
<tr>
<th width="15%">姓名</th>
<td width="35%">{{ orderInfo.receiverName }}</td>
<th width="15%">电话</th>
<td width="35%">{{ orderInfo.receiverPhone }}</td>
</tr>
<tr>
<th>团长名称</th>
<td>{{ orderInfo.leaderName }}</td>
<th>提货点</th>
<td>{{ orderInfo.takeName }}</td>
</tr>
<tr>
<th>收货地址</th>
<td colspan="3">{{ orderInfo.receiverAddress }}</td>
</tr>
</tbody>
</table>
<h4>购物项信息</h4>
<el-table
v-loading="listLoading"
:data="orderInfo.orderItemList"
border
fit
highlight-current-row
>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="商品图片" width="250" align="center">
<template slot-scope="scope">
<img :src="scope.row.imgUrl" alt="scope.row.title" width="80px">
</template>
</el-table-column>
<el-table-column prop="skuName" label="商品名称" />
<el-table-column prop="skuPrice" label="价格" width="150" />
<el-table-column prop="skuNum" label="数量" width="100" />
</el-table>
<h4>支付信息</h4>
<table class="table table-striped table-condenseda table-bordered" width="100%">
<tbody>
<tr>
<th width="15%">商品总金额</th>
<td width="35%">{{ orderInfo.originalTotalAmount }}</td>
<th width="15%">返现</th>
<td width="35%">{{ orderInfo.activityAmount }}</td>
</tr>
<tr>
<th>使用优惠券</th>
<td>{{ orderInfo.couponAmount }}</td>
<th>实际支付</th>
<td>{{ orderInfo.totalAmount }}</td>
</tr>
</tbody>
</table>
<br>
<el-row>
<el-button @click="back">返回</el-button>
</el-row>
</div>
</template>
<script>
import api from '@/api/order/orderInfo'
export default {
data() {
return {
orderInfo: null
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.init()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
init() {
const id = this.$route.params.id
api.show(id).then(response => {
debugger
this.orderInfo = response.data
})
},
back() {
window.history.go(-1)
}
}
}
</script>

View File

@@ -0,0 +1,127 @@
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="属性名称">
<el-input v-model="attr.name" />
</el-form-item>
<el-form-item label="属性录入方式:">
<el-radio-group v-model="attr.inputType">
<el-radio :label="0">手动录入</el-radio>
<el-radio :label="1">列表中选择</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="属性值可选值:">
<el-input v-model="attr.selectList" :autosize="true" type="textarea" />
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import api from '@/api/product/attr'
const defaultForm = {
id: '',
name: '',
inputType: '',
selectList: '',
attrGroupId: ''
}
export default {
data() {
return {
attr: defaultForm,
saveBtnDisabled: false
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
// debugger
if (this.$route.params && this.$route.params.id) {
const id = this.$route.params.id
this.fetchDataById(id)
} else {
// 对象拓展运算符:拷贝对象,而不是赋值对象的引用
this.attr = { ...defaultForm }
this.attr.attrGroupId = this.$route.query.attrGroupId
}
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.attr.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
api.save(this.attr).then(response => {
// debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/attr/list/' + this.attr.attrGroupId })
}
})
},
// 根据id更新记录
updateData() {
api.updateById(this.attr).then(response => {
debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/attr/list/' + this.attr.attrGroupId })
}
})
},
back() {
this.$router.push({ path: '/product/attr/list/' + this.attr.attrGroupId })
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
// debugger
this.attr = response.data
})
}
}
}
</script>

View File

@@ -0,0 +1,187 @@
<template>
<div class="app-container">
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="add()">添加</el-button>
<el-button class="btn-add" size="mini" style="margin: 0 10px;" @click="removeRows()">批量删除</el-button>
<el-button class="btn-add" size="mini" @click="back()">返回</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="name" label="属性名" />
<el-table-column prop="inputType" label="属性录入方式">
<template slot-scope="scope">
{{ scope.row.inputType === 0 ? '手动录入' : '选择' }}
</template>
</el-table-column>
<el-table-column prop="selectList" label="可选值列表" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column prop="updateTime" label="更新时间" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<router-link :to="'/product/attr/edit/'+scope.row.id">
<el-button type="text" size="mini">修改</el-button>
</router-link>
<el-button type="text" size="mini" @click="removeDataById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import api from '@/api/product/attr'
export default {
data() {
return {
attrGroupId: this.$route.params.id,
listLoading: true, // 数据是否正在加载
list: null, // banner列表
multipleSelection: [] // 批量选择中选择的记录列表
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
add() {
this.$router.push({ path: '/product/attr/add', query: { attrGroupId: this.attrGroupId }})
},
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
api.getList(this.attrGroupId).then(
response => {
debugger
this.list = response.data
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
// 当表格复选框选项发生变化的时候触发
handleSelectionChange(selection) {
console.log('handleSelectionChange......')
console.log(selection)
this.multipleSelection = selection
},
// 批量删除
removeRows() {
console.log('removeRows......')
if (this.multipleSelection.length === 0) {
this.$message({
type: 'warning',
message: '请选择要删除的记录!'
})
return
}
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
// 遍历selection将id取出放入id列表
var idList = []
this.multipleSelection.forEach(item => {
idList.push(item.id)
// console.log(idList)
})
// 调用api
return api.removeRows(idList)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
back() {
this.$router.push({ path: '/product/attrGroup/list' })
}
}
}
</script>

View File

@@ -0,0 +1,122 @@
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="组名">
<el-input v-model="attrGroup.name" />
</el-form-item>
<el-form-item label="排序">
<el-input v-model="attrGroup.sort" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="attrGroup.remark" />
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import api from '@/api/product/attrGroup'
const defaultForm = {
id: '',
name: '',
sort: '',
remark: ''
}
export default {
data() {
return {
attrGroup: defaultForm,
saveBtnDisabled: false
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
// debugger
if (this.$route.params && this.$route.params.id) {
const id = this.$route.params.id
this.fetchDataById(id)
} else {
// 对象拓展运算符:拷贝对象,而不是赋值对象的引用
this.attrGroup = { ...defaultForm }
}
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.attrGroup.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
api.save(this.attrGroup).then(response => {
// debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/attrGroup/list' })
}
})
},
// 根据id更新记录
updateData() {
api.updateById(this.attrGroup).then(response => {
debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/attrGroup/list' })
}
})
},
back() {
this.$router.push({ path: '/product/attrGroup/list' })
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
// debugger
this.attrGroup = response.data
})
}
}
}
</script>

View File

@@ -0,0 +1,228 @@
<template>
<div class="app-container">
<el-card class="operate-container" shadow="never">
<el-form :inline="true">
<el-form-item label="输入搜索:">
<el-input v-model="searchObj.name" style="width: 203px" placeholder="关键字" />
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form>
</el-card>
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="add()">添加</el-button>
<el-button class="btn-add" size="mini" style="margin: 0 10px;" @click="removeRows()">批量删除</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="name" label="组名" />
<el-table-column prop="sort" label="排序" />
<el-table-column prop="remark" label="备注" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column prop="updateTime" label="更新时间" />
<el-table-column label="设置" width="200" align="center">
<template slot-scope="scope">
<router-link :to="'/product/attr/list/'+scope.row.id">
<el-button size="mini" icon="el-icon-edit">平台属性列表</el-button>
</router-link>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<router-link :to="'/product/attrGroup/edit/'+scope.row.id">
<el-button type="text" size="mini">修改</el-button>
</router-link>
<el-button type="text" size="mini" @click="removeDataById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
</div>
</template>
<script>
import api from '@/api/product/attrGroup'
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
multipleSelection: [] // 批量选择中选择的记录列表
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
add() {
this.$router.push({ path: '/product/attrGroup/add' })
},
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
debugger
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.fetchData()
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
// 当表格复选框选项发生变化的时候触发
handleSelectionChange(selection) {
console.log('handleSelectionChange......')
console.log(selection)
this.multipleSelection = selection
},
// 批量删除
removeRows() {
console.log('removeRows......')
if (this.multipleSelection.length === 0) {
this.$message({
type: 'warning',
message: '请选择要删除的记录!'
})
return
}
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
// 遍历selection将id取出放入id列表
var idList = []
this.multipleSelection.forEach(item => {
idList.push(item.id)
// console.log(idList)
})
// 调用api
return api.removeRows(idList)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped></style>

View File

@@ -0,0 +1,140 @@
<template>
<div class="app-container">
<el-form ref="categoryForm" :model="category" :rules="validateRules" label-width="120px">
<el-form-item label="分类名称" prop="name">
<el-input v-model="category.name" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="category.sort" />
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import api from '@/api/product/category'
const isNum = (rule, value, callback) => {
const age = /^[0-9]*$/
if (!age.test(value)) {
callback(new Error('只能为数字'))
} else {
callback()
}
}
const defaultForm = {
id: '',
name: '',
status: '',
sort: ''
}
export default {
data() {
return {
category: defaultForm,
saveBtnDisabled: false,
validateRules: {
name: [{ required: true, trigger: 'blur', message: '必须输入' }],
sort: [
{ required: true, decimal: 2, trigger: 'blur', message: '必须输入' },
{ validator: isNum, trigger: 'blur' }
]
}
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
// debugger
if (this.$route.params && this.$route.params.id) {
const id = this.$route.params.id
this.fetchDataById(id)
} else {
// 对象拓展运算符:拷贝对象,而不是赋值对象的引用
this.category = { ...defaultForm }
}
},
saveOrUpdate() {
this.$refs.categoryForm.validate(valid => {
if (valid) {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.category.id) {
this.saveData()
} else {
this.updateData()
}
}
})
},
// 新增
saveData() {
api.save(this.category).then(response => {
// debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/category/list' })
}
})
},
// 根据id更新记录
updateData() {
api.updateById(this.category).then(response => {
debugger
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/category/list' })
}
})
},
back() {
this.$router.push({ path: '/product/category/list' })
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
// debugger
this.category = response.data
})
}
}
}
</script>

View File

@@ -0,0 +1,219 @@
<template>
<div class="app-container">
<el-card class="operate-container" shadow="never">
<el-form :inline="true">
<el-form-item label="输入搜索:">
<el-input v-model="searchObj.name" style="width: 203px" placeholder="关键字" />
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form>
</el-card>
<!-- 工具条 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px" />
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" size="mini" @click="add()">添加</el-button>
<el-button class="btn-add" size="mini" style="margin: 0 10px;" @click="removeRows()">批量删除</el-button>
</el-card>
<!-- banner列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
label="序号"
width="70"
align="center"
>
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="name" label="分类名称" />
<el-table-column prop="sort" label="排序" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column prop="updateTime" label="更新时间" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<router-link :to="'/product/category/edit/'+scope.row.id">
<el-button type="text" size="mini">修改</el-button>
</router-link>
<el-button type="text" size="mini" @click="removeDataById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
</div>
</template>
<script>
import api from '@/api/product/category'
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
multipleSelection: [] // 批量选择中选择的记录列表
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
add() {
this.$router.push({ path: '/product/category/add' })
},
// 加载banner列表数据
fetchData(page = 1) {
console.log('翻页。。。' + page)
// 异步获取远程数据ajax
this.page = page
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
debugger
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.fetchData()
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
// 当表格复选框选项发生变化的时候触发
handleSelectionChange(selection) {
console.log('handleSelectionChange......')
console.log(selection)
this.multipleSelection = selection
},
// 批量删除
removeRows() {
console.log('removeRows......')
if (this.multipleSelection.length === 0) {
this.$message({
type: 'warning',
message: '请选择要删除的记录!'
})
return
}
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定远程调用ajax
// 遍历selection将id取出放入id列表
var idList = []
this.multipleSelection.forEach(item => {
idList.push(item.id)
// console.log(idList)
})
// 调用api
return api.removeRows(idList)
}).then((response) => {
this.fetchData(this.page)
if (response.code) {
this.$message({
type: 'success',
message: '删除成功!'
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped></style>

View File

@@ -0,0 +1,433 @@
<template>
<div class="app-container">
<el-form label-width="120px" size="small">
<div style="background-color:#E0E0E0;width: 100%;padding: 0 10px;margin: 10px 0;"><h3>基本信息</h3></div>
<el-form-item label="sku名称">
<el-input v-model="skuInfo.skuName" />
</el-form-item>
<el-form-item label="商品分类" prop="tmId">
<el-select v-model="skuInfo.categoryId" placeholder="请选择">
<el-option
v-for="category in categoryList"
:key="category.id"
:label="category.name"
:value="category.id"
/>
</el-select>
</el-form-item>
<el-form-item label="SKU编号">
<el-input v-model="skuInfo.skuCode" />
</el-form-item>
<el-form-item label="商品售价:">
<el-input v-model="skuInfo.price" />
</el-form-item>
<el-form-item label="市场价:">
<el-input v-model="skuInfo.marketPrice" />
</el-form-item>
<el-form-item label="是否新人专享:">
<el-radio-group v-model="skuInfo.isNewPerson">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序">
<el-input v-model="skuInfo.sort" />
</el-form-item>
<div style="background-color:#E0E0E0;width: 100%;padding: 0 10px;margin: 10px 0;"><h3>平台属性</h3></div>
<el-form-item label="属性分组" prop="tmId">
<el-select v-model="skuInfo.attrGroupId" placeholder="请选择" @change="selectAttrChange">
<el-option
v-for="attrGroup in attrGroupList"
:key="attrGroup.id"
:label="attrGroup.name"
:value="attrGroup.id"
/>
</el-select>
</el-form-item>
<el-form-item label="平台属性:">
<el-card shadow="never" class="cardBg">
<div v-for="(item,index) in attrList" :key="item.id" :class="{littleMarginTop:index!==0}">
<div class="paramInputLabel">{{ item.name }}:</div>
<el-select
v-if="item.inputType===1"
v-model="attrList[index].value"
class="paramInput"
style="width: 300px"
>
<el-option
v-for="item in getParamSelectList(item.selectList)"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<el-input v-else v-model="attrList[index].value" class="paramInput" style="width: 300px" />
</div>
</el-card>
</el-form-item>
<div style="background-color:#E0E0E0;width: 100%;padding: 0 10px;margin: 10px 0;"><h3>商品图片</h3></div>
<el-form-item label="图片上传">
<el-upload
:on-success="onUploadSuccess"
:before-upload="beforeUpload"
:on-preview="onUploadPreview"
:on-remove="onUploadRemove"
:multiple="true"
:action="BASE_API+'/admin/product/fileUpload'"
class="upload-demo"
list-type="picture-card"
:file-list="fileList"
>
<i class="el-icon-plus" />
<div slot="tip" class="el-upload__tip">只能上传jpg/png/gif文件且不超过2MB</div>
</el-upload>
</el-form-item>
<div style="background-color:#E0E0E0;width: 100%;padding: 0 10px;margin: 10px 0;"><h3>商品库存</h3></div>
<el-form-item label="商品库存:">
<el-input v-model="skuInfo.stock" />
</el-form-item>
<el-form-item label="商品预警库存:">
<el-input v-model="skuInfo.lowStock" />
</el-form-item>
<el-form-item label="限购个数:">
<el-input v-model="skuInfo.perLimit" />
</el-form-item>
<!-- <div style="background-color:#E0E0E0;width: 100%;padding: 0 10px;margin: 10px 0;"><h3>商品详情</h3></div>-->
<!-- <el-form-item label="商品详情">-->
<!-- <el-input v-model="skuInfo.skuDetail.detailHtml" type="textarea" :rows="10"></el-input>-->
<!-- </el-form-item>-->
<div style="background-color:#E0E0E0;width: 100%;padding: 0 10px;margin: 10px 0;"><h3>商品海报</h3></div>
<el-form-item label="上传海报">
<el-upload
:on-success="onUploadPosterSuccess"
:before-upload="beforeUpload"
:on-preview="onUploadPreview"
:on-remove="onUploadPosterRemove"
:multiple="true"
:action="BASE_API+'/admin/product/fileUpload'"
class="upload-demo"
list-type="picture-card"
:file-list="filePosterList"
>
<i class="el-icon-plus" />
<div slot="tip" class="el-upload__tip">只能上传jpg/png/gif文件且不超过2MB</div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveOrUpdate">保存</el-button>
<el-button @click="back">返回</el-button>
</el-form-item>
</el-form>
<!--图片预览-->
<el-dialog :visible.sync="dialogImageVisible">
<img :src="dialogImageUrl" width="100%" alt="">
</el-dialog>
</div>
</template>
<script>
import api from '@/api/product/skuInfo'
import categoryApi from '@/api/product/category'
import attrGroupApi from '@/api/product/attrGroup'
import attrApi from '@/api/product/attr'
const defaultForm = {
id: '',
categoryId: '',
attrGroupId: '',
skuType: 0,
skuName: '',
perLimit: 1,
isNewPerson: 0,
sort: '',
imgUrl: '',
skuCode: '',
price: '',
marketPrice: '',
stock: '',
lowStock: '',
skuAttrValueList: [],
skuPosterList: [],
skuImagesList: []
}
export default {
data() {
return {
// 接口API地址
BASE_API: 'http://localhost:8203',
skuInfo: defaultForm,
saveBtnDisabled: false,
categoryList: [],
attrGroupList: [],
attrList: [],
fileList: [],
filePosterList: [],
// 图片预览区域url
dialogImageUrl: '',
// 图片预览对话框
dialogImageVisible: false
}
},
// 监听器
watch: {
$route(to, from) {
console.log('路由变化......')
console.log(to)
console.log(from)
this.init()
}
},
// 生命周期方法(在路由切换,组件不变的情况下不会被调用)
created() {
console.log('form created ......')
this.init()
},
methods: {
// 表单初始化
init() {
// debugger
if (this.$route.params && this.$route.params.id) {
const id = this.$route.params.id
this.fetchDataById(id)
} else {
// 对象拓展运算符:拷贝对象,而不是赋值对象的引用
this.skuInfo = { ...defaultForm }
}
this.fetchCategoryList()
this.fetchAttrGroupList()
},
fetchCategoryList() {
categoryApi.findAllList().then(response => {
this.categoryList = response.data
})
},
fetchAttrGroupList() {
attrGroupApi.findAllList().then(response => {
this.attrGroupList = response.data
})
},
fetchAttrList(attrGroupId) {
attrApi.getList(attrGroupId).then(response => {
this.attrList = response.data
// 修改属性回显
debugger
if (this.skuInfo.skuAttrValueList.length > 0) {
this.attrList.forEach(attrItem => {
this.skuInfo.skuAttrValueList.forEach(valueItem => {
if (attrItem.id === valueItem.attrId) {
attrItem.value = valueItem.attrValue
}
})
})
}
})
},
getParamSelectList(selectList) {
return selectList.split(',')
},
selectAttrChange(attrGroupId) {
this.fetchAttrList(attrGroupId)
},
saveOrUpdate() {
// this.saveBtnDisabled = true // 防止表单重复提交
if (!this.skuInfo.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
const attrListArray = []
this.attrList.forEach(item => {
attrListArray.push({
attrId: item.id,
attrName: item.name,
attrValue: item.value
})
})
this.skuInfo.skuAttrValueList = attrListArray
if (this.skuInfo.skuImagesList.length > 0) {
this.skuInfo.imgUrl = this.skuInfo.skuImagesList[0].imgUrl
}
api.save(this.skuInfo).then(response => {
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/skuInfo/list' })
this.saveBtnDisabled = false
}
})
},
// 根据id更新记录
updateData() {
debugger
const attrListArray = []
this.attrList.forEach(item => {
attrListArray.push({
attrId: item.id,
attrName: item.name,
attrValue: item.value
})
})
this.skuInfo.skuAttrValueList = attrListArray
if (this.skuInfo.skuImagesList.length > 0) {
this.skuInfo.imgUrl = this.skuInfo.skuImagesList[0].imgUrl
}
api.updateById(this.skuInfo).then(response => {
if (response.code) {
this.$message({
type: 'success',
message: response.message
})
this.$router.push({ path: '/product/skuInfo/list' })
}
})
},
back() {
this.$router.push({ path: '/product/skuInfo/list' })
},
// 根据id查询记录
fetchDataById(id) {
api.getById(id).then(response => {
debugger
this.skuInfo = response.data
// 平台属性回显
this.fetchAttrList(this.skuInfo.attrGroupId)
// 图片回显
this.skuInfo.skuImagesList.forEach(item => {
const obj = new Object()
obj.url = item.imgUrl
this.fileList.push(obj)
})
this.skuInfo.skuPosterList.forEach(item => {
const obj = new Object()
obj.url = item.imgUrl
this.filePosterList.push(obj)
})
})
},
// 文件上传限制条件
beforeUpload(file) {
const isJPG = file.type === 'image/jpeg'
const isPNG = file.type === 'image/png'
const isGIF = file.type === 'image/gif'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG && !isPNG && !isGIF) {
this.$message.error('上传头像图片只能是 JPG、PNG 或 GIF 格式!')
return false
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
return false
}
return true
},
// 上传图片成功的回调
onUploadSuccess(res, file) {
// 填充上传文件列表
this.skuInfo.skuImagesList.push({
imgName: file.name,
imgUrl: file.response.data
})
},
// 上传的文件预览
onUploadPreview(file) {
this.dialogImageUrl = file.url
this.dialogImageVisible = true
},
// 删除上传的文件
onUploadRemove(file, fileList) {
var list = []
this.skuInfo.skuImagesList.forEach(item => {
if (file.url != item.imgUrl) {
list.push(item)
}
})
this.skuInfo.skuImagesList = list
},
// -----------------
// 上传图片成功的回调
onUploadPosterSuccess(res, file) {
debugger
// 填充上传文件列表
this.skuInfo.skuPosterList.push({
imgName: file.name,
imgUrl: file.response.data
})
},
// 删除上传的文件
onUploadPosterRemove(file, fileList) {
var list = []
this.skuInfo.skuPosterList.forEach(item => {
if (file.url != item.imgUrl) {
list.push(item)
}
})
this.skuInfo.skuPosterList = list
}
}
}
</script>
<style scoped>
.littleMarginTop {
margin-top: 10px;
}
.paramInput {
width: 250px;
}
.paramInputLabel {
display: inline-block;
width: 100px;
text-align: right;
padding-right: 10px
}
.cardBg {
background: #F8F9FC;
}
</style>

Some files were not shown because too many files have changed in this diff Show More