Unverified Commit b68151cb authored by Menethil's avatar Menethil Committed by GitHub
Browse files

Merge pull request #1 from linlinjava/master

update
parents 126d027a 02679509
import request from '@/utils/request'
export function listCart(query) {
return request({
url: '/cart/list',
method: 'get',
params: query
})
}
export function createCart(data) {
return request({
url: '/cart/create',
method: 'post',
data
})
}
export function readCart(data) {
return request({
url: '/cart/read',
method: 'get',
data
})
}
export function updateCart(data) {
return request({
url: '/cart/update',
method: 'post',
data
})
}
export function deleteCart(data) {
return request({
url: '/cart/delete',
method: 'post',
data
})
}
import request from '@/utils/request'
export function listGoodsAttribute(query) {
return request({
url: '/goods-attribute/list',
method: 'get',
params: query
})
}
export function createGoodsAttribute(data) {
return request({
url: '/goods-attribute/create',
method: 'post',
data
})
}
export function readGoodsAttribute(data) {
return request({
url: '/goods-attribute/read',
method: 'get',
data
})
}
export function updateGoodsAttribute(data) {
return request({
url: '/goods-attribute/update',
method: 'post',
data
})
}
export function deleteGoodsAttribute(data) {
return request({
url: '/goods-attribute/delete',
method: 'post',
data
})
}
import request from '@/utils/request'
export function listGoodsSpecification(query) {
return request({
url: '/goods-specification/list',
method: 'get',
params: query
})
}
export function createGoodsSpecification(data) {
return request({
url: '/goods-specification/create',
method: 'post',
data
})
}
export function readGoodsSpecification(data) {
return request({
url: '/goods-specification/read',
method: 'get',
data
})
}
export function updateGoodsSpecification(data) {
return request({
url: '/goods-specification/update',
method: 'post',
data
})
}
export function deleteGoodsSpecification(data) {
return request({
url: '/goods-specification/delete',
method: 'post',
data
})
}
export function listGoodsSpecificationVo(query) {
return request({
url: '/goods-specification/volist',
method: 'get',
params: query
})
}
......@@ -8,7 +8,15 @@ export function listGoods(query) {
})
}
export function createGoods(data) {
export function deleteGoods(data) {
return request({
url: '/goods/delete',
method: 'post',
data
})
}
export function publishGoods(data) {
return request({
url: '/goods/create',
method: 'post',
......@@ -16,15 +24,15 @@ export function createGoods(data) {
})
}
export function readGoods(data) {
export function detailGoods(id) {
return request({
url: '/goods/read',
url: '/goods/detail',
method: 'get',
data
params: { id }
})
}
export function updateGoods(data) {
export function editGoods(data) {
return request({
url: '/goods/update',
method: 'post',
......@@ -32,10 +40,9 @@ export function updateGoods(data) {
})
}
export function deleteGoods(data) {
export function listCatAndBrand() {
return request({
url: '/goods/delete',
method: 'post',
data
url: '/goods/catAndBrand',
method: 'get'
})
}
......@@ -8,33 +8,17 @@ export function listOrder(query) {
})
}
export function createOrder(data) {
export function shipOrder(data) {
return request({
url: '/order/create',
url: '/order/ship',
method: 'post',
data
})
}
export function readOrder(data) {
export function refundOrder(data) {
return request({
url: '/order/read',
method: 'get',
data
})
}
export function updateOrder(data) {
return request({
url: '/order/update',
method: 'post',
data
})
}
export function deleteOrder(data) {
return request({
url: '/order/delete',
url: '/order/refund',
method: 'post',
data
})
......
import request from '@/utils/request'
export function listProduct(query) {
return request({
url: '/product/list',
method: 'get',
params: query
})
}
export function createProduct(data) {
return request({
url: '/product/create',
method: 'post',
data
})
}
export function readProduct(data) {
return request({
url: '/product/read',
method: 'get',
data
})
}
export function updateProduct(data) {
return request({
url: '/product/update',
method: 'post',
data
})
}
export function deleteProduct(data) {
return request({
url: '/product/delete',
method: 'post',
data
})
}
import request from '@/utils/request'
export function statUser(query) {
return request({
url: '/stat/user',
method: 'get',
params: query
})
}
export function statOrder(query) {
return request({
url: '/stat/order',
method: 'get',
params: query
})
}
export function statGoods(query) {
return request({
url: '/stat/goods',
method: 'get',
params: query
})
}
......@@ -60,3 +60,6 @@ export function deleteStorage(data) {
data
})
}
const uploadPath = process.env.OS_API + '/storage/create'
export { uploadPath }
const vueSticky = {}
let listenAction
vueSticky.install = Vue => {
Vue.directive('sticky', {
inserted(el, binding) {
const params = binding.value || {}
const stickyTop = params.stickyTop || 0
const zIndex = params.zIndex || 1000
const elStyle = el.style
elStyle.position = '-webkit-sticky'
elStyle.position = 'sticky'
// if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
// if (~elStyle.position.indexOf('sticky')) {
// elStyle.top = `${stickyTop}px`;
// elStyle.zIndex = zIndex;
// return
// }
const elHeight = el.getBoundingClientRect().height
const elWidth = el.getBoundingClientRect().width
elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
const parentElm = el.parentNode || document.documentElement
const placeholder = document.createElement('div')
placeholder.style.display = 'none'
placeholder.style.width = `${elWidth}px`
placeholder.style.height = `${elHeight}px`
parentElm.insertBefore(placeholder, el)
let active = false
const getScroll = (target, top) => {
const prop = top ? 'pageYOffset' : 'pageXOffset'
const method = top ? 'scrollTop' : 'scrollLeft'
let ret = target[prop]
if (typeof ret !== 'number') {
ret = window.document.documentElement[method]
}
return ret
}
const sticky = () => {
if (active) {
return
}
if (!elStyle.height) {
elStyle.height = `${el.offsetHeight}px`
}
elStyle.position = 'fixed'
elStyle.width = `${elWidth}px`
placeholder.style.display = 'inline-block'
active = true
}
const reset = () => {
if (!active) {
return
}
elStyle.position = ''
placeholder.style.display = 'none'
active = false
}
const check = () => {
const scrollTop = getScroll(window, true)
const offsetTop = el.getBoundingClientRect().top
if (offsetTop < stickyTop) {
sticky()
} else {
if (scrollTop < elHeight + stickyTop) {
reset()
}
}
}
listenAction = () => {
check()
}
window.addEventListener('scroll', listenAction)
},
unbind() {
window.removeEventListener('scroll', listenAction)
}
})
}
export default vueSticky
import waves from './waves'
const install = function(Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves
.waves-ripple {
position: absolute;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.15);
background-clip: padding-box;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 1;
}
.waves-ripple.z-active {
opacity: 0;
-webkit-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}
\ No newline at end of file
import './waves.css'
export default{
bind(el, binding) {
el.addEventListener('click', e => {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit点击位置扩散center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
}, customOpts)
const target = opts.ele
if (target) {
target.style.position = 'relative'
target.style.overflow = 'hidden'
const rect = target.getBoundingClientRect()
let ripple = target.querySelector('.waves-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'waves-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'waves-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
break
default:
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.color
ripple.className = 'waves-ripple z-active'
return false
}
}, false)
}
}
......@@ -14,7 +14,7 @@ function hasPermission(roles, permissionRoles) {
return roles.some(role => permissionRoles.indexOf(role) >= 0)
}
const whiteList = ['/login', '/authredirect']// no redirect whitelist
const whiteList = ['/login']// no redirect whitelist
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
......@@ -33,7 +33,7 @@ router.beforeEach((to, from, next) => {
})
}).catch(() => {
store.dispatch('FedLogOut').then(() => {
Message.error('Verification failed, please login again')
Message.error('验证失败,请输入正确的用户名和密码')
next({ path: '/login' })
})
})
......
......@@ -29,7 +29,6 @@ import Layout from '../views/layout/Layout'
**/
export const constantRouterMap = [
{ path: '/login', component: _import('login/index'), hidden: true },
{ path: '/authredirect', component: _import('login/authredirect'), hidden: true },
{ path: '/404', component: _import('error/404'), hidden: true },
{ path: '/401', component: _import('error/401'), hidden: true },
{
......@@ -66,8 +65,7 @@ export const asyncRouterMap = [
{ path: 'address', component: _import('user/address'), name: 'address', meta: { title: '收货地址', noCache: true }},
{ path: 'collect', component: _import('user/collect'), name: 'collect', meta: { title: '会员收藏', noCache: true }},
{ path: 'footprint', component: _import('user/footprint'), name: 'footprint', meta: { title: '会员足迹', noCache: true }},
{ path: 'history', component: _import('user/history'), name: 'history', meta: { title: '搜索历史', noCache: true }},
{ path: 'cart', component: _import('user/cart'), name: 'cart', meta: { title: '购物车', noCache: true }}
{ path: 'history', component: _import('user/history'), name: 'history', meta: { title: '搜索历史', noCache: true }}
]
},
......@@ -100,13 +98,13 @@ export const asyncRouterMap = [
icon: 'chart'
},
children: [
{ path: 'goods', component: _import('goods/goods'), name: 'goods', meta: { title: '商品管理', noCache: true }},
{ path: 'attribute', component: _import('goods/attribute'), name: 'attribute', meta: { title: '商品参数', noCache: true }},
{ path: 'specification', component: _import('goods/specification'), name: 'specification', meta: { title: '商品规格', noCache: true }},
{ path: 'product', component: _import('goods/product'), name: 'product', meta: { title: '货品管理', noCache: true }},
{ path: 'comment', component: _import('goods/comment'), name: 'comment', meta: { title: '用户评论', noCache: true }}
{ path: 'list', component: _import('goods/list'), name: 'goodsList', meta: { title: '商品列表', noCache: true }},
{ path: 'create', component: _import('goods/create'), name: 'goodsCreate', meta: { title: '商品上架', noCache: true }},
{ path: 'edit', component: _import('goods/edit'), name: 'goodsEdit', meta: { title: '商品编辑', noCache: true }, hidden: true },
{ path: 'comment', component: _import('goods/comment'), name: 'goodsComment', meta: { title: '商品评论', noCache: true }}
]
},
{
path: '/promotion',
component: Layout,
......@@ -121,6 +119,7 @@ export const asyncRouterMap = [
{ path: 'topic', component: _import('promotion/topic'), name: 'topic', meta: { title: '专题管理', noCache: true }}
]
},
{
path: '/sys',
component: Layout,
......@@ -136,5 +135,21 @@ export const asyncRouterMap = [
]
},
{
path: '/stat',
component: Layout,
redirect: 'noredirect',
name: 'statManage',
meta: {
title: '统计',
icon: 'chart'
},
children: [
{ path: 'user', component: _import('stat/user'), name: 'statUser', meta: { title: '用户统计', noCache: true }},
{ path: 'order', component: _import('stat/order'), name: 'statOrder', meta: { title: '订单统计', noCache: true }},
{ path: 'goods', component: _import('stat/goods'), name: 'statGoods', meta: { title: '商品统计', noCache: true }}
]
},
{ path: '*', redirect: '/404', hidden: true }
]
......@@ -26,22 +26,32 @@ service.interceptors.request.use(config => {
service.interceptors.response.use(
response => {
const res = response.data
if (res.errno === 502) {
MessageBox.alert('系统内部错误,请联系管理员维护', '错误', {
if (res.errno === 501) {
MessageBox.alert('系统未登录,请重新登录', '未登录', {
confirmButtonText: '确定',
type: 'error'
})
return Promise.reject('error')
} else if (res.errno !== 0) {
MessageBox.alert('超时自动退出系统,请重新登录', '已退出', {
confirmButtonText: '重新登录',
type: 'error'
}).then(() => {
store.dispatch('FedLogOut').then(() => {
location.reload()
})
})
return Promise.reject('error')
} else if (res.errno === 502) {
MessageBox.alert('系统内部错误,请联系管理员维护', '错误', {
confirmButtonText: '确定',
type: 'error'
})
return Promise.reject('error')
} else if (res.errno === 503) {
MessageBox.alert('请求业务目前未支持', '警告', {
confirmButtonText: '确定',
type: 'error'
})
return Promise.reject('error')
} else if (res.errno !== 0) {
// 非5xx的错误属于业务错误,留给具体页面处理
return Promise.reject(response)
} else {
return response
}
......
<template>
<div class="app-container calendar-list-container">
<!-- 查询和其他操作 -->
<div class="filter-container">
<el-input clearable class="filter-item" style="width: 200px;" placeholder="请输入商品ID" v-model="listQuery.goodsId">
</el-input>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">查找</el-button>
<el-button class="filter-item" type="primary" @click="handleCreate" icon="el-icon-edit">添加</el-button>
<el-button class="filter-item" type="primary" :loading="downloadLoading" v-waves icon="el-icon-download" @click="handleDownload">导出</el-button>
</div>
<!-- 查询结果 -->
<el-table size="small" :data="list" v-loading="listLoading" element-loading-text="正在查询中。。。" border fit highlight-current-row>
<el-table-column align="center" width="150px" label="商品参数ID" prop="id" sortable>
</el-table-column>
<el-table-column align="center" min-width="100px" label="商品ID" prop="goodsId">
</el-table-column>
<el-table-column align="center" min-width="100px" label="商品参数名称" prop="attribute">
</el-table-column>
<el-table-column align="center" min-width="200px" label="商品参数值" prop="value">
</el-table-column>
<el-table-column align="center" label="操作" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listQuery.page"
:page-sizes="[10,20,30,50]" :page-size="listQuery.limit" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
<!-- 添加或修改对话框 -->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form :rules="rules" ref="dataForm" :model="dataForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="商品ID" prop="goodsId">
<el-input v-model="dataForm.goodsId"></el-input>
</el-form-item>
<el-form-item label="商品参数名" prop="attribute">
<el-input v-model="dataForm.attribute"></el-input>
</el-form-item>
<el-form-item label="商品参数值" prop="value">
<el-input v-model="dataForm.value"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">确定</el-button>
<el-button v-else type="primary" @click="updateData">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listGoodsAttribute, createGoodsAttribute, updateGoodsAttribute, deleteGoodsAttribute } from '@/api/goods-attribute'
import waves from '@/directive/waves' // 水波纹指令
export default {
name: 'GoodsAttribute',
directives: {
waves
},
data() {
return {
list: null,
total: null,
listLoading: true,
listQuery: {
page: 1,
limit: 10,
goodsId: undefined,
sort: '+id'
},
dataForm: {
id: undefined,
goodsId: undefined,
attribute: undefined,
value: undefined
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '创建'
},
rules: {
goodsId: [{ required: true, message: '商品ID不能为空', trigger: 'blur' }],
attribute: [{ required: true, message: '商品参数名称不能为空', trigger: 'blur' }],
value: [{ required: true, message: '商品参数值不能为空', trigger: 'blur' }]
},
downloadLoading: false
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
listGoodsAttribute(this.listQuery).then(response => {
this.list = response.data.data.items
this.total = response.data.data.total
this.listLoading = false
}).catch(() => {
this.list = []
this.total = 0
this.listLoading = false
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.limit = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.page = val
this.getList()
},
resetForm() {
this.dataForm = {
id: undefined,
goodsId: undefined,
attribute: undefined,
value: undefined
}
},
handleCreate() {
this.resetForm()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createGoodsAttribute(this.dataForm).then(response => {
this.list.unshift(response.data.data)
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.dataForm = Object.assign({}, row)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
updateGoodsAttribute(this.dataForm).then(() => {
for (const v of this.list) {
if (v.id === this.dataForm.id) {
const index = this.list.indexOf(v)
this.list.splice(index, 1, this.dataForm)
break
}
}
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
deleteGoodsAttribute(row).then(response => {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['商品参数ID', '商品ID', '商品参数名称', '商品参数值']
const filterVal = ['id', 'goodsId', 'attribute', 'value']
excel.export_json_to_excel2(tHeader, this.list, filterVal, '商品参数信息')
this.downloadLoading = false
})
}
}
}
</script>
......@@ -7,33 +7,33 @@
</el-input>
<el-input clearable class="filter-item" style="width: 200px;" placeholder="请输入商品ID" v-model="listQuery.valueId">
</el-input>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">查找</el-button>
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
<el-button class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">添加</el-button>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-download" @click="handleDownload" :loading="downloadLoading">导出</el-button>
<el-button class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload" :loading="downloadLoading">导出</el-button>
</div>
<!-- 查询结果 -->
<el-table size="small" :data="list" v-loading="listLoading" element-loading-text="正在查询中。。。" border fit highlight-current-row>
<el-table-column align="center" width="150px" label="评论ID" prop="id" sortable>
<el-table-column align="center" label="用户ID" prop="userId">
</el-table-column>
<el-table-column align="center" width="100px" label="用户ID" prop="userId">
<el-table-column align="center" label="商品ID" prop="valueId">
</el-table-column>
<el-table-column align="center" width="100px" label="商品ID" prop="valueId">
<el-table-column align="center" label="评论内容" prop="content">
</el-table-column>
<el-table-column align="center" min-width="200px" label="评论内容" prop="content">
</el-table-column>
<el-table-column align="center" min-width="200px" label="评论图片" prop="picUrls">
<el-table-column align="center" label="评论图片" prop="picUrls">
<template slot-scope="scope">
<img v-for="item in scope.row.picUrls" :key="item" :src="item" width="40"/>
</template>
</el-table-column>
<el-table-column align="center" min-width="100px" label="时间" prop="addTime">
<el-table-column align="center" label="时间" prop="addTime">
</el-table-column>
<el-table-column align="center" label="操作" width="250" class-name="small-padding fixed-width">
<el-table-column align="center" label="操作" width="200" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
......@@ -98,13 +98,9 @@
<script>
import { listComment, createComment, updateComment, deleteComment } from '@/api/comment'
import { createStorage } from '@/api/storage'
import waves from '@/directive/waves' // 水波纹指令
export default {
name: 'Comment',
directives: {
waves
},
data() {
return {
list: undefined,
......@@ -115,7 +111,8 @@ export default {
limit: 20,
userId: undefined,
valueId: undefined,
sort: '+id'
sort: 'add_time',
order: 'desc'
},
dataForm: {
id: undefined,
......@@ -123,8 +120,7 @@ export default {
valueId: undefined,
content: undefined,
hasPicture: false,
picUrls: [],
addTime: undefined
picUrls: []
},
dialogFormVisible: false,
dialogStatus: '',
......@@ -174,8 +170,7 @@ export default {
userId: undefined,
valueId: undefined,
content: undefined,
picUrls: [],
addTime: undefined
picUrls: []
}
},
handleCreate() {
......
<template>
<div class="app-container calendar-list-container">
<el-card class="box-card">
<h3>商品介绍</h3>
<el-form :rules="rules" ref="goods" :model="goods" label-width="150px">
<el-form-item label="商品编号" prop="goodsSn">
<el-input v-model="goods.goodsSn"></el-input>
</el-form-item>
<el-form-item label="商品名称" prop="name">
<el-input v-model="goods.name"></el-input>
</el-form-item>
<el-form-item label="专柜价格" prop="counterPrice">
<el-input v-model="goods.counterPrice" placeholder="0.00">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="当前价格" prop="retailPrice">
<el-input v-model="goods.retailPrice" placeholder="0.00">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="是否新品" prop="isNew">
<el-radio-group v-model="goods.isNew">
<el-radio :label="true">新品</el-radio>
<el-radio :label="false">非新品</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否热卖" prop="isHot">
<el-radio-group v-model="goods.isHot">
<el-radio :label="false">普通</el-radio>
<el-radio :label="true">热卖</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否在售" prop="isOnSale">
<el-radio-group v-model="goods.isOnSale">
<el-radio :label="true">在售</el-radio>
<el-radio :label="false">未售</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品图片">
<el-upload class="avatar-uploader" :action="uploadPath" list-type="picture-card" :show-file-list="false" accept=".jpg,.jpeg,.png,.gif" :on-success="uploadPicUrl">
<img v-if="goods.picUrl" :src="goods.picUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="宣传画廊">
<el-upload :action="uploadPath" :limit="5" multiple accept=".jpg,.jpeg,.png,.gif" list-type="picture-card" :on-exceed="uploadOverrun" :on-success="handleGalleryUrl" :on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
</el-form-item>
<el-form-item label="商品单位">
<el-input v-model="goods.unit" placeholder="件 / 个 / 盒"></el-input>
</el-form-item>
<el-form-item label="关键字">
<el-tag :key="tag" v-for="tag in keywords" closable type="primary" @close="handleClose(tag)">
{{tag}}
</el-tag>
<el-input class="input-new-keyword" v-if="newKeywordVisible" v-model="newKeyword" ref="newKeywordInput" size="small" @keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm">
</el-input>
<el-button v-else class="button-new-keyword" size="small" type="primary" @click="showInput">+ 增加</el-button>
</el-form-item>
<el-form-item label="所属分类">
<el-cascader expand-trigger="hover" :options="categoryList" @change="handleCategoryChange"></el-cascader>
</el-form-item>
<el-form-item label="所属品牌商">
<el-select v-model="goods.brandId">
<el-option v-for="item in brandList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品简介">
<el-input v-model="goods.brief"></el-input>
</el-form-item>
<el-form-item label="商品详细介绍">
<editor :init="editorInit" v-model="goods.desc"></editor>
</el-form-item>
</el-form>
</el-card>
<el-card class="box-card">
<h3>商品规格</h3>
<el-row type="flex" align="middle" :gutter="20" style="padding:20px 0;">
<el-col :span="10" >
<el-radio-group v-model="multipleSpec" @change="specChanged">
<el-radio-button :label="false" >默认标准规格</el-radio-button>
<el-radio-button :label="true">多规格支持</el-radio-button>
</el-radio-group>
</el-col>
<el-col :span="10" v-if="multipleSpec">
<el-button :plain="true" @click="handleSpecificationShow" type="primary">添加</el-button>
</el-col>
</el-row>
<el-table :data="specifications">
<el-table-column property="specification" label="规格名" ></el-table-column>
<el-table-column property="value" label="规格值" >
<template slot-scope="scope">
<el-tag type="primary">
{{scope.row.value}}
</el-tag>
</template>
</el-table-column>
<el-table-column property="picUrl" label="规格图片">
<template slot-scope="scope">
<img :src="scope.row.picUrl" width="40" v-if="scope.row.picUrl"/>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="250" class-name="small-padding fixed-width" v-if="multipleSpec">
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="handleSpecificationDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="设置规格" :visible.sync="specVisiable">
<el-form :rules="rules" ref="specForm" :model="specForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="规格名" prop="specification">
<el-input v-model="specForm.specification"></el-input>
</el-form-item>
<el-form-item label="规格值" prop="value">
<el-input v-model="specForm.value"></el-input>
</el-form-item>
<el-form-item label="规格图片" prop="picUrl">
<el-upload class="avatar-uploader" :action='uploadPath' list-type="picture-card" :show-file-list="false" accept=".jpg,.jpeg,.png,.gif" :on-success="uploadSpecPicUrl">
<img v-if="specForm.picUrl" :src="specForm.picUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="specVisiable = false">取消</el-button>
<el-button type="primary" @click="handleSpecificationAdd">确定</el-button>
</div>
</el-dialog>
</el-card>
<el-card class="box-card">
<h3>商品库存</h3>
<el-table :data="products">
<el-table-column property="value" label="货品规格" >
<template slot-scope="scope">
<el-tag :key="tag" v-for="tag in scope.row.specifications">
{{tag}}
</el-tag>
</template>
</el-table-column>
<el-table-column property="price" width="100" label="货品售价">
</el-table-column>
<el-table-column property="number" width="100" label="货品数量">
</el-table-column>
<el-table-column property="url" width="100" label="货品图片">
<template slot-scope="scope">
<img :src="scope.row.url" width="40" v-if="scope.row.url"/>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleProductShow(scope.row)">设置</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="设置货品" :visible.sync="productVisiable">
<el-form ref="productForm" :model="productForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="货品规格列" prop="specifications">
<el-tag :key="tag" v-for="tag in productForm.specifications">
{{tag}}
</el-tag>
</el-form-item>
<el-form-item label="货品售价" prop="price">
<el-input v-model="productForm.price"></el-input>
</el-form-item>
<el-form-item label="货品数量" prop="number">
<el-input v-model="productForm.number"></el-input>
</el-form-item>
<el-form-item label="货品图片" prop="url">
<el-upload class="avatar-uploader" :action='uploadPath' list-type="picture-card" :show-file-list="false" accept=".jpg,.jpeg,.png,.gif" :on-success="uploadProductUrl">
<img v-if="productForm.url" :src="productForm.url" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="productVisiable = false">取消</el-button>
<el-button type="primary" @click="handleProductEdit">确定</el-button>
</div>
</el-dialog>
</el-card>
<el-card class="box-card">
<h3>商品参数</h3>
<el-button :plain="true" @click="handleAttributeShow" type="primary">添加</el-button>
<el-table :data="attributes">
<el-table-column property="attribute" label="商品参数名称">
</el-table-column>
<el-table-column property="value" label="商品参数值">
</el-table-column>
<el-table-column align="center" label="操作" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="handleAttributeDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="设置商品参数" :visible.sync="attributeVisiable">
<el-form ref="attributeForm" :model="attributeForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="商品参数名称" prop="attribute">
<el-input v-model="attributeForm.attribute"></el-input>
</el-form-item>
<el-form-item label="商品参数值" prop="value">
<el-input v-model="attributeForm.value"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="attributeVisiable = false">取消</el-button>
<el-button type="primary" @click="handleAttributeAdd">确定</el-button>
</div>
</el-dialog>
</el-card>
<div class="op-container">
<el-button @click="handleCancel">取消</el-button>
<el-button @click="handlePublish" type="primary">上架</el-button>
</div>
</div>
</template>
<style>
.el-card {
margin-bottom: 10px;
}
.el-tag + .el-tag {
margin-left: 10px;
}
.input-new-keyword {
width: 90px;
margin-left: 10px;
vertical-align: bottom;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #20a0ff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 120px;
height: 120px;
display: block;
}
</style>
<script>
import { publishGoods, listCatAndBrand } from '@/api/goods'
import { createStorage, uploadPath } from '@/api/storage'
import Editor from '@tinymce/tinymce-vue'
import { MessageBox } from 'element-ui'
export default {
name: 'GoodsCreate',
components: { Editor },
data() {
return {
uploadPath,
newKeywordVisible: false,
newKeyword: '',
keywords: [],
categoryList: [],
brandList: [],
goods: { picUrl: '', gallery: [] },
specVisiable: false,
specForm: { specification: '', value: '', picUrl: '' },
multipleSpec: false,
specifications: [{ specification: '规格', value: '标准', picUrl: '' }],
productVisiable: false,
productForm: { id: 0, specifications: [], price: 0.00, number: 0, url: '' },
products: [{ id: 0, specifications: ['标准'], price: 0.00, number: 0, url: '' }],
attributeVisiable: false,
attributeForm: { attribute: '', value: '' },
attributes: [],
rules: {
goodsSn: [{ required: true, message: '商品编号不能为空', trigger: 'blur' }],
name: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }]
},
editorInit: {
language: 'zh_CN',
plugins: ['advlist anchor autolink autoresize autosave emoticons fullscreen hr image imagetools importcss insertdatetime legacyoutput link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace tabfocus table template textcolor textpattern visualblocks visualchars wordcount'],
toolbar: ['bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript ', 'hr bullist numlist link image charmap preview anchor pagebreak fullscreen media table emoticons forecolor backcolor'],
images_upload_handler: function(blobInfo, success, failure) {
const formData = new FormData()
formData.append('file', blobInfo.blob())
createStorage(formData).then(res => {
success(res.data.data.url)
}).catch(() => {
failure('上传失败,请重新上传')
})
}
}
}
},
created() {
this.init()
},
methods: {
init: function() {
listCatAndBrand().then(response => {
this.categoryList = response.data.data.categoryList
this.brandList = response.data.data.brandList
})
},
handleCategoryChange(value) {
this.goods.categoryId = value[value.length - 1]
},
handleCancel: function() {
this.$router.push({ path: '/goods/goods' })
},
handlePublish: function() {
const finalGoods = {
goods: this.goods,
specifications: this.specifications,
products: this.products,
attributes: this.attributes
}
publishGoods(finalGoods).then(response => {
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
this.$router.push({ path: '/goods/list' })
}).catch(response => {
MessageBox.alert('业务错误:' + response.data.errmsg, '警告', {
confirmButtonText: '确定',
type: 'error'
})
})
},
handleClose(tag) {
this.keywords.splice(this.keywords.indexOf(tag), 1)
this.goods.keywords = this.keywords.toString()
},
showInput() {
this.newKeywordVisible = true
this.$nextTick(_ => {
this.$refs.newKeywordInput.$refs.input.focus()
})
},
handleInputConfirm() {
const newKeyword = this.newKeyword
if (newKeyword) {
this.keywords.push(newKeyword)
this.goods.keywords = this.keywords.toString()
}
this.newKeywordVisible = false
this.newKeyword = ''
},
uploadPicUrl: function(response) {
this.goods.picUrl = response.data.url
},
uploadOverrun: function() {
this.$message({
type: 'error',
message: '上传文件个数超出限制!最多上传5张图片!'
})
},
handleGalleryUrl(response, file, fileList) {
if (response.errno === 0) {
this.goods.gallery.push(response.data.url)
}
},
handleRemove: function(file, fileList) {
for (var i = 0; i < this.goods.gallery.length; i++) {
// 这里存在两种情况
// 1. 如果所删除图片是刚刚上传的图片,那么图片地址是file.response.data.url
// 此时的file.url虽然存在,但是是本机地址,而不是远程地址。
// 2. 如果所删除图片是后台返回的已有图片,那么图片地址是file.url
var url
if (file.response === undefined) {
url = file.url
} else {
url = file.response.data.url
}
if (this.goods.gallery[i] === url) {
this.goods.gallery.splice(i, 1)
}
}
},
specChanged: function(label) {
if (label === false) {
this.specifications = [{ specification: '规格', value: '标准', picUrl: '' }]
this.products = [{ id: 0, specifications: ['标准'], price: 0.00, number: 0, url: '' }]
} else {
this.specifications = []
this.products = []
}
},
uploadSpecPicUrl: function(response) {
this.specForm.picUrl = response.data.url
},
handleSpecificationShow() {
this.specForm = {}
this.specVisiable = true
},
handleSpecificationAdd() {
var index = this.specifications.length - 1
for (var i = 0; i < this.specifications.length; i++) {
const v = this.specifications[i]
if (v.specification === this.specForm.specification) {
index = i
}
}
this.specifications.splice(index + 1, 0, this.specForm)
this.specVisiable = false
this.specToProduct()
},
handleSpecificationDelete(row) {
const index = this.specifications.indexOf(row)
this.specifications.splice(index, 1)
this.specToProduct()
},
specToProduct() {
if (this.specifications.length === 0) {
return
}
// 根据specifications创建临时规格列表
var specValues = []
var spec = this.specifications[0].specification
var values = []
values.push(0)
for (var i = 1; i < this.specifications.length; i++) {
const aspec = this.specifications[i].specification
if (aspec === spec) {
values.push(i)
} else {
specValues.push(values)
spec = aspec
values = []
values.push(i)
}
}
specValues.push(values)
// 根据临时规格列表生产货品规格
// 算法基于 https://blog.csdn.net/tyhj_sf/article/details/53893125
var productsIndex = 0
var products = []
var combination = []
var n = specValues.length
for (var s = 0; s < n; s++) {
combination[s] = 0
}
var index = 0
var isContinue = false
do {
var specifications = []
for (var x = 0; x < n; x++) {
var z = specValues[x][combination[x]]
specifications.push(this.specifications[z].value)
}
products[productsIndex] = { id: productsIndex, specifications: specifications, price: 0.00, number: 0, url: '' }
productsIndex++
index++
combination[n - 1] = index
for (var j = n - 1; j >= 0; j--) {
if (combination[j] >= specValues[j].length) {
combination[j] = 0
index = 0
if (j - 1 >= 0) {
combination[j - 1] = combination[j - 1] + 1
}
}
}
isContinue = false
for (var p = 0; p < n; p++) {
if (combination[p] !== 0) {
isContinue = true
}
}
} while (isContinue)
this.products = products
},
handleProductShow(row) {
this.productForm = Object.assign({}, row)
this.productVisiable = true
},
uploadProductUrl: function(response) {
this.productForm.url = response.data.url
},
handleProductEdit() {
for (var i = 0; i < this.products.length; i++) {
const v = this.products[i]
if (v.id === this.productForm.id) {
this.products.splice(i, 1, this.productForm)
break
}
}
this.productVisiable = false
},
handleAttributeShow() {
this.attributeForm = {}
this.attributeVisiable = true
},
handleAttributeAdd() {
this.attributes.unshift(this.attributeForm)
this.attributeVisiable = false
},
handleAttributeDelete(row) {
const index = this.attributes.indexOf(row)
this.attributes.splice(index, 1)
}
}
}
</script>
\ No newline at end of file
<template>
<div class="app-container calendar-list-container">
<el-card class="box-card">
<h3>商品介绍</h3>
<el-form :rules="rules" ref="goods" :model="goods" label-width="150px">
<el-form-item label="商品编号" prop="goodsSn">
<el-input v-model="goods.goodsSn"></el-input>
</el-form-item>
<el-form-item label="商品名称" prop="name">
<el-input v-model="goods.name"></el-input>
</el-form-item>
<el-form-item label="专柜价格" prop="counterPrice">
<el-input v-model="goods.counterPrice" placeholder="0.00">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="当前价格" prop="retailPrice">
<el-input v-model="goods.retailPrice" placeholder="0.00">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="是否新品" prop="isNew">
<el-radio-group v-model="goods.isNew">
<el-radio :label="true">新品</el-radio>
<el-radio :label="false">非新品</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否热卖" prop="isHot">
<el-radio-group v-model="goods.isHot">
<el-radio :label="false">普通</el-radio>
<el-radio :label="true">热卖</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否在售" prop="isOnSale">
<el-radio-group v-model="goods.isOnSale">
<el-radio :label="true">在售</el-radio>
<el-radio :label="false">未售</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品图片">
<el-upload class="avatar-uploader" :action='uploadPath' list-type="picture-card" :show-file-list="false" accept=".jpg,.jpeg,.png,.gif" :on-success="uploadPicUrl">
<img v-if="goods.picUrl" :src="goods.picUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="宣传画廊">
<el-upload :action='uploadPath' :limit='5' multiple accept=".jpg,.jpeg,.png,.gif" :file-list="galleryFileList" list-type="picture-card" :on-exceed='uploadOverrun' :on-success="handleGalleryUrl" :on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
</el-form-item>
<el-form-item label="商品单位">
<el-input v-model="goods.unit" placeholder="件 / 个 / 盒"></el-input>
</el-form-item>
<el-form-item label="关键字">
<el-tag :key="tag" v-for="tag in keywords" closable type="primary" @close="handleClose(tag)">
{{tag}}
</el-tag>
<el-input class="input-new-keyword" v-if="newKeywordVisible" v-model="newKeyword" ref="newKeywordInput" size="small" @keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm">
</el-input>
<el-button v-else class="button-new-keyword" size="small" type="primary" @click="showInput">+ 增加</el-button>
</el-form-item>
<el-form-item label="所属分类">
<el-cascader expand-trigger="hover" :options="categoryList" v-model="categoryIds" @change="handleCategoryChange"></el-cascader>
</el-form-item>
<el-form-item label="所属品牌商">
<el-select v-model="goods.brandId">
<el-option v-for="item in brandList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品简介">
<el-input v-model="goods.brief"></el-input>
</el-form-item>
<el-form-item label="商品详细介绍">
<editor :init="editorInit" v-model="goods.desc"></editor>
</el-form-item>
</el-form>
</el-card>
<el-card class="box-card">
<h3>商品规格</h3>
<el-button :plain="true" @click="handleSpecificationShow" type="primary">添加</el-button>
<el-table :data="specifications">
<el-table-column property="specification" label="规格名" ></el-table-column>
<el-table-column property="value" label="规格值" >
<template slot-scope="scope">
<el-tag type="primary">
{{scope.row.value}}
</el-tag>
</template>
</el-table-column>
<el-table-column property="picUrl" label="规格图片">
<template slot-scope="scope">
<img :src="scope.row.picUrl" width="40" v-if="scope.row.picUrl"/>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="handleSpecificationDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="设置规格" :visible.sync="specVisiable">
<el-form :rules="rules" ref="specForm" :model="specForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="规格名" prop="specification">
<el-input v-model="specForm.specification"></el-input>
</el-form-item>
<el-form-item label="规格值" prop="value">
<el-input v-model="specForm.value"></el-input>
</el-form-item>
<el-form-item label="规格图片" prop="picUrl">
<el-upload class="avatar-uploader" :action='uploadPath' list-type="picture-card" :show-file-list="false" accept=".jpg,.jpeg,.png,.gif" :on-success="uploadSpecPicUrl">
<img v-if="specForm.picUrl" :src="specForm.picUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="specVisiable = false">取消</el-button>
<el-button type="primary" @click="handleSpecificationAdd">确定</el-button>
</div>
</el-dialog>
</el-card>
<el-card class="box-card">
<h3>商品库存</h3>
<el-table :data="products">
<el-table-column property="value" label="货品规格" >
<template slot-scope="scope">
<el-tag :key="tag" v-for="tag in scope.row.specifications">
{{tag}}
</el-tag>
</template>
</el-table-column>
<el-table-column property="price" width="100" label="货品售价">
</el-table-column>
<el-table-column property="number" width="100" label="货品数量">
</el-table-column>
<el-table-column property="url" width="100" label="货品图片">
<template slot-scope="scope">
<img :src="scope.row.url" width="40" v-if="scope.row.url"/>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleProductShow(scope.row)">设置</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="设置货品" :visible.sync="productVisiable">
<el-form ref="productForm" :model="productForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="货品规格列" prop="specifications">
<el-tag :key="tag" v-for="tag in productForm.specifications">
{{tag}}
</el-tag>
</el-form-item>
<el-form-item label="货品售价" prop="price">
<el-input v-model="productForm.price"></el-input>
</el-form-item>
<el-form-item label="货品数量" prop="number">
<el-input v-model="productForm.number"></el-input>
</el-form-item>
<el-form-item label="货品图片" prop="url">
<el-upload class="avatar-uploader" :action='uploadPath' list-type="picture-card" :show-file-list="false" accept=".jpg,.jpeg,.png,.gif" :on-success="uploadProductUrl">
<img v-if="productForm.url" :src="productForm.url" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="productVisiable = false">取消</el-button>
<el-button type="primary" @click="handleProductEdit">确定</el-button>
</div>
</el-dialog>
</el-card>
<el-card class="box-card">
<h3>商品参数</h3>
<el-button :plain="true" @click="handleAttributeShow" type="primary">添加</el-button>
<el-table :data="attributes">
<el-table-column property="attribute" label="商品参数名称">
</el-table-column>
<el-table-column property="value" label="商品参数值">
</el-table-column>
<el-table-column align="center" label="操作" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="handleAttributeDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="设置商品参数" :visible.sync="attributeVisiable">
<el-form ref="attributeForm" :model="attributeForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="商品参数名称" prop="attribute">
<el-input v-model="attributeForm.attribute"></el-input>
</el-form-item>
<el-form-item label="商品参数值" prop="value">
<el-input v-model="attributeForm.value"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="attributeVisiable = false">取消</el-button>
<el-button type="primary" @click="handleAttributeAdd">确定</el-button>
</div>
</el-dialog>
</el-card>
<div class="op-container">
<el-button @click="handleCancel">取消</el-button>
<el-button @click="handleEdit" type="primary">更新商品</el-button>
</div>
</div>
</template>
<style>
.el-card {
margin-bottom: 10px;
}
.el-tag + .el-tag {
margin-left: 10px;
}
.input-new-keyword {
width: 90px;
margin-left: 10px;
vertical-align: bottom;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #20a0ff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 120px;
height: 120px;
display: block;
}
</style>
<script>
import { detailGoods, editGoods, listCatAndBrand } from '@/api/goods'
import { createStorage, uploadPath } from '@/api/storage'
import Editor from '@tinymce/tinymce-vue'
import { MessageBox } from 'element-ui'
export default {
name: 'GoodsEdit',
components: { Editor },
data() {
return {
uploadPath,
newKeywordVisible: false,
newKeyword: '',
keywords: [],
galleryFileList: [],
categoryList: [],
brandList: [],
categoryIds: [],
goods: { gallery: [] },
specVisiable: false,
specForm: { specification: '', value: '', picUrl: '' },
specifications: [{ specification: '规格', value: '标准', picUrl: '' }],
productVisiable: false,
productForm: { id: 0, specifications: [], price: 0.00, number: 0, url: '' },
products: [{ id: 0, specifications: ['标准'], price: 0.00, number: 0, url: '' }],
attributeVisiable: false,
attributeForm: { attribute: '', value: '' },
attributes: [],
rules: {
goodsSn: [{ required: true, message: '商品编号不能为空', trigger: 'blur' }],
name: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }]
},
editorInit: {
language: 'zh_CN',
plugins: ['advlist anchor autolink autoresize autosave emoticons fullscreen hr image imagetools importcss insertdatetime legacyoutput link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace tabfocus table template textcolor textpattern visualblocks visualchars wordcount'],
toolbar: ['bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript ', 'hr bullist numlist link image charmap preview anchor pagebreak fullscreen media table emoticons forecolor backcolor'],
images_upload_handler: function(blobInfo, success, failure) {
const formData = new FormData()
formData.append('file', blobInfo.blob())
createStorage(formData).then(res => {
success(res.data.data.url)
}).catch(() => {
failure('上传失败,请重新上传')
})
}
}
}
},
created() {
this.init()
},
methods: {
init: function() {
if (this.$route.query.id == null) {
return
}
const goodsId = this.$route.query.id
detailGoods(goodsId).then(response => {
this.goods = response.data.data.goods
this.specifications = response.data.data.specifications
this.products = response.data.data.products
this.attributes = response.data.data.attributes
this.categoryIds = response.data.data.categoryIds
this.galleryFileList = []
for (var i = 0; i < this.goods.gallery.length; i++) {
this.galleryFileList.push({
url: this.goods.gallery[i]
})
}
})
listCatAndBrand().then(response => {
this.categoryList = response.data.data.categoryList
this.brandList = response.data.data.brandList
})
},
handleCategoryChange(value) {
this.goods.categoryId = value[value.length - 1]
},
handleCancel: function() {
this.$router.push({ path: '/goods/list' })
},
handleEdit: function() {
const finalGoods = {
goods: this.goods,
specifications: this.specifications,
products: this.products,
attributes: this.attributes
}
editGoods(finalGoods).then(response => {
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
this.$router.push({ path: '/goods/list' })
}).catch(response => {
MessageBox.alert('业务错误:' + response.data.errmsg, '警告', {
confirmButtonText: '确定',
type: 'error'
})
})
},
handleClose(tag) {
this.keywords.splice(this.keywords.indexOf(tag), 1)
this.goods.keywords = this.keywords.toString()
},
showInput() {
this.newKeywordVisible = true
this.$nextTick(_ => {
this.$refs.newKeywordInput.$refs.input.focus()
})
},
handleInputConfirm() {
const newKeyword = this.newKeyword
if (newKeyword) {
this.keywords.push(newKeyword)
this.goods.keywords = this.keywords.toString()
}
this.newKeywordVisible = false
this.newKeyword = ''
},
uploadPicUrl: function(response) {
this.goods.picUrl = response.data.url
},
uploadOverrun: function() {
this.$message({
type: 'error',
message: '上传文件个数超出限制!最多上传5张图片!'
})
},
handleGalleryUrl(response, file, fileList) {
if (response.errno === 0) {
this.goods.gallery.push(response.data.url)
}
},
handleRemove: function(file, fileList) {
for (var i = 0; i < this.goods.gallery.length; i++) {
// 这里存在两种情况
// 1. 如果所删除图片是刚刚上传的图片,那么图片地址是file.response.data.url
// 此时的file.url虽然存在,但是是本机地址,而不是远程地址。
// 2. 如果所删除图片是后台返回的已有图片,那么图片地址是file.url
var url
if (file.response === undefined) {
url = file.url
} else {
url = file.response.data.url
}
if (this.goods.gallery[i] === url) {
this.goods.gallery.splice(i, 1)
}
}
},
specChanged: function(label) {
if (label === false) {
this.specifications = [{ specification: '规格', value: '标准', picUrl: '' }]
this.products = [{ id: 0, specifications: ['标准'], price: 0.00, number: 0, url: '' }]
} else {
this.specifications = []
this.products = []
}
},
uploadSpecPicUrl: function(response) {
this.specForm.picUrl = response.data.url
},
handleSpecificationShow() {
this.specForm = {}
this.specVisiable = true
},
handleSpecificationAdd() {
var index = this.specifications.length - 1
for (var i = 0; i < this.specifications.length; i++) {
const v = this.specifications[i]
if (v.specification === this.specForm.specification) {
index = i
}
}
this.specifications.splice(index + 1, 0, this.specForm)
this.specVisiable = false
this.specToProduct()
},
handleSpecificationDelete(row) {
const index = this.specifications.indexOf(row)
this.specifications.splice(index, 1)
this.specToProduct()
},
specToProduct() {
if (this.specifications.length === 0) {
return
}
// 根据specifications创建临时规格列表
var specValues = []
var spec = this.specifications[0].specification
var values = []
values.push(0)
for (var i = 1; i < this.specifications.length; i++) {
const aspec = this.specifications[i].specification
if (aspec === spec) {
values.push(i)
} else {
specValues.push(values)
spec = aspec
values = []
values.push(i)
}
}
specValues.push(values)
// 根据临时规格列表生产货品规格
// 算法基于 https://blog.csdn.net/tyhj_sf/article/details/53893125
var productsIndex = 0
var products = []
var combination = []
var n = specValues.length
for (var s = 0; s < n; s++) {
combination[s] = 0
}
var index = 0
var isContinue = false
do {
var specifications = []
for (var x = 0; x < n; x++) {
var z = specValues[x][combination[x]]
specifications.push(this.specifications[z].value)
}
products[productsIndex] = { id: productsIndex, specifications: specifications, price: 0.00, number: 0, url: '' }
productsIndex++
index++
combination[n - 1] = index
for (var j = n - 1; j >= 0; j--) {
if (combination[j] >= specValues[j].length) {
combination[j] = 0
index = 0
if (j - 1 >= 0) {
combination[j - 1] = combination[j - 1] + 1
}
}
}
isContinue = false
for (var p = 0; p < n; p++) {
if (combination[p] !== 0) {
isContinue = true
}
}
} while (isContinue)
this.products = products
},
handleProductShow(row) {
this.productForm = Object.assign({}, row)
this.productVisiable = true
},
uploadProductUrl: function(response) {
this.productForm.url = response.data.url
},
handleProductEdit() {
for (var i = 0; i < this.products.length; i++) {
const v = this.products[i]
if (v.id === this.productForm.id) {
this.products.splice(i, 1, this.productForm)
break
}
}
this.productVisiable = false
},
handleAttributeShow() {
this.attributeForm = {}
this.attributeVisiable = true
},
handleAttributeAdd() {
this.attributes.unshift(this.attributeForm)
this.attributeVisiable = false
},
handleAttributeDelete(row) {
const index = this.attributes.indexOf(row)
this.attributes.splice(index, 1)
}
}
}
</script>
\ No newline at end of file
<template>
<div class="app-container calendar-list-container">
<!-- 查询和其他操作 -->
<div class="filter-container">
<el-input clearable class="filter-item" style="width: 200px;" placeholder="请输入商品编号" v-model="listQuery.goodsSn">
</el-input>
<el-input clearable class="filter-item" style="width: 200px;" placeholder="请输入商品名称" v-model="listQuery.name">
</el-input>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">查找</el-button>
<el-button class="filter-item" type="primary" @click="handleCreate" icon="el-icon-edit">添加</el-button>
<el-button class="filter-item" type="primary" :loading="downloadLoading" v-waves icon="el-icon-download" @click="handleDownload">导出</el-button>
</div>
<!-- 查询结果 -->
<el-table size="small" :data="list" v-loading="listLoading" element-loading-text="正在查询中。。。" border fit highlight-current-row>
<el-table-column type="expand">
<template slot-scope="props">
<el-form label-position="left" class="demo-table-expand">
<el-form-item label="首页主图">
<span>{{ props.row.listPicUrl }}</span>
</el-form-item>
<el-form-item label="宣传画廊">
<span>{{ props.row.gallery }}</span>
</el-form-item>
<el-form-item label="商品介绍">
<span>{{ props.row.goodsBrief }}</span>
</el-form-item>
<el-form-item label="商品详细介绍">
<span>{{ props.row.goodsDesc }}</span>
</el-form-item>
<el-form-item label="商品主图">
<span>{{ props.row.primaryPicUrl }}</span>
</el-form-item>
<el-form-item label="商品单位">
<span>{{ props.row.goodsUnit }}</span>
</el-form-item>
<el-form-item label="关键字">
<span>{{ props.row.keyword }}</span>
</el-form-item>
<el-form-item label="类目ID">
<span>{{ props.row.categoryId }}</span>
</el-form-item>
<el-form-item label="品牌商ID">
<span>{{ props.row.brandId }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column align="center" width="100px" label="商品ID" prop="id" sortable>
</el-table-column>
<el-table-column align="center" min-width="100px" label="商品编号" prop="goodsSn">
</el-table-column>
<el-table-column align="center" min-width="100px" label="名称" prop="name">
</el-table-column>
<el-table-column align="center" min-width="100px" label="专柜价格" prop="counterPrice">
</el-table-column>
<el-table-column align="center" min-width="100px" label="当前价格" prop="retailPrice">
</el-table-column>
<el-table-column align="center" min-width="100px" label="是否新品" prop="isNew">
<template slot-scope="scope">
<el-tag :type="scope.row.isNew ? 'success' : 'error' ">{{scope.row.isNew ? '新品' : '非新品'}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" min-width="100px" label="是否热品" prop="isHot">
<template slot-scope="scope">
<el-tag :type="scope.row.isHot ? 'success' : 'error' ">{{scope.row.isHot ? '热品' : '非热品'}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" min-width="100px" label="是否在售" prop="isOnSale">
<template slot-scope="scope">
<el-tag :type="scope.row.isOnSale ? 'success' : 'error' ">{{scope.row.isOnSale ? '在售' : '未售'}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listQuery.page"
:page-sizes="[10,20,30,50]" :page-size="listQuery.limit" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
<el-tooltip placement="top" content="返回顶部">
<back-to-top :visibilityHeight="100" ></back-to-top>
</el-tooltip>
<!-- 添加或修改对话框 -->
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form :rules="rules" ref="dataForm" :model="dataForm" status-icon label-position="left" label-width="100px" style='width: 400px; margin-left:50px;'>
<el-form-item label="商品编号" prop="goodsSn">
<el-input v-model="dataForm.goodsSn"></el-input>
</el-form-item>
<el-form-item label="商品名称" prop="name">
<el-input v-model="dataForm.name"></el-input>
</el-form-item>
<el-form-item label="专柜价格" prop="counterPrice">
<el-input v-model="dataForm.counterPrice"></el-input>
</el-form-item>
<el-form-item label="当前价格" prop="retailPrice">
<el-input v-model="dataForm.retailPrice"></el-input>
</el-form-item>
<el-form-item label="是否新品" prop="isNew">
<el-select v-model="dataForm.isNew" placeholder="请选择">
<el-option label="新品" :value="true">
</el-option>
<el-option label="新品" :value="false">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="是否热品" prop="isHot">
<el-select v-model="dataForm.isHot" placeholder="请选择">
<el-option label="热品" :value="true">
</el-option>
<el-option label="非热品" :value="false">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="是否在售" prop="isOnSale">
<el-select v-model="dataForm.isOnSale" placeholder="请选择">
<el-option label="在售" :value="true">
</el-option>
<el-option label="未售" :value="false">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="首页主图">
<el-input v-model="dataForm.listPicUrl"></el-input>
</el-form-item>
<el-form-item label="宣传画廊">
<el-input v-model="dataForm.gallery"></el-input>
</el-form-item>
<el-form-item label="商品介绍">
<el-input v-model="dataForm.goodsBrief"></el-input>
</el-form-item>
<el-form-item style="width: 700px;" label="商品详细介绍">
<editor :init="editorInit" v-model="dataForm.goodsDesc"></editor>
</el-form-item>
<el-form-item label="商品主图">
<el-input v-model="dataForm.primaryPicUrl"></el-input>
</el-form-item>
<el-form-item label="商品单位">
<el-input v-model="dataForm.goodsUnit"></el-input>
</el-form-item>
<el-form-item label="关键字">
<el-input v-model="dataForm.keyword"></el-input>
</el-form-item>
<el-form-item label="类目ID">
<el-input v-model="dataForm.categoryId"></el-input>
</el-form-item>
<el-form-item label="品牌商ID">
<el-input v-model="dataForm.brandId"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">确定</el-button>
<el-button v-else type="primary" @click="updateData">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<style>
.demo-table-expand {
font-size: 0;
}
.demo-table-expand label {
width: 200px;
color: #99a9bf;
}
.demo-table-expand .el-form-item {
margin-right: 0;
margin-bottom: 0;
}
.el-dialog {
width: 800px;
}
</style>
<script>
import { listGoods, createGoods, updateGoods, deleteGoods } from '@/api/goods'
import { createStorage } from '@/api/storage'
import waves from '@/directive/waves' // 水波纹指令
import BackToTop from '@/components/BackToTop'
import Editor from '@tinymce/tinymce-vue'
export default {
name: 'Goods',
components: { BackToTop, Editor },
directives: { waves },
data() {
return {
list: undefined,
total: undefined,
listLoading: true,
listQuery: {
page: 1,
limit: 20,
goodsSn: undefined,
name: undefined,
sort: '+id'
},
dataForm: {
id: undefined,
goodsSn: undefined,
name: undefined,
counterPrice: undefined,
retailPrice: undefined,
isHot: false,
isNew: true,
isOnSale: true,
listPicUrl: undefined,
primaryPicUrl: undefined,
goodsBrief: undefined,
goodsDesc: '',
keywords: undefined,
gallery: undefined,
categoryId: undefined,
brandId: undefined
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '创建'
},
rules: {
goodsSn: [{ required: true, message: '商品编号不能为空', trigger: 'blur' }],
name: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }]
},
downloadLoading: false,
editorInit: {
language: 'zh_CN',
plugins: ['advlist anchor autolink autoresize autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime legacyoutput link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace tabfocus table template textcolor textpattern visualblocks visualchars wordcount'],
toolbar: ['bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript ', 'hr bullist numlist link image charmap preview anchor pagebreak fullscreen media table emoticons forecolor backcolor'],
images_upload_handler: function(blobInfo, success, failure) {
const formData = new FormData()
formData.append('file', blobInfo.blob())
createStorage(formData).then(res => {
success(res.data.data.url)
}).catch(() => {
failure('上传失败,请重新上传')
})
}
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
listGoods(this.listQuery).then(response => {
this.list = response.data.data.items
this.total = response.data.data.total
this.listLoading = false
}).catch(() => {
this.list = []
this.total = 0
this.listLoading = false
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.limit = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.page = val
this.getList()
},
resetForm() {
this.dataForm = {
id: undefined,
goodsSn: undefined,
name: undefined,
counterPrice: undefined,
retailPrice: undefined,
isHot: false,
isNew: true,
isOnSale: true,
listPicUrl: undefined,
primaryPicUrl: undefined,
goodsBrief: undefined,
goodsDesc: '',
keywords: undefined,
gallery: undefined,
categoryId: undefined,
brandId: undefined
}
},
filterLevel(value, row) {
return row.level === value
},
handleCreate() {
this.resetForm()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createGoods(this.dataForm).then(response => {
this.list.unshift(response.data.data)
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.dataForm = Object.assign({}, row)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
updateGoods(this.dataForm).then(() => {
for (const v of this.list) {
if (v.id === this.dataForm.id) {
const index = this.list.indexOf(v)
this.list.splice(index, 1, this.dataForm)
break
}
}
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
deleteGoods(row).then(response => {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['商品ID', '商品编号', '名称', '专柜价格', '当前价格', '是否新品', '是否热品', '是否在售', '首页主图', '宣传画廊', '商品介绍', '详细介绍', '商品主图', '商品单位', '关键字', '类目ID', '品牌商ID']
const filterVal = ['id', 'goodsSn', 'name', 'counterPrice', 'retailPrice', 'isNew', 'isHot', 'isOnSale', 'listPicUrl', 'gallery', 'goodsBrief', 'goodsDesc', 'primaryPicUrl', 'goodsUnit', 'keywords', 'categoryId', 'brandId']
excel.export_json_to_excel2(tHeader, this.list, filterVal, '商品信息')
this.downloadLoading = false
})
}
}
}
</script>
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment