feat: 商品分类和商品基础信息升级改造
This commit is contained in:
parent
c741d89b15
commit
d465fd6b47
20
README.md
20
README.md
|
|
@ -503,7 +503,7 @@ vite-plugin-svg-icons 使用说明:https://github.com/anncwb/vite-plugin-svg-i
|
|||
**安装**
|
||||
|
||||
```
|
||||
npm i vite-plugin-svg-icons -D
|
||||
npm i -D vite-plugin-svg-icons
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -550,4 +550,20 @@ router.afterEach(() => {
|
|||
NProgress.done()
|
||||
})
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## TinyMCE 富文本编辑器
|
||||
|
||||
**官网:** http://tinymce.ax-z.cn/integrations/integrate-index.php
|
||||
|
||||
**安装**
|
||||
|
||||
```
|
||||
npm i -S tinymce
|
||||
|
||||
npm i -S @tinymce/tinymce-vue
|
||||
|
||||
npm i -D @types/tinymce
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons": "0.0.11",
|
||||
"@tinymce/tinymce-vue": "^4.0.5",
|
||||
"axios": "^0.24.0",
|
||||
"element-plus": "^1.2.0-beta.6",
|
||||
"nprogress": "^0.2.0",
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
"path-to-regexp": "^6.2.0",
|
||||
"pinia": "^2.0.9",
|
||||
"screenfull": "^6.0.0",
|
||||
"tinymce": "^5.10.2",
|
||||
"vue": "^3.2.16",
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
|
|
@ -22,6 +24,7 @@
|
|||
"@types/node": "^16.11.7",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/path-browserify": "^1.0.0",
|
||||
"@types/tinymce": "^4.6.4",
|
||||
"@vitejs/plugin-vue": "^1.9.3",
|
||||
"sass": "^1.43.4",
|
||||
"typescript": "^4.4.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,255 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{fullscreen: fullscreen}"
|
||||
class="tinymce-container"
|
||||
:style="{width: containerWidth}"
|
||||
>
|
||||
<TinymceEditor
|
||||
:id="id"
|
||||
v-model:value="tinymceContent"
|
||||
:init="initOptions"
|
||||
/>
|
||||
<div class="editor-custom-btn-container">
|
||||
<EditorImageUpload
|
||||
:color="uploadButtonColor"
|
||||
class="editor-upload-btn"
|
||||
@success-callback="imageSuccessCBK"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// Docs: https://www.tiny.cloud/docs/advanced/usage-with-module-loaders/
|
||||
// Import TinyMCE
|
||||
import 'tinymce'
|
||||
// Default icons are required for TinyMCE 5.3 or above
|
||||
import 'tinymce/icons/default'
|
||||
// Import themes
|
||||
import 'tinymce/themes/silver'
|
||||
import 'tinymce/themes/mobile'
|
||||
// Any plugins you want to use has to be imported
|
||||
import 'tinymce/plugins/advlist'
|
||||
import 'tinymce/plugins/anchor'
|
||||
import 'tinymce/plugins/autoresize'
|
||||
import 'tinymce/plugins/autolink'
|
||||
import 'tinymce/plugins/autosave'
|
||||
import 'tinymce/plugins/charmap'
|
||||
import 'tinymce/plugins/code'
|
||||
import 'tinymce/plugins/codesample'
|
||||
import 'tinymce/plugins/directionality'
|
||||
import 'tinymce/plugins/emoticons'
|
||||
import 'tinymce/plugins/fullpage'
|
||||
import 'tinymce/plugins/fullscreen'
|
||||
import 'tinymce/plugins/help'
|
||||
import 'tinymce/plugins/hr'
|
||||
import 'tinymce/plugins/image'
|
||||
import 'tinymce/plugins/imagetools'
|
||||
import 'tinymce/plugins/insertdatetime'
|
||||
import 'tinymce/plugins/link'
|
||||
import 'tinymce/plugins/lists'
|
||||
import 'tinymce/plugins/media'
|
||||
import 'tinymce/plugins/nonbreaking'
|
||||
import 'tinymce/plugins/noneditable'
|
||||
import 'tinymce/plugins/pagebreak'
|
||||
import 'tinymce/plugins/paste'
|
||||
import 'tinymce/plugins/preview'
|
||||
import 'tinymce/plugins/print'
|
||||
import 'tinymce/plugins/save'
|
||||
import 'tinymce/plugins/searchreplace'
|
||||
import 'tinymce/plugins/spellchecker'
|
||||
import 'tinymce/plugins/tabfocus'
|
||||
import 'tinymce/plugins/table'
|
||||
import 'tinymce/plugins/template'
|
||||
import 'tinymce/plugins/textpattern'
|
||||
import 'tinymce/plugins/visualblocks'
|
||||
import 'tinymce/plugins/visualchars'
|
||||
import 'tinymce/plugins/wordcount'
|
||||
import TinymceEditor from '@tinymce/tinymce-vue' // TinyMCE vue wrapper
|
||||
import EditorImageUpload, { UploadObject } from './components/EditorImage.vue'
|
||||
import { plugins, toolbar } from './config'
|
||||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
toRefs,
|
||||
watch,
|
||||
nextTick,
|
||||
ref,
|
||||
computed
|
||||
} from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
const defaultId = () =>
|
||||
'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TinymceEditor,
|
||||
EditorImageUpload
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: defaultId
|
||||
},
|
||||
toolbar: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
menubar: {
|
||||
type: String,
|
||||
default: 'file edit insert view format table'
|
||||
},
|
||||
height: {
|
||||
type: String || Number,
|
||||
default: '360px'
|
||||
},
|
||||
width: {
|
||||
type: String || Number,
|
||||
default: 'auto'
|
||||
}
|
||||
},
|
||||
emits: ['input'],
|
||||
setup(props, ctx) {
|
||||
const store = useStore()
|
||||
const dataMap = reactive({
|
||||
hasChange: false,
|
||||
hasInit: false,
|
||||
fullscreen: true,
|
||||
getlanguage: () => {
|
||||
return store.state.app.language
|
||||
},
|
||||
uploadButtonColor: () => {
|
||||
return store.state.settings.theme
|
||||
},
|
||||
tinymceContent: computed(() => {
|
||||
return props.value
|
||||
}),
|
||||
containerWidth: () => {
|
||||
const width = props.width
|
||||
// Test matches `100`, `'100'`
|
||||
if (/^[\d]+(\.[\d]+)?$/.test(width.toString())) {
|
||||
return `${width}px`
|
||||
}
|
||||
return width
|
||||
}
|
||||
})
|
||||
|
||||
const initOptions = ref(
|
||||
{
|
||||
selector: `#${props.id}`,
|
||||
height: props.height,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
body_class: 'panel-body',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
object_resizing: false,
|
||||
toolbar: props.toolbar.length > 0 ? props.toolbar : toolbar,
|
||||
menubar: props.menubar,
|
||||
plugins: plugins,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
language_url: store.state.app.language === 'en' ? '' : `${process.env.BASE_URL}tinymce/langs/${store.state.app.language}.js`,
|
||||
language: 'zh_CN',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
skin_url: `${process.env.BASE_URL}tinymce/skins/`,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
emoticons_database_url: `${process.env.BASE_URL}tinymce/emojis.min.js`,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
end_container_on_empty_block: true,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
powerpaste_word_import: 'clean',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
code_dialog_height: 450,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
code_dialog_width: 1000,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
advlist_bullet_styles: 'square',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
advlist_number_styles: 'default',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
default_link_target: '_blank',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
link_title: false,
|
||||
// inserting nonbreaking space need Nonbreaking Space Plugin
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
nonbreaking_force_tab: true,
|
||||
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
|
||||
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
convert_urls: false,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
init_instance_callback: (editor: any) => {
|
||||
if (props.value) {
|
||||
editor.setContent(props.value)
|
||||
}
|
||||
dataMap.hasInit = true
|
||||
editor.on('NodeChange Change KeyUp SetContent', () => {
|
||||
dataMap.hasChange = true
|
||||
ctx.emit('input', editor.getContent())
|
||||
})
|
||||
},
|
||||
setup: (editor: any) => {
|
||||
editor.on('FullscreenStateChanged', (e: any) => {
|
||||
dataMap.fullscreen = e.state
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(() => store.state.app.language, () => {
|
||||
const tinymceManager = (window as any).tinymce
|
||||
const tinymceInstance = tinymceManager.get(props.id)
|
||||
if (dataMap.fullscreen) {
|
||||
tinymceInstance.execCommand('mceFullScreen')
|
||||
}
|
||||
if (tinymceInstance) {
|
||||
tinymceInstance.destroy()
|
||||
}
|
||||
nextTick(() => {
|
||||
tinymceManager.init(initOptions)
|
||||
})
|
||||
})
|
||||
|
||||
watch(() => dataMap.tinymceContent, (value) => {
|
||||
console.log(value)
|
||||
})
|
||||
|
||||
const imageSuccessCBK = (arr: UploadObject[]) => {
|
||||
const tinymce = (window as any).tinymce.get(props.id)
|
||||
arr.forEach((v) => {
|
||||
tinymce.insertContent(`<img class="wscnph" src="${v.url}" >`)
|
||||
})
|
||||
}
|
||||
return { ...toRefs(dataMap), imageSuccessCBK, initOptions }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tinymce-container {
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
.mce-fullscreen {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
.editor-custom-btn-container {
|
||||
position: absolute !important;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
z-index: 1002;
|
||||
}
|
||||
.editor-upload-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
textarea {
|
||||
visibility: hidden;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button
|
||||
:style="{background: color, borderColor: color}"
|
||||
icon="el-icon-upload"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="dialogVisible = true"
|
||||
>
|
||||
上传
|
||||
</el-button>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-upload
|
||||
:multiple="true"
|
||||
:file-list="defaultFileList"
|
||||
:show-file-list="true"
|
||||
:on-remove="handleRemove"
|
||||
:on-success="handleSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
class="editor-slide-upload"
|
||||
action="https://httpbin.org/post"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
Click upload
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">
|
||||
Cancel
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
Confirm
|
||||
</el-button>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import { reactive, defineComponent, toRefs } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
export interface UploadObject {
|
||||
hasSuccess: boolean
|
||||
uid: number
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
export default defineComponent({
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
|
||||
},
|
||||
emits: ['success-callback'],
|
||||
setup(_, ctx) {
|
||||
let listObj: { [key: string]: UploadObject } = {}
|
||||
const dataMap = reactive({
|
||||
dialogVisible: false,
|
||||
defaultFileList: [],
|
||||
checkAllSuccess: () => {
|
||||
return Object.keys(listObj).every(item => listObj[item].hasSuccess)
|
||||
},
|
||||
handleSubmi: () => {
|
||||
const arr = Object.keys(listObj).map(v => listObj[v])
|
||||
if (!dataMap.checkAllSuccess()) {
|
||||
ElMessage.success('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
|
||||
}
|
||||
ctx.emit('success-callback', arr)
|
||||
listObj = {}
|
||||
dataMap.defaultFileList = []
|
||||
dataMap.dialogVisible = false
|
||||
},
|
||||
|
||||
handleSuccess: (response: any, file: any) => {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (listObj[objKeyArr[i]].uid === uid) {
|
||||
listObj[objKeyArr[i]].url = response.files.file
|
||||
listObj[objKeyArr[i]].hasSuccess = true
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
handleRemove: (file: any) => {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (listObj[objKeyArr[i]].uid === uid) {
|
||||
delete listObj[objKeyArr[i]]
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeUpload: (file: any) => {
|
||||
const fileName = file.uid
|
||||
const img = new Image()
|
||||
img.src = window.URL.createObjectURL(file)
|
||||
img.onload = () => {
|
||||
listObj[fileName] = {
|
||||
hasSuccess: false,
|
||||
uid: file.uid,
|
||||
url: '',
|
||||
width: img.width,
|
||||
height: img.height
|
||||
}
|
||||
}
|
||||
},
|
||||
handleSubmit() {
|
||||
const arr = Object.keys(listObj).map(v => listObj[v])
|
||||
if (!dataMap.checkAllSuccess()) {
|
||||
ElMessage.warning('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
|
||||
return
|
||||
}
|
||||
ctx.emit('success-callback', arr)
|
||||
listObj = {}
|
||||
dataMap.defaultFileList = []
|
||||
dataMap.dialogVisible = false
|
||||
}
|
||||
})
|
||||
|
||||
return { ...toRefs(dataMap), listObj }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.editor-slide-upload {
|
||||
.el-upload--picture-card {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editor-slide-upload {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// Import plugins that you want to use
|
||||
// Detail plugins list see: https://www.tiny.cloud/apps/#core-plugins
|
||||
// Custom builds see: https://www.tiny.cloud/get-tiny/custom-builds/
|
||||
export const plugins = ['advlist anchor autolink autoresize autosave charmap code codesample directionality emoticons fullpage fullscreen help hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount']
|
||||
|
||||
// Here is the list of toolbar control components
|
||||
// Details see: https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
|
||||
export const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample help', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons charmap forecolor backcolor fullpage fullscreen']
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
</div>
|
||||
<div class="components-container__footer">
|
||||
<el-button type="primary" @click="onNextStepClick">下一步,填写商品信息</el-button>
|
||||
<el-button type="primary" @click="handleNext">下一步,填写商品信息</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -32,8 +32,10 @@ import {ElCascaderPanel, ElMessage} from "element-plus";
|
|||
const emit = defineEmits(['next'])
|
||||
|
||||
const props = defineProps({
|
||||
goodsId: Number,
|
||||
categoryId: Number
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default:{ }
|
||||
}
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
|
|
@ -44,15 +46,11 @@ const state = reactive({
|
|||
|
||||
const {categoryOptions, pathLabels, categoryId} = toRefs(state)
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
function loadData() {
|
||||
listCascadeCategories({}).then(response => {
|
||||
state.categoryOptions = response.data
|
||||
if (props.goodsId) {
|
||||
state.categoryId = props.categoryId as any
|
||||
if (props.modelValue.id) {
|
||||
state.categoryId = props.modelValue.categoryId
|
||||
nextTick(() => {
|
||||
handleCategoryChange()
|
||||
})
|
||||
|
|
@ -67,8 +65,7 @@ function handleCategoryChange() {
|
|||
state.categoryId = checkNode.value
|
||||
}
|
||||
|
||||
|
||||
function onNextStepClick() {
|
||||
function handleNext() {
|
||||
if (!state.categoryId) {
|
||||
ElMessage.warning('请选择商品分类')
|
||||
return false
|
||||
|
|
@ -76,6 +73,11 @@ function onNextStepClick() {
|
|||
emit('next' )
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,49 +1,62 @@
|
|||
<!--
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<div class="components-container__main">
|
||||
<el-form
|
||||
ref="goodsForm"
|
||||
:rules="rules"
|
||||
:model="value"
|
||||
label-width="150px">
|
||||
ref="goodsForm"
|
||||
:rules="rules"
|
||||
:model="modelValue"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input style="width: 400px" v-model="value.name"/>
|
||||
<el-input style="width: 400px" v-model="modelValue.name"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="原价" prop="originPrice">
|
||||
<el-input style="width: 400px" v-model="value.originPrice"/>
|
||||
<el-input style="width: 400px" v-model="modelValue.originPrice"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="现价" prop="price">
|
||||
<el-input style="width: 400px" v-model="value.price"/>
|
||||
<el-input style="width: 400px" v-model="modelValue.price"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品品牌" prop="brandId">
|
||||
<el-select
|
||||
v-model="value.brandId"
|
||||
clearable
|
||||
style="width:400px">
|
||||
<el-option v-for="item in brandOptions" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
v-model="modelValue.brandId"
|
||||
style="width:400px"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in brandOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品简介">
|
||||
<el-input type="textarea" style="width: 400px" v-model="value.description"/>
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="modelValue.description"
|
||||
style="width: 400px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品相册">
|
||||
<el-row :gutter="10">
|
||||
<el-col style="width: 180px" v-for="(item,index) in pictures">
|
||||
<el-card :body-style="{ padding: '10px' }">
|
||||
<single-upload v-model="item.url"></single-upload>
|
||||
|
||||
<single-upload v-model="item.url"/>
|
||||
|
||||
<div class="bottom" v-if="item.url">
|
||||
<el-button type="text" class="button" v-if="item.main==true" style="color:#ff4d51">商品主图</el-button>
|
||||
<el-button type="text" class="button" v-else @click="setMainPicture(index)">设为主图</el-button>
|
||||
<el-button type="text" class="button" @click="handlePictureRemove(index)">删除图片</el-button>
|
||||
<el-button type="text" class="button" v-else @click="changeMainPicture(index)">设为主图</el-button>
|
||||
<el-button type="text" class="button" @click="removePicture(index)">删除图片</el-button>
|
||||
</div>
|
||||
|
||||
<div class="bottom" v-else>
|
||||
<el-button type="text" class="button"></el-button>
|
||||
<el-button type="text" class="button"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
|
@ -51,7 +64,7 @@
|
|||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品详情" prop="detail">
|
||||
<tinymce v-model="value.detail" :height="400"/>
|
||||
<tinymce v-model="modelValue.detail" :height="400"/>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
|
@ -62,101 +75,107 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import {listBrands} from "@/api/pms/brand"
|
||||
import SingleUpload from '@/components/Upload/SingleUpload.vue'
|
||||
import Tinymce from '@/components/Tinymce/index.vue'
|
||||
import {onMounted, reactive, ref, toRefs, unref} from "vue"
|
||||
import {ElForm} from "element-plus"
|
||||
|
||||
import {list as listBrand} from "@/api/pms/brand"
|
||||
import SingleUpload from '@/components/Upload/SingleUpload'
|
||||
import Tinymce from '@/components/Tinymce'
|
||||
const emit = defineEmits(['prev', 'next'])
|
||||
const dataForm = ref(ElForm)
|
||||
|
||||
export default {
|
||||
name: "GoodsInfo",
|
||||
components: {SingleUpload, Tinymce},
|
||||
props: {
|
||||
value: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
brandOptions: [],
|
||||
pictures: [],
|
||||
rules: {
|
||||
name: [{required: true, message: '请填写商品名称', trigger: 'blur'}],
|
||||
originPrice: [{required: true, message: '请填写原价', trigger: 'blur'}],
|
||||
price: [{required: true, message: '请填写现价', trigger: 'blur'}],
|
||||
brandId: [{required: true, message: '请选择商品品牌', trigger: 'blur'}],
|
||||
}
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
brandOptions: [],
|
||||
// 商品图册
|
||||
pictures: [] as Array<any>,
|
||||
rules: {
|
||||
name: [{required: true, message: '请填写商品名称', trigger: 'blur'}],
|
||||
originPrice: [{required: true, message: '请填写原价', trigger: 'blur'}],
|
||||
price: [{required: true, message: '请填写现价', trigger: 'blur'}],
|
||||
brandId: [{required: true, message: '请选择商品品牌', trigger: 'blur'}],
|
||||
}
|
||||
})
|
||||
|
||||
const {brandOptions, pictures, rules} = toRefs(state)
|
||||
|
||||
function loadData() {
|
||||
listBrands({}).then(response => {
|
||||
state.brandOptions = response.data
|
||||
})
|
||||
const goodsId = props.modelValue.id
|
||||
if (goodsId) {
|
||||
const mainPicUrl = props.modelValue.picUrl
|
||||
if (mainPicUrl) {
|
||||
state.pictures.filter(item => item.main)[0].url = mainPicUrl
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.handleFormReset()
|
||||
|
||||
await listBrand().then(response => {
|
||||
this.brandOptions = response.data
|
||||
})
|
||||
|
||||
const goodsId = this.value.id
|
||||
if (goodsId) {
|
||||
|
||||
const mainPicUrl = this.value.picUrl
|
||||
if (mainPicUrl) {
|
||||
this.pictures.filter(item => item.main == true)[0].url = mainPicUrl
|
||||
}
|
||||
|
||||
const subPicUrls = this.value.subPicUrls
|
||||
if (subPicUrls && subPicUrls.length > 0) {
|
||||
for (let i = 1; i <= subPicUrls.length; i++) {
|
||||
this.pictures[i].url = subPicUrls[i - 1]
|
||||
}
|
||||
}
|
||||
const subPicUrls = props.modelValue.subPicUrls
|
||||
if (subPicUrls && subPicUrls.length > 0) {
|
||||
for (let i = 1; i <= subPicUrls.length; i++) {
|
||||
state.pictures[i].url = subPicUrls[i - 1]
|
||||
}
|
||||
},
|
||||
// 设置主图
|
||||
setMainPicture(changeIndex) {
|
||||
const mainPicture = JSON.parse(JSON.stringify( this.pictures[0]))
|
||||
const changePicture = JSON.parse(JSON.stringify( this.pictures[changeIndex]))
|
||||
|
||||
console.log(changeIndex,changePicture.url,mainPicture.url)
|
||||
|
||||
this.pictures[0].url = changePicture.url
|
||||
this.pictures[changeIndex].url = mainPicture.url
|
||||
|
||||
},
|
||||
handlePictureRemove(index) {
|
||||
this.pictures[index].url = undefined
|
||||
},
|
||||
handleFormReset: function () {
|
||||
this.pictures = [
|
||||
{url: undefined, main: true},
|
||||
{url: undefined, main: false},
|
||||
{url: undefined, main: false},
|
||||
{url: undefined, main: false},
|
||||
{url: undefined, main: false},
|
||||
]
|
||||
},
|
||||
handlePrev: function () {
|
||||
this.$emit('prev')
|
||||
},
|
||||
handleNext: function () {
|
||||
this.$refs["goodsForm"].validate((valid) => {
|
||||
if (valid) {
|
||||
// 商品图片处理
|
||||
const tempMainPicUrl = this.pictures.filter(item => item.main == true && item.url).map(item => item.url)
|
||||
if (tempMainPicUrl && tempMainPicUrl.length > 0) {
|
||||
this.value.picUrl = tempMainPicUrl[0]
|
||||
}
|
||||
const tempSubPicUrl = this.pictures.filter(item => item.main == false && item.url).map(item => item.url)
|
||||
if (tempSubPicUrl && tempSubPicUrl.length > 0) {
|
||||
this.value.subPicUrls = tempSubPicUrl
|
||||
}
|
||||
this.$emit('next')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
state.pictures = [
|
||||
{url: undefined, main: true},
|
||||
{url: undefined, main: false},
|
||||
{url: undefined, main: false},
|
||||
{url: undefined, main: false},
|
||||
{url: undefined, main: false},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换主图
|
||||
*/
|
||||
function changeMainPicture(changeIndex: number) {
|
||||
const currMainPicture = JSON.parse(JSON.stringify(state.pictures[0]))
|
||||
const nextMainPicture = JSON.parse(JSON.stringify(state.pictures[changeIndex]))
|
||||
|
||||
state.pictures[0].url = nextMainPicture.url
|
||||
state.pictures[changeIndex].url = currMainPicture.url
|
||||
}
|
||||
|
||||
function removePicture(index: number) {
|
||||
state.pictures[index].url = undefined
|
||||
}
|
||||
|
||||
function handlePrev() {
|
||||
emit('prev')
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
const form = unref(dataForm)
|
||||
form.validate((valid: any) => {
|
||||
if (valid) {
|
||||
// 商品图片
|
||||
const mainPicUrl = state.pictures.filter(item => item.main == true && item.url).map(item => item.url)
|
||||
if (mainPicUrl && mainPicUrl.length > 0) {
|
||||
props.modelValue.picUrl = mainPicUrl[0]
|
||||
}
|
||||
const subPicUrl = state.pictures.filter(item => item.main == false && item.url).map(item => item.url)
|
||||
if (subPicUrl && subPicUrl.length > 0) {
|
||||
props.modelValue.subPicUrls = subPicUrl
|
||||
}
|
||||
emit('next')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
resetForm()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -177,4 +196,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
-->
|
||||
|
|
|
|||
Loading…
Reference in New Issue