fix: 新增公众号管理相关页面,引入配置文件修改。
This commit is contained in:
257
src/components/WxEditor/WxEditor.vue
Normal file
257
src/components/WxEditor/WxEditor.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:headers="headers"
|
||||
class="editor-img-uploader"
|
||||
v-if="type == 'url'"
|
||||
>
|
||||
<i ref="uploadRef" class="editor-img-uploader"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div class="editor">
|
||||
<quill-editor
|
||||
ref="quillEditorRef"
|
||||
v-model:content="content"
|
||||
contentType="html"
|
||||
@textChange="(e) => $emit('update:modelValue', content)"
|
||||
:options="options"
|
||||
:style="styles"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { QuillEditor } from '@vueup/vue-quill'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const quillEditorRef = ref()
|
||||
// 微信素材上传地址
|
||||
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/wxmaterial/newsImgUpload') // 上传的图片服务器地址
|
||||
const headers = ref({
|
||||
Authorization: 'Bearer ' + getToken()
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
/* 编辑器的内容 */
|
||||
modelValue: {
|
||||
type: String
|
||||
},
|
||||
/* 高度 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
/* 最小高度 */
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
/* 只读 */
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/* 上传文件大小限制(MB) */
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
/* 类型(base64格式、url格式) */
|
||||
type: {
|
||||
type: String,
|
||||
default: 'url'
|
||||
}
|
||||
})
|
||||
|
||||
const options = ref({
|
||||
theme: 'snow',
|
||||
bounds: document.body,
|
||||
debug: 'warn',
|
||||
modules: {
|
||||
// 工具栏配置
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
|
||||
['blockquote', 'code-block'], // 引用 代码块
|
||||
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
|
||||
[{ indent: '-1' }, { indent: '+1' }], // 缩进
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
|
||||
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
|
||||
[{ align: [] }], // 对齐方式
|
||||
['clean'], // 清除文本格式
|
||||
['link', 'image', 'video'] // 链接、图片、视频
|
||||
]
|
||||
},
|
||||
placeholder: '请输入内容',
|
||||
readOnly: props.readOnly
|
||||
})
|
||||
|
||||
const styles = computed(() => {
|
||||
const style = {}
|
||||
if (props.minHeight) {
|
||||
style.minHeight = `${props.minHeight}px`
|
||||
}
|
||||
if (props.height) {
|
||||
style.height = `${props.height}px`
|
||||
}
|
||||
return style
|
||||
})
|
||||
|
||||
const content = ref('')
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
if (v !== content.value) {
|
||||
content.value = v === undefined ? '<p></p>' : v.replace(/data-src/g, 'src') //内容回显时替换data-src为src
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 如果设置了上传地址则自定义图片上传事件
|
||||
onMounted(() => {
|
||||
if (props.type == 'url') {
|
||||
const quill = quillEditorRef.value.getQuill()
|
||||
const toolbar = quill.getModule('toolbar')
|
||||
toolbar.addHandler('image', (value) => {
|
||||
if (value) {
|
||||
proxy.$refs.uploadRef.click()
|
||||
} else {
|
||||
quill.format('image', false)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 上传前校检格式和大小
|
||||
function handleBeforeUpload(file) {
|
||||
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg']
|
||||
const isJPG = type.includes(file.type)
|
||||
//检验文件格式
|
||||
if (!isJPG) {
|
||||
proxy.$modal.msgError('图片格式错误!')
|
||||
return false
|
||||
}
|
||||
// 校检文件大小
|
||||
if (props.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize
|
||||
if (!isLt) {
|
||||
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 上传成功处理
|
||||
function handleUploadSuccess(res, file) {
|
||||
// 如果上传成功
|
||||
if (res.link) {
|
||||
// 获取富文本实例
|
||||
const quill = toRaw(quillEditorRef.value).getQuill()
|
||||
// 获取光标位置
|
||||
const length = quill.selection.savedRange.index
|
||||
// 插入图片,res.url为服务器返回的图片链接地址
|
||||
quill.insertEmbed(length, 'image', res.link)
|
||||
// 调整光标到最后
|
||||
quill.setSelection(length + 1)
|
||||
} else {
|
||||
proxy.$modal.msgError('图片插入失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 上传失败处理
|
||||
function handleUploadError() {
|
||||
proxy.$modal.msgError('图片插入失败')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.editor-img-uploader {
|
||||
display: none;
|
||||
}
|
||||
.editor,
|
||||
.ql-toolbar {
|
||||
white-space: pre-wrap !important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
.quill-img {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode='link']::before {
|
||||
content: '请输入链接地址:';
|
||||
}
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0px;
|
||||
content: '保存';
|
||||
padding-right: 0px;
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode='video']::before {
|
||||
content: '请输入视频地址:';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: '14px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
|
||||
content: '10px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
|
||||
content: '18px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
|
||||
content: '32px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: '文本';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
|
||||
content: '标题1';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
|
||||
content: '标题2';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
|
||||
content: '标题3';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
|
||||
content: '标题4';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
|
||||
content: '标题5';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
|
||||
content: '标题6';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: '标准字体';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
|
||||
content: '衬线字体';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
|
||||
content: '等宽字体';
|
||||
}
|
||||
</style>
|
||||
260
src/components/WxEditor/index.vue
Normal file
260
src/components/WxEditor/index.vue
Normal file
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:headers="headers"
|
||||
class="editor-img-uploader"
|
||||
v-if="type == 'url'"
|
||||
>
|
||||
<i ref="uploadRef" class="editor-img-uploader"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div class="editor">
|
||||
<quill-editor
|
||||
ref="quillEditorRef"
|
||||
v-model:content="content"
|
||||
contentType="html"
|
||||
@textChange="(e) => $emit('update:modelValue', content)"
|
||||
:options="options"
|
||||
:style="styles"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { QuillEditor } from '@vueup/vue-quill'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const quillEditorRef = ref()
|
||||
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/common/upload') // 上传的图片服务器地址
|
||||
const headers = ref({
|
||||
Authorization: 'Bearer ' + getToken()
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
/* 编辑器的内容 */
|
||||
modelValue: {
|
||||
type: String
|
||||
},
|
||||
/* 高度 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
/* 最小高度 */
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
/* 只读 */
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/* 上传文件大小限制(MB) */
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
/* 类型(base64格式、url格式) */
|
||||
type: {
|
||||
type: String,
|
||||
default: 'url'
|
||||
}
|
||||
})
|
||||
|
||||
const options = ref({
|
||||
theme: 'snow',
|
||||
bounds: document.body,
|
||||
debug: 'warn',
|
||||
modules: {
|
||||
// 工具栏配置
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
|
||||
['blockquote', 'code-block'], // 引用 代码块
|
||||
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
|
||||
[{ indent: '-1' }, { indent: '+1' }], // 缩进
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
|
||||
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
|
||||
[{ align: [] }], // 对齐方式
|
||||
['clean'], // 清除文本格式
|
||||
['link', 'image', 'video'] // 链接、图片、视频
|
||||
]
|
||||
},
|
||||
placeholder: '请输入内容',
|
||||
readOnly: props.readOnly
|
||||
})
|
||||
|
||||
const styles = computed(() => {
|
||||
const style = {}
|
||||
if (props.minHeight) {
|
||||
style.minHeight = `${props.minHeight}px`
|
||||
}
|
||||
if (props.height) {
|
||||
style.height = `${props.height}px`
|
||||
}
|
||||
return style
|
||||
})
|
||||
|
||||
const content = ref('')
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
if (v !== content.value) {
|
||||
content.value = v === undefined ? '<p></p>' : v
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 如果设置了上传地址则自定义图片上传事件
|
||||
onMounted(() => {
|
||||
if (props.type == 'url') {
|
||||
const quill = quillEditorRef.value.getQuill()
|
||||
const toolbar = quill.getModule('toolbar')
|
||||
toolbar.addHandler('image', (value) => {
|
||||
if (value) {
|
||||
proxy.$refs.uploadRef.click()
|
||||
} else {
|
||||
quill.format('image', false)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 上传前校检格式和大小
|
||||
function handleBeforeUpload(file) {
|
||||
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg']
|
||||
const isJPG = type.includes(file.type)
|
||||
//检验文件格式
|
||||
if (!isJPG) {
|
||||
proxy.$modal.msgError('图片格式错误!')
|
||||
return false
|
||||
}
|
||||
// 校检文件大小
|
||||
if (props.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize
|
||||
if (!isLt) {
|
||||
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 上传成功处理
|
||||
function handleUploadSuccess(res, file) {
|
||||
// 如果上传成功
|
||||
if (res.code == 200) {
|
||||
// 获取富文本实例
|
||||
const quill = toRaw(quillEditorRef.value).getQuill()
|
||||
// 获取光标位置
|
||||
const length = quill.selection.savedRange.index
|
||||
// 插入图片,res.url为服务器返回的图片链接地址
|
||||
quill.insertEmbed(length, 'image', import.meta.env.VITE_APP_BASE_API + res.fileName)
|
||||
// 调整光标到最后
|
||||
quill.setSelection(length + 1)
|
||||
} else {
|
||||
proxy.$modal.msgError('图片插入失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 上传失败处理
|
||||
function handleUploadError() {
|
||||
proxy.$modal.msgError('图片插入失败')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.editor-img-uploader {
|
||||
display: none;
|
||||
}
|
||||
.editor,
|
||||
.ql-toolbar {
|
||||
white-space: pre-wrap !important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
/* 处理连续图片有间隙的问题 */
|
||||
.editor img {
|
||||
display: block;
|
||||
}
|
||||
.quill-img {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode='link']::before {
|
||||
content: '请输入链接地址:';
|
||||
}
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0px;
|
||||
content: '保存';
|
||||
padding-right: 0px;
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode='video']::before {
|
||||
content: '请输入视频地址:';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: '14px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
|
||||
content: '10px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
|
||||
content: '18px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
|
||||
content: '32px';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: '文本';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
|
||||
content: '标题1';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
|
||||
content: '标题2';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
|
||||
content: '标题3';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
|
||||
content: '标题4';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
|
||||
content: '标题5';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
|
||||
content: '标题6';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: '标准字体';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
|
||||
content: '衬线字体';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
|
||||
content: '等宽字体';
|
||||
}
|
||||
</style>
|
||||
267
src/components/wx-material-select/main.vue
Normal file
267
src/components/wx-material-select/main.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div v-if="objData.repType == 'image'">
|
||||
<div class="waterfall" v-loading="tableLoading">
|
||||
<div class="waterfall-item" v-for="item in tableData" :key="item.mediaId">
|
||||
<img class="material-img" :src="item.url" />
|
||||
<p class="item-name">{{ item.name }}</p>
|
||||
<el-row class="ope-row">
|
||||
<el-button size="small" type="success" @click="selectMaterial(item)"
|
||||
>选择<el-icon class="el-icon--right"><CircleCheck /></el-icon
|
||||
></el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tableData.length <= 0 && !tableLoading" class="el-table__empty-block">
|
||||
<span class="el-table__empty-text">暂无数据</span>
|
||||
</div>
|
||||
<span class="dialog-footer">
|
||||
<el-pagination
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
v-model:current-page="page.currentPage"
|
||||
:page-sizes="[10, 20]"
|
||||
:page-size="page.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="page.total"
|
||||
class="pagination"
|
||||
>
|
||||
</el-pagination>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else-if="objData.repType == 'voice'">
|
||||
<avue-crud
|
||||
ref="crud"
|
||||
:page="page"
|
||||
:data="tableData"
|
||||
:table-loading="tableLoading"
|
||||
:option="tableOptionVoice"
|
||||
@on-load="getPageF"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
>
|
||||
<template #menu="scope">
|
||||
<el-button link icon="CircleCheck" type="success" @click="selectMaterial(scope.row)">选择</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</div>
|
||||
<div v-else-if="objData.repType == 'video'">
|
||||
<avue-crud
|
||||
ref="crud"
|
||||
:page="page"
|
||||
:data="tableData"
|
||||
:table-loading="tableLoading"
|
||||
:option="tableOptionVideo"
|
||||
@on-load="getPageF"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
>
|
||||
<template #menu="scope">
|
||||
<el-button link icon="CircleCheck" type="success" @click="selectMaterial(scope.row)">选择</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</div>
|
||||
<div v-else-if="objData.repType == 'news'">
|
||||
<div class="waterfall" v-loading="tableLoading">
|
||||
<template v-for="(item, index) in tableData" :key="index">
|
||||
<div v-if="item.content && item.content.articles" class="waterfall-item">
|
||||
<WxNews :objData="item.content.articles"></WxNews>
|
||||
|
||||
<el-row class="ope-row">
|
||||
<el-button size="small" type="success" @click="selectMaterial(item)"
|
||||
>选择<el-icon class="el-icon--right"><CircleCheck /></el-icon
|
||||
></el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="tableData.length <= 0 && !tableLoading" class="el-table__empty-block">
|
||||
<span class="el-table__empty-text">暂无数据</span>
|
||||
</div>
|
||||
<span class="dialog-footer">
|
||||
<el-pagination
|
||||
style="margin-top: 20px"
|
||||
@size-change="sizeChange"
|
||||
v-model:current-page="page.currentPage"
|
||||
:page-sizes="[10, 20]"
|
||||
:page-size="page.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="page.total"
|
||||
class="pagination"
|
||||
>
|
||||
</el-pagination>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="WxMaterialSelect">
|
||||
import { getPage, getMaterialVideo } from '@/api/wxmp/wxmaterial'
|
||||
import { tableOptionVoice } from '@/const/crud/wxmp/wxmaterial_voice'
|
||||
import { tableOptionVideo } from '@/const/crud/wxmp/wxmaterial_video'
|
||||
import WxNews from '@/components/wx-news/main.vue'
|
||||
import { getPage as getPageNews } from '@/api/wxmp/wxfreepublish'
|
||||
import { getPage as getPageNewsDraft } from '@/api/wxmp/wxdraft'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
objData: {
|
||||
type: Object
|
||||
},
|
||||
newsType: {
|
||||
type: String,
|
||||
default: '1'
|
||||
}
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
tableLoading: false,
|
||||
tableData: [],
|
||||
page: {
|
||||
total: 0, // 总页数
|
||||
currentPage: 1, // 当前页数
|
||||
pageSize: 20, // 每页显示多少条
|
||||
ascs: [], //升序字段
|
||||
descs: [] //降序字段
|
||||
}
|
||||
})
|
||||
const { tableData, tableLoading, page } = toRefs(data)
|
||||
|
||||
function onCreated() {
|
||||
getPageF(data.page)
|
||||
}
|
||||
onCreated()
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
function selectMaterial(item) {
|
||||
proxy.$emit('selectMaterial', item)
|
||||
}
|
||||
|
||||
function getPageF(page, params) {
|
||||
data.tableLoading = true
|
||||
if (props.objData.repType == 'news') {
|
||||
if (props.newsType == '1') {
|
||||
getPageNews(
|
||||
Object.assign(
|
||||
{
|
||||
current: page.currentPage,
|
||||
size: page.pageSize,
|
||||
appId: /* Warn: Unknown source: appId */ proxy.appId
|
||||
},
|
||||
params
|
||||
)
|
||||
).then((response) => {
|
||||
const tableData = response.data.items
|
||||
tableData.forEach((item) => {
|
||||
item.mediaId = item.articleId
|
||||
item.content.articles = item.content.newsItem
|
||||
})
|
||||
data.tableData = tableData
|
||||
data.page.total = response.data.totalCount
|
||||
data.page.currentPage = page.currentPage
|
||||
data.page.pageSize = page.pageSize
|
||||
data.tableLoading = false
|
||||
})
|
||||
} else if (props.newsType == '2') {
|
||||
getPageNewsDraft(
|
||||
Object.assign(
|
||||
{
|
||||
current: page.currentPage,
|
||||
size: page.pageSize,
|
||||
appId: /* Warn: Unknown source: appId */ proxy.appId
|
||||
},
|
||||
params
|
||||
)
|
||||
).then((response) => {
|
||||
const tableData = response.data.items
|
||||
tableData.forEach((item) => {
|
||||
item.content.articles = item.content.newsItem
|
||||
})
|
||||
data.tableData = tableData
|
||||
data.page.total = response.data.totalCount
|
||||
data.page.currentPage = page.currentPage
|
||||
data.page.pageSize = page.pageSize
|
||||
data.tableLoading = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
getPage(
|
||||
Object.assign(
|
||||
{
|
||||
current: page.currentPage,
|
||||
size: page.pageSize,
|
||||
appId: /* Warn: Unknown source: appId */ proxy.appId,
|
||||
type: props.objData.repType
|
||||
},
|
||||
params
|
||||
)
|
||||
).then((response) => {
|
||||
data.tableData = response.data.items
|
||||
data.page.total = response.data.totalCount
|
||||
data.page.currentPage = page.currentPage
|
||||
data.page.pageSize = page.pageSize
|
||||
data.tableLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function sizeChange(val) {
|
||||
data.page.currentPage = 1
|
||||
data.page.pageSize = val
|
||||
getPageF(data.page)
|
||||
}
|
||||
|
||||
function currentChange(val) {
|
||||
data.page.currentPage = val
|
||||
getPageF(data.page)
|
||||
}
|
||||
|
||||
function refreshChange(page) {
|
||||
getPageF(data.page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.waterfall {
|
||||
width: 100%;
|
||||
column-gap: 10px;
|
||||
column-count: 5;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.waterfall-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
break-inside: avoid;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
.material-img {
|
||||
width: 100%;
|
||||
}
|
||||
.ope-row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
p {
|
||||
line-height: 30px;
|
||||
}
|
||||
@media (min-width: 992px) and (max-width: 1300px) {
|
||||
.waterfall {
|
||||
column-count: 3;
|
||||
}
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.waterfall {
|
||||
column-count: 2;
|
||||
}
|
||||
p {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.waterfall {
|
||||
column-count: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
357
src/components/wx-msg/main.vue
Normal file
357
src/components/wx-msg/main.vue
Normal file
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<div class="msg-main" v-loading="mainLoading">
|
||||
<div class="msg-div" :id="'msg-div' + nowStr">
|
||||
<div v-loading="tableLoading"></div>
|
||||
<div v-if="!tableLoading">
|
||||
<div class="el-table__empty-block" v-if="loadMore" @click="loadingMore">
|
||||
<span class="el-table__empty-text">点击加载更多</span>
|
||||
</div>
|
||||
<div class="el-table__empty-block" v-if="!loadMore">
|
||||
<span class="el-table__empty-text">没有更多了</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="execution" v-for="item in tableData" :key="item.id">
|
||||
<div class="avue-comment" :class="item.type == '2' ? 'avue-comment--reverse' : ''">
|
||||
<div class="avatar-div">
|
||||
<el-avatar :src="item.type == '1' ? item.headimgUrl : item.appLogo" class="avue-comment__avatar"
|
||||
><el-icon><UserFilled /></el-icon
|
||||
></el-avatar>
|
||||
<div class="avue-comment__author" style="margin-left: 8px">
|
||||
{{ item.type == '1' ? item.nickName : item.appName }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="avue-comment__main" :style="item.type == '1' ? 'margin-right:105px;' : 'margin-left:105px;'">
|
||||
<div class="avue-comment__header">
|
||||
<div class="avue-comment__create_time">{{ item.createTime }}</div>
|
||||
</div>
|
||||
<div class="avue-comment__body" :style="item.type == '2' ? 'background: #6BED72;' : ''">
|
||||
<div v-if="item.repType == 'event' && item.repEvent == 'subscribe'">
|
||||
<el-tag type="success" size="small">关注</el-tag>
|
||||
</div>
|
||||
<div v-if="item.repType == 'event' && item.repEvent == 'unsubscribe'">
|
||||
<el-tag type="danger" size="small">取消关注</el-tag>
|
||||
</div>
|
||||
<div v-if="item.repType == 'event' && item.repEvent == 'CLICK'"><el-tag size="small">点击菜单</el-tag>:【{{ item.repName }}】</div>
|
||||
<div v-if="item.repType == 'event' && item.repEvent == 'VIEW'"><el-tag size="small">点击菜单链接</el-tag>:【{{ item.repUrl }}】</div>
|
||||
<div v-if="item.repType == 'event' && item.repEvent == 'scancode_waitmsg'">
|
||||
<el-tag size="small">扫码结果:</el-tag>:【{{ item.repContent }}】
|
||||
</div>
|
||||
<div v-if="item.repType == 'text'">{{ item.repContent }}</div>
|
||||
<div v-if="item.repType == 'image'">
|
||||
<a target="_blank" :href="item.repUrl"><img :src="item.repUrl" style="width: 100px" /></a>
|
||||
</div>
|
||||
<div v-if="item.repType == 'voice'">
|
||||
<WxVoicePlayer :objData="item"></WxVoicePlayer>
|
||||
</div>
|
||||
<div v-if="item.repType == 'video'" style="text-align: center">
|
||||
<WxVideoPlayer :objData="item"></WxVideoPlayer>
|
||||
</div>
|
||||
<div v-if="item.repType == 'shortvideo'" style="text-align: center">
|
||||
<WxVideoPlayer :objData="item"></WxVideoPlayer>
|
||||
</div>
|
||||
<div v-if="item.repType == 'location'">
|
||||
<el-link
|
||||
type="primary"
|
||||
target="_blank"
|
||||
:href="
|
||||
'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' +
|
||||
item.repLocationY +
|
||||
'&pointy=' +
|
||||
item.repLocationX +
|
||||
'&name=' +
|
||||
item.repContent +
|
||||
'&ref=joolun'
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="
|
||||
'https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|' +
|
||||
item.repLocationX +
|
||||
',' +
|
||||
item.repLocationY +
|
||||
'&key=' +
|
||||
qqMapKey +
|
||||
'&size=250*180'
|
||||
"
|
||||
/>
|
||||
<p />
|
||||
<i class="el-icon-map-location"></i>{{ item.repContent }}
|
||||
</el-link>
|
||||
</div>
|
||||
<div v-if="item.repType == 'link'" class="avue-card__detail">
|
||||
<el-link type="success" :underline="false" target="_blank" :href="item.repUrl">
|
||||
<div class="avue-card__title"><i class="el-icon-link"></i>{{ item.repName }}</div>
|
||||
</el-link>
|
||||
<div class="avue-card__info" style="height: unset">
|
||||
{{ item.repDesc }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.repType == 'news'" style="width: 300px">
|
||||
<WxNews :objData="item.content.articles"></WxNews>
|
||||
</div>
|
||||
<div v-if="item.repType == 'music'">
|
||||
<el-link type="success" :underline="false" target="_blank" :href="item.repUrl">
|
||||
<div class="avue-card__body" style="padding: 10px; background-color: #fff; border-radius: 5px">
|
||||
<div class="avue-card__avatar">
|
||||
<img :src="item.repThumbUrl" alt="" />
|
||||
</div>
|
||||
<div class="avue-card__detail">
|
||||
<div class="avue-card__title" style="margin-bottom: unset">
|
||||
{{ item.repName }}
|
||||
</div>
|
||||
<div class="avue-card__info" style="height: unset">
|
||||
{{ item.repDesc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-send" v-loading="sendLoading">
|
||||
<WxReplySelect :objData="objData"></WxReplySelect>
|
||||
<el-button type="success" class="send-but" @click="sendMsg">发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="WxMsg">
|
||||
import { getPage, addObj, putObj } from '@/api/wxmp/wxmsg'
|
||||
import SockJS from 'sockjs-client/dist/sockjs.min.js'
|
||||
import Stomp from 'stompjs'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import WxReplySelect from '@/components/wx-reply/main.vue'
|
||||
import WxNews from '@/components/wx-news/main.vue'
|
||||
import WxVideoPlayer from '@/components/wx-video-play/main.vue'
|
||||
import WxVoicePlayer from '@/components/wx-voice-play/main.vue'
|
||||
import { getCurrentInstance, nextTick, onMounted, onUnmounted, reactive } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
wxUserId: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
nowStr: new Date().getTime(),
|
||||
objData: {
|
||||
repType: 'text'
|
||||
},
|
||||
mainLoading: false,
|
||||
sendLoading: false,
|
||||
tableLoading: false,
|
||||
loadMore: true,
|
||||
tableData: [],
|
||||
page: {
|
||||
total: 0, // 总页数
|
||||
currentPage: 1, // 当前页数
|
||||
pageSize: 14, // 每页显示多少条
|
||||
ascs: [], //升序字段
|
||||
descs: 'create_time' //降序字段
|
||||
},
|
||||
option: {
|
||||
props: {
|
||||
avatar: 'avatar',
|
||||
author: 'author',
|
||||
body: 'body'
|
||||
}
|
||||
},
|
||||
timer: undefined
|
||||
})
|
||||
const { tableData, page, mainLoading, sendLoading, tableLoading, dialogMsgVisible, objData, nowStr, loadMore, option } = toRefs(data)
|
||||
|
||||
// 链接socket
|
||||
const stompClient = ref(null)
|
||||
onMounted(() => {
|
||||
refreshChange()
|
||||
// 开源版接口没有实现websocket,固隐藏
|
||||
// initWebSocket()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
disconnect()
|
||||
if (data.timer) clearInterval(data.timer)
|
||||
})
|
||||
|
||||
function initWebSocket() {
|
||||
connection()
|
||||
//断开重连机制,尝试发送消息,捕获异常发生时重连
|
||||
data.timer = setInterval(() => {
|
||||
try {
|
||||
stompClient.value.send('test')
|
||||
} catch (err) {
|
||||
console.log('断线了: ' + err)
|
||||
connection()
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
function connection() {
|
||||
const token = getToken().access_token
|
||||
const headers = {
|
||||
Authorization: 'Bearer ' + token
|
||||
}
|
||||
const socket = new SockJS('/weixin/ws')
|
||||
stompClient.value = Stomp.over(socket)
|
||||
stompClient.value.connect(
|
||||
headers,
|
||||
() => {
|
||||
stompClient.value.subscribe('/weixin/wx_msg' + props.wxUserId, (msg) => {
|
||||
const msgBody = JSON.parse(msg.body)
|
||||
data.tableData = [...data.tableData, ...[msgBody]]
|
||||
scrollToBottom()
|
||||
|
||||
if (msgBody.type == '1') {
|
||||
putObj({
|
||||
id: msgBody.id,
|
||||
readFlag: '0'
|
||||
}).then(() => {})
|
||||
}
|
||||
})
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (stompClient.value != null) {
|
||||
stompClient.value.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
function sendMsg() {
|
||||
if (data.objData) {
|
||||
if (data.objData.repType == 'news') {
|
||||
data.objData.content.articles = [data.objData.content.articles[0]]
|
||||
proxy.$message({
|
||||
showClose: true,
|
||||
message: '图文消息条数限制在1条以内,已默认发送第一条',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
data.sendLoading = true
|
||||
addObj(
|
||||
Object.assign(
|
||||
{
|
||||
wxUserId: props.wxUserId
|
||||
},
|
||||
data.objData
|
||||
)
|
||||
)
|
||||
.then((response) => {
|
||||
data.sendLoading = false
|
||||
data.tableData = [...data.tableData, ...[response.data]]
|
||||
scrollToBottom()
|
||||
data.objData = {
|
||||
repType: 'text'
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
data.sendLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
const div = document.getElementById('msg-div' + data.nowStr)
|
||||
div.scrollTop = div.scrollHeight
|
||||
})
|
||||
}
|
||||
|
||||
function loadingMore() {
|
||||
data.page.currentPage++
|
||||
getPageF(data.page)
|
||||
}
|
||||
|
||||
function getPageF(page, params) {
|
||||
data.tableLoading = true
|
||||
getPage(
|
||||
Object.assign(
|
||||
{
|
||||
current: page.currentPage,
|
||||
size: page.pageSize,
|
||||
descs: page.descs,
|
||||
ascs: page.ascs,
|
||||
wxUserId: props.wxUserId
|
||||
},
|
||||
params
|
||||
)
|
||||
).then((response) => {
|
||||
const msgDiv = document.getElementById('msg-div' + data.nowStr)
|
||||
let scrollHeight = 0
|
||||
if (msgDiv) {
|
||||
scrollHeight = msgDiv.scrollHeight
|
||||
}
|
||||
const dataResult = response.data.records.reverse()
|
||||
data.tableData = [...dataResult, ...data.tableData]
|
||||
data.page.total = response.data.total
|
||||
data.tableLoading = false
|
||||
if (data.length < data.page.pageSize || data.length == 0) {
|
||||
data.loadMore = false
|
||||
}
|
||||
if (data.page.currentPage == 1) {
|
||||
//定位到消息底部
|
||||
scrollToBottom()
|
||||
} else {
|
||||
if (data.length != 0) {
|
||||
nextTick(() => {
|
||||
//定位滚动条
|
||||
if (scrollHeight != 0) {
|
||||
msgDiv.scrollTop = document.getElementById('msg-div' + data.nowStr).scrollHeight - scrollHeight - 100
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
data.page.currentPage = page.currentPage
|
||||
data.page.pageSize = page.pageSize
|
||||
})
|
||||
}
|
||||
|
||||
function refreshChange() {
|
||||
getPageF(data.page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.msg-main {
|
||||
margin-top: -5px;
|
||||
}
|
||||
.msg-div {
|
||||
height: 58vh;
|
||||
overflow: auto;
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
.msg-send {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.avue-comment__main {
|
||||
flex: unset !important;
|
||||
border-radius: 5px !important;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.avue-comment__header {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
.avue-comment__body {
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
.avatar-div {
|
||||
text-align: center;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.send-but {
|
||||
float: right;
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
</style>
|
||||
89
src/components/wx-news/main.vue
Normal file
89
src/components/wx-news/main.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="news-home">
|
||||
<div v-for="(news, index) in objData" :key="index" class="news-div">
|
||||
<a target="_blank" :href="news.url" v-if="index == 0">
|
||||
<div class="news-main">
|
||||
<div class="news-content">
|
||||
<img class="material-img" :src="news.thumbUrl" width="280px" height="120px" />
|
||||
<div class="news-content-title">
|
||||
<span>{{ news.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a target="_blank" :href="news.url" v-if="index > 0">
|
||||
<div class="news-main-item">
|
||||
<div class="news-content-item">
|
||||
<div class="news-content-item-title">{{ news.title }}</div>
|
||||
<div class="news-content-item-img">
|
||||
<img class="material-img" :src="news.thumbUrl" height="100%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="WxNews">
|
||||
const props = defineProps({
|
||||
objData: {
|
||||
type: Array
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.news-home {
|
||||
background-color: #ffffff;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
.news-main {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
.news-content {
|
||||
background-color: #acadae;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.news-content-title {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
background-color: black;
|
||||
width: 98%;
|
||||
padding: 1%;
|
||||
opacity: 0.65;
|
||||
white-space: normal;
|
||||
box-sizing: unset !important;
|
||||
}
|
||||
.news-main-item {
|
||||
background-color: #ffffff;
|
||||
padding: 5px 0px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
}
|
||||
.news-content-item {
|
||||
position: relative;
|
||||
}
|
||||
.news-content-item-title {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
width: 70%;
|
||||
margin-left: 1%;
|
||||
white-space: normal;
|
||||
}
|
||||
.news-content-item-img {
|
||||
display: inline-block;
|
||||
width: 25%;
|
||||
background-color: #acadae;
|
||||
margin-right: 1%;
|
||||
}
|
||||
.material-img {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
611
src/components/wx-reply/main.vue
Normal file
611
src/components/wx-reply/main.vue
Normal file
@@ -0,0 +1,611 @@
|
||||
<!--
|
||||
- Copyright (C) 2024
|
||||
- All rights reserved, Designed By www.joolun.com
|
||||
-->
|
||||
<template>
|
||||
<el-tabs type="border-card" v-model="objDataCopy.repType" @tab-click="handleClick">
|
||||
<el-tab-pane name="text">
|
||||
<template #label>
|
||||
<div class="flex-c">
|
||||
<el-icon><el-icon-document /></el-icon>文本
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="objDataCopy.repContent" type="textarea" :rows="5" placeholder="请输入内容" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="image">
|
||||
<template #label>
|
||||
<span class="flex-c"
|
||||
><el-icon><el-icon-picture /></el-icon>图片</span
|
||||
>
|
||||
</template>
|
||||
<el-row>
|
||||
<div v-if="objDataCopy.repUrl" class="select-item">
|
||||
<img class="material-img" :src="objDataCopy.repUrl" />
|
||||
<p v-if="objDataCopy.repName" class="item-name">
|
||||
{{ objDataCopy.repName }}
|
||||
</p>
|
||||
<el-row class="ope-row">
|
||||
<el-button type="danger" icon="delete" circle @click="deleteObj" />
|
||||
</el-row>
|
||||
</div>
|
||||
<div v-if="!objDataCopy.repUrl" class="w-full">
|
||||
<el-row style="text-align: center">
|
||||
<el-col :span="12" class="col-select">
|
||||
<el-button type="success" @click="openMaterial"
|
||||
>素材库选择<el-icon class="el-icon--right">
|
||||
<el-icon-circle-check />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12" class="col-add">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeImageUpload"
|
||||
:data="uploadData"
|
||||
>
|
||||
<el-button type="primary">上传图片</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持bmp/png/jpeg/jpg/gif格式,大小不超过2M</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-dialog v-model="dialogImageVisible" title="选择图片" width="90%" append-to-body>
|
||||
<WxMaterialSelect :objData="objDataCopy" @selectMaterial="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="voice">
|
||||
<template #label>
|
||||
<span class="flex-c"
|
||||
><el-icon><el-icon-phone /></el-icon>语音</span
|
||||
>
|
||||
</template>
|
||||
<el-row v-if="objDataCopy.repType == 'voice'">
|
||||
<div v-if="objDataCopy.repName" class="select-item2">
|
||||
<p class="item-name">{{ objDataCopy.repName }}</p>
|
||||
<div class="item-infos">
|
||||
<WxVoicePlayer
|
||||
:objData="
|
||||
Object.assign(tempPlayerObj, {
|
||||
repMediaId: objDataCopy.media_id,
|
||||
repName: objDataCopy.repName
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<el-row class="ope-row">
|
||||
<el-button type="danger" icon="delete" circle @click="deleteObj" />
|
||||
</el-row>
|
||||
</div>
|
||||
<div v-if="!objDataCopy.repName" class="w-full">
|
||||
<el-row style="text-align: center">
|
||||
<el-col :span="12" class="col-select">
|
||||
<el-button type="success" @click="openMaterial"
|
||||
>素材库选择<el-icon class="el-icon--right">
|
||||
<el-icon-circle-check />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12" class="col-add">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeVoiceUpload"
|
||||
:data="uploadData"
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">格式支持mp3/wma/wav/amr,文件大小不超过2M,播放长度不超过60s</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-dialog v-model="dialogVoiceVisible" title="选择语音" width="90%" append-to-body>
|
||||
<WxMaterialSelect :objData="objDataCopy" @selectMaterial="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="video">
|
||||
<template #label>
|
||||
<span class="flex-c"
|
||||
><el-icon><el-icon-share /></el-icon>视频</span
|
||||
>
|
||||
</template>
|
||||
<el-row class="w-full">
|
||||
<div class="w-full">
|
||||
<el-input v-model="objDataCopy.repName" placeholder="请输入标题">
|
||||
<template #prepend>标题</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div style="margin: 20px 0" />
|
||||
<el-input style="margin-top: 10px" v-model="objDataCopy.repDesc" placeholder="请输入描述">
|
||||
<template #prepend>描述</template>
|
||||
</el-input>
|
||||
<div v-if="objDataCopy.repUrl" style="color: #888; font-size: 12px; background: #ececec; padding: 5px; word-wrap: break-word; word-break: break-all">
|
||||
<a target="_blank" class="flex-c" :href="objDataCopy.repUrl"
|
||||
><el-icon size="18"><VideoPlay /></el-icon>
|
||||
<span style="margin-left: 5px"> {{ objDataCopy.repUrl }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-c w-full"></div>
|
||||
<div style="text-align: center; margin-top: 20px" class="flex-c w-full">
|
||||
<el-button type="success" @click="openMaterial"
|
||||
>素材库选择<el-icon class="el-icon--right">
|
||||
<el-icon-circle-check />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<el-dialog v-model="dialogVideoVisible" title="选择视频" width="90%" append-to-body>
|
||||
<WxMaterialSelect :objData="objDataCopy" @selectMaterial="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="news">
|
||||
<template #label>
|
||||
<span class="flex-c"
|
||||
><el-icon> <Message /> </el-icon>图文</span
|
||||
>
|
||||
</template>
|
||||
<el-row>
|
||||
<div v-if="objDataCopy.content" class="select-item">
|
||||
<WxNews :objData="objDataCopy.content.articles" />
|
||||
<el-row class="ope-row">
|
||||
<el-button type="danger" icon="delete" circle @click="deleteObj" />
|
||||
</el-row>
|
||||
</div>
|
||||
<div v-if="!objDataCopy.content" class="w-full">
|
||||
<el-row style="text-align: center">
|
||||
<el-col :span="24" class="col-select2">
|
||||
<el-button type="success" icon="edit" @click="openMaterial"
|
||||
>{{ newsType == '1' ? '选择已发布图文' : '选择草稿箱图文' }}<i class="el-icon-circle-check el-icon--right"></i
|
||||
></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-dialog v-model="dialogNewsVisible" title="选择图文" width="90%" append-to-body>
|
||||
<WxMaterialSelect :objData="objDataCopy" :newsType="newsType" @selectMaterial="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="music">
|
||||
<template #label>
|
||||
<span class="flex-c"
|
||||
><el-icon><el-icon-service /></el-icon>音乐</span
|
||||
>
|
||||
</template>
|
||||
<div class="w-full">
|
||||
<el-row class="w-full">
|
||||
<el-col :span="6">
|
||||
<div class="thumb-div">
|
||||
<img v-if="objDataCopy.repThumbUrl" style="width: 80px" :src="objDataCopy.repThumbUrl" />
|
||||
<el-icon v-else class="avatar-uploader-icon">
|
||||
<el-icon-plus />
|
||||
</el-icon>
|
||||
<div class="flex-c">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeThumbImageUpload"
|
||||
:data="uploadData"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button size="small" link>本地上传</el-button>
|
||||
</template>
|
||||
<el-button size="small" link type="primary" @click.stop="openMaterial">素材库选择</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="dialogThumbVisible" title="选择图片" width="80%" append-to-body>
|
||||
<WxMaterialSelect :objData="{ repType: 'image' }" @selectMaterial="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<el-input v-model="objDataCopy.repName" placeholder="请输入标题" />
|
||||
<div style="margin: 10px 0" />
|
||||
<el-input v-model="objDataCopy.repDesc" placeholder="请输入描述" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div style="margin: 20px 0" />
|
||||
<div class="w-full">
|
||||
<el-input v-model="objDataCopy.repUrl" placeholder="请输入音乐链接" />
|
||||
</div>
|
||||
<div style="margin: 10px 0" />
|
||||
<div>
|
||||
<el-input v-model="objDataCopy.repHqUrl" placeholder="请输入高质量音乐链接" />
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
<script setup name="WxReply">
|
||||
import { getPage, getMaterialVideo } from '@/api/wxmp/wxmaterial'
|
||||
import WxNews from '@/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/components/wx-material-select/main.vue'
|
||||
import WxVoicePlayer from '@/components/wx-voice-play/main.vue'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
objData: {
|
||||
type: Object
|
||||
},
|
||||
newsType: {
|
||||
type: String,
|
||||
default: '1'
|
||||
}
|
||||
})
|
||||
const objDataCopy = ref(props.objData)
|
||||
if (!objDataCopy.value.repType) {
|
||||
objDataCopy.value.repType = 'text'
|
||||
}
|
||||
const data = reactive({
|
||||
tempPlayerObj: {
|
||||
type: '2'
|
||||
},
|
||||
tableData: [],
|
||||
page: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
ascs: '',
|
||||
descs: ''
|
||||
},
|
||||
tableLoading: false,
|
||||
dialogNewsVisible: false,
|
||||
dialogImageVisible: false,
|
||||
dialogVoiceVisible: false,
|
||||
dialogVideoVisible: false,
|
||||
dialogThumbVisible: false,
|
||||
tempObj: new Map().set(objDataCopy.value.repType, Object.assign({}, objDataCopy.value)),
|
||||
fileList: [],
|
||||
uploadData: {
|
||||
mediaType: objDataCopy.value.repType,
|
||||
title: '',
|
||||
introduction: ''
|
||||
},
|
||||
actionUrl: '/weixin/wxmaterial/materialFileUpload',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getToken().access_token
|
||||
}
|
||||
})
|
||||
|
||||
const {
|
||||
tableData,
|
||||
tableLoading,
|
||||
page,
|
||||
tempPlayerObj,
|
||||
dialogNewsVisible,
|
||||
dialogImageVisible,
|
||||
dialogVoiceVisible,
|
||||
dialogVideoVisible,
|
||||
dialogThumbVisible,
|
||||
fileList,
|
||||
actionUrl,
|
||||
headers,
|
||||
uploadData
|
||||
} = toRefs(data)
|
||||
|
||||
function beforeThumbImageUpload(file) {
|
||||
const isType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/bmp' || file.type === 'image/jpg'
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
|
||||
if (!isType) {
|
||||
proxy.$message.error('上传图片格式不对!')
|
||||
}
|
||||
|
||||
if (!isLt) {
|
||||
proxy.$message.error('上传图片大小不能超过2M!')
|
||||
}
|
||||
|
||||
return isType && isLt
|
||||
}
|
||||
|
||||
function deleteObj() {
|
||||
objDataCopy.value.repUrl = ''
|
||||
objDataCopy.value.repName = ''
|
||||
objDataCopy.value.content = ''
|
||||
data.tempObj.set(objDataCopy.value.repType, objDataCopy.value)
|
||||
proxy.$emit('update:objData', objDataCopy.value)
|
||||
}
|
||||
|
||||
function beforeVoiceUpload(file) {
|
||||
data.tableLoading = true
|
||||
const isType = file.type === 'audio/mp3' || file.type === 'audio/wma' || file.type === 'audio/wav' || file.type === 'audio/amr'
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
|
||||
if (!isType) {
|
||||
proxy.$message.error('上传图片格式不对!')
|
||||
}
|
||||
|
||||
if (!isLt) {
|
||||
proxy.$message.error('上传图片大小不能超过2M!')
|
||||
}
|
||||
|
||||
return isType && isLt
|
||||
}
|
||||
|
||||
function beforeImageUpload(file) {
|
||||
const isType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/bmp' || file.type === 'image/jpg'
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
|
||||
if (!isType) {
|
||||
proxy.$message.error('上传图片格式不对!')
|
||||
}
|
||||
|
||||
if (!isLt) {
|
||||
proxy.$message.error('上传图片大小不能超过2M!')
|
||||
}
|
||||
|
||||
return isType && isLt
|
||||
}
|
||||
|
||||
function handleUploadSuccess(response) {
|
||||
if (response.code == '0') {
|
||||
data.fileList = []
|
||||
data.uploadData.title = ''
|
||||
data.uploadData.introduction = ''
|
||||
const item = response.data
|
||||
selectMaterial(item)
|
||||
} else {
|
||||
proxy.$message.error('上传出错:' + response.msg)
|
||||
}
|
||||
}
|
||||
function handleUploadError(response) {
|
||||
proxy.$message.error('上传出错:' + response)
|
||||
}
|
||||
|
||||
function handleClick(tab) {
|
||||
objDataCopy.value.repType = tab.paneName
|
||||
data.uploadData.mediaType = objDataCopy.value.repType
|
||||
const tempObjItem = data.tempObj.get(objDataCopy.value.repType)
|
||||
|
||||
if (tempObjItem) {
|
||||
objDataCopy.value.repName = tempObjItem.repName ? tempObjItem.repName : null
|
||||
objDataCopy.value.repMediaId = tempObjItem.repMediaId ? tempObjItem.repMediaId : null
|
||||
objDataCopy.value.media_id = tempObjItem.media_id ? tempObjItem.media_id : null
|
||||
objDataCopy.value.repUrl = tempObjItem.repUrl ? tempObjItem.repUrl : null
|
||||
objDataCopy.value.content = tempObjItem.content ? tempObjItem.content : null
|
||||
objDataCopy.value.repDesc = tempObjItem.repDesc ? tempObjItem.repDesc : null
|
||||
proxy.$emit('update:objData', objDataCopy.value)
|
||||
} else {
|
||||
objDataCopy.value.repUrl = ''
|
||||
objDataCopy.value.repName = ''
|
||||
objDataCopy.value.repMediaId = ''
|
||||
objDataCopy.value.media_id = ''
|
||||
objDataCopy.value.content = ''
|
||||
objDataCopy.value.repDesc = ''
|
||||
proxy.$emit('update:objData', objDataCopy.value)
|
||||
}
|
||||
}
|
||||
|
||||
function selectMaterial(item) {
|
||||
const tempObjItem = {}
|
||||
tempObjItem.repType = objDataCopy.value.repType
|
||||
tempObjItem.repMediaId = item.mediaId
|
||||
tempObjItem.media_id = item.mediaId
|
||||
tempObjItem.content = item.content
|
||||
data.dialogNewsVisible = false
|
||||
data.dialogImageVisible = false
|
||||
data.dialogVoiceVisible = false
|
||||
data.dialogVideoVisible = false
|
||||
objDataCopy.value.repMediaId = item.mediaId
|
||||
objDataCopy.value.media_id = item.mediaId
|
||||
objDataCopy.value.content = item.content
|
||||
|
||||
if (objDataCopy.value.repType == 'music') {
|
||||
tempObjItem.repThumbMediaId = item.mediaId
|
||||
tempObjItem.repThumbUrl = item.url
|
||||
objDataCopy.value.repThumbMediaId = item.mediaId
|
||||
objDataCopy.value.repThumbUrl = item.url
|
||||
data.dialogThumbVisible = false
|
||||
} else {
|
||||
tempObjItem.repName = item.name
|
||||
tempObjItem.repUrl = item.url
|
||||
objDataCopy.value.repName = item.name
|
||||
objDataCopy.value.repUrl = item.url
|
||||
}
|
||||
|
||||
if (objDataCopy.value.repType === 'video') {
|
||||
getMaterialVideo({
|
||||
mediaId: item.mediaId
|
||||
}).then((response) => {
|
||||
if (response.code === 200) {
|
||||
objDataCopy.value.repName = response.data.title
|
||||
objDataCopy.value.repDesc = response.data.description
|
||||
objDataCopy.value.repUrl = response.data.downUrl
|
||||
tempObjItem.repDesc = response.data.description
|
||||
tempObjItem.repUrl = response.data.downUrl
|
||||
data.tempObj.set(objDataCopy.value.repType, tempObjItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (props.oneNews && objDataCopy.value.repType == 'news' && item.content.articles.length > 1) {
|
||||
// eslint-disable-next-line no-tabs
|
||||
proxy.$alert('您选择的是多图文,微信限制只能回复1条图文消息,将默认选择第一篇 ', '提示', {
|
||||
confirmButtonText: '确认'
|
||||
})
|
||||
item.content.articles = item.content.articles.slice(0, 1)
|
||||
}
|
||||
|
||||
data.tempObj.set(objDataCopy.value.repType, tempObjItem)
|
||||
}
|
||||
|
||||
function openMaterial() {
|
||||
if (objDataCopy.value.repType == 'news') {
|
||||
data.dialogNewsVisible = true
|
||||
} else if (objDataCopy.value.repType == 'image') {
|
||||
data.dialogImageVisible = true
|
||||
} else if (objDataCopy.value.repType == 'voice') {
|
||||
data.dialogVoiceVisible = true
|
||||
} else if (objDataCopy.value.repType == 'video') {
|
||||
data.dialogVideoVisible = true
|
||||
} else if (objDataCopy.value.repType == 'music') {
|
||||
data.dialogThumbVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
function getPageF(page, params) {
|
||||
data.tableLoading = true
|
||||
getPage(
|
||||
Object.assign(
|
||||
{
|
||||
current: page.currentPage,
|
||||
size: page.pageSize,
|
||||
type: objDataCopy.value.repType
|
||||
},
|
||||
params
|
||||
)
|
||||
)
|
||||
.then((response) => {
|
||||
data.tableData = response.data.items
|
||||
data.page.total = response.data.totalCount
|
||||
data.page.currentPage = page.currentPage
|
||||
data.page.pageSize = page.pageSize
|
||||
data.tableLoading = false
|
||||
})
|
||||
.catch(() => {
|
||||
data.tableLoading = false
|
||||
})
|
||||
}
|
||||
|
||||
function sizeChange(val) {
|
||||
data.page.currentPage = 1
|
||||
data.page.pageSize = val
|
||||
getPageF(data.page)
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
try {
|
||||
const errorMsg = err.message ? JSON.parse(err.message).msg : err.message
|
||||
proxy.$message.error(errorMsg)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handleSuccess(response) {
|
||||
objDataCopy.value.repUrl = response.link
|
||||
}
|
||||
|
||||
function handleSuccess2(response) {
|
||||
objDataCopy.value.repHqUrl = response.link
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
// .public-account-management {
|
||||
// .el-input {
|
||||
// width: 70%;
|
||||
// margin-right: 2%;
|
||||
// }
|
||||
// }
|
||||
|
||||
.pagination {
|
||||
margin-right: 25px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.select-item {
|
||||
width: 280px;
|
||||
padding: 10px;
|
||||
margin: 0 auto 10px;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.select-item2 {
|
||||
padding: 10px;
|
||||
margin: 0 auto 10px;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.ope-row {
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
line-height: unset !important;
|
||||
}
|
||||
|
||||
.col-select {
|
||||
width: 49.5%;
|
||||
height: 160px;
|
||||
padding: 50px 0;
|
||||
border: 1px solid rgb(234 234 234);
|
||||
}
|
||||
|
||||
.col-select2 {
|
||||
height: 160px;
|
||||
padding: 50px 0;
|
||||
border: 1px solid rgb(234 234 234);
|
||||
}
|
||||
|
||||
.col-add {
|
||||
float: right;
|
||||
width: 49.5%;
|
||||
height: 160px;
|
||||
padding: 50px 0;
|
||||
border: 1px solid rgb(234 234 234);
|
||||
}
|
||||
|
||||
.avatar-uploader-icon {
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
font-size: 28px;
|
||||
line-height: 100px !important;
|
||||
color: #8c939d;
|
||||
text-align: center;
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.material-img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.thumb-div {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-infos {
|
||||
width: 30%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.flex-c {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
121
src/components/wx-video-play/main.vue
Normal file
121
src/components/wx-video-play/main.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!outTime">
|
||||
<el-button icon="VideoPlay" link @click="playVideo()">点击播放视频</el-button>
|
||||
</div>
|
||||
<div v-if="outTime">
|
||||
<i class="el-icon-video-play shipin-i"></i>
|
||||
<p />
|
||||
<i class="el-icon-time"> 该视频已过期</i>
|
||||
</div>
|
||||
<el-dialog title="视频播放" v-model="dialogVideo" width="40%" v-loading="mainLoading" append-to-body>
|
||||
<video-player
|
||||
v-if="playerOptions.sources[0].src"
|
||||
class="video-player vjs-custom-skin"
|
||||
ref="videoPlayer"
|
||||
:playsinline="true"
|
||||
:options="playerOptions"
|
||||
@play="onPlayerPlay($event)"
|
||||
@pause="onPlayerPause($event)"
|
||||
>
|
||||
</video-player>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup named="WxVideoPlay">
|
||||
import { VideoPlayer } from 'vue-video-player'
|
||||
import { getTempMaterialOther, getMaterialOther } from '@/api/wxmp/wxmaterial'
|
||||
import 'video.js/dist/video-js.css'
|
||||
const props = defineProps({
|
||||
objData: {
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
dialogVideo: false,
|
||||
outTime: false,
|
||||
mainLoading: false,
|
||||
playerOptions: {
|
||||
// playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度
|
||||
autoplay: false, //如果true,浏览器准备好时开始回放。
|
||||
muted: false, // 默认情况下将会消除任何音频。
|
||||
loop: false, // 导致视频一结束就重新开始。
|
||||
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
|
||||
language: 'zh-CN',
|
||||
aspectRatio: '4:3', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
|
||||
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
|
||||
sources: [
|
||||
{
|
||||
type: 'video/mp4',
|
||||
src: '' //你的视频地址(必填)
|
||||
}
|
||||
],
|
||||
poster: '', //你的封面地址
|
||||
width: document.documentElement.clientWidth,
|
||||
notSupportedMessage: '此视频暂无法播放,请稍后再试' //允许覆盖Video.js无法播放媒体源时显示的默认信息。
|
||||
// controlBar: {
|
||||
// timeDivider: true,
|
||||
// durationDisplay: true,
|
||||
// remainingTimeDisplay: false,
|
||||
// fullscreenToggle: true //全屏按钮
|
||||
// }
|
||||
}
|
||||
})
|
||||
const { dialogVideo, outTime, mainLoading, playerOptions } = toRefs(data)
|
||||
|
||||
function onCreated() {
|
||||
data.outTime = props.objData.type === '1' && parseInt(new Date().getTime() - new Date(props.objData.createTime).getTime()) >= 259200000
|
||||
}
|
||||
onCreated()
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
function playVideo() {
|
||||
data.dialogVideo = true
|
||||
getVideo()
|
||||
}
|
||||
|
||||
async function getTempMaterialOtherF(repMediaId, fileName) {
|
||||
let url
|
||||
data.mainLoading = true
|
||||
await getTempMaterialOther({
|
||||
mediaId: repMediaId,
|
||||
fileName
|
||||
}).then((response) => {
|
||||
data.mainLoading = false
|
||||
url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
})
|
||||
return url
|
||||
}
|
||||
|
||||
async function getMaterialOtherF(repMediaId, fileName) {
|
||||
let url
|
||||
data.mainLoading = true
|
||||
await getMaterialOther({
|
||||
mediaId: repMediaId,
|
||||
fileName
|
||||
}).then((response) => {
|
||||
data.mainLoading = false
|
||||
url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
})
|
||||
return url
|
||||
}
|
||||
|
||||
function getVideo() {
|
||||
if (props.objData.type == '2') {
|
||||
data.playerOptions.sources[0].src = props.objData.repUrl
|
||||
} else if (props.objData.type == '1') {
|
||||
getTempMaterialOtherF(props.objData.repMediaId, props.objData.repMediaId + '.mp4').then((val) => {
|
||||
data.playerOptions.sources[0].src = val
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onPlayerPlay(player) {}
|
||||
|
||||
function onPlayerPause(player) {}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
128
src/components/wx-voice-play/main.vue
Normal file
128
src/components/wx-voice-play/main.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div v-loading="mainLoading" class="wx-voice-div" @click="outTime ? '' : playVoice(objData)">
|
||||
<el-button v-if="!outTime" link :icon="objData.amrPlaying != true ? 'Microphone' : 'Headset'">
|
||||
<span class="amr-duration" v-if="objData.amrDuration">{{ objData.amrDuration }}"</span>语音
|
||||
</el-button>
|
||||
<i v-if="outTime" class="el-icon-time">
|
||||
<span class="amr-duration">该语音已过期</span>
|
||||
</i>
|
||||
<div v-if="objData.repContent"><el-tag type="success" size="small">语音识别</el-tag>{{ objData.repContent }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="WxVoicePlay">
|
||||
import { getTempMaterialOther, getMaterialOther } from '@/api/wxmp/wxmaterial'
|
||||
import BenzAMRRecorder from 'benz-amr-recorder'
|
||||
|
||||
const props = defineProps({
|
||||
objData: {
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
outTime: false,
|
||||
mainLoading: false
|
||||
})
|
||||
|
||||
const { outTime, mainLoading } = toRefs(data)
|
||||
|
||||
function onCreated() {
|
||||
data.outTime = props.objData.type === '1' && parseInt(new Date().getTime() - new Date(props.objData.createTime).getTime()) >= 259200000
|
||||
}
|
||||
onCreated()
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
function amrPlay(amr, val) {
|
||||
val.amrPlaying = true
|
||||
amr.play()
|
||||
}
|
||||
|
||||
function amrStop(amr, val) {
|
||||
val.amrPlaying = false
|
||||
amr.stop()
|
||||
}
|
||||
|
||||
function playVoice(val) {
|
||||
const amr = val.amr
|
||||
if (amr == undefined) {
|
||||
if (val.type == '2') {
|
||||
getMaterialOtherF(val.repMediaId, val.repName).then((url) => {
|
||||
val.repUrl = url
|
||||
handleAudio(val)
|
||||
})
|
||||
} else if (val.type == '1') {
|
||||
getTempMaterialOtherF(val.repMediaId, val.repName).then((url) => {
|
||||
val.repUrl = url
|
||||
handleAudio(val)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (amr.isPlaying()) {
|
||||
amrStop(amr, val)
|
||||
} else {
|
||||
amrPlay(amr, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getTempMaterialOtherF(repMediaId, fileName) {
|
||||
let url
|
||||
data.mainLoading = true
|
||||
await getTempMaterialOther({
|
||||
mediaId: repMediaId,
|
||||
fileName
|
||||
})
|
||||
.then((response) => {
|
||||
data.mainLoading = false
|
||||
url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
})
|
||||
.catch(() => {
|
||||
data.mainLoading = false
|
||||
})
|
||||
return url
|
||||
}
|
||||
|
||||
async function getMaterialOtherF(repMediaId, fileName) {
|
||||
let url
|
||||
data.mainLoading = true
|
||||
await getMaterialOther({
|
||||
mediaId: repMediaId,
|
||||
fileName
|
||||
})
|
||||
.then((response) => {
|
||||
data.mainLoading = false
|
||||
url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
})
|
||||
.catch(() => {
|
||||
data.mainLoading = false
|
||||
})
|
||||
return url
|
||||
}
|
||||
|
||||
function handleAudio(val) {
|
||||
val.amr = new BenzAMRRecorder()
|
||||
const amr = val.amr
|
||||
const that = this
|
||||
amr.initWithUrl(val.repUrl).then(function () {
|
||||
that.amrPlay(amr, val)
|
||||
that.$set(val, 'amrDuration', amr.getDuration())
|
||||
})
|
||||
amr.onEnded(function () {
|
||||
that.$set(val, 'amrPlaying', false) //播放完了
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wx-voice-div {
|
||||
padding: 5px;
|
||||
background-color: #eaeaea;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.amr-duration {
|
||||
font-size: 11px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user