Commit a21f5a9a authored by liang.tang's avatar liang.tang
Browse files

mall4cloud

parents
Pipeline #244 canceled with stages
<template>
<el-tabs class="icons-container" type="border-card">
<el-tab-pane label="Icons">
<div class="grid">
<el-button v-for="item of svgIcons" :key="item" @click="selectIcon(getIconCode(item))">
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
</div>
</el-button>
</div>
</el-tab-pane>
<el-tab-pane label="Element-UI Icons">
<div class="grid">
<el-button v-for="item of elementIcons" :key="item" @click="selectIcon(getElementIconCode(item))">
<div class="icon-item">
<i :class="'el-icon-' + item" />
</div>
</el-button>
</div>
</el-tab-pane>
</el-tabs>
</template>
<script>
import svgIcons from './svg-icons'
import elementIcons from './element-icons'
export default {
name: 'Icons',
data() {
return {
svgIcons,
elementIcons
}
},
methods: {
getIconCode(symbol) {
return `${symbol}`
},
getElementIconCode(symbol) {
return `el-icon-${symbol}`
},
selectIcon(text) {
this.$emit('selectIcon', text)
}
}
}
</script>
<style lang="scss">
.icons-container {
.el-tabs__content{
height: 200px;
overflow: scroll;
}
.grid{
padding: 0;
margin: -8px 0 0 -8px;
> .el-button {
padding: 8px;
margin: 8px 0 0 8px;
> span {
display: inline-block;
vertical-align: middle;
width: 18px;
height: 18px;
font-size: 18px;
}
}
}
}
</style>
const req = require.context('../../icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const svgIcons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default svgIcons
<template>
<div>
<div class="plugin-images">
<div tabindex="0" class="el-upload el-upload--text" @click="elxImgboxHandle">
<img v-if="value" :src="getImgSrc()" class="pic" />
<i v-else class="el-icon-plus pic-uploader-icon"></i>
</div>
</div>
<!-- 弹窗, 新增图片 -->
<elx-imgbox v-if="elxImgboxVisible" ref="elxImgbox" @refreshPic="refreshPic"></elx-imgbox>
<el-dialog :visible.sync="visible" :append-to-body="visible">
<img width="100%" :src="getImgSrc()" alt />
</el-dialog>
</div>
</template>
<script>
import ElxImgbox from '@/components/imgbox'
export default {
data() {
return {
resourcesUrl: process.env.VUE_APP_RESOURCES_URL,
elxImgboxVisible: false,
visible: false
}
},
props: {
value: {
default: '',
type: String
},
disabled: {
default: false,
type: Boolean
}
},
components: {
ElxImgbox
},
methods: {
// 打开图片选择窗
elxImgboxHandle() {
if (this.disabled) {
this.openImg()
return false
}
this.elxImgboxVisible = true
this.$nextTick(() => {
this.$refs.elxImgbox.init(1)
})
},
/**
* 获取图片路径
*/
getImgSrc() {
if (!this.value) {
return ''
}
if (this.value.indexOf('http://') === 0 || this.value.indexOf('https://') === 0) {
return this.value
}
return this.resourcesUrl + this.value
},
openImg() {
this.visible = true
},
refreshPic(imagePath) {
console.log('imagePath', imagePath)
this.$emit('input', imagePath)
}
}
}
</script>
<style lang="scss">
.plugin-images {
display: inline-block;
width: auto;
.el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.pic-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.pic {
width: 120px;
height: 120px;
display: block;
}
.el-upload:hover {
border-color: #409eff;
}
}
}
</style>
<template>
<div class="mul-pic-upload">
<vue-draggable
v-model="imageList"
@start="onDragStart"
@end="onDragEnd"
class="el-upload-list el-upload-list--picture-card"
>
<!--拷贝上传图片组件生成的预览图元素代码,用绑定的model循环生成可拖拽元素-->
<li
v-for="(item,index) in imageList"
:key="index"
tabindex="0"
class="el-upload-list__item is-success"
>
<img :src="item.url" alt class="el-upload-list__item-thumbnail" />
<a class="el-upload-list__item-name">
<i class="el-icon-document"></i>
</a>
<label class="el-upload-list__item-status-label">
<i class="el-icon-upload-success el-icon-check"></i>
</label>
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-preview">
<i class="el-icon-zoom-in" @click="handlePictureCardPreview(item.url)"></i>
</span>
<span class="el-upload-list__item-delete" @click="handleRemove(index)" v-if="!disabled">
<i class="el-icon-delete"></i>
</span>
</span>
</li>
<li
class="el-upload-list__item"
@click="elxImgboxHandle"
v-if="!disabled && this.imageList.length < this.limit"
>
<div tabindex="0" class="el-upload el-upload--picture-card">
<i class="el-icon-plus"></i>
</div>
<!-- 弹窗, 新增图片 -->
<elx-imgbox v-if="elxImgboxVisible" ref="elxImgbox" @refreshPic="refreshPic"></elx-imgbox>
</li>
</vue-draggable>
<!-- <div v-if="prompt">{{$t("biz.imgbox.PicMaxQuantity")}}{{limit}}</div> -->
<el-dialog :visible.sync="dialogVisible" :modal="modal" top="7vh">
<img width="100%" :src="dialogImageUrl" alt />
</el-dialog>
</div>
</template>
<script>
import VueDraggable from 'vuedraggable'
import ElxImgbox from '@/components/imgbox'
export default {
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
elxImgboxVisible: false,
resourcesUrl: process.env.VUE_APP_RESOURCES_URL,
imageList: []
}
},
components: {
VueDraggable,
ElxImgbox
},
props: {
value: {
default: '',
type: String
},
// 最大上传数量
limit: {
default: 9,
type: Number
},
// false: 能对图片进行操作 true: 不能对图片进行操作
disabled: {
default: false,
type: Boolean
},
modal: {
default: true,
type: Boolean
},
prompt: {
default: true,
type: Boolean
}
},
watch: {
value: function (newVal, oldVal) {
let res = []
if (this.value) {
let imageArray = this.value.split(',')
for (let i = 0; i < imageArray.length; i++) {
if (imageArray[i]) {
res.push({ url: this.getImgSrc(imageArray[i]), response: imageArray[i] })
}
}
}
this.imageList = res
},
imageList: function (newVal, oldVal) {
let pics = this.imageList.map(file => {
return file.response
}).join(',')
this.$emit('input', pics)
}
},
methods: {
/**
* 获取图片路径
*/
getImgSrc(img) {
if (!img) {
return ''
}
if (img.indexOf('http://') === 0 || img.indexOf('https://') === 0) {
return img
}
return this.resourcesUrl + img
},
/**
* 删除图片
*/
handleRemove(index) {
this.imageList.splice(index, 1)
let pics = this.imageList.map(file => {
return file.response
}).join(',')
this.$emit('input', pics)
},
/**
* 放大图片
*/
handlePictureCardPreview(imgUrl) {
this.dialogImageUrl = imgUrl
this.dialogVisible = true
},
onDragStart(e) {
e.target.classList.add('hideShadow')
},
onDragEnd(e) {
e.target.classList.remove('hideShadow')
},
/**
* 打开图片选择窗
*/
elxImgboxHandle() {
let num = this.limit - this.imageList.length
if (num < 1) {
this.$message.error('可选择照片数量已达上限')
return
}
this.elxImgboxVisible = true
this.$nextTick(() => {
this.$refs.elxImgbox.init(0, num)
})
},
/**
* 接收回调的图片数据
*/
refreshPic(imagePath) {
let imageArray = imagePath.split(',')
let pics = imageArray.map(img => {
return img
}).join(',')
if (this.value) {
// let picArray = imagePath.split(',')
// console.log(picArray.length, this.value, !this.value)
pics = this.value + ',' + pics
}
this.$emit('input', pics)
}
}
}
</script>
<style lang="scss" scope>
.mul-pic-upload {
.upload-component {
display: inline;
}
.el-upload-list--picture-card .el-upload-list__item {
width: 120px;
height: 120px;
border: 1px dashed #d9d9d9;
}
.el-upload--picture-card {
border: 0;
font-size: 28px;
color: #8c939d;
background: #fff;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.hideShadow {
.el-upload-list__item-actions {
display: none;
}
}
}
</style>
<template>
<el-dropdown trigger="click" class="international" @command="handleSetLanguage">
<div>
<svg-icon class-name="international-icon" icon-class="language" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="language==='zh'" command="zh">
中文
</el-dropdown-item>
<el-dropdown-item :disabled="language==='en'" command="en">
English
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
computed: {
language() {
return this.$store.getters.language
}
},
methods: {
handleSetLanguage(lang) {
this.$i18n.locale = lang
this.$store.dispatch('app/setLanguage', lang)
this.$message({
message: 'Switch Language Success',
type: 'success'
})
}
}
}
</script>
<template>
<el-dialog
:visible.sync="operateDialogVisible"
:close-on-click-modal="false"
title="下线管理"
:lock-scroll="true"
:append-to-body="true"
:destroy-on-close="true"
width="500px"
top="0"
class="operate-dlg"
>
<div class="off-shelf-mag">
<div class="msg">
<div class="msg-item">
<div class="tit">处理人:</div>
<div class="int">{{ offlineDetail.handler }}</div>
</div>
<div class="msg-item">
<div class="tit">下线时间:</div>
<div class="int">{{ offlineDetail.createTime }}</div>
</div>
<div class="msg-item">
<div class="tit">处理状态:</div>
<el-tag v-if="offlineDetail.status === 1" type="danger">平台下线</el-tag>
<el-tag v-if="offlineDetail.status === 2" type="warning">等待审核</el-tag>
<el-tag v-if="offlineDetail.status === 3" type="success">审核通过</el-tag>
<el-tag v-if="offlineDetail.status === 4" type="danger">审核未通过</el-tag>
</div>
<div class="msg-item">
<div class="tit">下线原因:</div>
<div class="int">{{ offlineDetail.offlineReason }}</div>
</div>
<div class="msg-item">
<div class="tit">申请理由:</div>
<div class="int"><el-input v-model="reapplyReason" type="textarea" /></div>
</div>
</div>
<div v-if="offlineDetail.offlineHandleEventItemList.length > 0" class="log">
<div class="log-tit">申请历史</div>
<div v-for="(item,index) in offlineDetail.offlineHandleEventItemList" :key="index" class="log-item">
<p>申请时间:{{ item.reapplyTime }}</p>
<p>申请理由:{{ item.reapplyReason }}</p>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="operateDialogVisible = false">取消</el-button>
<el-button type="primary" @click="rereapplyDataSubmit()">确认审核</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import * as api from '@/api/product/list'
export default {
data() {
return {
operateDialogVisible: false, // 操作对话框
offlineDetail: {}, // 最新下线商品
offlineReasonError: false, // 理由出错
reapplyReason: '', // 申请理由
}
},
methods: {
init(data) {
this.operateDialogVisible = true
this.offlineDetail = data
},
/**
* 提交上架申请
*/
rereapplyDataSubmit() {
if (!this.reapplyReason) {
this.$message({
message: '请填写申请理由',
type: 'warning',
duration: 1000
})
return
}
let data = {
eventId: this.offlineDetail.eventId,
reapplyReason: this.reapplyReason
}
this.$emit('rereapplyDataSubmit', data)
}
}
}
</script>
<style lang="scss" scoped>
// 下架管理-操作弹窗
.operate-dlg.el-dialog__wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.operate-dlg {
.el-dialog__body {
padding: 10px 20px;
.off-shelf-mag {
.msg {
margin-bottom: 10px;
.msg-item {
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
margin-bottom: 20px;
.tit {
width: 80px;
padding-right: 10px;
text-align: right;
}
.int {
width: 380px;
}
}
}
.log {
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
padding: 10px 0;
p {
margin: 0;
padding: 0;
line-height: 1.5em;
}
.log-tit {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.log-item:not(:last-child) {
margin-bottom: 8px;
}
}
.dialog-footer {
text-align: right;
margin-bottom: 10px;
margin-top: 30px;
}
}
}
}
</style>
<template>
<div :class="{ hidden: hidden }" class="pagination-container">
<el-pagination
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scroll-to'
export default {
name: 'Pagination',
props: {
total: {
required: true,
type: Number
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default () {
return [this.limit, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
computed: {
currentPage: {
get () {
return this.page
},
set (val) {
this.$emit('update:page', val)
}
},
pageSize: {
get () {
return this.limit
},
set (val) {
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange (val) {
this.$emit('pagination', { pageNum: this.currentPage, pageSize: val })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange (val) {
this.$emit('pagination', { pageNum: val, pageSize: this.page })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 10px 16px 30px;
text-align: right;
}
.pagination-container.hidden {
display: none;
}
.el-pagination {
font-weight: normal;
}
</style>
<template>
<div>
<el-upload
class="pic-uploader-component"
:action="resourcesUrl"
:data="dataForm"
:show-file-list="false"
:on-success="handleUploadSuccess"
:before-upload="beforeUpload"
>
<img v-if="value" :src="getImgSrc" class="pic">
<i v-else class="el-icon-plus pic-uploader-icon" />
</el-upload>
</div>
</template>
<script>
import { ossInfo } from '@/api/biz/oss'
import { getUUID } from '@/utils/index'
export default {
props: {
value: {
default: '',
type: String
}
},
data() {
return {
dataForm: {
policy: '',
signature: '',
key: '',
ossaccessKeyId: '',
dir: '',
host: ''
},
resourcesUrl: process.env.VUE_APP_RESOURCES_URL
}
},
computed: {
getImgSrc() {
if (!this.value) {
return ''
}
if (this.value.indexOf('http://') === 0 || this.value.indexOf('https://') === 0) {
return this.value
}
return this.resourcesUrl + this.value
}
},
methods: {
// 图片上传
handleUploadSuccess(response, file, fileList) {
this.dataForm.key
this.$emit('input', '/' + this.dataForm.key)
},
// 限制图片上传大小
beforeUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('上传图片大小不能超过 2MB!')
return false
}
const _self = this
return new Promise((resolve, reject) => {
ossInfo().then(response => {
_self.dataForm.policy = response.policy
_self.dataForm.signature = response.signature
_self.dataForm.ossaccessKeyId = response.accessid
_self.dataForm.key = response.dir + getUUID()
_self.dataForm.dir = response.dir
_self.dataForm.host = response.host
resolve(true)
}).catch(err => {
console.log(err)
reject(false)
})
})
}
}
}
</script>
<style lang="scss">
.pic-uploader-component .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.pic-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.pic {
width: auto;
height: 120px;
display: block;
}
}
.pic-uploader-component .el-upload:hover {
border-color: #409EFF;
}
</style>
<template>
<el-dialog
:close-on-click-modal="false"
:visible.sync="visible"
top="10vh"
:append-to-body="true"
:lock-scroll="true"
class="img-preview" width="650px">
<div class="img-box">
<img :src="(imgUrl).indexOf('http')===-1 ? resourcesUrl + imgUrl : imgUrl" class="img">
</div>
</el-dialog>
</template>
<script>
export default {
data() {
return {
imgUrl: '',
visible: false,
resourcesUrl: process.env.VUE_APP_RESOURCES_URL,
}
},
methods: {
init(imgUrl) {
this.imgUrl = imgUrl
this.visible = true
}
}
}
</script>
<style lang="scss" scoped>
.img-preview {
.img-box {
display: block;
width: 100%;
max-height: 700px;
overflow: auto;
}
.img-box::-webkit-scrollbar {
display: none;
}
.img {
display: block;
width: 100%;
height: auto;
}
}
</style>
\ No newline at end of file
<template>
<el-dialog
title="商品选择"
:modal-append-to-body="false"
:append-to-body="true"
top="4vh"
:close-on-click-modal="false"
:visible.sync="visible"
width="800px"
@close="closeModule"
>
<el-form :inline="true" :model="dataForm" class="demo-form-inline">
<el-form-item label="商品名称">
<el-input
v-model.trim="dataForm.name"
placeholder="商品名称"
clearable
style="width: 180px"
></el-input>
</el-form-item>
<el-form-item label="上级分类">
<el-cascader
expand-trigger="hover"
:options="categoryList"
:props="categoryTreeProps"
:clearable="true"
v-model="selectedCategory"
@change="handleChange"
style="width: 180px"
></el-cascader>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchProd" icon="el-icon-search">查询</el-button>
</el-form-item>
<el-form-item>
<el-button @click="clean" icon="el-icon-delete">清空</el-button>
</el-form-item>
</el-form>
<div class="prods-select-body">
<el-table
ref="prodTable"
:data="pageVO.list"
border
v-loading="dataListLoading"
highlight-current-row
@selection-change="selectChangeHandle"
style="width: 100%"
>
<el-table-column
v-if="isSingle"
width="50"
header-align="center"
align="center"
>
<template slot-scope="{row}">
<div>
<el-radio
:label="row.spuId"
v-model="singleSelectspuId"
@change.native="getSelectProdRow(row)"
>&nbsp;</el-radio
>
</div>
</template>
</el-table-column>
<el-table-column
v-if="!isSingle"
type="selection"
header-align="center"
align="center"
width="50"
></el-table-column>
<el-table-column
align="center"
width="140"
label="商品图片"
>
<template slot-scope="{row}">
<img :src="(row.mainImgUrl).indexOf('http')===-1 ? resourcesUrl + row.mainImgUrl : row.mainImgUrl" width="100" height="100" />
</template>
</el-table-column>
<el-table-column
prop="spuName"
header-align="center"
align="center"
label="商品名称"
></el-table-column>
<el-table-column
prop="priceFee"
header-align="center"
align="center"
label="商品价格"
width="200px"
></el-table-column>
</el-table>
</div>
<!-- 分页条 -->
<pagination v-show="pageVO.total>0" :total="pageVO.total" :page.sync="pageQuery.pageNum" :limit.sync="pageQuery.pageSize" @pagination="getDataList()" />
<span slot="footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="submitProds()">确认</el-button>
</span>
</el-dialog>
</template>
<script>
import { treeDataTranslate, idList } from '@/utils'
import { page } from '@/api/product/list'
import { shopCategoryPage } from '@/api/product/category'
import Pagination from '@/components/Pagination'
import Big from 'big.js'
export default {
components: { Pagination },
data() {
return {
visible: false,
resourcesUrl: process.env.VUE_APP_RESOURCES_URL,
// 查询的参数
pageQuery: {
pageSize: 10,
pageNum: 1,
spuStatus: 1
},
// 返回参数
pageVO: {
list: [], // 返回的列表
total: 0, // 一共多少条数据
pages: 0 // 一共多少页
},
// 查询参数
searchParam: {
},
dataForm: {
name: '',
product: ''
},
singleSelectspuId: 0,
allData: [],
selectProds: [],
shopCategoryId: null,
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
addOrUpdateVisible: false,
dataListSelections: [],
categoryList: [],
selectedCategory: [],
categoryTreeProps: {
value: 'categoryId',
label: 'name'
}
}
},
props: {
isSingle: {
default: false,
type: Boolean
},
prodType: {
default: null,
type: Number
},
dataUrl: {
default: '/prod/prod/page',
type: String
}
},
activated() {
this.getDataList()
},
methods: {
// 获取数据列表
init(selectProds) {
this.singleSelectspuId = 0
this.selectProds = selectProds
this.visible = true
this.dataListLoading = true
this.clean()
if (this.selectProds) {
this.selectProds.forEach(row => {
this.dataListSelections.push(row)
})
}
this.getDataList()
this.getCategoryList()
},
getCategoryList() {
shopCategoryPage().then((data) => {
this.categoryList = treeDataTranslate(data, 'categoryId', 'parentId')
})
},
getDataList() {
page({ ...this.pageQuery, ...this.searchParam }).then((pageVO) => {
this.pageVO = pageVO
this.pageVO.list.forEach(prod => {
prod.priceFee = new Big(prod.priceFee).div(100).toFixed(2)
})
this.dataListLoading = false
if (this.selectProds) {
this.$nextTick(() => {
this.selectProds.forEach(row => {
let index = this.pageVO.list.findIndex((prodItem) => prodItem.spuId === row.spuId)
this.$refs.prodTable.toggleRowSelection(this.pageVO.list[index])
})
})
}
})
},
// 每页数
sizeChangeHandle(val) {
this.pageQuery.pageSize = val
this.pageQuery.pageNum = 1
this.getDataList()
},
// 当前页
currentChangeHandle(val) {
this.pageQuery.pageNum = val
this.getDataList()
},
// 单选商品事件
getSelectProdRow(row) {
this.dataListSelections = [row]
},
// 多选点击事件
selectChangeHandle(selection) {
this.pageVO.list.forEach((tableItem) => {
let selectedProdIndex = selection.findIndex((selectedProd) => {
if (!selectedProd) {
return false
}
return selectedProd.spuId === tableItem.spuId
})
let dataSelectedProdIndex = this.dataListSelections.findIndex((dataSelectedProd) => dataSelectedProd.spuId === tableItem.spuId)
if (selectedProdIndex > -1 && dataSelectedProdIndex === -1) {
this.dataListSelections.push(tableItem)
} else if (selectedProdIndex === -1 && dataSelectedProdIndex > -1) {
this.dataListSelections.splice(dataSelectedProdIndex, 1)
}
})
},
/**
* 获取分类id
*/
handleChange(val) {
this.shopCategoryId = val[val.length - 1]
},
/**
* 根据条件搜索商品
*/
searchProd() {
this.pageQuery.pageNum = 1
this.searchParam = {
name: this.dataForm.name,
categoryId: this.shopCategoryId
}
this.getDataList()
},
/**
* 清空搜索条件
*/
clean() {
this.name = ''
this.shopCategoryId = null
this.selectedCategory = idList(this.categoryList, this.shopCategoryId, 'categoryId', 'children').reverse()
},
closeModule() {
this.name = ''
this.shopCategoryId = null
},
// 确定事件
submitProds() {
let prods = []
this.dataListSelections.forEach(item => {
let prodIndex = prods.findIndex((prod) => prod.spuId === item.spuId)
if (prodIndex === -1) {
prods.push(
{
spuId: item.spuId,
spuName: item.spuName,
mainImgUrl: item.mainImgUrl,
activityId: item.activityId,
prodType: item.prodType
}
)
}
})
// var msgInfo = ''
// // 秒杀活动选择商品的提示
// if (this.dataUrl.includes('canSekcillProdPage')) {
// msgInfo = this.$i18n.t('components.seckillWhetherToContinue')
// } else if (this.dataUrl.includes('getNotGroupProdPage')) {
// // 拼团活动选择商品的提示
// msgInfo = this.$i18n.t('components.groupWhetherToContinue')
// }
// if (msgInfo !== '' && msgInfo !== null) {
// this.prodIsSeckill(prods, msgInfo)
// }else {
this.$emit('refreshSelectProds', prods)
this.dataListSelections = []
this.visible = false
// }
},
/**
* 查询商品是否在参与秒杀活动
*/
// prodIsSeckill(prods, msgInfo) {
// let spuIds = []
// for (let index = 0; index < prods.length; index++) {
// spuIds.push(prods[index].spuId)
// }
// this.$http({
// url: this.$http.adornUrl('/admin/discount/prodIsDiscount'),
// method: 'post',
// data: spuIds
// }).then(({ data }) => {
// var msg = data
// if (msg !== undefined && msg !== null && msg !== '') {
// this.$confirm(msgInfo, this.$i18n.t('text.tips'), {
// confirmButtonText: this.$i18n.t('crud.filter.submitBtn'),
// cancelButtonText: this.$i18n.t('crud.filter.cancelBtn'),
// type: 'warning'
// }).then(() => {
// this.$emit('refreshSelectProds', prods)
// this.dataListSelections = []
// this.visible = false
// }).catch(() => { })
// } else {
// this.$emit('refreshSelectProds', prods)
// this.dataListSelections = []
// this.visible = false
// }
// })
// }
}
}
</script>
<style lang="scss" scope>
.demo-form-inline {
.el-form-item.el-form-item--medium {
margin-bottom: 22px;
}
}
.el-dialog__body {
padding: 20px;
}
.el-dialog__header {
border-bottom: 1px solid #eee;
}
.pagination-container {
padding: 0 !important;
margin-top: 20px;
}
.prods-select-body {
height: 600px;
overflow: auto;
// border-top: 1px solid #eeeeee;
// border-right: 1px solid #eeeeee;
// border-bottom: 1px solid #eeeeee;
}
.el-dialog__footer {
padding-top: 0;
}
</style>
<template>
<div class="prod-details">
<el-tabs type="card">
<el-form-item label="产品详情">
<tiny-mce
ref="content"
v-model="dataForm.detail"
:width="850"
/>
</el-form-item>
<!-- 中文 -->
<!-- <el-tab-pane label="中文信息">
<el-form-item label="商品名称" prop="nameCn">
<el-col :span="8">
<el-input
v-model="dataForm.prodNameCn"
placeholder="请输入商品名称"
maxlength="50"
/>
</el-col>
</el-form-item>
<el-form-item label="产品卖点" prop="briefCn">
<el-col :span="8">
<el-input
v-model="dataForm.briefCn"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="请输入产品卖点"
/>
</el-col>
</el-form-item>
<el-form-item label="产品详情" prop="contentCn">
<tiny-mce
ref="content"
v-model="dataForm.contentCn"
style="width: 1000px"
/>
</el-form-item>
</el-tab-pane> -->
<!-- 英文 -->
<!-- <el-tab-pane label="English Information">
<el-form-item label="Prod name" prop="prodNameEn">
<el-col :span="8">
<el-input
v-model="dataForm.prodNameEn"
placeholder="Prod name"
maxlength="50"
/>
</el-col>
</el-form-item>
<el-form-item label="Selling point" prop="briefEn">
<el-col :span="8">
<el-input
v-model="dataForm.briefEn"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="Selling point"
/>
</el-col>
</el-form-item>
<el-form-item label="product details" prop="contentEn">
<tiny-mce
ref="content"
v-model="dataForm.contentEn"
style="width: 1000px"
/>
</el-form-item>
</el-tab-pane> -->
</el-tabs>
</div>
</template>
<script>
import TinyMce from '@/components/Tinymce'
export default {
components: {
TinyMce
},
props: {
dataForm: {
type: Object,
default() {
return {}
}
},
},
data() {
return {
}
}
}
</script>
<style lang="scss">
.prod-details {
.el-tabs--card > .el-tabs__header {
border-bottom: 0;
}
}
</style>
\ No newline at end of file
<template>
<div class="ease-sku">
<sku-group
v-for="(item, index) in data"
:key="index"
:index="index"
:sku="item"
:skuTree="skuTreeData"
:onChangeHasImg="onChangeHasImg"
:onSkuChange="rebuildSku"
:onSkuRemove="handleSkuRemove"
/>
<!-- 添加属性 -->
<div class="part-form-item">
<el-button class="add-btn" size="mini" @click="addSku"><i class="el-icon-plus"></i>添加属性</el-button>
<div v-if="!categoryId && warning" class="warning">请先选择分类!</div>
<div v-if="!hasSkuValue" class="warning">请先选择已添加属性的属性值!</div>
<div v-if="!hasSkuId" class="warning">请先选择已添加属性!</div>
</div>
</div>
</template>
<script>
import skuGroup from '@/components/Sku/SkuGroup'
import { Message } from 'element-ui'
const noop = res => res
const noopPromise = () => Promise.resolve(noop)
export default {
name: 'sku-block',
provide() {
return {
'ease': this
}
},
components: {
skuGroup
},
data() {
return {
data: this.salesAttrs,
skuTreeData: this.skuTree,
warning: false,
hasSkuValue: this.hasSkuVal,
hasSkuId: true
}
},
props: {
maxSize: {
type: Number,
default: 2
},
hasSkuImg: {
type: Boolean,
default: false
},
showAddSkuImage: {
type: Boolean,
default: true
},
salesAttrs: {
type: Array,
default() {
return []
}
},
saveSalesAttrs: {
type: Array,
default() {
return []
}
},
action: {
type: String,
default: ''
},
headers: {
type: Object,
default() {
return {}
}
},
accept: {
type: String,
default: ''
},
uploadName: {
type: String,
default: 'resource'
},
// 可选规格列表
skuTree: {
type: Array,
default() {
return []
}
},
sku: {
type: Object,
default() {
return {}
}
},
categoryId: {
type: Number,
default: null
},
// 自定义sku的id key
optionValue: {
type: String,
default: 'id'
},
// 自定义sku的text key
optionText: {
type: String,
default: 'text'
},
// 异步获取规格列表
onFetchGroup: {
type: Function,
default: noopPromise
},
// 异步获取规格可选值
onFetchSku: {
type: Function,
default: noopPromise
},
// 创建新的规格名
onCreateGroup: {
type: Function,
default: noopPromise
},
// 创建新的规格值
onCreateSku: {
type: Function,
default: noopPromise
},
// 图片预览
onPreviewImg: {
type: Function,
default: noopPromise
},
hasSkuVal: {
type: Boolean,
default: null
}
},
watch: {
data: {
deep: true,
immediate: true,
handler(salesAttrs) {
this.$emit('input', salesAttrs)
// 已选属性值,隐藏错误提示
if (this.data?.some((item) => item.id && item.leaf?.length > 0)) {
this.hasSkuValue = true
}
// 已选属性,隐藏错误提示
if (this.data?.some((item) => item.id)) {
this.hasSkuId = true
}
},
},
skuTree(skuTree) {
this.skuTreeData = skuTree
},
categoryId() {
if (!this.categoryId) {
this.data.splice(0, this.data.length)
}
}
},
methods: {
onChangeHasImg(data) {
this.hasSkuImg = data
},
addSku() {
this.hasSkuValue = true
this.hasSkuId = true
if (!this.categoryId) {
this.warning = true
return
}
// 未选属性值,提示用户并禁继续选属性
if (this.data?.some((item) => item.id && item.leaf && !item.leaf.length)) {
this.hasSkuValue = false
return
}
// 未选已添加属性,提示用户并禁止继续加选属性
if (this.data?.some((item) => !item.id)) {
this.hasSkuId = false
return
}
this.data.push({
leaf: []
})
},
rebuildSku(sku, index) {
let { skuTreeData, optionValue, optionText } = this
if (
this.data.some(
(item, idx) => item[optionText] === sku[optionText] && index !== idx
)
) {
this.$message({
message: '规格名不能重复',
duration: 800
});
return false
}
this.$set(this.data, index, Object.assign({}, sku))
this.$emit('on-change', this.data)
},
handleSkuRemove(sku, index) {
let { data, skuTreeData, optionValue } = this
data.splice(index, 1)
if (index === 0 && data.length > 0) {
data[0].leaf.map((item) => {
item.is_show = sku?.leaf[0].is_show
})
// this.hasSkuImg = false
}
// 属性选择列表中不存在被删除的已选属性
if (!skuTreeData?.some(item => item[optionValue] === sku[optionValue])) {
// 被删除的已选属性id为数字类型(即非手动创建的属性)
if(typeof (sku[optionValue]) === 'number') {
// 删除某个已选属性后,往属性选项列表中增加被删除的已选属性
skuTreeData.push(sku)
}
}
// data[index].leaf[0].is_show = false
this.$emit('on-change', this.data)
}
},
beforeMount() {
let { onFetchGroup } = this
if (typeof (onFetchGroup) == 'function') {
onFetchGroup().then(skuTree => {
this.skuTreeData = skuTree
})
}
}
}
</script>
<style lang="scss">
.ease-sku {
.add-btn.el-button {
border-color: #02a1e9;
color: #02a1e9;
}
.warning {
display: inline-block;
margin-left: 15px;
color: #e43130;
font-size: 13px;
}
}
</style>
<template>
<div class="group-container">
<div class="spec-name">
<div
v-for="(item, index) in sku.leaf"
:key="index"
class="spec-name-int">
<el-tag :type="item.type" effect="plain" closable @close="handleRemoveSkuLeaf(index)">{{ item[optionText] }}</el-tag>
<div v-if="item.imgUrl" class="spec-imgbox">
<img-upload
v-if="hasSkuImage"
v-model="item.imgUrl"
@input="handleUploadSuccess(item, $event)"
class="spec-img" />
<div v-if="hasSkuImage && item.imgUrl" class="preview-btn" @click="picturePreview(item.imgUrl)">预览</div>
</div>
</div>
<!-- 新增 -->
<div class="sku-item" v-if="sku[optionValue] && sku[optionValue] !== 0">
<el-popover
placement="bottom"
width="200"
trigger="click"
v-model="visiable">
<el-select
class="popover-select"
size="mini"
v-model="leafValue"
multiple
filterable
allow-create
default-first-option
:popper-append-to-body="false"
style="width:100%"
placeholder="属性值"
@change="createSkuLeaf"
@visible-change="status => !status && this.handleSelectSku()"
>
<el-option
v-for="item in skuOptions"
:key="item[optionValue]"
:label="item[optionText]"
:value="item[optionValue]">
</el-option>
</el-select>
<!-- 新增 -->
<el-button slot="reference" circle type="primary" plain size="mini" class="cursor"><i class="el-icon-plus" /></el-button>
</el-popover>
</div>
</div>
</div>
</template>
<script>
import ImgUpload from '@/components/ImgUpload'
const noop = res => res
export default {
inject: [
'ease'
],
components: { ImgUpload },
data() {
return {
visiable: false,
leafValue: [],
skuOptions: [],
id: 0
}
},
props: {
sku: {
type: Object,
default() {
return {}
}
},
hasSkuImage: {
type: Boolean,
default: false
},
onSkuLeafChange: {
type: Function,
default: noop
}
},
computed: {
optionValue() {
return this.ease.optionValue
},
optionText() {
return this.ease.optionText
}
},
watch: {
sku: {
deep: true,
immediate: true,
handler(sku) {
this.fetchLeafById(sku[this.optionValue])
}
}
},
methods: {
handleHideVisiable() {
this.visiable = false
},
handleResetLeafValue() {
this.leafValue = []
},
fetchLeafById(id) {
if (!id) return
this.ease.onFetchSku(id).then(skuOptions => {
this.id = id
this.skuOptions = skuOptions
let skuList = []
// 筛选未选择的属性
this.skuOptions.forEach(skuItem => {
if (!this.sku.leaf.find(item => item.id === skuItem.id)) {
skuList.push(skuItem)
}
})
this.skuOptions = skuList
})
},
handleRemoveSkuLeaf(index) {
let { sku } = this
sku.leaf.splice(index, 1)
this.onSkuLeafChange(sku.leaf)
},
handleRemoveImage(id) {
let { sku, optionValue } = this
sku?.leaf?.forEach(item => {
if (item[optionValue] === id) {
item.imgUrl = ''
}
})
this.onSkuLeafChange(sku.leaf)
},
filterSkuOptions(data) {
const oldSellData = [];
const addSelData = []
const skuOptions = this.skuOptions
data.forEach(item => {
if (skuOptions.find(skuItem => skuItem.id === item)) {
oldSellData.push(item)
} else {
addSelData.push(item)
}
})
return {
oldSellData, addSelData
}
},
createSkuLeaf(selVal) {
let { sku, optionValue, skuOptions } = this
// 过滤需要新增的规格值
// data = data.filter(item => typeof (item) === 'string')
const { addSelData, oldSellData } = this.filterSkuOptions(selVal)
if (!addSelData.length) return
this.ease.onCreateSku({
data: addSelData,
id: sku[optionValue]
}).then((processedNewOptions) => {
if (processedNewOptions[0].text.length > 20) {
this.$message({
message: `属性名长度不可超过20个字符`,
duration: 1500
})
processedNewOptions = []
}
skuOptions.push(...processedNewOptions)
this.$nextTick(() => {
const values = processedNewOptions.map(item => item.id).concat(oldSellData)
this.leafValue = values;
// const values = processedNewOptions.map(item => item.id)
// this.leafValue = this.leafValue.filter(item => typeof (item) === 'number')
// this.leafValue.push(...values)
})
})
},
handleSelectSku(data) {
let { sku, hasSkuImage, optionValue, optionText, skuOptions, leafValue } = this
let skuLeaf = skuOptions.filter(item => leafValue.indexOf(item[optionValue]) >= 0)
skuLeaf.map(item => {
item.is_show = hasSkuImage
})
let skuLeafIds = sku.leaf.map(item => item[optionValue])
skuLeaf.forEach(item => {
if (skuLeafIds.indexOf(item[optionValue]) < 0) {
sku.leaf.push(item)
}
})
// 过滤同名规格值
for (var i = 0; i < sku.leaf.length; i++) {
for (var j = i + 1; j < sku.leaf.length; j++) {
if (sku.leaf[i][optionText] === sku.leaf[j][optionText]) {
sku.leaf.splice(i, 1)
j--
}
}
}
this.handleResetLeafValue()
this.handleHideVisiable()
this.onSkuLeafChange(sku.leaf)
},
handleUploadSuccess(item, urls) {
this.onSkuLeafChange(this.sku.leaf)
},
// handleUploadSuccess2(response, file, fileList, id) {
// let { sku, optionValue } = this
// sku.leaf.forEach(item => {
// if (item[optionValue] === id) {
// item.imgUrl = response.imgUrl
// }
// })
// this.onSkuLeafChange(sku.leaf)
// },
// 图片预览
picturePreview(imgUrl) {
this.ease.onPreviewImg(imgUrl)
},
created() {
let { sku, optionValue } = this
sku[optionValue] && this.fetchLeafById(sku[optionValue])
}
},
created() {
let { sku, optionValue } = this
sku[optionValue] && this.fetchLeafById(sku[optionValue])
}
}
</script>
<style lang="scss">
.group-container {
.spec-name {
margin: 15px 5px;
.spec-name-int {
display: inline-block;
margin-bottom: 15px;
margin-right: 15px;
vertical-align: top;
// 属性名称
.spec-imgbox {
display: block;
width: 60px;
height: 60px;
position: relative;
.preview-btn {
position: absolute;
bottom: -1px;
left: 1px;
display: none;
width: 100%;
background: rgba(0, 0, 0, 0.2);
line-height: 1.5em;
text-align: center;
font-size: 12px;
border-radius: 0 0 6px 6px;
cursor: pointer;
}
.spec-img {
// margin-left: 25px;
margin-top: 10px;
// sku上传图片组件大小修改
.plugin-images {
.el-upload {
.pic-uploader-icon {
width: 60px;
height: 60px;
line-height: 60px;
font-size: 18px;
}
.pic {
height: 60px;
}
}
}
}
}
.spec-imgbox:hover .preview-btn {
display: block;
}
.error-tips {
margin-left: 25px;
margin-top: 10px;
color: #e43130;
}
.el-tag--plain {
position: relative;
min-width: 100px;
color: #606266;
border-color: #dcdfe6;
padding-right: 22px;
}
.el-tag--plain .el-tag__close {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 4px;
color: #606266;
}
.el-tag--plain .el-tag__close:hover {
background: #dcdfe6;
}
}
.add-spec-btn {
display: inline-block;
font-size: 12px;
line-height: 1.5em;
margin-top: 10px;
color: #02a1e9;
cursor: pointer;
}
.sku-item {
display: inline-block;
}
}
}
</style>
<template>
<!-- 属性组 -->
<div class="part-spec-section">
<div class="spec-box">
<div class="spec-select">
<div class="sel-box">
<el-select
size="mini"
v-model="skuValue"
placeholder="请选择"
allow-create
filterable
default-first-option
:popper-append-to-body="false"
:filter-method="filterMethod"
style="min-width:100px"
@change="handleSelectSku">
<el-option
v-for="item in ease.skuTreeData"
:key="item[optionValue]"
:label="item[optionText]"
:value="item[optionValue]"
/>
</el-select>
</div>
<!-- <div v-if="index === 0 && ease.showAddSkuImage" class="sel-add-img">-->
<!-- <el-checkbox v-model="hasSkuImage" @change="handleCheckedSkuImage">添加属性图片</el-checkbox>-->
<!-- </div>-->
</div>
<div class="del-btn" @click="onSkuRemove(sku, index)">删除属性</div>
</div>
<!-- sku值 -->
<sku-container :sku="sku" :hasSkuImage="hasSkuImage" :onSkuLeafChange="handleSkuLeafChange"/>
</div>
</template>
<script>
// import ImgUpload from '@/components/ImgUpload'
import skuContainer from '@/components/Sku/SkuContainer'
const noop = res => res
export default {
components: { skuContainer },
inject: [
'ease'
],
props: {
index: {
type: Number,
default: 0
},
sku: {
type: Object,
default() {
return {}
}
},
// 自定义sku的id key
optionValue: {
type: String,
default: 'id'
},
// 自定义sku的text key
optionText: {
type: String,
default: 'text'
},
onSkuChange: {
type: Function,
default: noop
},
onSkuRemove: {
type: Function,
default: noop
}
},
data() {
return {
currentValue: '',
currentSku: '',
skuValue: '',
newsSkuText: '',
skus: this.sku,
hasSkuImage: this.sku.leaf
? this.sku.leaf.some(item => item.imgUrl)
: false
}
},
watch: {
sku: {
deep: true,
immediate: true,
handler(sku) {
if (sku[this.optionText]) {
this.$nextTick(() => {
this.skuValue = sku[this.optionText]
this.currentSku = sku
})
}
}
}
},
methods: {
filterMethod(keyword) {
// let { optionText } = this
// if (this.ease.skuTreeData.some(item => item[optionText] === keyword)) return
// this.newsSkuText = keyword
},
// 选择sku
handleSelectSku(value) {
if (value.length > 10) {
this.$message({
message: `属性名长度不可超过10个字符`,
duration: 1500
})
this.skuValue = ''
return
}
let { index, optionValue } = this
// 当切换当前属性时,把当前属性重新放入可选择列表中
console.log(value)
console.log(this.currentSku)
if (this.currentSku !== '' && value !== this.currentSku.id) {
console.log(value)
this.ease.skuTreeData.push(this.currentSku)
}
if (typeof (value) === 'number') {
let sku = this.ease.skuTreeData.find(item => item[optionValue] === value)
sku.leaf = []
if (this.onSkuChange(sku, index) === false) {
this.skuValue = ''
} else {
this.ease.skuTreeData.some((item,idx) => {
// 列表删除已选中属性
if (item[optionValue] === value) {
console.log(this.ease)
this.currentSku = this.ease.skuTreeData[idx]
this.ease.skuTreeData.splice(idx,1)
}
return false
})
}
return
}
this.createSku(value)
},
// 创建sku
createSku(text) {
let { sku, index, optionValue, optionText } = this
this.ease.onCreateGroup(text).then(data => {
if (data > 0) {
sku = {
[optionValue]: data,
[optionText]: text,
leaf: [],
}
this.onSkuChange(sku, index)
}
})
},
// 添加图片复选框
handleCheckedSkuImage(checked) {
let { sku, index } = this
sku.leaf = sku.leaf.map(item => {
item.is_show = checked
return item
})
this.onSkuChange(sku, index)
},
handleSkuLeafChange(leaf) {
let { sku, index } = this
sku.leaf = leaf
this.onSkuChange(sku, index)
}
}
}
</script>
<style lang="scss">
/* 商品属性 */
.part-spec-section {
margin-top: 10px;
margin-bottom: 20px;
.spec-box {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-width: 400px;
padding: 8px 10px;
background: #f4f5f9;
.spec-select {
display: flex;
align-items: center;
}
.spec-select .sel-add-img {
margin-left: 20px;
}
.del-btn {
font-size: 13px;
color: #02a1e9;
cursor: pointer;
}
}
}
</style>
<template>
<div class="ease-sku-table">
<!-- 批量设置 -->
<div class="batch-settings" v-if="lists[0] && lists[0].spuSkuAttrValues">
<div class="batch">
<div>
<span class="bat-tit">批量设置</span>
<span class="set-tips">在下方栏中选择内容进行批量填充</span>
</div>
<el-button size="mini" plain @click="setNow" class="set">立即设置</el-button>
</div>
<div class="bat-set-sel">
<el-select v-if="firstSkuValOptions.length>0" v-model="firstSkuVal" size="mini" class="bat-set-item" placeholder="请选择" style="width:90px">
<el-option
v-for="item in firstSkuValOptions"
:key="item.id"
:label="item.text"
:value="item.id"
/>
</el-select>
<el-select v-if="secondSkuValOptions.length>0" v-model="secondSkuVal" size="mini" class="bat-set-item" placeholder="全部" style="width:90px">
<el-option
v-for="item in secondSkuValOptions"
:key="item.id"
:label="item.text"
:value="item.id"
/>
</el-select>
<!-- 库存 -->
<el-input class="bat-set-item" size="mini" v-model.number="stockIntVal" :mini="0 "
oninput="this.value=this.value.replace(/^\.+|[^\d.]/g,'')"
placeholder="库存" style="width:90px"
/>
<!-- 市场价 -->
<el-input-number
class="bat-set-item"
type='number'
controls-position="right"
:precision="2" :step="0.1"
:min="0" :max="1000000000"
size="mini"
v-model.number="markedPriceIntVal"
placeholder="市场价"
style="width:90px"
/>
<!-- 销售价 -->
<el-input-number
class="bat-set-item"
type='number'
controls-position="right"
:precision="2" :step="0.1"
:min="0.01" :max="1000000000"
size="mini"
v-model.number="priceIntVal"
placeholder="销售价"
style="width:90px"
/>
<!-- 商品条形码 -->
<el-input class="bat-set-item" size="mini" v-model.number="barCodeIntVal"
oninput="this.value=this.value.replace(/^\.+|[^\d.]/g,'')"
placeholder="商品条形码" style="width:90px"
/>
<!-- 商品编码 -->
<el-input class="bat-set-item" size="mini" v-model.number="skuCodeIntVal"
oninput="this.value=this.value.replace(/^\.+|[^\d.]/g,'')"
placeholder="商品编码" style="width:90px" />
</div>
</div>
<!-- 表格 -->
<el-table
border
size="mini"
:data="lists"
:span-method="handleSpanMethod"
class="tab-con"
>
<template v-for="(label, index) in columns">
<!-- 为什么要判断label: 动态添加规格名的时候规格名不为undefiend时未动态显示, 没有看table-column实现暂时这么解决 -->
<el-table-column
v-if="label"
:label="label"
:key="index">
<template slot-scope="scope">
{{scope.row.spuSkuAttrValues[index] && scope.row.spuSkuAttrValues[index].attrValueName}}
</template>
</el-table-column>
</template>
<el-table-column
prop="stock"
label="库存"
class="tab-int"
>
<template slot-scope="scope">
<el-input-number :min="0" @blur="stockValidAndChange(scope)" v-model.number="scope.row.stock" :disabled="scope.row.status===0" controls-position="right" class="tab-int"></el-input-number>
</template>
</el-table-column>
<el-table-column
prop="marketPriceFee"
label="市场价(元)"
class="tab-int"
>
<template slot-scope="scope">
<el-input-number :min="0" :max="1000000000" v-model="scope.row.marketPriceFee" :disabled="scope.row.status===0" controls-position="right" :precision="2" class="tab-int"></el-input-number>
</template>
</el-table-column>
<el-table-column
prop="priceFee"
label="销售价(元)"
class="tab-int"
>
<template slot-scope="scope">
<el-input-number :min="0.01" :max="1000000000" v-model="scope.row.priceFee" :disabled="scope.row.status===0" controls-position="right" :precision="2" class="tab-int"></el-input-number>
</template>
</el-table-column>
<el-table-column
prop="barCode"
label="商品条形码"
class="tab-int"
>
<template slot-scope="scope">
<el-input :min="0" v-model.number="scope.row.modelId" :disabled="scope.row.status===0" oninput="this.value=this.value.replace(/^\.+|[^\d.]/g,'')" class="tab-int"></el-input>
</template>
</el-table-column>
<el-table-column
prop="skuCode"
label="商品编码"
class="tab-int"
>
<template slot-scope="scope">
<el-input :min="0" v-model.number="scope.row.partyCode" :disabled="scope.row.status===0" oninput="this.value=this.value.replace(/^\.+|[^\d.]/g,'')" class="tab-int"></el-input>
</template>
</el-table-column>
<el-table-column
prop="skuCode"
label="sku状态"
v-if="spuId && !isNoSkuValue"
>
<template slot-scope="scope">
<el-button type="text" @click="skuStatusOperation(scope)">{{scope.row.status === 1 ? '禁用' : '启用' }}</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { flatten as genFlatten } from '@/utils'
export default {
name: 'sku-table',
props: {
data: {
type: Array,
default() {
return []
}
},
// 需要附加的字段
flatten: {
type: Array,
default() {
return []
}
},
// 自定义sku的id key
optionValue: {
type: String,
default: 'id'
},
// 自定义sku的text key
optionText: {
type: String,
default: 'text'
},
spuId: {
type: Number,
default: null
},
isNoSkuValue: {
type: Boolean,
default: false
}
},
originList: [],
data() {
return {
rowspan: [],
lists: [],
firstSkuVal: -1,
secondSkuVal: -1,
stockIntVal: '', // 库存
markedPriceIntVal: '',
priceIntVal: '',
barCodeIntVal: '', // 条形码
skuCodeIntVal: '',
}
},
computed: {
filter() {
return this.data.filter(item => item.text && item.leaf.length)
},
columns() {
return this.filter.map(item => item[this.optionText])
},
firstSkuValOptions() {
const { data } = this
if (data[0]) {
if (data[0].leaf) {
return [{ id: -1, is_show: true, text: '全部' }, ...data[0].leaf]
}
} else {
return []
}
},
secondSkuValOptions() {
const { data } = this
if (data[1]) {
if (data[1].leaf) {
return [{ id: -1, is_show: true, text: '全部' }, ...data[1].leaf]
}
} else {
return []
}
},
},
watch: {
filter: {
deep: true,
immediate: true,
handler() {
const lists = this.genLists(this.filter, this.flatten)
this.lists = lists
this.computeRowspan()
}
},
flatten() {
const lists = this.genLists(this.filter, this.flatten)
this.originList = JSON.parse(JSON.stringify(this.genLists(this.filter, this.flatten)))
if(this.lists.length === 1 && !this.lists[0].spuSkuAttrValues) {
this.flatten.forEach(el => {
let baseData = {
stock: el.stock, // 库存
marketPriceFee: el.marketPriceFee, // 市场价
priceFee: el.priceFee, // 销售价
partyCode: el.partyCode, // 商品编码
modelId: el.modelId, // 条形码
}
this.lists = [baseData]
})
}
},
lists: {
deep: true,
immediate: true,
handler(data) {
this.$emit('on-change-data', data)
}
}
},
methods: {
genLists: (filter, flatten) => {
let baseData = {
stock: 0, // 库存
marketPriceFee: '', // 市场价
priceFee: 0.01, // 销售价
partyCode: '', // 商品编码
modelId: '', // 条形码
}
if (filter.length && genFlatten(filter, flatten).length) {
return genFlatten(filter, flatten, { extraData: baseData })
} else {
return [baseData]
}
},
computeRowspan() {
this.rowspan = []
const rowspan = (index) => {
let span = []
let dot = 0
this.lists.map((item, idx) => {
if (idx === 0) {
span.push(1)
} else {
if (item.spuSkuAttrValues?.[index].attrValueName === this.lists[idx - 1].spuSkuAttrValues?.[index].attrValueName) {
span[dot] += 1
span.push(0)
} else {
dot = idx
span.push(1)
}
}
})
this.rowspan.push(span)
}
this.filter.map((item, index) => {
rowspan(index)
})
},
handleSpanMethod({ row, column, rowIndex, columnIndex }) {
for (let i = 0; i < this.filter.length; i++) {
if (columnIndex === i) {
if (this.rowspan[i] && this.rowspan[i][rowIndex]) {
return {
rowspan: this.rowspan[i][rowIndex],
colspan: 1
}
} else {
return {
rowspan: 0,
colspan: 0
}
}
}
}
},
// 库存修改与验证
stockValidAndChange(scope) {
const { $index, row} = scope;
const originStock = this.originList?.[$index]?.stock;
if (!this.lists[$index].stock) {
this.lists[$index].stock = 0
}
if (originStock !== undefined) {
if (originStock > row.stock) {
// 用户输入错误
// row.stock = originStock;
this.lists[$index].stock = originStock
this.$message({
message: `输入库存不得小于原有库存`,
duration: 1000
})
return
}
this.$set(this.lists[$index], 'changeStock', parseInt(row.stock) - parseInt(originStock))
}
},
// 立即设置
setNow() {
const {
lists, firstSkuVal = '', secondSkuVal = '', spuId,
stockIntVal = parseInt(stockIntVal), markedPriceIntVal = parseInt(markedPriceIntVal),
priceIntVal = parseFloat(priceIntVal), skuCodeIntVal = parseInt(skuCodeIntVal),
barCodeIntVal = parseInt(barCodeIntVal)
} = this
let newItem = {}
const setItem = (item, index) => {
// item.stock = stockIntVal ? parseInt(stockIntVal) : item.stock || 0 // 库存,正整数
item.marketPriceFee = markedPriceIntVal >= 0 ? markedPriceIntVal : item.marketPriceFee || 0
item.priceFee = priceIntVal ? priceIntVal : item.priceFee || 0.01 // 销售价
item.partyCode = skuCodeIntVal ? skuCodeIntVal : item.partyCode || '' // 商品编码
item.modelId = barCodeIntVal ? barCodeIntVal : item.modelId || '' // 商品条形码
// 回显 && 原库存 > 0
if (spuId && item.stock >= 0) {
// 输入库存 是否>=0 且 是否>=原有库存
if (stockIntVal >= item.stock) {
item.stock = stockIntVal
this.$set(this.lists[index], 'changeStock', stockIntVal - parseInt(this.originList[index].stock)) // 改变的库存数量(新增-原有)
}
} else {
item.stock = stockIntVal >= 0 ? stockIntVal : item.stock || 0
}
return item
}
const vaildSkuValArr = [firstSkuVal, secondSkuVal]
lists.forEach((item, index) => {
const { spuSkuAttrValues } = item;
// secondSkuVal
if (
spuSkuAttrValues.every((attr, idx) => vaildSkuValArr[idx] === -1 || attr.attrValueId === vaildSkuValArr[idx])
) {
setItem(item, index)
}
})
this.lists = lists
},
/**
* sku状态
*/
skuStatusOperation(scope) {
const { $index, row} = scope
const currentStatus = this.lists?.[$index]?.status
let newStatus = currentStatus === 0 ? 1 : 0
this.lists[$index].status = newStatus
}
}
}
</script>
<style lang="scss">
.ease-sku-table {
.el-table__row {
.el-input-number {
display: block;
width: 100%;
}
.el-input-number.is-controls-right[class*="medium"] [class*="increase"],
.el-input-number.is-controls-right[class*="medium"] [class*="decrease"] {
display: none;
border: 0;
background: #fff;
}
}
// 批量设置
.batch-settings {
margin-bottom: 15px;
.batch {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.set-now {
font-size: 14px;
line-height: 1em;
padding: 6px 10px;
color: #02a1e9;
border: 1px solid #02a1e9;
cursor: pointer;
}
.set.el-button {
border-color: #02a1e9;
color: #02a1e9;
}
.set.el-button:active {
background: #ecf5ff;
}
.set-tips {
font-size: 13px;
color: #999;
margin-left: 15px;
}
}
.bat-set-item {
margin-right: 10px;
}
.el-input .el-input__inner {
padding: 0 8px;
}
.el-input-number.is-controls-right .el-input__inner {
text-align: left;
padding: 0 8px;
}
.el-input-number.is-controls-right[class*=mini] [class*=increase],
.el-input-number.is-controls-right[class*=mini] [class*=decrease] {
display: none;
}
}
.el-table .cell {
padding: 0;
text-align: center;
}
.el-table--border th:first-child .cell, .el-table--border td:first-child .cell {
padding-left: 0;
}
.el-input-number.is-controls-right .el-input__inner {
text-align: center;
}
.el-table--mini td {
padding: 0;
}
.el-input--medium .el-input__inner {
width: 100%;
height: 50px;
border: 0;
outline: none;
padding: 0 10px;
font-size: 13px;
text-align: center;
}
.el-table--enable-row-hover .el-table__body tr:hover > td {
background: transparent;
}
}
</style>
<template>
<div class="spu-catagory-attrs">
<div class="part-section">
<div v-for="(attrs, attrIdx) in attrsList" :key="attrIdx" class="part-sec-item">
<div class="flx">
<div class="sec-tit">
<span v-if="attrs.searchType == 1" class="stars-icon" />
<span class="name">{{ attrs.name }}</span>
</div>
<el-select v-if="attrs.attrValues && attrs.attrValues.length > 0" v-model="attrs.attrValue" placeholder="请选择" @change="getValueOfBasicAttrs(attrs, 0)" style="min-width: 150px; width:60%">
<el-option
v-for="item in attrs.attrValues"
:key="item.attrValueId"
:label="item.value"
:value="item.attrValueId"
/>
</el-select>
<el-input v-else v-model="attrs.attrValue" :placeholder="'请输入'+attrs.name" @blur="getValueOfBasicAttrs(attrs, 1)" style="min-width: 150px; width:60%" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
attrsList: {
type: Array,
default() {
return []
}
},
},
data() {
return {
}
},
methods: {
// 获取数据
getValueOfBasicAttrs(attrs, st) {
this.$emit('getValueOfBasicAttrs', this.attrsList, attrs, st)
}
}
}
</script>
<style lang="scss">
.spu-catagory-attrs {
.part-section {
background: #f9f9f9;
padding: 20px;
padding-bottom: 0;
text-align: left;
width: 100%;
max-width: 100%;
overflow: hidden;
.part-sec-item {
display: inline-block;
width: 48%;
margin-bottom: 20px;
margin-right: 10px;
.flx {
display: flex;
align-items: center;
.sec-tit {
position: relative;
margin-right: 15px;
width: auto;
text-align: right;
line-height: 1.5em;
// 三角形样式
.stars-icon {
display: inline-block;
width: 12px;
height: 8px;
}
.stars-icon::before {
position: absolute;
top: 16px;
left: 0;
content: "";
width: 0;
height: 0;
margin-top: -10px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 8px solid #ff7300;
}
}
}
.int-box {
width: 300px;
}
}
.part-sec-item:nth-child(2n) {
margin-right: 0;
}
}
}
</style>
\ No newline at end of file
<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>
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click="clickUpload()">
upload
</el-button>
<!-- 弹窗, 新增图片 -->
<elx-imgbox v-if="elxImgboxVisible" ref="elxImgbox" @refreshPic="refreshPic"></elx-imgbox>
</div>
</template>
<script>
// import { getToken } from 'api/qiniu'
import ImgsUpload from '@/components/ImgsUpload'
import ElxImgbox from '@/components/imgbox'
export default {
name: 'EditorSlideUpload',
props: {
color: {
type: String,
default: '#1890ff'
}
},
data() {
return {
elxImgboxVisible: false,
maxNum: 15, // 可选择的最大图片数量
imgUrls: [],
resourcesUrl: process.env.VUE_APP_RESOURCES_URL,
}
},
components: {
ImgsUpload,
ElxImgbox
},
methods: {
/**
* 打开图片选择窗
*/
clickUpload () {
this.imgUrls = ''
this.elxImgboxVisible = true
this.$nextTick(() => {
this.$refs.elxImgbox.init(0, this.maxNum)
})
},
/**
* 接收回调的图片数据
*/
refreshPic(imagePath) {
let imageArray = imagePath.split(',')
var data = []
imageArray.forEach(img => {
data.push(this.resourcesUrl + img)
})
this.imgUrls = ''
this.dialogVisible = false
this.$emit('successCBK', data)
}
// handleSubmit() {
// let imageArray = this.imgUrls.split(',')
// var data = []
// imageArray.forEach(img => {
// data.push(this.resourcesUrl + img)
// })
// this.imgUrls = ''
// this.dialogVisible = false
// this.$emit('successCBK', data)
// }
}
}
</script>
<style lang="scss" scoped>
.editor-slide-upload {
margin-bottom: 20px;
::v-deep .el-upload--picture-card {
width: 100%;
}
}
</style>
let callbacks = []
function loadedTinymce() {
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
// check is successfully downloaded script
return window.tinymce
}
const dynamicLoadScript = (src, callback) => {
const existingScript = document.getElementById(src)
const cb = callback || function() {}
if (!existingScript) {
const script = document.createElement('script')
script.src = src // src url for the third-party library being loaded.
script.id = src
document.body.appendChild(script)
callbacks.push(cb)
const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
onEnd(script)
}
if (existingScript && cb) {
if (loadedTinymce()) {
cb(null, existingScript)
} else {
callbacks.push(cb)
}
}
function stdOnEnd(script) {
script.onload = function() {
// this.onload = null here is necessary
// because even IE9 works not like others
this.onerror = this.onload = null
for (const cb of callbacks) {
cb(null, script)
}
callbacks = null
}
script.onerror = function() {
this.onerror = this.onload = null
cb(new Error('Failed to load ' + src), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
for (const cb of callbacks) {
cb(null, script) // there is no way to catch loading errors in IE8
}
callbacks = null
}
}
}
export default dynamicLoadScript
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
<script>
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import editorImage from './components/EditorImage'
import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
export default {
name: 'Tinymce',
components: { editorImage },
props: {
id: {
type: String,
default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
type: String,
default: ''
},
toolbar: {
type: Array,
required: false,
default() {
return []
}
},
menubar: {
type: String,
default: 'file edit insert view format table'
},
height: {
type: [Number, String],
required: false,
default: 360
},
width: {
type: [Number, String],
required: false,
default: 'auto'
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN',
'es': 'es_MX',
'ja': 'ja'
}
}
},
computed: {
language() {
return this.languageTypeList[this.$store.getters.language]
},
containerWidth() {
const width = this.width
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
return `${width}px`
}
return width
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || ''))
}
},
language() {
this.destroyTinymce()
this.$nextTick(() => this.initTinymce())
}
},
mounted() {
this.init()
},
activated() {
if (window.tinymce) {
this.initTinymce()
}
},
deactivated() {
this.destroyTinymce()
},
destroyed() {
this.destroyTinymce()
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) => {
if (err) {
this.$message.error(err.message)
return
}
this.initTinymce()
})
},
initTinymce() {
const _this = this
window.tinymce.init({
language: this.language,
selector: `#${this.tinymceId}`,
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true
this.$emit('input', editor.getContent())
})
},
setup(editor) {
editor.on('FullscreenStateChanged', (e) => {
_this.fullscreen = e.state
})
},
// it will try to keep these URLs intact
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
convert_urls: false
// 整合七牛上传
// images_dataimg_filter(img) {
// setTimeout(() => {
// const $image = $(img);
// $image.removeAttr('width');
// $image.removeAttr('height');
// if ($image[0].height && $image[0].width) {
// $image.attr('data-wscntype', 'image');
// $image.attr('data-wscnh', $image[0].height);
// $image.attr('data-wscnw', $image[0].width);
// $image.addClass('wscnph');
// }
// }, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = _this.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('出现未知问题,刷新页面,或者联系程序员')
// console.log(err);
// });
// },
})
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId)
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen')
}
if (tinymce) {
tinymce.destroy()
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v}" >`)
})
}
}
}
</script>
<style lang="scss" scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container {
::v-deep {
.mce-fullscreen {
z-index: 10000;
}
}
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>
Markdown is supported
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