feat: 商品分类和商品基础信息升级改造
This commit is contained in:
255
src/components/Tinymce/Index.vue
Normal file
255
src/components/Tinymce/Index.vue
Normal file
@@ -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>
|
||||
152
src/components/Tinymce/components/EditorImage.vue
Normal file
152
src/components/Tinymce/components/EditorImage.vue
Normal file
@@ -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>
|
||||
8
src/components/Tinymce/config.ts
Normal file
8
src/components/Tinymce/config.ts
Normal file
@@ -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']
|
||||
Reference in New Issue
Block a user