New file |
| | |
| | | <template> |
| | | <div class="upload-container"> |
| | | <el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true"> |
| | | upload |
| | | </el-button> |
| | | <el-dialog :visible.sync="dialogVisible"> |
| | | <el-upload |
| | | :multiple="true" |
| | | :file-list="fileList" |
| | | :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> |
| | | // import { getToken } from 'api/qiniu' |
| | | |
| | | export default { |
| | | name: 'EditorSlideUpload', |
| | | props: { |
| | | color: { |
| | | type: String, |
| | | default: '#1890ff' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | dialogVisible: false, |
| | | listObj: {}, |
| | | fileList: [] |
| | | } |
| | | }, |
| | | methods: { |
| | | checkAllSuccess() { |
| | | return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess) |
| | | }, |
| | | handleSubmit() { |
| | | const arr = Object.keys(this.listObj).map(v => this.listObj[v]) |
| | | if (!this.checkAllSuccess()) { |
| | | this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!') |
| | | return |
| | | } |
| | | this.$emit('successCBK', arr) |
| | | this.listObj = {} |
| | | this.fileList = [] |
| | | this.dialogVisible = false |
| | | }, |
| | | handleSuccess(response, file) { |
| | | const uid = file.uid |
| | | const objKeyArr = Object.keys(this.listObj) |
| | | for (let i = 0, len = objKeyArr.length; i < len; i++) { |
| | | if (this.listObj[objKeyArr[i]].uid === uid) { |
| | | this.listObj[objKeyArr[i]].url = response.files.file |
| | | this.listObj[objKeyArr[i]].hasSuccess = true |
| | | return |
| | | } |
| | | } |
| | | }, |
| | | handleRemove(file) { |
| | | const uid = file.uid |
| | | const objKeyArr = Object.keys(this.listObj) |
| | | for (let i = 0, len = objKeyArr.length; i < len; i++) { |
| | | if (this.listObj[objKeyArr[i]].uid === uid) { |
| | | delete this.listObj[objKeyArr[i]] |
| | | return |
| | | } |
| | | } |
| | | }, |
| | | beforeUpload(file) { |
| | | const _self = this |
| | | const _URL = window.URL || window.webkitURL |
| | | const fileName = file.uid |
| | | this.listObj[fileName] = {} |
| | | return new Promise((resolve, reject) => { |
| | | const img = new Image() |
| | | img.src = _URL.createObjectURL(file) |
| | | img.onload = function() { |
| | | _self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height } |
| | | } |
| | | resolve(true) |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .editor-slide-upload { |
| | | margin-bottom: 20px; |
| | | ::v-deep .el-upload--picture-card { |
| | | width: 100%; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | 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 |
New file |
| | |
| | | <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: { |
| | | 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 || '')) |
| | | } |
| | | } |
| | | }, |
| | | 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({ |
| | | selector: `#${this.tinymceId}`, |
| | | language: this.languageTypeList['en'], |
| | | 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 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) { |
| | | arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)) |
| | | } |
| | | } |
| | | } |
| | | </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> |
New file |
| | |
| | | // Any plugins you want to use has to be imported |
| | | // Detail plugins list see https://www.tinymce.com/docs/plugins/ |
| | | // Custom builds see https://www.tinymce.com/download/custom-builds/ |
| | | |
| | | const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount'] |
| | | |
| | | export default plugins |
New file |
| | |
| | | // Here is a list of the toolbar |
| | | // Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols |
| | | |
| | | const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'] |
| | | |
| | | export default toolbar |
| | |
| | | <el-form-item label="是否上架:"> |
| | | <el-switch v-model="mData.isUp" :active-value="1" :inactive-value="0" /> |
| | | </el-form-item> |
| | | <el-form-item label="详情:" prop="content"> |
| | | <el-form-item label="富文本1:" prop="content"> |
| | | <!-- 富文本 --> |
| | | <WangEnduit :catchdata="catchData" :content="mData.content" :rangenum="rangenum" /> |
| | | </el-form-item> |
| | | <el-form-item label="富文本2:" prop="content2"> |
| | | <tinymce v-model="mData.content2" :height="300" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import Tinymce from '@/components/Tinymce' |
| | | import WangEnduit from '@/components/WangEnduit' // 富文本 |
| | | import mixin_upload from '@/mixins/upload.js' // 通用上传图片预览 |
| | | import UploadSingleImg from '@/components_simple/UploadSingleImg' |
| | |
| | | import BackToTop from '@/components/BackToTop' |
| | | export default { |
| | | name: 'DemoForm', |
| | | components: { WangEnduit, UploadSingleImg, BackToTop }, |
| | | components: { WangEnduit, UploadSingleImg, BackToTop, Tinymce }, |
| | | mixins: [ |
| | | mixin_upload |
| | | ], |
| | |
| | | required: true, message: '请选择地址', trigger: 'change' |
| | | }], |
| | | content: [{ |
| | | required: true, message: '请填写优惠券详情' |
| | | required: true, message: '请填写富文本1详情' |
| | | }], |
| | | content2: [{ |
| | | required: true, message: '请填写富文本2详情' |
| | | }], |
| | | uploadImgs: [{ |
| | | required: true, message: '请选择图片' |
| | |
| | | </el-table> |
| | | |
| | | <!-- 新增&编辑 --> |
| | | <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'新增医院科室':'编辑医院科室'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog"> |
| | | <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'新增医院科室':'编辑医院科室'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog" :close-on-click-modal="false"> |
| | | <el-form ref="refDialog" :model="dialogData" label-width="110px" :rules="rules" size="small"> |
| | | <el-form-item label="名称" prop="name"> |
| | | <el-input v-model="dialogData.name" placeholder="请输入名称" maxlength="50" /> |
| | |
| | | } |
| | | }, () => { |
| | | this.$messageSuc(text + '成功') |
| | | }, (res) => { |
| | | item.isUp = item.isUp === 1 ? 0 : 1 |
| | | this.$messageError(res.msg) |
| | | }) |
| | | }).catch(() => { |
| | | item.isUp = item.isUp === 1 ? 0 : 1 |
| | |
| | | </el-table> |
| | | |
| | | <!-- 新增&编辑 --> |
| | | <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'新增医院科室':'编辑医院科室'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog"> |
| | | <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'新增医院科室':'编辑医院科室'" width="500px" :visible.sync="dialogVisible" append-to-body :before-close="hideDialog" :close-on-click-modal="false"> |
| | | <el-form ref="refDialog" :model="dialogData" label-width="110px" :rules="rules" size="small"> |
| | | <el-form-item label="名称" prop="name"> |
| | | <el-input v-model="dialogData.name" placeholder="请输入名称" maxlength="50" /> |
| | |
| | | } |
| | | }, () => { |
| | | this.$messageSuc(text + '成功') |
| | | }, (res) => { |
| | | item.isUp = item.isUp === 1 ? 0 : 1 |
| | | this.$messageError(res.msg) |
| | | }) |
| | | }).catch(() => { |
| | | item.isUp = item.isUp === 1 ? 0 : 1 |
| | |
| | | /> |
| | | |
| | | <!-- 新增&编辑 --> |
| | | <el-dialog v-el-drag-dialog :title="adminDialogData.type==='add'?'新增管理员':'编辑管理员'" width="500px" :visible.sync="adminDialogVisible" append-to-body> |
| | | <el-dialog v-el-drag-dialog :title="adminDialogData.type==='add'?'新增管理员':'编辑管理员'" width="500px" :visible.sync="adminDialogVisible" append-to-body :close-on-click-modal="false"> |
| | | <el-form ref="adminDialog" :model="adminDialogData" label-width="80px" :rules="rules" size="small"> |
| | | <el-form-item label="名称" prop="name"> |
| | | <el-input v-model="adminDialogData.name" placeholder="请输入名称" maxlength="50" /> |
| | |
| | | /> |
| | | |
| | | <!-- 新增&编辑 --> |
| | | <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'新增轮播图':'编辑轮播图'" width="500px" :visible.sync="isShowDialog" append-to-body :before-close="hideDialog"> |
| | | <el-dialog v-el-drag-dialog :title="dialogData.type=='add'?'新增轮播图':'编辑轮播图'" width="500px" :visible.sync="isShowDialog" append-to-body :before-close="hideDialog" :close-on-click-modal="false"> |
| | | <el-form :ref="formName" :model="dialogData" label-width="120px" :rules="rules" size="small"> |
| | | <el-form-item label="排序号:" prop="orderNum"> |
| | | <el-input v-model="dialogData.orderNum" placeholder="请输入排序号" maxlength="10" /> |