fix:项目初始化
This commit is contained in:
Executable
+94
@@ -0,0 +1,94 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-05-16 10:46:46
|
||||
* @LastEditors: 王涛
|
||||
* @LastEditTime: 2021-01-27 10:44:03
|
||||
-->
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||
<span
|
||||
v-if="item.redirect==='noRedirect'||index==levelList.length-1"
|
||||
class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route(route) {
|
||||
// if you go to the redirect page, do not update the breadcrumbs
|
||||
if (route.path.startsWith('/redirect/')) {
|
||||
return
|
||||
}
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb() {
|
||||
// only show routes with meta.title
|
||||
const matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||
// const first = matched[0]
|
||||
|
||||
// if (!this.isDashboard(first)) {
|
||||
// matched = [{ path: '/promptPage', meta: { title: '首页' }}].concat(matched)
|
||||
// }
|
||||
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
},
|
||||
isDashboard(route) {
|
||||
const name = route && route.name
|
||||
if (!name) {
|
||||
return false
|
||||
}
|
||||
return name.trim().toLocaleLowerCase() === 'Home'.toLocaleLowerCase()
|
||||
},
|
||||
pathCompile(path) {
|
||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||
const { params } = this.$route
|
||||
var toPath = pathToRegexp.compile(path)
|
||||
return toPath(params)
|
||||
},
|
||||
handleLink(item) {
|
||||
const { redirect, path } = item
|
||||
if (redirect) {
|
||||
this.$router.push(redirect)
|
||||
return
|
||||
}
|
||||
this.$router.push(this.pathCompile(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/variables.scss';
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
line-height: 50px;
|
||||
margin-left: 8px;
|
||||
|
||||
.no-redirect {
|
||||
color: $themeColor;
|
||||
cursor: text;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
* @Author:
|
||||
* @Mail:
|
||||
* @Date: 2021-11-16 17:10:43
|
||||
* @LastEditTime: 2022-04-07 20:17:07
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @FilePath: /comics-admin/src/components/Hamburger/index.vue
|
||||
-->
|
||||
<template>
|
||||
<div style="padding: 0 15px" @click="toggleClick">
|
||||
<svg :class="{'is-active':isActive}" class="hamburger" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64">
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import variables from '@/styles/variables.scss'
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
variables() {
|
||||
return variables
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleClick() {
|
||||
this.$emit('toggleClick')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #fff;
|
||||
|
||||
}
|
||||
|
||||
.hamburger.is-active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
Executable
+180
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div :class="{'show':show}" class="header-search">
|
||||
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
|
||||
<el-select ref="headerSearchSelect" v-model="search" :remote-method="querySearch" filterable default-first-option remote placeholder="Search" class="header-search-select" @change="change">
|
||||
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// fuse is a lightweight fuzzy-search module
|
||||
// make search results more in line with expectations
|
||||
import Fuse from 'fuse.js'
|
||||
import path from 'path'
|
||||
|
||||
export default {
|
||||
name: 'HeaderSearch',
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
options: [],
|
||||
searchPool: [],
|
||||
show: false,
|
||||
fuse: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
routes() {
|
||||
return this.$store.getters.permission_routes
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
routes() {
|
||||
this.searchPool = this.generateRoutes(this.routes)
|
||||
},
|
||||
searchPool(list) {
|
||||
this.initFuse(list)
|
||||
},
|
||||
show(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', this.close)
|
||||
} else {
|
||||
document.body.removeEventListener('click', this.close)
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.searchPool = this.generateRoutes(this.routes)
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.show = !this.show
|
||||
if (this.show) {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
|
||||
this.options = []
|
||||
this.show = false
|
||||
},
|
||||
change(val) {
|
||||
this.$router.push(val.path)
|
||||
this.search = ''
|
||||
this.options = []
|
||||
this.$nextTick(() => {
|
||||
this.show = false
|
||||
})
|
||||
},
|
||||
initFuse(list) {
|
||||
this.fuse = new Fuse(list, {
|
||||
shouldSort: true, // 是否按分数对结果列表排序
|
||||
threshold: 0.4, // 匹配算法阀值,阀值为0时需完全匹配,1为完全不匹配
|
||||
location: 0, // 确定文本中预期找到的模式的大致位置
|
||||
distance: 100,
|
||||
maxPatternLength: 32, // 模式的最大长度
|
||||
minMatchCharLength: 1, // 模式的最小字符长度
|
||||
keys: [
|
||||
{
|
||||
// 搜索标题与路径
|
||||
name: 'title',
|
||||
weight: 0.7,
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
weight: 0.3,
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
// Filter out the routes that can be displayed in the sidebar
|
||||
// And generate the internationalized title
|
||||
generateRoutes(routes, basePath = '/', prefixTitle = []) {
|
||||
let res = []
|
||||
|
||||
for (const router of routes) {
|
||||
// skip hidden router
|
||||
if (router.hidden) {
|
||||
continue
|
||||
}
|
||||
|
||||
const data = {
|
||||
path: path.resolve(basePath, router.path),
|
||||
title: [...prefixTitle],
|
||||
}
|
||||
|
||||
if (router.meta && router.meta.title) {
|
||||
data.title = [...data.title, router.meta.title]
|
||||
|
||||
if (router.redirect !== 'noRedirect') {
|
||||
// only push the routes with title
|
||||
// special case: need to exclude parent router without redirect
|
||||
res.push(data)
|
||||
}
|
||||
}
|
||||
|
||||
// recursive child routes
|
||||
if (router.children) {
|
||||
const tempRoutes = this.generateRoutes(
|
||||
router.children,
|
||||
data.path,
|
||||
data.title
|
||||
)
|
||||
if (tempRoutes.length >= 1) {
|
||||
res = [...res, ...tempRoutes]
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
querySearch(query) {
|
||||
if (query !== '') {
|
||||
this.options = this.fuse.search(query)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-search {
|
||||
font-size: 0 !important;
|
||||
|
||||
.search-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-search-select {
|
||||
font-size: 18px;
|
||||
transition: width 0.2s;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
/deep/ .el-input__inner {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.show {
|
||||
.header-search-select {
|
||||
width: 210px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<!--
|
||||
* @Author:
|
||||
* @Mail:
|
||||
* @Date: 2021-11-13 14:46:20
|
||||
* @LastEditTime: 2023-01-02 19:27:36
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @FilePath: /web_admin/src/components/QuillEditor/index.vue
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<quill-editor v-model="content" ref="myQuillEditor" :options="editorOption" @ready="onEditorReady($event)"></quill-editor>
|
||||
<!-- el-uploader不好手动触发上传功能 -->
|
||||
<uploader
|
||||
:style="{ display: 'none' }"
|
||||
:options="{ ...options, target: '/api/web/file/uploadImg' }"
|
||||
class="uploader-example"
|
||||
@file-success="imageSuccess">
|
||||
<uploader-drop>
|
||||
<uploader-btn ref="editorUploadImg" :attrs="attrsImg"></uploader-btn>
|
||||
</uploader-drop>
|
||||
</uploader>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import 'quill/dist/quill.core.css'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import 'quill/dist/quill.bubble.css'
|
||||
import Quill from 'quill'
|
||||
const Link = Quill.import('formats/link')
|
||||
Link.PROTOCOL_WHITELIST = ['http', 'https', 'mailto', 'tel', 'yinseinner']
|
||||
import { quillEditor } from 'vue-quill-editor'
|
||||
import { ImageDrop } from 'quill-image-drop-module'
|
||||
import ImageResize from 'quill-image-resize-module'
|
||||
Quill.register('modules/imageDrop', ImageDrop)
|
||||
Quill.register('modules/imageResize', ImageResize)
|
||||
|
||||
// 自定义字体类型
|
||||
var fonts = ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'Times-New-Roman', 'sans-serif']
|
||||
var Font = Quill.import('formats/font')
|
||||
Font.whitelist = fonts // 将字体加入到白名单
|
||||
Quill.register(Font, true)
|
||||
|
||||
export default {
|
||||
name: 'Editor',
|
||||
components: {
|
||||
quillEditor
|
||||
},
|
||||
props: {
|
||||
textNode: {
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return {
|
||||
chunkSize: 1024 * 1024 * 30, // 切片大小
|
||||
method: 'application/json',
|
||||
testChunks: false,
|
||||
allowDuplicateUploads: false, // 是否可以重复选择
|
||||
query: {},
|
||||
singleFile: false, // 是否只能选择单项
|
||||
headers: {
|
||||
Authorization: this.$store.getters.token
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const _that = this
|
||||
return {
|
||||
content: '',
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq,
|
||||
attrsImg: {
|
||||
accept: ['image/*']
|
||||
},
|
||||
trackId: undefined,
|
||||
editorOption: {
|
||||
placeholder: '请输入帖子内容',
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{ header: 1 }, { header: 2 }], // custom button values
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
|
||||
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
|
||||
[{ direction: 'rtl' }], // text direction
|
||||
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
|
||||
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
|
||||
[{ font: [] }],
|
||||
[{ align: [] }],
|
||||
['link', 'image'],
|
||||
['clean']
|
||||
],
|
||||
handlers: {
|
||||
// 自定义上传图片
|
||||
image: value => {
|
||||
_that.$refs.editorUploadImg.$el.childNodes[0].click()
|
||||
}
|
||||
}
|
||||
},
|
||||
// //配置富文本框图片大小调整功能
|
||||
// imageDrop: true,
|
||||
imageResize: {
|
||||
displayStyles: {
|
||||
backgroundColor: 'black',
|
||||
border: 'none',
|
||||
color: 'white'
|
||||
},
|
||||
modules: ['Resize', 'DisplaySize', 'Toolbar']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暂存图片base64
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {},
|
||||
methods: {
|
||||
// 父组件获取数据
|
||||
getdata() {
|
||||
const str = this.content.replace(this.domain, '')
|
||||
|
||||
return str
|
||||
},
|
||||
// 当文本框加载完成时,编译插入数据
|
||||
async onEditorReady(e) {
|
||||
const data = this.textNode.replace('img src="', 'img src="' + this.domain)
|
||||
e.container.querySelector('.ql-blank').innerHTML = data
|
||||
},
|
||||
|
||||
imageSuccess(rootFile, file, message) {
|
||||
const { code, data } = JSON.parse(message)
|
||||
if (code === 200) {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file.file)
|
||||
reader.onload = e => {
|
||||
const myeditor = this.$refs.myQuillEditor.quill
|
||||
// editor方法获取当前光标位置
|
||||
const index = myeditor.getSelection().index
|
||||
myeditor.insertEmbed(index, 'image', this.domain + data.path)
|
||||
// 调整光标到图片之后的位置上
|
||||
myeditor.setSelection(index + 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clearInput() {
|
||||
this.value = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+145
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
|
||||
<div class="rightPanel-background" />
|
||||
<div class="rightPanel">
|
||||
<div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
|
||||
<i :class="show?'el-icon-close':'el-icon-setting'" />
|
||||
</div>
|
||||
<div class="rightPanel-items">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addClass, removeClass } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'RightPanel',
|
||||
props: {
|
||||
clickNotClose: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
buttonTop: {
|
||||
default: 250,
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
theme() {
|
||||
return this.$store.state.settings.theme
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(value) {
|
||||
if (value && !this.clickNotClose) {
|
||||
this.addEventClick()
|
||||
}
|
||||
if (value) {
|
||||
addClass(document.body, 'showRightPanel')
|
||||
} else {
|
||||
removeClass(document.body, 'showRightPanel')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.insertToBody()
|
||||
},
|
||||
beforeDestroy() {
|
||||
const elx = this.$refs.rightPanel
|
||||
elx.remove()
|
||||
},
|
||||
methods: {
|
||||
addEventClick() {
|
||||
window.addEventListener('click', this.closeSidebar)
|
||||
},
|
||||
closeSidebar(evt) {
|
||||
const parent = evt.target.closest('.rightPanel')
|
||||
if (!parent) {
|
||||
this.show = false
|
||||
window.removeEventListener('click', this.closeSidebar)
|
||||
}
|
||||
},
|
||||
insertToBody() {
|
||||
const elx = this.$refs.rightPanel
|
||||
const body = document.querySelector('body')
|
||||
body.insertBefore(elx, body.firstChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.showRightPanel {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rightPanel-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
|
||||
background: rgba(0, 0, 0, .2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
|
||||
transition: all .25s cubic-bezier(.7, .3, .1, 1);
|
||||
transform: translate(100%);
|
||||
background: #fff;
|
||||
z-index: 40000;
|
||||
}
|
||||
|
||||
.show {
|
||||
transition: all .3s cubic-bezier(.7, .3, .1, 1);
|
||||
|
||||
.rightPanel-background {
|
||||
z-index: 20000;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
transform: translate(0);
|
||||
}
|
||||
}
|
||||
|
||||
.handle-button {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
z-index: 0;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
line-height: 48px;
|
||||
i {
|
||||
font-size: 24px;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import screenfull from 'screenfull'
|
||||
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroy()
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!screenfull.enabled) {
|
||||
this.$message({
|
||||
message: 'you browser can not work',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
screenfull.toggle()
|
||||
},
|
||||
change() {
|
||||
this.isFullscreen = screenfull.isFullscreen
|
||||
},
|
||||
init() {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.on('change', this.change)
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.off('change', this.change)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.screenfull-svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
fill: #5a5e66;;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-10-05 11:37:08
|
||||
* @LastEditors: 王涛
|
||||
* @LastEditTime: 2020-11-24 17:04:24
|
||||
-->
|
||||
<template>
|
||||
<el-dialog append-to-body class="img_dialog" :title="title" align="center" :visible.sync="dialogVisible" width="40%" :before-close="handleClose" :close-on-click-modal="false" :modal-append-to-body='false'>
|
||||
<el-carousel v-if="isArry" indicator-position="outside">
|
||||
<el-carousel-item v-for="item in mediaUrl" :key="item">
|
||||
<div class="media-box" id="img-box" > <img :src="domain+item" /></div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div v-else class="media-box" id="img-box" > <img :src="mediaUrl" /></div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
baseConfig: {},
|
||||
dialogVisible: false,
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 存放图片的域名
|
||||
mediaUrl: '',
|
||||
title: '查看图片',
|
||||
isArry: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
token() {
|
||||
return this.$store.state.token
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
if (this.player) {
|
||||
this.player.dispose()
|
||||
}
|
||||
this.dialogVisible = false
|
||||
this.mediaUrl = ''
|
||||
},
|
||||
open(url, type, data) {
|
||||
if (typeof url === 'string') {
|
||||
this.isArry = false
|
||||
} else {
|
||||
this.isArry = true
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.title = '封面图查看'
|
||||
this.mediaUrl = url
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.my_data{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.img_dialog{
|
||||
::v-deep{
|
||||
.el-dialog__body{
|
||||
height: auto;
|
||||
.el-carousel__item{
|
||||
overflow: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.media-box {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
img {
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#my-video {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,165 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-10-05 11:36:16
|
||||
* @LastEditors: 江涛
|
||||
* @LastEditTime: 2021-03-03 17:50:26
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="img-wrap">
|
||||
<div v-if='showChangeBtn' class="changeLeft" @click="changeLeft">
|
||||
<i class="el-icon-arrow-left"></i>
|
||||
</div>
|
||||
<div class="showImg">
|
||||
<div v-if="showNoImg">暂无</div>
|
||||
<el-image v-else :src="src" style="width: 100%, height: 100%" @click="checkMedia()" lazy >
|
||||
<div slot="placeholder" class="image-slot" :style="{width: width, height: height, lineHeight: height}">
|
||||
<i class="el-icon-loading"></i>加载中
|
||||
</div>
|
||||
</el-image>
|
||||
</div>
|
||||
<div v-if='showChangeBtn' class="changeRight" @click="changeRight">
|
||||
<i class="el-icon-arrow-right"></i>
|
||||
</div>
|
||||
<!--封面图、视频弹窗组件-->
|
||||
<Dialog ref="dialog"></Dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Dialog from './components/dialog'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showNoImg: false,
|
||||
src: undefined,
|
||||
showChangeBtn: false,
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 存放图片的域名
|
||||
imgsUrl: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
props: ['width', 'height', 'urls'],
|
||||
watch: {
|
||||
urls(newval, oldval) {
|
||||
this.urls = newval
|
||||
// if (this.type && this.type === 'daichong') {
|
||||
// this.domain = ''
|
||||
// }
|
||||
this.imgsUrl = []
|
||||
if (this.urls) { // 判断是否存在
|
||||
if (typeof (this.urls) === 'string') { // 判断是单张图片还是数组
|
||||
this.src = this.domain + this.urls
|
||||
this.showNoImg = false
|
||||
} else {
|
||||
if (this.urls.length) { // 判断是否为空数组
|
||||
if (this.urls.length === 1) { // 判断数组中是否只有一张图片
|
||||
this.showChangeBtn = false
|
||||
} else {
|
||||
this.showChangeBtn = true
|
||||
}
|
||||
this.urls.forEach(item => {
|
||||
this.imgsUrl.push(this.domain + item)
|
||||
})
|
||||
this.src = this.imgsUrl[0]
|
||||
this.showNoImg = false
|
||||
} else {
|
||||
this.showNoImg = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.showChangeBtn = false
|
||||
this.showNoImg = true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.imgsUrl = []
|
||||
if (this.urls) { // 判断是否存在
|
||||
if (typeof (this.urls) === 'string') { // 判断是单张图片还是数组
|
||||
this.src = this.domain + this.urls
|
||||
} else {
|
||||
if (this.urls.length) { // 判断是否为空数组
|
||||
if (this.urls.length === 1) { // 判断数组中是否只有一张图片
|
||||
this.showChangeBtn = false
|
||||
} else {
|
||||
this.showChangeBtn = true
|
||||
}
|
||||
this.urls.forEach(item => {
|
||||
this.imgsUrl.push(this.domain + item)
|
||||
})
|
||||
this.src = this.imgsUrl[0]
|
||||
} else {
|
||||
this.showNoImg = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.showChangeBtn = false
|
||||
this.showNoImg = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeLeft() {
|
||||
const index = this.imgsUrl.indexOf(this.src)
|
||||
if (index === 0) {
|
||||
this.src = this.imgsUrl[this.imgsUrl.length - 1]
|
||||
} else {
|
||||
this.src = this.imgsUrl[index - 1]
|
||||
}
|
||||
},
|
||||
changeRight() {
|
||||
const index = this.imgsUrl.indexOf(this.src)
|
||||
if (index === this.imgsUrl.length - 1) {
|
||||
this.src = this.imgsUrl[0]
|
||||
} else {
|
||||
this.src = this.imgsUrl[index + 1]
|
||||
}
|
||||
},
|
||||
// 查看媒体
|
||||
checkMedia() {
|
||||
if (this.urls) { // 判断是否存在
|
||||
if (typeof (this.urls) === 'string') { // 判断是单张图片还是数组
|
||||
this.$refs.dialog.open(this.domain + this.urls, 'img')
|
||||
} else {
|
||||
this.$refs.dialog.open(this.urls, 'img')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.img-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
.changeLeft,
|
||||
.changeRight {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
margin-top: 15%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.showImg {
|
||||
width: 100%;
|
||||
height:100%;
|
||||
.image-slot {
|
||||
background: #f5f7fa;
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
.el-image__error {
|
||||
font-size: 12px;
|
||||
}
|
||||
.el-image{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
align="center"
|
||||
:visible.sync="dialogVisible"
|
||||
width="40%"
|
||||
:before-close="handleClose"
|
||||
:close-on-click-modal="false"
|
||||
append-to-body>
|
||||
<div :data="myData" class="my_data">
|
||||
<span>视频高度:{{ this.myData.height }}</span>
|
||||
<span>视频宽度:{{ this.myData.width }}</span>
|
||||
<span>视频时长:{{ this.myData.playTime + "s" }}</span>
|
||||
</div>
|
||||
<!-- <div class="media-box" id="media-box"></div> -->
|
||||
<div v-if="mediaUrl" style="width: 400px; heigth: 130px; background: red;margin-top:10px">
|
||||
<DPlayer
|
||||
ref="player"
|
||||
:video="{
|
||||
url: mediaUrl,
|
||||
type: type === 'mp4' ? '' : 'hls',
|
||||
pic: '',
|
||||
}"
|
||||
hotkey
|
||||
:autoplay="true"
|
||||
@playing="onPlayerPlay"
|
||||
@pause="onPlayerPause"
|
||||
@loadeddata="onPlayerLoadeddata"
|
||||
@error="onPlayerError"
|
||||
@ended="onPlayerEnded"
|
||||
@waiting="onPlayerWaiting"></DPlayer>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import videojs from 'video.js'
|
||||
// import 'videojs-contrib-hls'
|
||||
import { videoFeviewList } from '@/api/video.js'
|
||||
import DPlayer from '../VueDplayerHls/index'
|
||||
// import API from '@/api'
|
||||
export default {
|
||||
name: 'ShowVideo',
|
||||
components: {
|
||||
DPlayer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
mediaUrl: '',
|
||||
title: '封面图查看',
|
||||
params: {},
|
||||
myData: {},
|
||||
type: undefined,
|
||||
domain: '/api/web/media/m3u8/' // 存放图片的域名
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
token() {
|
||||
return this.$store.state.user.token
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// / 监听视频播放的回调函数
|
||||
onPlayerPlay() {},
|
||||
// / 监听暂停播放的回调函数
|
||||
onPlayerPause() {},
|
||||
// / 监听当前视频数据加载完成
|
||||
onPlayerLoadeddata() {},
|
||||
// / 监听当前视频数据加载失败
|
||||
onPlayerError() {},
|
||||
// / 监听当前视频数据加载ing
|
||||
onPlayerWaiting() {},
|
||||
// / 监听当前视频播放完成
|
||||
onPlayerEnded() {},
|
||||
// 视频审查列表
|
||||
videoFeviewList() {
|
||||
videoFeviewList(this.params, this.id).then((res) => {
|
||||
// this.myData = res.data,
|
||||
})
|
||||
},
|
||||
handleClose() {
|
||||
if (this.player) {
|
||||
this.player.dispose()
|
||||
}
|
||||
this.dialogVisible = false
|
||||
this.mediaUrl = ''
|
||||
},
|
||||
open(url, data, id) {
|
||||
this.myData = { ...data }
|
||||
this.dialogVisible = true
|
||||
this.title = '视频查看'
|
||||
this.mediaUrl =
|
||||
this.domain + (url.match(RegExp(/.m3u8/)) ? url : url + '.m3u8')
|
||||
if (url.indexOf('mp4') !== -1) {
|
||||
this.mediaUrl = this.$store.getters.getSystemConfig.webImgReq + url
|
||||
this.type = 'mp4'
|
||||
} else {
|
||||
this.mediaUrl =
|
||||
this.domain + (url.match(RegExp(/.m3u8/)) ? url : url + '.m3u8')
|
||||
this.videoFeviewList()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.my_data {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.media-box {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
img {
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#my-video {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<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>
|
||||
@@ -0,0 +1,167 @@
|
||||
<!--
|
||||
* @Author: your name
|
||||
* @Date: 2020-09-10 16:03:32
|
||||
* @LastEditTime: 2021-03-24 11:27:17
|
||||
* @LastEditors: 江涛
|
||||
* @Description: In User Settings Edit
|
||||
* @FilePath: /web-admin-MDDSP/src/components/TagSelect/index.vue
|
||||
-->
|
||||
<template>
|
||||
<div class="reason-action-box">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<el-form-item label="二级标签:" label-width="75px">
|
||||
<div style="max-width:470px;max-height:100px;overflow: auto">
|
||||
<el-tag :key="index" v-for="(item,index) in useselectTag" closable :disable-transitions="false" @close="clickselectTag(item,index)">{{item}}</el-tag>
|
||||
<el-button class="button-new-tag" size="mini" @click="showInput">+ 新增标签</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24" v-if="inputVisible">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<el-form-item label="搜索:" label-width="45px">
|
||||
<el-autocomplete
|
||||
class="inline-input"
|
||||
v-model="inputValue"
|
||||
:fetch-suggestions="querySearch"
|
||||
placeholder="请输入内容"
|
||||
:trigger-on-focus="false"></el-autocomplete>
|
||||
<el-button @click="addTag">添加</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="50px" style="margin-top:15px">
|
||||
<div style="max-width:470px;height:500px;overflow: auto;min-width:300px">
|
||||
<div v-for="(val, key, index) in tagsObj" :key='index'>
|
||||
<el-button style="widht:120px" plain size="mini" type="info" v-for="(item,indexl) in val" :key="indexl" @click="clickCusTag(item,indexl,key)">{{item}}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// import API from '@/api'
|
||||
import { mediatagList } from '@/api/mediaLibrary/mediaTag'
|
||||
export default {
|
||||
props: {
|
||||
selectTag: {
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mediatagRes: [], // 模糊查询使用的数据
|
||||
allCusTag: {}, // 所有的标签
|
||||
useselectTag: this.selectTag.length ? this.selectTag.split(',') : [], // 存储已选择标签
|
||||
tagsObj: {}, // 剩余未选择的标签
|
||||
inputValue: '',
|
||||
inputVisible: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
mediatagList({ pageNum: 1, pageSize: 1000, status: true,
|
||||
type: 2 }).then(res => {
|
||||
if (res.code === 200) {
|
||||
const aaa = {}
|
||||
const bbb = []
|
||||
res.data.list.filter(item => {
|
||||
bbb.push({ value: item.name, label: item.name })
|
||||
if (aaa[item.type]) {
|
||||
aaa[item.type].push(item.name)
|
||||
} else {
|
||||
aaa[item.type] = []
|
||||
aaa[item.type].push(item.name)
|
||||
}
|
||||
})
|
||||
this.allCusTag = JSON.parse(JSON.stringify(aaa))
|
||||
this.tagsObj = JSON.parse(JSON.stringify(aaa))
|
||||
this.mediatagRes = bbb
|
||||
this.removeTag()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
querySearch(queryString, cb) {
|
||||
var restaurants = this.mediatagRes
|
||||
var results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants
|
||||
// 调用 callback 返回建议列表的数据
|
||||
cb(results)
|
||||
},
|
||||
createFilter(queryString) {
|
||||
return (restaurant) => {
|
||||
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
|
||||
}
|
||||
},
|
||||
addTag() {
|
||||
this.useselectTag.push(this.inputValue)
|
||||
this.removeTag()
|
||||
},
|
||||
|
||||
showInput() {
|
||||
this.inputVisible = true
|
||||
},
|
||||
// 第一次渲染时从标签列表中移除已有标签
|
||||
removeTag() {
|
||||
this.useselectTag.forEach(item => {
|
||||
Object.keys(this.tagsObj).forEach(key => {
|
||||
if (this.tagsObj[key].indexOf(item) !== -1 || this.tagsObj[key].indexOf(item) === 0) {
|
||||
this.tagsObj[key].splice(this.tagsObj[key].indexOf(item), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 删除已选择标签
|
||||
clickselectTag(data, index) {
|
||||
this.useselectTag.splice(index, 1)
|
||||
Object.keys(this.tagsObj).forEach(item => {
|
||||
if (this.allCusTag[item].indexOf(data) !== -1 || this.allCusTag[item].indexOf(data) === 0) {
|
||||
this.tagsObj[item].push(data)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 点击客服标签
|
||||
clickCusTag(data, index, key) {
|
||||
this.tagsObj[key].splice(index, 1)
|
||||
this.useselectTag.push(data)
|
||||
},
|
||||
// 返回已选择但不是客服标签的标签
|
||||
NotCusTag() {
|
||||
const arry = []
|
||||
this.useselectTag.forEach((item) => {
|
||||
if (this.allCusTag.indexOf(item) === -1) {
|
||||
arry.push(item)
|
||||
}
|
||||
})
|
||||
return arry
|
||||
},
|
||||
// 返回已选择的标签
|
||||
returnSelectTag() {
|
||||
return this.useselectTag
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.reason-action-box {
|
||||
.el-tag {
|
||||
padding: 0 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.input {
|
||||
width: 90px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
button {
|
||||
padding: 9px 15px;
|
||||
}
|
||||
}
|
||||
.reason-action-box button[data-v-2b5b3839]{
|
||||
width: 80px;
|
||||
padding: 9px 15px;
|
||||
margin-right: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<div class="reason-action-box">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<el-form-item label="已选标签" v-if="type !== 'resourceManagement'">
|
||||
<el-tag :key="index + 'tag'" v-for="(item, index) in selectTags" :disable-transitions="true" closable
|
||||
style="margin: 2px" @close="handleTagClose(item)">
|
||||
{{ item | changeName(allTags, 'id', 'name') }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="已选标签" v-else>
|
||||
<el-tag :key="index + 'tag'" v-for="(item, index) in selectTags" :disable-transitions="true" closable
|
||||
style="margin: 2px" @close="handleTagClose(item)">
|
||||
{{ item | changeName(newAllTags, 'id', 'name') }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<el-form-item label="可选标签">
|
||||
<el-button :type="showTagsFlag ? 'warning' : 'success'" @click="showTagsFun">{{ showTagsFlag ? '隐藏标签' :
|
||||
'展示标签'
|
||||
}}</el-button>
|
||||
<el-form-item label="标签分类过滤">
|
||||
<el-select placeholder="标签分类" @change="changeTagsCategory" multiple v-model="tagCategory" clearable
|
||||
filterable @clear="tagCategory = null">
|
||||
<el-option :key="index" :label="item.name" :value="item.id" v-for="(item, index) in mediatagCategory" />
|
||||
</el-select>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item label="关键字过滤">
|
||||
<el-input v-model="searchTagTxt" placeholder="请输入标签名称" clearable />
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item >
|
||||
<el-button @click="tagFilter">搜索标签</el-button>
|
||||
</el-form-item>
|
||||
<div style="width: 1000px;height: 500px;overflow-y: auto;" v-if="type !== 'resourceManagement'">
|
||||
<template v-for="(itemList, index) in newTagList">
|
||||
<div v-if="itemList.showItem && tagCategory.length" :key="index">
|
||||
<span v-show="showTagsFlag">{{ itemList.id | changeName(newTagList, 'id', 'name') }}</span>
|
||||
<el-tag v-show="showTagsFlag" :key="index + 'tagData'" style="margin: 2px; cursor: pointer"
|
||||
v-for="(item, index) in itemList.tags" :disable-transitions="true"
|
||||
:effect="item && item.hasTopic ? 'dark' : 'light'" :type="fliterTagType(item, item.id, item.topicIds)"
|
||||
@click="handleClickTag(item.id)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div v-else-if="!itemList.showItem && !tagCategory.length" :key="index">
|
||||
<span v-show="showTagsFlag">{{ itemList.id | changeName(newTagList, 'id', 'name') }}</span>
|
||||
<el-tag v-show="showTagsFlag" :key="index + 'tagData'" style="margin: 2px; cursor: pointer"
|
||||
v-for="(item, index) in itemList.tags" :disable-transitions="true"
|
||||
:effect="item && item.hasTopic ? 'dark' : 'light'" :type="fliterTagType(item, item.id, item.topicIds)"
|
||||
@click="handleClickTag(item.id)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<div style="width: 1000px;height: 500px;overflow-y: auto;" v-else>
|
||||
<template v-for="(itemList, index) in newAllTagsCopy">
|
||||
<div :key="index" v-if="itemList.showItem && tagCategory.length">
|
||||
<span v-show="showTagsFlag">{{ itemList.id | changeName(allTags, 'id', 'name') }}</span>
|
||||
<el-tag v-show="showTagsFlag" :key="index + 'tagData'" style="margin: 2px; cursor: pointer"
|
||||
v-for="(item, index) in itemList.list" :disable-transitions="true"
|
||||
:effect="item && item.hasTopic ? 'dark' : 'light'" :type="fliterTagType(item, item.id, item.topicIds)"
|
||||
@click="handleClickTag(item.id)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div v-else-if="!itemList.showItem && !tagCategory.length" :key="index">
|
||||
<span v-show="showTagsFlag">{{ itemList.id | changeName(newAllTagsCopy, 'id', 'name') }}</span>
|
||||
<el-tag v-show="showTagsFlag" :key="index + 'tagData'" style="margin: 2px; cursor: pointer"
|
||||
v-for="(item, index) in itemList.list" :disable-transitions="true"
|
||||
:effect="item && item.hasTopic ? 'dark' : 'light'" :type="fliterTagType(item, item.id, item.topicIds)"
|
||||
@click="handleClickTag(item.id)">
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
<script>
|
||||
// import API from '@/api'
|
||||
import {
|
||||
|
||||
mediaAllList
|
||||
} from '@/api/postManagement/newList'
|
||||
export default {
|
||||
props: {
|
||||
formTags: {//传入以选择标签
|
||||
default: []
|
||||
},
|
||||
allTags: {//所有标签
|
||||
default: []
|
||||
},
|
||||
mediatagCategory: {//标签分类
|
||||
default: []
|
||||
},
|
||||
type: {//标签分类
|
||||
default: String
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectTags: [],//已选择标签
|
||||
newTagList: [],//经过分类的标签组
|
||||
showTagsFlag: true,
|
||||
tagCategory: [],
|
||||
searchTagTxt: '',
|
||||
newAllTagsCopy:[]
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.type !== 'resourceManagement') {
|
||||
// 标签绑定标签分类
|
||||
if (this.mediatagCategory.length) {
|
||||
this.newTagList = JSON.parse(JSON.stringify(this.mediatagCategory))
|
||||
this.newTagList.forEach(item => {
|
||||
item.showItem = false
|
||||
item.tags = this.allTags.filter(sItem => {
|
||||
return item.id === sItem.categoryId
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.newTagList = [{ id: 0, name: '全部' }]
|
||||
this.newTagList.forEach(item => {
|
||||
item.showItem = false
|
||||
item.tags = this.allTags
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
'formTags': {
|
||||
handler(n, o) {
|
||||
this.selectTags = n
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
'allTags':{
|
||||
handler(n, o) {
|
||||
this.newAllTagsCopy = JSON.parse(JSON.stringify(n))
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
newAllTags() {
|
||||
let arr = []
|
||||
if (this.type == 'resourceManagement') {
|
||||
this.allTags.map(item => {
|
||||
item.showItem = false
|
||||
if (item.list && item.list.length) {
|
||||
arr = [...arr, ...item.list]
|
||||
}
|
||||
})
|
||||
return arr
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeTagsCategory() {
|
||||
if (this.type !== 'resourceManagement') {
|
||||
this.newTagList.forEach(element => {
|
||||
if (this.tagCategory.includes(element.id)) {
|
||||
element.showItem = true
|
||||
} else {
|
||||
element.showItem = false
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let index = 0; index < this.newAllTagsCopy.length; index++) {
|
||||
|
||||
if (this.tagCategory.includes(this.newAllTagsCopy[index].id)) {
|
||||
this.newAllTagsCopy[index].showItem = true
|
||||
} else {
|
||||
this.newAllTagsCopy[index].showItem = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
handleTagClose(tag) {
|
||||
this.selectTags.splice(this.selectTags.indexOf(tag), 1)
|
||||
this.$emit('selectTags', this.selectTags)
|
||||
|
||||
},
|
||||
showTagsFun() {
|
||||
this.showTagsFlag = !this.showTagsFlag
|
||||
},
|
||||
// 点击可选择列表中的标签
|
||||
handleClickTag(tagID) {
|
||||
if (this.selectTags.indexOf(tagID) === -1) {
|
||||
this.selectTags.push(tagID)
|
||||
} else {
|
||||
this.selectTags.splice(this.selectTags.indexOf(tagID), 1)
|
||||
}
|
||||
this.$emit('selectTags', this.selectTags)
|
||||
|
||||
},
|
||||
fliterTagType(item, tagID, topicIds) {
|
||||
if (this.selectTags.indexOf(tagID) !== -1) {
|
||||
return 'danger'
|
||||
} else {
|
||||
if (topicIds) {return 'default' } else {
|
||||
if(this.searchTagTxt){
|
||||
if (item.name.indexOf(this.searchTagTxt) !== -1) {
|
||||
return 'warning'
|
||||
}
|
||||
}else {
|
||||
return 'info'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getValue() {
|
||||
return this.selectTags
|
||||
},
|
||||
// 标签搜索
|
||||
async tagFilter(){
|
||||
// TODO:请求所有分类标签列表
|
||||
await mediaAllList({ pageNum: 1, pageSize: 100,name:this.searchTagTxt }).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.newAllTagsCopy = res.data.list
|
||||
} else {
|
||||
this.newAllTagsCopy = []
|
||||
this.$message.error('请求标签分类失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.reason-action-box {
|
||||
|
||||
// height: 200px;
|
||||
.el-tag {
|
||||
padding: 0 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 90px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 9px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.reason-action-box button[data-v-2b5b3839] {
|
||||
width: 80px;
|
||||
padding: 9px 15px;
|
||||
margin-right: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,111 @@
|
||||
<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" append-to-body>
|
||||
<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>
|
||||
@@ -0,0 +1,59 @@
|
||||
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
|
||||
@@ -0,0 +1,291 @@
|
||||
<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'
|
||||
// import './plugins/media-plugin-magic-by-snowmile'
|
||||
|
||||
// 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,
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 存放图片的域名
|
||||
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['zh'],
|
||||
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,
|
||||
file_picker_types: 'file image media',
|
||||
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', (event) => {
|
||||
this.hasChange = true
|
||||
this.$emit('input', editor.getContent())
|
||||
})
|
||||
},
|
||||
setup(editor) {
|
||||
editor.on('FullscreenStateChanged', (e) => {
|
||||
_this.fullscreen = e.state
|
||||
})
|
||||
},
|
||||
|
||||
images_upload_handler: function(blobInfo, succFun, failFun) {
|
||||
const file = blobInfo.blob() // 转化为易于理解的file对象
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.withCredentials = false
|
||||
xhr.open('POST', '/api/web/mediafile/uploadImg')
|
||||
xhr.onload = function() {
|
||||
if (xhr.status !== 200) {
|
||||
failFun('HTTP Error: ' + xhr.status)
|
||||
return
|
||||
}
|
||||
const json = JSON.parse(xhr.responseText)
|
||||
|
||||
succFun(`${_this.domain}${json.data.path}`)
|
||||
_this.$emit('addPictur', json.data.path)
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', file, file.name) // 此处与源文档不一样
|
||||
xhr.send(formData)
|
||||
},
|
||||
|
||||
// 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) {
|
||||
console.log(arr, '===========')
|
||||
// arr.forEach((v) =>
|
||||
// window.tinymce
|
||||
// .get(this.tinymceId)
|
||||
// .insertContent(`<img class="wscnph" style="width:164px;height:92px;margin-right:15px;" 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>
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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
|
||||
@@ -0,0 +1,6 @@
|
||||
// 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
|
||||
@@ -0,0 +1,236 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-29 19:56:44
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @LastEditTime: 2022-03-29 15:47:16
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<draggable v-model="locaImg" class="list-group" tag="ul" v-bind="dragOptions" @start="drag = true" @end="drag = false">
|
||||
<div v-for="(item,index) in locaImg" :key="index" class="image-list" style="float: left;">
|
||||
<img class="el-upload-list__item-thumbnail image-detail" :src="domain+item.comicsPic" alt="">
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<a href="#">
|
||||
<div class="image-mask">
|
||||
<span class="add" @click="handlePictureCardPreview(domain+item.comicsPic)">
|
||||
<i class="el-icon-view"></i>
|
||||
</span>
|
||||
<span class="delete" @click="removeImage(index)">
|
||||
<i class="el-icon-delete"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<el-dialog :visible.sync="dialogVisible" append-to-body>
|
||||
<img width="100%" :src="dialogImageUrl" alt="">
|
||||
</el-dialog>
|
||||
<el-input v-model.number="item.high" placeholder="高" style="width: 70px" />
|
||||
<el-input v-model.number="item.width" placeholder="宽" style="width: 70px" />
|
||||
</div>
|
||||
</draggable>
|
||||
<el-upload action="/api/web/file/uploadImg" :show-file-list="false" list-type="picture-card" :file-list="fileList" :on-preview="handlePictureCardPreview" :on-change="handleChange" multiple :limit='100'>
|
||||
<i class="el-icon-plus"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
export default {
|
||||
components: { draggable },
|
||||
data() {
|
||||
return {
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 播放视频的域名
|
||||
returnURL: [],
|
||||
dialogImageUrl: '',
|
||||
dialogVisible: false,
|
||||
fileList: [
|
||||
// { name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' },
|
||||
// { name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' }
|
||||
],
|
||||
drag: false, // 拖拽效果是否开启
|
||||
locaImg: [],
|
||||
}
|
||||
},
|
||||
props: {
|
||||
img: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return []
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dragOptions() {
|
||||
return {
|
||||
animation: 200,
|
||||
group: 'description',
|
||||
disabled: false,
|
||||
ghostClass: 'ghost',
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听拖动事件改变时,传出新的图片数组
|
||||
drag() {
|
||||
this.changeImgUrl()
|
||||
},
|
||||
img(New, old) {
|
||||
this.locaImg = []
|
||||
New.filter((item) => {
|
||||
this.locaImg.push(item)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.locaImg = []
|
||||
if (this.img) {
|
||||
this.img.filter((item) => {
|
||||
this.locaImg.push(item)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange(file, fileList) {
|
||||
if (file.status === 'success') {
|
||||
// this.locaImg.push(file.response.data.path)
|
||||
this.locaImg.push({
|
||||
high: undefined,
|
||||
width: undefined,
|
||||
comicsPic: file.response.data.path,
|
||||
})
|
||||
this.changeImgUrl()
|
||||
}
|
||||
},
|
||||
|
||||
// 移除图片
|
||||
removeImage(val) {
|
||||
this.locaImg.splice(val, 1)
|
||||
this.changeImgUrl()
|
||||
},
|
||||
// 预览
|
||||
handlePictureCardPreview(url) {
|
||||
this.dialogImageUrl = url
|
||||
this.dialogVisible = true
|
||||
},
|
||||
changeImgUrl(arr) {
|
||||
this.$emit('changeImgUrl', this.locaImg)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list-group {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 148px;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
display: block;
|
||||
}
|
||||
.image-list {
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
border: 1px solid #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 202px;
|
||||
margin: 0 8px 8px 0;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
img {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
}
|
||||
}
|
||||
.image-list a:hover .image-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
.el-icon-check {
|
||||
color: #fff;
|
||||
}
|
||||
.input-box {
|
||||
width: 80%;
|
||||
}
|
||||
.el-upload--picture-card {
|
||||
background-color: #fbfdff;
|
||||
border: 1px dashed #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 146px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.image-mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.image-detail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.image-mask::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.image-mask span {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
margin: 40% auto;
|
||||
}
|
||||
.image-mask .el-upload-list__item-delete {
|
||||
position: static;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
.add {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.delete {
|
||||
padding-right: 20px;
|
||||
}
|
||||
// .el-icon-delete {
|
||||
// z-index: 9999999;
|
||||
// position: absolute;
|
||||
// bottom: 70px;
|
||||
// right: 10px;
|
||||
// color: red;
|
||||
// }
|
||||
</style>
|
||||
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-loading="loading" element-loading-text="图片上传中" customClass >
|
||||
<div style="display: block;overflow: hidden;max-width: 655px;padding-top: 25px;">
|
||||
<draggable v-model="locaImg" class="list-group" tag="ul" v-bind="dragOptions" @start="drag = true"
|
||||
@end="drag = false">
|
||||
<div v-for="(item, index) in locaImg" :key="index" class="image-list" style="float: left;">
|
||||
<img class="el-upload-list__item-thumbnail image-detail" @load="imgLoad(item)" :src="item.imgLocalUrl||domain + item.comicsPic" alt="">
|
||||
<!-- 遮罩层 -->
|
||||
<a href="#">
|
||||
<div class="image-mask">
|
||||
<span class="add" @click="handlePictureCardPreview(item.imgLocalUrl||domain + item.comicsPic)">
|
||||
<i class="el-icon-view"></i>
|
||||
</span>
|
||||
<span class="delete" @click="removeImage(index)">
|
||||
<i class="el-icon-delete"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<el-dialog :visible.sync="dialogVisible" append-to-body>
|
||||
<img width="100%" :src="dialogImageUrl" alt="">
|
||||
</el-dialog>
|
||||
<el-input v-model.number="item.high" placeholder="高" style="width: 70px" />
|
||||
<el-input v-model.number="item.width" placeholder="宽" style="width: 70px" />
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
<div style="display: block;overflow: hidden;max-width: 655px;">
|
||||
<el-upload action="/api/web/file/uploadImg" ref="upload" :httpRequest="httpRequest" :show-file-list="false"
|
||||
:auto-upload="false" list-type="text" :on-preview="handlePictureCardPreview" :on-change="handleChange"
|
||||
multiple :limit='10000'>
|
||||
<!-- <i class="el-icon-plus"></i> -->
|
||||
|
||||
<el-button slot="trigger" icon="el-icon-folder-opened" size="small" style="font-weight: bold;" type="primary">选取图片</el-button>
|
||||
<el-button size="small" icon="el-icon-delete" style="margin-left: 10px;font-weight: bold;" @click="clearFiles" type="primary">清空选择图片</el-button>
|
||||
<el-button size="small" icon="el-icon-delete" style="margin-left: 10px;font-weight: bold;" @click="clearUploadFiles" type="primary">清空上传图片
|
||||
</el-button>
|
||||
<el-button style="margin-left: 10px;font-weight: bold;" size="small" icon="el-icon-upload" type="primary" @click="submitUpload">上传图片</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: block;overflow: hidden;width: 655px;margin-top: 45px;">
|
||||
<el-table :data="fileList" empty-text="暂无选择图片" :header-row-style="{ 'height': '30px !important' }"
|
||||
header-cell-class-name="uploadHeader" height="200" size="mini" style="width: 100%">
|
||||
<el-table-column prop="name" :label="`已选择图片(${fileList.length}张)`">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
import axios from 'axios'
|
||||
const CancelToken = axios.CancelToken
|
||||
export const source = CancelToken.source()
|
||||
import { upLoadFile } from '@/api/common'
|
||||
|
||||
export default {
|
||||
components: { draggable },
|
||||
data() {
|
||||
const source = axios.CancelToken.source()
|
||||
return {
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 播放视频的域名
|
||||
returnURL: [],
|
||||
dialogImageUrl: '',
|
||||
dialogVisible: false,
|
||||
fileList: [],
|
||||
drag: false, // 拖拽效果是否开启
|
||||
locaImg: [],
|
||||
loading: false,
|
||||
source
|
||||
}
|
||||
},
|
||||
props: {
|
||||
img: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return []
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dragOptions() {
|
||||
return {
|
||||
animation: 200,
|
||||
group: 'description',
|
||||
disabled: false,
|
||||
ghostClass: 'ghost',
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 监听拖动事件改变时,传出新的图片数组
|
||||
drag() {
|
||||
this.changeImgUrl()
|
||||
},
|
||||
img(New, old) {
|
||||
this.locaImg = []
|
||||
New.filter((item) => {
|
||||
this.locaImg.push(item)
|
||||
})
|
||||
},
|
||||
// locaImg: {
|
||||
// handler: function (val, oldVal) {
|
||||
// if (this.fileList.length !== 0 && val.length === this.fileList.length) {
|
||||
// this.setIsUploadIng(false)
|
||||
// }
|
||||
// },
|
||||
// deep: true
|
||||
// }
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.locaImg = []
|
||||
if (this.img) {
|
||||
this.img.filter((item) => {
|
||||
this.locaImg.push(item)
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.source.cancel('组件关闭取消所有请求')
|
||||
this.source = axios.CancelToken.source()
|
||||
},
|
||||
methods: {
|
||||
handleChange(file, fileList) {
|
||||
this.fileList = fileList
|
||||
},
|
||||
imgLoad(item) {
|
||||
// if (item.imgLocalUrl) {
|
||||
// URL.revokeObjectURL(item.imgLocalUrl)
|
||||
// }
|
||||
},
|
||||
clearFiles() {
|
||||
if (this.$refs.upload && this.$refs.upload.clearFiles) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
this.fileList = []
|
||||
},
|
||||
clearUploadFiles() {
|
||||
this.locaImg = []
|
||||
this.changeImgUrl()
|
||||
},
|
||||
httpRequest() {
|
||||
return false
|
||||
},
|
||||
async upLoad() {
|
||||
this.loading = true
|
||||
this.setIsUploadIng(true)
|
||||
let isUploadFail = false
|
||||
const filePathPromises = this.fileList.map(async item => {
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('file', item.raw)
|
||||
fd.append('filename', item.name)
|
||||
const response = await upLoadFile(fd, this.source)
|
||||
response.data.imgLocalUrl = URL.createObjectURL(item.raw)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
isUploadFail = true
|
||||
if (axios.isCancel(error)) {
|
||||
console.log('CancelToken canceled', error.message)
|
||||
} else {
|
||||
this.source.cancel('有任意一个图片上传错误,取消所有请求')
|
||||
this.source = axios.CancelToken.source()
|
||||
this.$notify.error({
|
||||
title: '上传失败',
|
||||
message: '请您重新上传图片!',
|
||||
duration: 0
|
||||
})
|
||||
}
|
||||
this.loading = false
|
||||
this.locaImg = []
|
||||
}
|
||||
})
|
||||
for (const filePathPromise of filePathPromises) {
|
||||
try {
|
||||
const res = await filePathPromise
|
||||
if (res && res.path) {
|
||||
this.locaImg.push({
|
||||
high: undefined,
|
||||
width: undefined,
|
||||
comicsPic: res.path,
|
||||
imgLocalUrl: res.imgLocalUrl
|
||||
})
|
||||
} else {
|
||||
isUploadFail = true
|
||||
throw new Error('locaImg.push阶段错误,请求无返回数据')
|
||||
}
|
||||
} catch (error) {
|
||||
isUploadFail = true
|
||||
this.loading = false
|
||||
this.locaImg = []
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
!isUploadFail && this.clearFiles()
|
||||
if (isUploadFail) {
|
||||
this.locaImg = []
|
||||
}
|
||||
this.loading = false
|
||||
this.setIsUploadIng(false)
|
||||
this.changeImgUrl()
|
||||
},
|
||||
submitUpload() {
|
||||
this.upLoad()
|
||||
},
|
||||
// 移除图片
|
||||
removeImage(val) {
|
||||
this.locaImg.splice(val, 1)
|
||||
this.changeImgUrl()
|
||||
},
|
||||
// 预览
|
||||
handlePictureCardPreview(url) {
|
||||
this.dialogImageUrl = url
|
||||
this.dialogVisible = true
|
||||
},
|
||||
changeImgUrl(arr) {
|
||||
this.$emit('changeImgUrl', this.locaImg)
|
||||
},
|
||||
setIsUploadIng(status) {
|
||||
this.$emit('setIsUploadIng', status)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list-group {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 148px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-list {
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
border: 1px solid #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 202px;
|
||||
margin: 0 8px 8px 0;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
}
|
||||
}
|
||||
|
||||
.image-list a:hover .image-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.el-icon-check {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.el-upload--picture-card {
|
||||
background-color: #fbfdff;
|
||||
border: 1px dashed #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 146px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.image-mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.image-detail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-mask::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.image-mask span {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
margin: 40% auto;
|
||||
}
|
||||
|
||||
.image-mask .el-upload-list__item-delete {
|
||||
position: static;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.add {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.delete {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
// .el-icon-delete {
|
||||
// z-index: 9999999;
|
||||
// position: absolute;
|
||||
// bottom: 70px;
|
||||
// right: 10px;
|
||||
// color: red;
|
||||
// }
|
||||
// .el-table .uploadHeader{
|
||||
// background-color: #d9d9d9 !important;
|
||||
// }
|
||||
</style>
|
||||
<style lang="scss" >
|
||||
.el-table .uploadHeader {
|
||||
background-color: #666 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-table .uploadHeader+th {
|
||||
padding: 0;
|
||||
background-color: #666 !important;
|
||||
border-bottom: 1px solid #dfdfdf !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,177 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-29 19:56:44
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @LastEditTime: 2022-06-21 20:05:54
|
||||
-->
|
||||
<template>
|
||||
<!-- 使用方法 <UploadImg @changeImgUrl='changeMobileAvatar' :img='form.mobileAvatar' :width='"100px"' :height='"100px"'></UploadImg>
|
||||
changeMobileAvatar(data) {
|
||||
this.form.mobileAvatar = data
|
||||
}, -->
|
||||
<el-upload class="avatar-uploader" action="/api/web/file/uploadImg" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
|
||||
<div ref="uploader">
|
||||
<img v-if="imageUrl" :src="domain + imageUrl" class="avatar" />
|
||||
<i v-if="imageUrl" @click.stop="handleRemove" class="el-icon-delete"></i>
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
|
||||
</div>
|
||||
</el-upload>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq,
|
||||
imageUrl: ''
|
||||
}
|
||||
},
|
||||
props: {
|
||||
img: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '180px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '180px'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
img: function(val, oldval) {
|
||||
this.imageUrl = val
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$refs['uploader'].style.width = this.width
|
||||
this.$refs['uploader'].style.height = this.height
|
||||
this.$refs['uploader'].style.lineHeight = this.height
|
||||
if (this.img) {
|
||||
this.imageUrl = this.img
|
||||
} else {
|
||||
this.imageUrl = ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAvatarSuccess(res, file) {
|
||||
if (res.code === 200) {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '上传成功!'
|
||||
})
|
||||
|
||||
this.imageUrl = res.data.path
|
||||
this.changeImgUrl()
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '上传失败!'
|
||||
})
|
||||
this.imageUrl = ''
|
||||
}
|
||||
},
|
||||
changeImgUrl() {
|
||||
this.$emit('changeImgUrl', this.imageUrl)
|
||||
},
|
||||
handleRemove(f) {
|
||||
this.imageUrl = ''
|
||||
this.$emit('onRemove', f)
|
||||
},
|
||||
// 上传前进行压缩
|
||||
beforeAvatarUpload(file) {
|
||||
const isJPG =
|
||||
file.type === 'image/jpeg' ||
|
||||
file.type === 'image/png' ||
|
||||
file.type === 'image/gif' ||
|
||||
file.type === 'image/webp'
|
||||
// const isLt2M = file.size / 1024 / 1024 < 2
|
||||
if (!isJPG) {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '上传图片只能是 JPG 格式!!'
|
||||
})
|
||||
}
|
||||
// if (!isLt2M) {
|
||||
// this.$message({
|
||||
// type: 'error',
|
||||
// message: '上传图片大小不能超过 2MB!!'
|
||||
// })
|
||||
// }
|
||||
return isJPG
|
||||
},
|
||||
|
||||
// 图片等比压缩
|
||||
handleCompressImage(file, type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = (e) => {
|
||||
const image = new Image()
|
||||
image.src = e.target.result
|
||||
image.onload = () => {
|
||||
if (image.width > 720) {
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
const imageWidth = 720
|
||||
const imageHeight = (720 / image.width) * image.height
|
||||
canvas.width = imageWidth
|
||||
canvas.height = imageHeight
|
||||
context.drawImage(image, 0, 0, imageWidth, imageHeight)
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
resolve(new Blob([blob], { type: type }))
|
||||
},
|
||||
'image/jpeg',
|
||||
0.9
|
||||
)
|
||||
} else {
|
||||
resolve(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.el-icon-delete {
|
||||
z-index: 9999999;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,217 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-29 19:56:44
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @LastEditTime: 2022-03-29 15:36:11
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<draggable v-model="locaImg" class="list-group" tag="ul" v-bind="dragOptions" @start="drag = true" @end="drag = false">
|
||||
<div v-for="(img,index) in locaImg" :key="index" class="image-list" style="float: left;">
|
||||
<img class="el-upload-list__item-thumbnail image-detail" :src="domain+img" alt="">
|
||||
<!-- 遮罩层 -->
|
||||
<a href="#">
|
||||
<div class="image-mask">
|
||||
<span class="add" @click="handlePictureCardPreview(domain+img)">
|
||||
<i class="el-icon-view"></i>
|
||||
</span>
|
||||
<span class="delete" @click="removeImage(index)">
|
||||
<i class="el-icon-delete"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<el-dialog :visible.sync="dialogVisible" append-to-body>
|
||||
<img width="100%" :src="dialogImageUrl" alt="">
|
||||
</el-dialog>
|
||||
</div>
|
||||
</draggable>
|
||||
<el-upload action="/api/web/file/uploadImg" :show-file-list="false" list-type="picture-card" :file-list="fileList" :on-preview="handlePictureCardPreview" :on-change="handleChange" multiple :limit='100'>
|
||||
<i class="el-icon-plus"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
export default {
|
||||
components: { draggable },
|
||||
data() {
|
||||
return {
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 播放视频的域名
|
||||
returnURL: [],
|
||||
dialogImageUrl: '',
|
||||
dialogVisible: false,
|
||||
fileList: [
|
||||
// { name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' },
|
||||
// { name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' }
|
||||
],
|
||||
drag: false, // 拖拽效果是否开启
|
||||
locaImg: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
img: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dragOptions() {
|
||||
return {
|
||||
animation: 200,
|
||||
group: 'description',
|
||||
disabled: false,
|
||||
ghostClass: 'ghost'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听拖动事件改变时,传出新的图片数组
|
||||
drag() {
|
||||
this.changeImgUrl()
|
||||
},
|
||||
img(New, old) {
|
||||
this.locaImg = []
|
||||
New.filter((item) => {
|
||||
this.locaImg.push(item)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.locaImg = []
|
||||
if (this.img) {
|
||||
this.img.filter((item) => {
|
||||
this.locaImg.push(item)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange(file, fileList) {
|
||||
if (file.status === 'success') {
|
||||
this.locaImg.push(file.response.data.path)
|
||||
this.changeImgUrl()
|
||||
}
|
||||
},
|
||||
|
||||
// 移除图片
|
||||
removeImage(val) {
|
||||
this.locaImg.splice(val, 1)
|
||||
this.changeImgUrl()
|
||||
},
|
||||
// 预览
|
||||
handlePictureCardPreview(url) {
|
||||
this.dialogImageUrl = url
|
||||
this.dialogVisible = true
|
||||
},
|
||||
changeImgUrl(arr) {
|
||||
this.$emit('changeImgUrl', this.locaImg)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list-group {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 148px;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
display: block;
|
||||
}
|
||||
.image-list {
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
border: 1px solid #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
margin: 0 8px 8px 0;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.image-list a:hover .image-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
.el-icon-check {
|
||||
color: #fff;
|
||||
}
|
||||
.input-box {
|
||||
width: 80%;
|
||||
}
|
||||
.el-upload--picture-card {
|
||||
background-color: #fbfdff;
|
||||
border: 1px dashed #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 146px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.image-mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.image-detail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.image-mask::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.image-mask span {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
margin: 40% auto;
|
||||
}
|
||||
.image-mask .el-upload-list__item-delete {
|
||||
position: static;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
.add {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.delete {
|
||||
padding-right: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,240 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-29 19:56:44
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @LastEditTime: 2022-03-29 15:36:11
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<draggable v-model="locaImg" class="list-group" tag="ul" v-bind="dragOptions" @start="drag = true" @end="drag = false">
|
||||
<div v-for="(img, index) in locaImg" :key="index" class="image-list" style="float: left">
|
||||
<img class="el-upload-list__item-thumbnail image-detail" :src="domain + img.image" alt="" />
|
||||
<!-- 遮罩层 -->
|
||||
<a href="#">
|
||||
<div class="image-mask">
|
||||
<span class="add" @click="handlePictureCardPreview(domain + img.image)">
|
||||
<i class="el-icon-view"></i>
|
||||
</span>
|
||||
<span class="delete" @click="removeImage(index)">
|
||||
<i class="el-icon-delete"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<el-dialog :visible.sync="dialogVisible" append-to-body>
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
</el-dialog>
|
||||
<div class="input"><el-input v-model="locaImg[index].url" @input="changeImgUrl" :placeholder="`请输入第${index + 1}张图片跳转url`"></el-input></div>
|
||||
</div>
|
||||
</draggable>
|
||||
<el-upload
|
||||
action="/api/web/file/uploadImg"
|
||||
:show-file-list="false"
|
||||
list-type="picture-card"
|
||||
:file-list="fileList"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-change="handleChange"
|
||||
multiple
|
||||
:limit="100">
|
||||
<i class="el-icon-plus"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
export default {
|
||||
components: { draggable },
|
||||
data() {
|
||||
return {
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 播放视频的域名
|
||||
returnURL: [],
|
||||
dialogImageUrl: '',
|
||||
dialogVisible: false,
|
||||
fileList: [
|
||||
// { name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' },
|
||||
// { name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' }
|
||||
],
|
||||
drag: false, // 拖拽效果是否开启
|
||||
locaImg: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
img: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dragOptions() {
|
||||
return {
|
||||
animation: 200,
|
||||
group: 'description',
|
||||
disabled: false,
|
||||
ghostClass: 'ghost'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听拖动事件改变时,传出新的图片数组
|
||||
drag() {
|
||||
this.changeImgUrl()
|
||||
},
|
||||
img(New, old) {
|
||||
this.locaImg = []
|
||||
New.filter(item => {
|
||||
this.locaImg.push({
|
||||
image: item.image,
|
||||
url: item.url
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.locaImg = []
|
||||
if (this.img) {
|
||||
this.img.filter(item => {
|
||||
this.locaImg.push({
|
||||
image: item.image,
|
||||
url: item.url
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange(file, fileList) {
|
||||
if (file.status === 'success') {
|
||||
this.locaImg.push({ image: file.response.data.path, url: '' })
|
||||
this.changeImgUrl()
|
||||
}
|
||||
},
|
||||
|
||||
// 移除图片
|
||||
removeImage(val) {
|
||||
this.locaImg.splice(val, 1)
|
||||
this.changeImgUrl()
|
||||
},
|
||||
// 预览
|
||||
handlePictureCardPreview(url) {
|
||||
this.dialogImageUrl = url
|
||||
this.dialogVisible = true
|
||||
},
|
||||
changeImgUrl() {
|
||||
console.log(this.locaImg)
|
||||
this.$emit('changeImgUrl', this.locaImg)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list-group {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 148px;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
display: block;
|
||||
}
|
||||
.image-list {
|
||||
background-color: #fff;
|
||||
border: 1px solid #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
margin: 0 8px 8px 0;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin-bottom: 50px;
|
||||
.input {
|
||||
height: 30px;
|
||||
width: 250px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -35px;
|
||||
}
|
||||
}
|
||||
.image-list a:hover .image-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
.el-icon-check {
|
||||
color: #fff;
|
||||
}
|
||||
.input-box {
|
||||
width: 80%;
|
||||
}
|
||||
.el-upload--picture-card {
|
||||
background-color: #fbfdff;
|
||||
border: 1px dashed #c0ccda;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 146px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.image-mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.image-detail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.image-mask::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.image-mask span {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
margin: 40% auto;
|
||||
}
|
||||
.image-mask .el-upload-list__item-delete {
|
||||
position: static;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
.add {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.delete {
|
||||
padding-right: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,192 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-29 19:56:44
|
||||
* @LastEditors: 王涛
|
||||
* @LastEditTime: 2021-01-07 22:22:03
|
||||
-->
|
||||
<template>
|
||||
<div class="box">
|
||||
<div v-on:paste="handlePaste" v-drag="handleDrag" >
|
||||
<img v-if='imageUrl' ref="preview" :src="domain+imageUrl" alt="" style="width:300px;height:300px;">
|
||||
<div v-else style="width:300px;height:300px;border:1px solid;padding:20px;">
|
||||
<div style="text-indent:2em;">将图片按<span style="color:red">cmd+C复制</span>到粘贴板,<span style="color:red">cmd+V粘贴</span>至此处;</div>
|
||||
<div style="text-indent:2em;">或者将图片拖拽至此处</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mediaUploadImg } from '@/api/components/UploadImgPaste'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
domain: this.$store.getters.getSystemConfig.webImgReq, // 存放图片的域名
|
||||
imageUrl: ''
|
||||
}
|
||||
},
|
||||
props: {
|
||||
img: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
img: function(val) {
|
||||
this.imageUrl = val
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.img) {
|
||||
this.imageUrl = this.img
|
||||
} else {
|
||||
this.imageUrl = ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleDrag(event) {
|
||||
const items = Array.from(event.dataTransfer.items)
|
||||
let file = null
|
||||
if (!items || items.length === 0) {
|
||||
this.$message.error('当前浏览器不支持本地')
|
||||
return
|
||||
}
|
||||
// 搜索剪切板items
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.indexOf('image') !== -1) {
|
||||
file = items[i].getAsFile()
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!file) {
|
||||
this.$message.error('粘贴内容非图片')
|
||||
return
|
||||
}
|
||||
// 此时file就是我们的剪切板中的图片对象
|
||||
// 如果需要预览,可以执行下面代码
|
||||
this.file = file
|
||||
this.uploadPlans()
|
||||
},
|
||||
// 图片等比压缩
|
||||
handleCompressImage(file, type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = (e) => {
|
||||
const image = new Image()
|
||||
image.src = e.target.result
|
||||
|
||||
image.onload = () => {
|
||||
if (image.width > 720) {
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
const imageWidth = 720
|
||||
const imageHeight = (720 / image.width) * image.height
|
||||
canvas.width = imageWidth
|
||||
canvas.height = imageHeight
|
||||
console.log(imageWidth, imageHeight)
|
||||
context.drawImage(image, 0, 0, imageWidth, imageHeight)
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
resolve(new Blob([blob], { type: type }))
|
||||
},
|
||||
'image/jpeg',
|
||||
0.9
|
||||
)
|
||||
} else {
|
||||
resolve(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
async handlePaste(event) {
|
||||
const items = (event.clipboardData || window.clipboardData).items
|
||||
let file = null
|
||||
if (!items || items.length === 0) {
|
||||
this.$message.error('当前浏览器不支持本地')
|
||||
return
|
||||
}
|
||||
// 搜索剪切板items
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.indexOf('image') !== -1) {
|
||||
file = items[i].getAsFile()
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!file) {
|
||||
this.$message.error('粘贴内容非图片')
|
||||
return
|
||||
}
|
||||
// 此时file就是我们的剪切板中的图片对象
|
||||
// 如果需要预览,可以执行下面代码
|
||||
// 先进行压缩再上传
|
||||
// const res = await this.handleCompressImage(file)
|
||||
// this.file = res
|
||||
this.file = file
|
||||
this.uploadPlans()
|
||||
},
|
||||
// 上传文件成功后回调
|
||||
uploadPlans() {
|
||||
const file = this.file
|
||||
if (!file) {
|
||||
this.$message.error('请粘贴图片后上传')
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
form.append('type', this.type)
|
||||
mediaUploadImg(form).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '上传成功!'
|
||||
})
|
||||
this.imageUrl = res.data.path
|
||||
this.changeImgUrl()
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '上传失败!'
|
||||
})
|
||||
this.imageUrl = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
changeImgUrl() {
|
||||
this.$emit('changeImgUrl', this.imageUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
line-height: 148px;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-30 11:10:55
|
||||
* @LastEditors: 江涛
|
||||
* @LastEditTime: 2021-03-24 11:39:53
|
||||
-->
|
||||
<template>
|
||||
<uploader
|
||||
:options="options"
|
||||
class="uploader-example"
|
||||
@file-success="fileSuccess"
|
||||
@file-added="fileAdd">
|
||||
<uploader-unsupport></uploader-unsupport>
|
||||
<uploader-drop>
|
||||
<uploader-btn>选择文件</uploader-btn>
|
||||
</uploader-drop>
|
||||
<uploader-list></uploader-list>
|
||||
</uploader>
|
||||
</template>
|
||||
<script>
|
||||
// import { resumeUploadIsSuccess } from '@/api/video/video'
|
||||
import { videoOk } from '@/api/video.js'
|
||||
import { getToken } from '@/utils/auth'
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 视频校验唯一码
|
||||
trackId: '',
|
||||
duration: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return {
|
||||
target: '/api/web/mediafile/videoBody',
|
||||
method: 'application/json',
|
||||
chunkSize: 2 * 1024 * 1024,
|
||||
testChunks: false,
|
||||
forceChunkSize: true,
|
||||
maxChunkRetries: 3,
|
||||
singleFile: true,
|
||||
query: this.uploadQuery,
|
||||
headers: {
|
||||
Authorization: getToken()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
uploadQuery(file, chunk) {
|
||||
return {
|
||||
pos: chunk.offset,
|
||||
taskId: this.trackId,
|
||||
type: 1,
|
||||
data: 'flie'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 上传视频-点击上传视频
|
||||
*/
|
||||
async fileSuccess(rootFile, file, message, chunk) {
|
||||
const video = document.createElement('video')
|
||||
video.preload = 'metadata'
|
||||
video.src = URL.createObjectURL(rootFile.file)
|
||||
video.onloadedmetadata = () => {
|
||||
window.URL.revokeObjectURL(video.src)
|
||||
// eslint-disable-next-line no-const-assign
|
||||
|
||||
this.duration = video.duration
|
||||
}
|
||||
|
||||
const data = {
|
||||
id: this.trackId,
|
||||
videoUrl: JSON.parse(message).data.videoUri
|
||||
}
|
||||
videoOk(data).then((res) => {
|
||||
if (res.code === 200) {
|
||||
const param = {
|
||||
id:video.id,
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight,
|
||||
duration: this.duration,
|
||||
videoUri: JSON.parse(message).data.videoUri,
|
||||
size: file.size
|
||||
}
|
||||
this.$emit('changeVideoUrl', param)
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '视频上传失败!'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
fileAdd() {
|
||||
this.trackId = new Date().getTime()+''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,144 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-30 11:10:55
|
||||
* @LastEditors: 江涛
|
||||
* @LastEditTime: 2021-03-24 11:39:53
|
||||
-->
|
||||
<template>
|
||||
<uploader :options="options" class="uploader-example" @file-success="fileSuccess" @file-added="fileAdd">
|
||||
<uploader-unsupport></uploader-unsupport>
|
||||
<uploader-drop>
|
||||
<uploader-btn>选择文件</uploader-btn>
|
||||
</uploader-drop>
|
||||
<uploader-list></uploader-list>
|
||||
</uploader>
|
||||
</template>
|
||||
<script>
|
||||
// import { resumeUploadIsSuccess } from '@/api/video/video'
|
||||
import { videoOk } from '@/api/video.js';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import SparkMD5 from 'spark-md5';
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// 视频校验唯一码
|
||||
trackId: '',
|
||||
duration: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return {
|
||||
target: '/api/web/mediafile/videoBody',
|
||||
method: 'application/json',
|
||||
chunkSize: 2 * 1024 * 1024,
|
||||
testChunks: false,
|
||||
forceChunkSize: true,
|
||||
maxChunkRetries: 3,
|
||||
singleFile: true,
|
||||
query: this.uploadQuery,
|
||||
headers: {
|
||||
Authorization: getToken(),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
uploadQuery(file, chunk) {
|
||||
return {
|
||||
pos: chunk.offset,
|
||||
taskId: this.trackId,
|
||||
type: this.type,
|
||||
data: 'file',
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 上传视频-点击上传视频
|
||||
*/
|
||||
async fileSuccess(rootFile, file, message, chunk) {
|
||||
const video = document.createElement('video');
|
||||
video.preload = 'metadata';
|
||||
video.src = URL.createObjectURL(rootFile.file);
|
||||
video.onloadedmetadata = () => {
|
||||
window.URL.revokeObjectURL(video.src);
|
||||
// eslint-disable-next-line no-const-assign
|
||||
|
||||
this.duration = video.duration;
|
||||
};
|
||||
if (JSON.parse(message).code !== 200) {
|
||||
alert(`任务id:${this.trackId}上传出错!`)
|
||||
}
|
||||
const data = {
|
||||
id: JSON.parse(message).data.id,
|
||||
videoUrl: JSON.parse(message).data.videoUri,
|
||||
};
|
||||
videoOk(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
const param = {
|
||||
id: data.id,
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight,
|
||||
duration: this.duration,
|
||||
videoUri: JSON.parse(message).data.videoUri,
|
||||
size: file.size,
|
||||
};
|
||||
this.$emit('changeVideoUrl', param);
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '视频上传失败!',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async fileAdd(file) {
|
||||
const fileReader = new FileReader();
|
||||
const time = new Date().getTime();
|
||||
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
||||
let currentChunk = 0;
|
||||
const chunkSize = 10 * 1024 * 1000;
|
||||
const chunks = Math.ceil(file.size / chunkSize);
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
file.pause();
|
||||
loadNext();
|
||||
fileReader.onload = e => {
|
||||
if (this.type == 4 && file.fileType.indexOf('audio') == -1) {
|
||||
file.cancel();
|
||||
return this.$message.error('此视频归类仅可上传MP3格式内容!');
|
||||
}
|
||||
spark.append(e.target.result);
|
||||
if (currentChunk < chunks) {
|
||||
currentChunk++;
|
||||
loadNext();
|
||||
console.log('校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%');
|
||||
} else {
|
||||
this.trackId = spark.end();
|
||||
this.$message('开始上传,请等待');
|
||||
file.resume();
|
||||
console.log(`MD5计算完毕:${file.name} \nMD5:${spark} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
|
||||
}
|
||||
};
|
||||
fileReader.onerror = function () {
|
||||
this.$message(`文件${file.name}读取出错,请检查该文件`);
|
||||
file.cancel();
|
||||
};
|
||||
|
||||
function loadNext() {
|
||||
const start = currentChunk * chunkSize;
|
||||
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
||||
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
* @Author: 王涛
|
||||
* @Mail: no
|
||||
* @Date: 2020-09-30 11:10:55
|
||||
* @LastEditors: 江涛
|
||||
* @LastEditTime: 2021-03-03 20:26:11
|
||||
-->
|
||||
<template>
|
||||
<el-upload class="upload-demo" action="/api/web/media/uploadImg" :on-success='success' :file-list="fileList">
|
||||
<el-button size="mini" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
success(response, file, fileList) {
|
||||
if (response.code === 200) {
|
||||
const param = {
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
duration: undefined,
|
||||
videoUri: response.data.path,
|
||||
size: undefined
|
||||
}
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '视频上传成功!'
|
||||
})
|
||||
this.$emit('changeVideoUrl', param)
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '视频上传失败!'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div id="dplayer" ref="dplayer"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DPlayer from 'dplayer'
|
||||
import Hls from 'hls.js';
|
||||
window.Hls = Hls;
|
||||
export default {
|
||||
props: {
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
theme: { // 主题色
|
||||
type: String,
|
||||
default: '#FADFA3'
|
||||
},
|
||||
loop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
lang: {
|
||||
type: String,
|
||||
default: 'zh'
|
||||
},
|
||||
screenshot: { // 是否开启截图
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hotkey: { // 是否开启热键
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
preload: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
contextmenu: { // 自定义右键菜单
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
logo: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
video: {
|
||||
type: Object, // 可选值:‘auto’,'hls','flv','dash','webtorrent','normal'
|
||||
required: true,
|
||||
validator(value) {
|
||||
return typeof value.url === 'string'
|
||||
}
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
require: false,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dp: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
video(val) {
|
||||
if (this.dp) {
|
||||
this.dp.switchVideo(val) // 切换到其他视频
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.dp) {
|
||||
this.dp.destroy()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const player = (this.dp = new DPlayer({
|
||||
...{
|
||||
container: this.$refs.dplayer,
|
||||
autoplay: false,
|
||||
theme: this.theme,
|
||||
loop: this.loop,
|
||||
lang: this.lang,
|
||||
screenshot: this.screenshot,
|
||||
hotkey: this.hotkey,
|
||||
preload: this.preload,
|
||||
contextmenu: this.contextmenu,
|
||||
logo: this.logo,
|
||||
video: {
|
||||
url: this.video.url,
|
||||
pic: this.video.pic,
|
||||
type: this.video.type
|
||||
}
|
||||
},
|
||||
...this.option
|
||||
}))
|
||||
player.video.muted = true
|
||||
player.on('play', () => {
|
||||
this.$emit('play')
|
||||
})
|
||||
player.on('playing', () => {
|
||||
this.$emit('playing')
|
||||
})
|
||||
player.on('pause', () => {
|
||||
this.$emit('pause')
|
||||
})
|
||||
player.on('canplay', () => {
|
||||
this.$emit('canplay')
|
||||
})
|
||||
player.on('ended', () => {
|
||||
this.$emit('ended')
|
||||
})
|
||||
player.on('error', () => {
|
||||
this.$emit('error')
|
||||
})
|
||||
player.on('loadeddata', () => {
|
||||
this.$emit('loadeddata')
|
||||
if (this.autoplay) {
|
||||
player.play()
|
||||
player.video.muted = false
|
||||
}
|
||||
})
|
||||
player.on('loadedmetadata', () => {
|
||||
this.$emit('loadedmetadata')
|
||||
})
|
||||
player.on('waiting', () => {
|
||||
this.$emit('waiting')
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
#dplayer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
export function jumptabChange(val) {
|
||||
switch (val) {
|
||||
case 1:
|
||||
return '漫画'
|
||||
case 2:
|
||||
return '动漫'
|
||||
case 3:
|
||||
return '视频'
|
||||
}
|
||||
}
|
||||
export function jumptabChangeList() {
|
||||
return [
|
||||
{
|
||||
value: 1,
|
||||
label: '漫画'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '动漫'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '视频'
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user