Merge branch 'projects/mesxc-test' into projects/mesxc-zhp

This commit is contained in:
‘937886381’
2024-03-27 09:30:38 +08:00
209 changed files with 307 additions and 10774 deletions

View File

@@ -1,290 +0,0 @@
<template>
<div class="app-container">
<doc-alert title="公众号接入" url="https://doc.iocoder.cn/mp/account/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:account:create']">新增
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="名称" align="center" prop="name"/>
<el-table-column label="微信号" align="center" prop="account" width="180"/>
<el-table-column label="appId" align="center" prop="appId" width="180"/>
<!-- <el-table-column label="appSecret" align="center" prop="appSecret" width="180"/>-->
<!-- <el-table-column label="token" align="center" prop="token"/>-->
<!-- <el-table-column label="消息加解密密钥" align="center" prop="aesKey"/>-->
<el-table-column label="服务器地址(URL)" align="center" prop="appId" width="360">
<template v-slot="scope">
{{ 'http://服务端地址/mp/open/' + scope.row.appId }}
</template>
</el-table-column>
<el-table-column label="二维码" align="center" prop="qrCodeUrl">
<template v-slot="scope">
<img v-if="scope.row.qrCodeUrl" :src="scope.row.qrCodeUrl" alt="二维码" style="height: 100px;" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark"/>
<!-- <el-table-column label="创建时间" align="center" prop="createTime" width="180">-->
<!-- <template v-slot="scope">-->
<!-- <span>{{ parseTime(scope.row.createTime) }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:account:update']">修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['mp:account:delete']">删除
</el-button>
<el-button size="mini" type="text" icon="el-icon-refresh" @click="handleGenerateQrCode(scope.row)"
v-hasPermi="['mp:account:qr-code']">生成二维码
</el-button>
<el-button size="mini" type="text" icon="el-icon-share" @click="handleCleanQuota(scope.row)"
v-hasPermi="['mp:account:clear-quota']">清空 API 配额
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称"/>
</el-form-item>
<el-form-item label="微信号" prop="account">
<span slot="label">
<el-tooltip content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 账号详情] 中能找到「微信号」" placement="top">
<i class="el-icon-question" />
</el-tooltip>
微信号
</span>
<el-input v-model="form.account" placeholder="请输入微信号"/>
</el-form-item>
<el-form-item label="appId" prop="appId">
<span slot="label">
<el-tooltip content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者ID(AppID)」" placement="top">
<i class="el-icon-question" />
</el-tooltip>
appId
</span>
<el-input v-model="form.appId" placeholder="请输入公众号 appId"/>
</el-form-item>
<el-form-item label="appSecret" prop="appSecret">
<span slot="label">
<el-tooltip content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者密码(AppSecret)」" placement="top">
<i class="el-icon-question" />
</el-tooltip>
appSecret
</span>
<el-input v-model="form.appSecret" placeholder="请输入公众号 appSecret"/>
</el-form-item>
<el-form-item label="token" prop="token">
<el-input v-model="form.token" placeholder="请输入公众号token"/>
</el-form-item>
<el-form-item label="消息加解密密钥" prop="aesKey">
<el-input v-model="form.aesKey" placeholder="请输入消息加解密密钥"/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
clearAccountQuota,
createAccount,
deleteAccount,
generateAccountQrCode,
getAccount,
getAccountPage,
updateAccount
} from '@/api/mp/account'
export default {
name: 'MpAccount',
components: {},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 公众号账号列表
list: [],
// 弹出层标题
title: '',
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
account: null,
appId: null,
},
// 表单参数
form: {},
// 表单校验
rules: {
name: [{required: true, message: '名称不能为空', trigger: 'blur'}],
account: [{required: true, message: '公众号账号不能为空', trigger: 'blur'}],
appId: [{required: true, message: '公众号 appId 不能为空', trigger: 'blur'}],
appSecret: [{required: true, message: '公众号密钥不能为空', trigger: 'blur'}],
token: [{required: true, message: '公众号 token 不能为空', trigger: 'blur'}],
},
}
},
created() {
this.getList()
},
methods: {
/** 查询列表 */
getList() {
this.loading = true
// 处理查询参数
let params = {...this.queryParams}
// 执行查询
getAccountPage(params).then(response => {
this.list = response.data.list
this.total = response.data.total
this.loading = false
})
},
/** 取消按钮 */
cancel() {
this.open = false
this.reset()
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
account: undefined,
appId: undefined,
appSecret: undefined,
token: undefined,
aesKey: undefined,
remark: undefined,
}
this.resetForm('form')
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = []
this.resetForm('queryForm')
this.handleQuery()
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = '添加公众号账号'
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const id = row.id
getAccount(id).then(response => {
this.form = response.data
this.open = true
this.title = '修改公众号账号'
})
},
/** 提交按钮 */
submitForm() {
this.$refs['form'].validate(valid => {
if (!valid) {
return
}
// 修改的提交
if (this.form.id != null) {
updateAccount(this.form).then(response => {
this.$modal.msgSuccess('修改成功')
this.open = false
this.getList()
})
return
}
// 添加的提交
createAccount(this.form).then(response => {
this.$modal.msgSuccess('新增成功')
this.open = false
this.getList()
})
})
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id
this.$modal.confirm('是否确认删除公众号账号编号为"' + row.name + '"的数据项?').then(function () {
return deleteAccount(id)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {
})
},
/** 生成二维码的按钮操作 */
handleGenerateQrCode(row) {
const id = row.id
this.$modal.confirm('是否确认生成公众号账号编号为"' + row.name + '"的二维码?').then(function () {
return generateAccountQrCode(id)
}).then(() => {
this.getList()
this.$modal.msgSuccess('生成二维码成功')
}).catch(() => {
})
},
/** 清空二维码 API 配额的按钮操作 */
handleCleanQuota(row) {
const id = row.id
this.$modal.confirm('是否确认清空生成公众号账号编号为"' + row.name + '"的 API 配额?').then(function () {
return clearAccountQuota(id)
}).then(() => {
this.$modal.msgSuccess('清空 API 配额成功')
}).catch(() => {
})
},
}
}
</script>

View File

@@ -1,384 +0,0 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
移除 avue 框架使用 element-ui 重写
重写代码保持和现有项目保持一致
-->
<template>
<div class="app-container">
<doc-alert title="自动回复" url="https://doc.iocoder.cn/mp/auto-reply/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- tab 切换 -->
<el-tabs v-model="type" @tab-click="handleClick">
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:auto-reply:create']" v-if="type !== '1' || list.length <= 0">新增
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<!-- tab -->
<el-tab-pane name="1">
<span slot="label"><i class="el-icon-star-off"></i> 关注时回复</span>
</el-tab-pane>
<el-tab-pane name="2">
<span slot="label"><i class="el-icon-chat-line-round"></i> 消息回复</span>
</el-tab-pane>
<el-tab-pane name="3">
<span slot="label"><i class="el-icon-news"></i> 关键词回复</span>
</el-tab-pane>
</el-tabs>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="请求消息类型" align="center" prop="requestMessageType" v-if="type === '2'" />
<el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
<el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch"/>
</template>
</el-table-column>
<el-table-column label="回复消息类型" align="center">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.MP_MESSAGE_TYPE" :value="scope.row.responseMessageType"/>
</template>
</el-table-column>
<el-table-column label="回复内容" align="center">
<template v-slot="scope">
<div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
<div v-else-if="scope.row.responseMessageType === 'voice'">
<wx-voice-player :url="scope.row.responseMediaUrl" />
</div>
<div v-else-if="scope.row.responseMessageType === 'image'">
<a target="_blank" :href="scope.row.responseMediaUrl">
<img :src="scope.row.responseMediaUrl" style="width: 100px">
</a>
</div>
<div v-else-if="scope.row.responseMessageType === 'video' || scope.row.responseMessageType === 'shortvideo'">
<wx-video-player :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
</div>
<div v-else-if="scope.row.responseMessageType === 'news'">
<wx-news :articles="scope.row.responseArticles" />
</div>
<div v-else-if="scope.row.responseMessageType === 'music'">
<wx-music :title="scope.row.responseTitle" :description="scope.row.responseDescription"
:thumb-media-url="scope.row.responseThumbMediaUrl"
:music-url="scope.row.responseMusicUrl" :hq-music-url="scope.row.responseHqMusicUrl" />
</div>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:auto-reply:update']">修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['mp:auto-reply:delete']">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改自动回复的对话框 -->
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
<el-select v-model="form.requestMessageType" placeholder="请选择">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"
v-if="requestMessageTypes.includes(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
<el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
<el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
</el-form-item>
<el-form-item label="回复消息">
<wx-reply-select :objData="objData" v-if="hackResetWxReplySelect" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxMsg from '@/views/mp/components/wx-msg/main.vue';
import WxLocation from '@/views/mp/components/wx-location/main.vue';
import WxMusic from '@/views/mp/components/wx-music/main.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
import { getSimpleAccounts } from "@/api/mp/account";
import { createAutoReply, deleteAutoReply, getAutoReply, getAutoReplyPage, updateAutoReply } from "@/api/mp/autoReply";
export default {
name: 'MpAutoReply',
components: {
WxVideoPlayer,
WxVoicePlayer,
WxMsg,
WxLocation,
WxMusic,
WxNews,
WxReplySelect
},
data() {
return {
// tab 类型1、关注时回复2、消息回复3、关键词回复
type: '3',
// 允许选择的请求消息类型
requestMessageTypes: ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'],
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 自动回复列表
list: [],
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: undefined,
},
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 表单参数
form: {},
// 回复消息
objData: {
type : 'text'
},
// 表单校验
rules: {
requestKeyword: [{ required: true, message: "请求的关键字不能为空", trigger: "blur" }],
requestMatch: [{ required: true, message: "请求的关键字的匹配不能为空", trigger: "blur" }],
},
hackResetWxReplySelect: false, // 重置 WxReplySelect 组件,解决无法清除的问题
// 公众号账号列表
accounts: []
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询自动回复')
return false
}
this.loading = false
// 处理查询参数
let params = {
...this.queryParams,
type: this.type
}
// 执行查询
getAutoReplyPage(params).then(response => {
this.list = response.data.list
this.total = response.data.total
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery()
},
handleClick(tab, event) {
this.type = tab.name
this.handleQuery()
},
/** 新增按钮操作 */
handleAdd(){
this.reset();
this.resetEditor();
// 打开表单,并设置初始化
this.open = true
this.title = '新增自动回复';
this.objData = {
type : 'text',
accountId: this.queryParams.accountId,
}
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
this.resetEditor();
const id = row.id;
getAutoReply(id).then(response => {
// 设置属性
this.form = {...response.data}
this.$delete(this.form, 'responseMessageType');
this.$delete(this.form, 'responseContent');
this.$delete(this.form, 'responseMediaId');
this.$delete(this.form, 'responseMediaUrl');
this.$delete(this.form, 'responseDescription');
this.$delete(this.form, 'responseArticles');
this.objData = {
type: response.data.responseMessageType,
accountId: this.queryParams.accountId,
content: response.data.responseContent,
mediaId: response.data.responseMediaId,
url: response.data.responseMediaUrl,
title: response.data.responseTitle,
description: response.data.responseDescription,
thumbMediaId: response.data.responseThumbMediaId,
thumbMediaUrl: response.data.responseThumbMediaUrl,
articles: response.data.responseArticles,
musicUrl: response.data.responseMusicUrl,
hqMusicUrl: response.data.responseHqMusicUrl,
}
// 打开表单
this.open = true
this.title = '修改自动回复';
})
},
handleSubmit() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 处理回复消息
const form = {...this.form};
form.responseMessageType = this.objData.type;
form.responseContent = this.objData.content;
form.responseMediaId = this.objData.mediaId;
form.responseMediaUrl = this.objData.url;
form.responseTitle = this.objData.title;
form.responseDescription = this.objData.description;
form.responseThumbMediaId = this.objData.thumbMediaId;
form.responseThumbMediaUrl = this.objData.thumbMediaUrl;
form.responseArticles = this.objData.articles;
form.responseMusicUrl = this.objData.musicUrl;
form.responseHqMusicUrl = this.objData.hqMusicUrl;
if (this.form.id !== undefined) {
updateAutoReply(form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
createAutoReply(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
});
},
// 表单重置
reset() {
this.form = {
id: undefined,
accountId: this.queryParams.accountId,
type: this.type,
requestKeyword: undefined,
requestMatch: this.type === '3' ? 1 : undefined,
requestMessageType: undefined,
};
this.resetForm("form");
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单 Editor 重置
resetEditor() {
this.hackResetWxReplySelect = false // 销毁组件
this.$nextTick(() => {
this.hackResetWxReplySelect = true // 重建组件
})
},
handleDelete: function(row) {
const ids = row.id;
this.$modal.confirm('是否确认删除此数据?').then(function() {
return deleteAutoReply(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>

View File

@@ -1,230 +0,0 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
调整 uploadData 属性只需要传入 accountId 即可
-->
<template>
<div id="wxEditor">
<div v-loading="quillUpdateImg" element-loading-text="请稍等,图片上传中">
<!-- 图片上传组件辅助-->
<el-upload class="avatar-uploader" name="file" :action="actionUrl" :headers="headers"
:show-file-list="false" :data="uploadData"
:on-success="uploadSuccess" :on-error="uploadError" :before-upload="beforeUpload">
</el-upload>
<quill-editor class="editor" v-model="content" ref="myQuillEditor" :options="editorOption"
@change="onEditorChange($event)">
</quill-editor>
</div>
</div>
</template>
<script>
// 工具栏配置
const toolbarOptions = [
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用 代码块
[{ header: 1 }, { header: 2 }], // 1、2 级标题
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
[{ script: "sub" }, { script: "super" }], // 上标/下标
[{ indent: "-1" }, { indent: "+1" }], // 缩进
// [{'direction': 'rtl'}], // 文本方向
[{ size: ["small", false, "large", "huge"] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ font: [] }], // 字体种类
[{ align: [] }], // 对齐方式
["clean"], // 清除文本格式
["link", "image", "video"] // 链接、图片、视频
]
import { quillEditor } from "vue-quill-editor"
import "quill/dist/quill.core.css"
import "quill/dist/quill.snow.css"
import "quill/dist/quill.bubble.css"
import { getAccessToken } from "@/utils/auth";
export default {
props: {
/* 公众号账号编号 */
accountId: {
type: Number,
required: true
},
/* 编辑器的内容 */
value: {
type: String
},
/* 图片大小 */
maxSize: {
type: Number,
default: 4000 // kb
}
},
name: 'wxEditor',
components: {
quillEditor
},
data() {
return {
editorType: '1',
content: this.value.replace(/data-src/g, "src"),
quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画刚开始是false,不显示
editorOption: {
theme: "snow", // or 'bubble'
placeholder: "请输入文章内容",
modules: {
toolbar: {
container: toolbarOptions,
// container: "#toolbar",
handlers: {
image: function(value) {
if (value) {
// 触发input框选择图片文件
document.querySelector(".avatar-uploader input").click();
} else {
this.quill.format("image", false);
}
},
link: function(value) {
if (value) {
const href = prompt('注意!只支持公众号图文链接');
this.quill.format("link", href);
} else {
this.quill.format("link", false);
}
},
}
}
}
},
actionUrl: process.env.VUE_APP_BASE_API +'/admin-api/mp/material/upload-news-image', // 这里写你要上传的图片服务器地址
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
uploadData: {
"type": 'image', // TODO 芋艿:试试要不要换成 thumb
"accountId": this.accountId,
},
}
},
methods: {
onEditorChange(editor) {
//内容改变事件
this.$emit("input", this.content)
},
// 富文本图片上传前
beforeUpload() {
// 显示 loading 动画
this.quillUpdateImg = true
},
// 图片上传成功
// 注意!由于微信公众号的图片有访问限制,所以会显示“此图片来自微信公众号,未经允许不可引用”
uploadSuccess(res, file) {
// res为图片服务器返回的数据
// 获取富文本组件实例
let quill = this.$refs.myQuillEditor.quill
// 如果上传成功
const link = res.data
if (link){
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片 res.info为服务器返回的图片地址
quill.insertEmbed(length, 'image', link)
// 调整光标到最后
quill.setSelection(length + 1)
} else {
this.$message.error('图片插入失败')
}
// loading 动画消失
this.quillUpdateImg = false;
},
// 富文本图片上传失败
uploadError() {
// loading 动画消失
this.quillUpdateImg = false;
this.$message.error("图片插入失败");
}
}
}
</script>
<style>
.editor {
line-height: normal !important;
height: 500px;
}
.ql-snow .ql-tooltip[data-mode=link]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: '保存';
padding-right: 0;
}
.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>

View File

@@ -1,36 +0,0 @@
<!--
微信消息 - 定位
-->
<template>
<div>
<el-link type="primary" target="_blank" :href="'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' + locationY + '&pointy=' + locationX + '&name=' + label + '&ref=yudao'">
<img :src="'https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|' + locationX + ',' + locationY + '&key=' + qqMapKey + '&size=250*180'">
<p/><i class="el-icon-map-location"></i>{{label}}
</el-link>
</div>
</template>
<script>
export default {
name: "wxLocation",
props: {
locationX: {
required: true,
type: Number
},
locationY: {
required: true,
type: Number
},
label: { // 地名
required: true,
type: String
},
qqMapKey: { // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
required: false,
type: String,
default: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E' // 需要自定义
}
}
};
</script>

View File

@@ -1,247 +0,0 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除 avue 组件使用 ElementUI 原生组件
-->
<template>
<!-- 类型图片 -->
<div v-if="objData.type === 'image'">
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :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="mini" type="success" @click="selectMaterial(item)">选择
<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getMaterialPage"/>
</div>
<!-- 类型语音 -->
<div v-else-if="objData.type === 'voice'">
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="语音" align="center">
<template v-slot="scope">
<wx-voice-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-circle-plus"
@click="selectMaterial(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getPage"/>
</div>
<div v-else-if="objData.type === 'video'">
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="介绍" align="center" prop="introduction" />
<el-table-column label="视频" align="center">
<template v-slot="scope">
<wx-video-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-circle-plus"
@click="selectMaterial(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getMaterialPage"/>
</div>
<div v-else-if="objData.type === 'news'">
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key="item.mediaId" v-if="item.content && item.content.newsItem">
<wx-news :articles="item.content.newsItem" />
<el-row class="ope-row">
<el-button size="mini" type="success" @click="selectMaterial(item)">
选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getMaterialPage"/>
</div>
</template>
<script>
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import { getMaterialPage } from "@/api/mp/material";
import { getFreePublishPage } from "@/api/mp/freePublish";
import { getDraftPage } from "@/api/mp/draft";
export default {
name: "wxMaterialSelect",
components: {
WxNews,
WxVoicePlayer,
WxVideoPlayer
},
props: {
objData: {
type: Object, // type - 类型accountId - 公众号账号编号
required: true
},
newsType:{ // 图文类型1、已发布图文2、草稿箱图文
type: String,
default: "1"
},
},
data() {
return {
// 遮罩层
loading: false,
// 总条数
total: 0,
// 数据列表
list: [],
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: this.objData.accountId,
},
}
},
created() {
this.getPage()
},
methods:{
selectMaterial(item) {
this.$emit('selectMaterial', item)
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getPage()
},
getPage() {
this.loading = true
if (this.objData.type === 'news' && this.newsType === '1') { // 【图文】+ 【已发布】
this.getFreePublishPage();
} else if (this.objData.type === 'news' && this.newsType === '2') { // 【图文】+ 【草稿】
this.getDraftPage();
} else { // 【素材】
this.getMaterialPage();
}
},
getMaterialPage() {
getMaterialPage({
...this.queryParams,
type: this.objData.type
}).then(response => {
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
getFreePublishPage() {
getFreePublishPage(this.queryParams).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
getDraftPage() {
getDraftPage((this.queryParams)).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
}
}
};
</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%;
}
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>

View File

@@ -1,101 +0,0 @@
.avue-card{
&__item{
margin-bottom: 16px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-sizing: border-box;
color: rgba(0,0,0,.65);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
list-style: none;
font-feature-settings: "tnum";
cursor: pointer;
height:200px;
&:hover{
border-color: rgba(0,0,0,.09);
box-shadow: 0 2px 8px rgba(0,0,0,.09);
}
&--add{
border:1px dashed #000;
width: 100%;
color: rgba(0,0,0,.45);
background-color: #fff;
border-color: #d9d9d9;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
i{
margin-right: 10px;
}
&:hover{
color: #40a9ff;
background-color: #fff;
border-color: #40a9ff;
}
}
}
&__body{
display: flex;
padding: 24px;
}
&__detail{
flex:1
}
&__avatar{
width: 48px;
height: 48px;
border-radius: 48px;
overflow: hidden;
margin-right: 12px;
img{
width: 100%;
height: 100%;
}
}
&__title{
color: rgba(0,0,0,.85);
margin-bottom: 12px;
font-size: 16px;
&:hover{
color:#1890ff;
}
}
&__info{
color: rgba(0,0,0,.45);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
height: 64px;
}
&__menu{
display: flex;
justify-content:space-around;
height: 50px;
background: #f7f9fa;
color: rgba(0,0,0,.45);
text-align: center;
line-height: 50px;
&:hover{
color:#1890ff;
}
}
}
/** joolun 额外加的 */
.avue-comment__main {
flex: unset!important;
border-radius: 5px!important;
margin: 0 8px!important;
}
.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;
}

View File

@@ -1,88 +0,0 @@
/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */
.avue-comment{
margin-bottom: 30px;
display: flex;
align-items: flex-start;
&--reverse{
flex-direction:row-reverse;
.avue-comment__main{
&:before,&:after{
left: auto;
right: -8px;
border-width: 8px 0 8px 8px;
}
&:before{
border-left-color: #dedede;
}
&:after{
border-left-color: #f8f8f8;
margin-right: 1px;
margin-left: auto;
}
}
}
&__avatar{
width: 48px;
height: 48px;
border-radius: 50%;
border: 1px solid transparent;
box-sizing: border-box;
vertical-align: middle;
}
&__header{
padding: 5px 15px;
background: #f8f8f8;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
justify-content: space-between;
}
&__author{
font-weight: 700;
font-size: 14px;
color: #999;
}
&__main{
flex:1;
margin: 0 20px;
position: relative;
border: 1px solid #dedede;
border-radius: 2px;
&:before,&:after{
position: absolute;
top: 10px;
left: -8px;
right: 100%;
width: 0;
height: 0;
display: block;
content: " ";
border-color: transparent;
border-style: solid solid outset;
border-width: 8px 8px 8px 0;
pointer-events: none;
}
&:before {
border-right-color: #dedede;
z-index: 1;
}
&:after{
border-right-color: #f8f8f8;
margin-left: 1px;
z-index: 2;
}
}
&__body{
padding: 15px;
overflow: hidden;
background: #fff;
font-family: Segoe UI,Lucida Grande,Helvetica,Arial,Microsoft YaHei,FreeSans,Arimo,Droid Sans,wenquanyi micro hei,Hiragino Sans GB,Hiragino Sans GB W3,FontAwesome,sans-serif;color: #333;
font-size: 14px;
}
blockquote{
margin:0;
font-family: Georgia,Times New Roman,Times,Kai,Kaiti SC,KaiTi,BiauKai,FontAwesome,serif;
padding: 1px 0 1px 15px;
border-left: 4px solid #ddd;
}
}

View File

@@ -1,294 +0,0 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除暂时用不到的 websocket
代码优化补充注释提升阅读性
-->
<template>
<div class="msg-main">
<div class="msg-div" :id="'msg-div' + nowStr">
<!-- 加载更多 -->
<div v-loading="loading"></div>
<div v-if="!loading">
<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 list" :key='item.id'>
<div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''">
<div class="avatar-div">
<img :src="item.sendFrom === 1 ? user.avatar : mp.avatar" class="avue-comment__avatar">
<div class="avue-comment__author">{{item.sendFrom === 1 ? user.nickname : mp.nickname }}</div>
</div>
<div class="avue-comment__main">
<div class="avue-comment__header">
<div class="avue-comment__create_time">{{ parseTime(item.createTime) }}</div>
</div>
<div class="avue-comment__body" :style="item.sendFrom === 2 ? 'background: #6BED72;' : ''">
<!-- 事件区域 -->
<div v-if="item.type === 'event' && item.event === 'subscribe'">
<el-tag type="success" size="mini">关注</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'unsubscribe'">
<el-tag type="danger" size="mini">取消关注</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'CLICK'">
<el-tag size="mini">点击菜单</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'VIEW'">
<el-tag size="mini">点击菜单链接</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'">
<el-tag size="mini">扫码结果</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'scancode_push'">
<el-tag size="mini">扫码结果</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'">
<el-tag size="mini">系统拍照发图</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'">
<el-tag size="mini">拍照或者相册</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_weixin'">
<el-tag size="mini">微信相册</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'location_select'">
<el-tag size="mini">选择地理位置</el-tag>
</div>
<div v-else-if="item.type === 'event'">
<el-tag type="danger" size="mini">未知事件类型</el-tag>
</div>
<!-- 消息区域 -->
<div v-else-if="item.type === 'text'">{{ item.content }}</div>
<div v-else-if="item.type === 'voice'">
<wx-voice-player :url="item.mediaUrl" :content="item.recognition" />
</div>
<div v-else-if="item.type === 'image'">
<a target="_blank" :href="item.mediaUrl">
<img :src="item.mediaUrl" style="width: 100px">
</a>
</div>
<div v-else-if="item.type === 'video' || item.type === 'shortvideo'" style="text-align: center">
<wx-video-player :url="item.mediaUrl" />
</div>
<div v-else-if="item.type === 'link'" class="avue-card__detail">
<el-link type="success" :underline="false" target="_blank" :href="item.url">
<div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div>
</el-link>
<div class="avue-card__info" style="height: unset">{{item.description}}</div>
</div>
<!-- TODO 芋艿待完善 -->
<div v-else-if="item.type === 'location'">
<wx-location :label="item.label" :location-y="item.locationY" :location-x="item.locationX" />
</div>
<div v-else-if="item.type === 'news'" style="width: 300px"> <!-- TODO 芋艿待测试详情页也存在类似的情况 -->
<wx-news :articles="item.articles" />
</div>
<div v-else-if="item.type === 'music'">
<wx-music :title="item.title" :description="item.description" :thumb-media-url="item.thumbMediaUrl"
:music-url="item.musicUrl" :hq-music-url="item.hqMusicUrl" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="msg-send" v-loading="sendLoading">
<wx-reply-select ref="replySelect" :objData="objData" />
<el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
</div>
</div>
</template>
<script>
import { getMessagePage, sendMessage } from '@/api/mp/message'
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxLocation from '@/views/mp/components/wx-location/main.vue';
import WxMusic from '@/views/mp/components/wx-music/main.vue';
import { getUser } from "@/api/mp/mpuser";
export default {
name: "wxMsg",
components: {
WxReplySelect,
WxVideoPlayer,
WxVoicePlayer,
WxNews,
WxLocation,
WxMusic
},
props: {
userId: {
type: Number,
required: true
},
},
data() {
return {
nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
loading: false, // 消息列表是否正在加载中
loadMore: true, // 是否可以加载更多
list: [], // 消息列表
queryParams: {
pageNo: 1, // 当前页数
pageSize: 14, // 每页显示多少条
accountId: undefined,
},
user: { // 由于微信不再提供昵称,直接使用“用户”展示
nickname: '用户',
avatar: require("@/assets/images/profile.jpg"),
accountId: 0, // 公众号账号编号
},
mp: {
nickname: '公众号',
avatar: require("@/assets/images/wechat.png"),
},
// ========= 消息发送 =========
sendLoading: false, // 发送消息是否加载中
objData: { // 微信发送消息
type: 'text',
},
}
},
created() {
// 获得用户信息
getUser(this.userId).then(response => {
this.user.nickname = response.data.nickname && response.data.nickname.length > 0 ?
response.data.nickname : this.user.nickname;
this.user.avatar = response.data.avatar && this.user.avatar.length > 0 ?
response.data.avatar : this.user.avatar;
this.user.accountId = response.data.accountId;
// 设置公众号账号编号
this.queryParams.accountId = response.data.accountId;
this.objData.accountId = response.data.accountId;
// 加载消息
console.log(this.queryParams)
this.refreshChange()
})
},
methods:{
sendMsg(){
if (!this.objData) {
return;
}
// 公众号限制:客服消息,公众号只允许发送一条
if (this.objData.type === 'news'
&& this.objData.articles.length > 1) {
this.objData.articles = [this.objData.articles[0]]
this.$message({
showClose: true,
message: '图文消息条数限制在 1 条以内,已默认发送第一条',
type: 'success'
})
}
// 执行发送
this.sendLoading = true
sendMessage(Object.assign({
userId: this.userId
}, {
...this.objData
})).then(response => {
this.sendLoading = false
// 添加到消息列表,并滚动
this.list = [...this.list , ...[response.data] ]
this.scrollToBottom()
// 重置 objData 状态
this.$refs['replySelect'].deleteObj(); // 重置,避免 tab 的数据未清理
}).catch(() => {
this.sendLoading = false
})
},
loadingMore() {
this.queryParams.pageNo++
this.getPage(this.queryParams)
},
getPage(page, params) {
this.loading = true
getMessagePage(Object.assign({
pageNo: page.pageNo,
pageSize: page.pageSize,
userId: this.userId,
accountId: page.accountId,
}, params)).then(response => {
// 计算当前的滚动高度
const msgDiv = document.getElementById('msg-div' + this.nowStr);
let scrollHeight = 0
if(msgDiv){
scrollHeight = msgDiv.scrollHeight
}
// 处理数据
const data = response.data.list.reverse();
this.list = [...data, ...this.list]
this.loading = false
if (data.length < this.queryParams.pageSize || data.length === 0){
this.loadMore = false
}
this.queryParams.pageNo = page.pageNo
this.queryParams.pageSize = page.pageSize
// 滚动到原来的位置
if(this.queryParams.pageNo === 1) { // 定位到消息底部
this.scrollToBottom()
} else if (data.length !== 0) { // 定位滚动条
this.$nextTick(() => {
if (scrollHeight !== 0){
msgDiv.scrollTop = document.getElementById('msg-div'+this.nowStr).scrollHeight - scrollHeight - 100
}
})
}
})
},
/**
* 刷新回调
*/
refreshChange() {
this.getPage(this.queryParams)
},
/** 定位到消息底部 */
scrollToBottom: function () {
this.$nextTick(() => {
let div = document.getElementById('msg-div' + this.nowStr)
div.scrollTop = div.scrollHeight
})
},
}
};
</script>
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
@import './comment.scss';
@import './card.scss';
.msg-main {
margin-top: -30px;
padding: 10px;
}
.msg-div {
height: 50vh;
overflow: auto;
background-color: #eaeaea;
margin-left: 10px;
margin-right: 10px;
}
.msg-send {
padding: 10px;
}
.avatar-div {
text-align: center;
width: 80px;
}
.send-but {
float: right;
margin-top: 8px!important;
}
</style>

View File

@@ -1,52 +0,0 @@
<!--
微信消息 - 音乐
-->
<template>
<div>
<el-link type="success" :underline="false" target="_blank" :href="hqMusicUrl ? hqMusicUrl : musicUrl">
<div class="avue-card__body" style="padding:10px;background-color: #fff;border-radius: 5px">
<div class="avue-card__avatar">
<img :src="thumbMediaUrl" alt=""/>
</div>
<div class="avue-card__detail">
<div class="avue-card__title" style="margin-bottom:unset">{{ title }}</div>
<div class="avue-card__info" style="height: unset">{{ description }}</div>
</div>
</div>
</el-link>
</div>
</template>
<script>
export default {
name: "wxMusic",
props: {
title: {
required: false,
type: String
},
description: {
required: false,
type: String
},
musicUrl: {
required: false,
type: String
},
hqMusicUrl: {
required: false,
type: String
},
thumbMediaUrl: {
required: true,
type: String
},
}
};
</script>
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scc */
@import '../wx-msg/card.scss';
</style>

View File

@@ -1,104 +0,0 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 图文
芋道源码
代码优化补充注释提升阅读性
-->
<template>
<div class="news-home">
<div v-for="(article, index) in articles" :key="index" class="news-div">
<!-- 头条 -->
<a target="_blank" :href="article.url" v-if="index === 0">
<div class="news-main">
<div class="news-content">
<img class="material-img" :src="article.picUrl" width="280px" height="120px"/>
<div class="news-content-title">
<span>{{article.title}}</span>
</div>
</div>
</div>
</a>
<!-- 二条/三条等等 -->
<a target="_blank" :href="article.url" v-else>
<div class="news-main-item">
<div class="news-content-item">
<div class="news-content-item-title">{{article.title}}</div>
<div class="news-content-item-img">
<img class="material-img" :src="article.picUrl" height="100%"/>
</div>
</div>
</div>
</a>
</div>
</div>
</template>
<script>
export default {
name: "wxNews",
props: {
articles: {
type: Array // title - 标题description - 描述picUrl - 图片连接url - 跳转链接
}
},
// created() {
// console.log(this.articles)
// },
};
</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: 0;
bottom: 0;
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 0;
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>

View File

@@ -1,547 +0,0 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除多余的 rep 为前缀的变量 message 消息更简单
代码优化补充注释提升阅读性
优化消息的临时缓存策略发送消息时只清理被发送消息的 tab不会强制切回到 text 输入
支持发送视频消息时支持新建视频
-->
<template>
<el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">
<!-- 类型 1文本 -->
<el-tab-pane name="text">
<span slot="label"><i class="el-icon-document"></i> 文本</span>
<el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="objData.content" @input="inputContent" />
</el-tab-pane>
<!-- 类型 2图片 -->
<el-tab-pane name="image">
<span slot="label"><i class="el-icon-picture"></i> 图片</span>
<el-row>
<!-- 情况一已经选择好素材或者上传好图片 -->
<div class="select-item" v-if="objData.url">
<img class="material-img" :src="objData.url">
<p class="item-name" v-if="objData.name">{{objData.name}}</p>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
</el-row>
</div>
<!-- 情况二未做完上述操作 -->
<div v-else>
<el-row style="text-align: center">
<!-- 选择素材 -->
<el-col :span="12" class="col-select">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
<el-dialog title="选择图片" :visible.sync="dialogImageVisible" width="90%" append-to-body>
<wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" />
</el-dialog>
</el-col>
<!-- 文件上传 -->
<el-col :span="12" class="col-add">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
<el-button type="primary">上传图片</el-button>
<div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</div>
</el-upload>
</el-col>
</el-row>
</div>
</el-row>
</el-tab-pane>
<!-- 类型 3语音 -->
<el-tab-pane name="voice">
<span slot="label"><i class="el-icon-phone"></i> 语音</span>
<el-row>
<div class="select-item2" v-if="objData.url">
<p class="item-name">{{objData.name}}</p>
<div class="item-infos">
<wx-voice-player :url="objData.url" />
</div>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
</el-row>
</div>
<div v-else>
<el-row style="text-align: center">
<!-- 选择素材 -->
<el-col :span="12" class="col-select">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
<el-dialog title="选择语音" :visible.sync="dialogVoiceVisible" width="90%" append-to-body>
<WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial"></WxMaterialSelect>
</el-dialog>
</el-col>
<!-- 文件上传 -->
<el-col :span="12" class="col-add">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeVoiceUpload" :on-success="handleUploadSuccess">
<el-button type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s</div>
</el-upload>
</el-col>
</el-row>
</div>
</el-row>
</el-tab-pane>
<!-- 类型 4视频 -->
<el-tab-pane name="video">
<span slot="label"><i class="el-icon-share"></i> 视频</span>
<el-row>
<el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<div style="text-align: center;">
<wx-video-player v-if="objData.url" :url="objData.url" />
</div>
<div style="margin: 20px 0;"></div>
<el-row style="text-align: center">
<!-- 选择素材 -->
<el-col :span="12">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
<el-dialog title="选择视频" :visible.sync="dialogVideoVisible" width="90%" append-to-body>
<wx-material-select :objData="objData" @selectMaterial="selectMaterial" />
</el-dialog>
</el-col>
<!-- 文件上传 -->
<el-col :span="12">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeVideoUpload" :on-success="handleUploadSuccess">
<el-button type="primary">新建视频<i class="el-icon-upload el-icon--right"></i></el-button>
</el-upload>
</el-col>
</el-row>
</el-row>
</el-tab-pane>
<!-- 类型 5图文 -->
<el-tab-pane name="news">
<span slot="label"><i class="el-icon-news"></i> 图文</span>
<el-row>
<div class="select-item" v-if="objData.articles">
<wx-news :articles="objData.articles" />
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />
</el-row>
</div>
<!-- 选择素材 -->
<div v-if="!objData.content">
<el-row style="text-align: center">
<el-col :span="24">
<el-button type="success" @click="openMaterial">{{newsType === '1' ? '选择已发布图文' : '选择草稿箱图文'}}<i class="el-icon-circle-check el-icon--right"></i></el-button>
</el-col>
</el-row>
</div>
<el-dialog title="选择图文" :visible.sync="dialogNewsVisible" width="90%" append-to-body>
<wx-material-select :objData="objData" @selectMaterial="selectMaterial" :newsType="newsType" />
</el-dialog>
</el-row>
</el-tab-pane>
<!-- 类型 6音乐 -->
<el-tab-pane name="music">
<span slot="label"><i class="el-icon-service"></i> 音乐</span>
<el-row>
<el-col :span="6">
<div class="thumb-div">
<img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
<div class="thumb-but">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeThumbImageUpload" :on-success="handleUploadSuccess">
<el-button slot="trigger" size="mini" type="text">本地上传</el-button>
<el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px">素材库选择</el-button>
</el-upload>
</div>
</div>
<el-dialog title="选择图片" :visible.sync="dialogThumbVisible" width="80%" append-to-body>
<wx-material-select :objData="{type:'image', accountId: objData.accountId}" @selectMaterial="selectMaterial" />
</el-dialog>
</el-col>
<el-col :span="18">
<el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />
</el-col>
</el-row>
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.hqMusicUrl" placeholder="请输入高质量音乐链接" @input="inputContent" />
</el-tab-pane>
</el-tabs>
</template>
<script>
import WxNews from '@/views/mp/components/wx-news/main.vue'
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import { getAccessToken } from '@/utils/auth'
export default {
name: "wxReplySelect",
components: {
WxNews,
WxMaterialSelect,
WxVoicePlayer,
WxVideoPlayer
},
props: {
objData: { // 消息对象。
type: Object, // 设置为 Object 的原因,方便属性的传递
required: true,
},
newsType:{ // 图文类型1、已发布图文2、草稿箱图文
type: String,
default: "1"
},
},
data() {
return {
tempPlayerObj: {
type: '2'
},
tempObj: new Map().set( // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据;
this.objData.type, // 消息类型
Object.assign({}, this.objData)), // 消息内容
// ========== 素材选择的弹窗,是否可见 ==========
dialogNewsVisible: false, // 图文
dialogImageVisible: false, // 图片
dialogVoiceVisible: false, // 语音
dialogVideoVisible: false, // 视频
dialogThumbVisible: false, // 缩略图
// ========== 文件上传(图片、语音、视频) ==========
fileList: [], // 文件列表
uploadData: {
"accountId": undefined,
"type": this.objData.type,
"title":'',
"introduction":''
},
actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
}
},
methods:{
beforeThumbImageUpload(file){
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!');
return false;
}
const isLt = file.size / 1024 / 1024 < 2;
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
beforeVoiceUpload(file){
// 校验格式
const isType = file.type === 'audio/mp3'
|| file.type === 'audio/mpeg'
|| file.type === 'audio/wma'
|| file.type === 'audio/wav'
|| file.type === 'audio/amr';
if (!isType) {
this.$message.error('上传语音格式不对!' + file.type);
return false;
}
// 校验大小
const isLt = file.size / 1024 / 1024 < 2;
if (!isLt) {
this.$message.error('上传语音大小不能超过 2M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
beforeImageUpload(file) {
// 校验格式
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!');
return false;
}
// 校验大小
const isLt = file.size / 1024 / 1024 < 2;
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
beforeVideoUpload(file){
// 校验格式
const isType = file.type === 'video/mp4';
if (!isType) {
this.$message.error('上传视频格式不对!');
return false;
}
// 校验大小
const isLt = file.size / 1024 / 1024 < 10;
if (!isLt) {
this.$message.error('上传视频大小不能超过 10M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
handleUploadSuccess(response, file, fileList) {
if (response.code !== 0) {
this.$message.error('上传出错:' + response.msg)
return false;
}
// 清空上传时的各种数据
this.fileList = []
this.uploadData.title = ''
this.uploadData.introduction = ''
// 上传好的文件,本质是个素材,所以可以进行选中
let item = response.data
this.selectMaterial(item)
},
/**
* 切换消息类型的 tab
*
* @param tab tab
*/
handleClick(tab) {
// 设置后续文件上传的文件类型
this.uploadData.type = this.objData.type;
if (this.uploadData.type === 'music') { // 【音乐】上传的是缩略图
this.uploadData.type = 'thumb';
}
// 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData
let tempObjItem = this.tempObj.get(this.objData.type)
if (tempObjItem) {
this.objData.content = tempObjItem.content ? tempObjItem.content : null
this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null
this.objData.url = tempObjItem.url ? tempObjItem.url : null
this.objData.name = tempObjItem.url ? tempObjItem.name : null
this.objData.title = tempObjItem.title ? tempObjItem.title : null
this.objData.description = tempObjItem.description ? tempObjItem.description : null
return;
}
// 如果获取不到,需要把 objData 复原
// 必须使用 $set 赋值,不然 input 无法输入内容
this.$set(this.objData, 'content', '');
this.$delete(this.objData, 'mediaId');
this.$delete(this.objData, 'url');
this.$set(this.objData, 'title', '');
this.$set(this.objData, 'description', '');
},
/**
* 选择素材,将设置设置到 objData 变量
*
* @param item 素材
*/
selectMaterial(item) {
// 选择好素材,所以隐藏弹窗
this.closeMaterial();
// 创建 tempObjItem 对象,并设置对应的值
let tempObjItem = {}
tempObjItem.type = this.objData.type;
if (this.objData.type === 'news') {
tempObjItem.articles = item.content.newsItem
this.objData.articles = item.content.newsItem
} else if (this.objData.type === 'music') { // 音乐需要特殊处理,因为选择的是图片的缩略图
tempObjItem.thumbMediaId = item.mediaId
this.objData.thumbMediaId = item.mediaId
tempObjItem.thumbMediaUrl = item.url
this.objData.thumbMediaUrl = item.url
// title、introduction、musicUrl、hqMusicUrl从 objData 到 tempObjItem避免上传素材后被覆盖掉
tempObjItem.title = this.objData.title || ''
tempObjItem.introduction = this.objData.introduction || ''
tempObjItem.musicUrl = this.objData.musicUrl || ''
tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || ''
} else if (this.objData.type === 'image'
|| this.objData.type === 'voice') {
tempObjItem.mediaId = item.mediaId
this.objData.mediaId = item.mediaId
tempObjItem.url = item.url;
this.objData.url = item.url;
tempObjItem.name = item.name
this.objData.name = item.name
} else if (this.objData.type === 'video') {
tempObjItem.mediaId = item.mediaId
this.objData.mediaId = item.mediaId
tempObjItem.url = item.url;
this.objData.url = item.url;
tempObjItem.name = item.name
this.objData.name = item.name
// title、introduction从 item 到 tempObjItem因为素材里有 title、introduction
if (item.title) {
this.objData.title = item.title || ''
tempObjItem.title = item.title || ''
}
if (item.introduction) {
this.objData.description = item.introduction || '' // 消息使用的是 description素材使用的是 introduction所以转换下
tempObjItem.description = item.introduction || ''
}
} else if (this.objData.type === 'text') {
this.objData.content = item.content || ''
}
// 最终设置到临时缓存
this.tempObj.set(this.objData.type, tempObjItem)
},
openMaterial() {
if (this.objData.type === 'news') {
this.dialogNewsVisible = true
} else if(this.objData.type === 'image') {
this.dialogImageVisible = true
} else if(this.objData.type === 'voice') {
this.dialogVoiceVisible = true
} else if(this.objData.type === 'video') {
this.dialogVideoVisible = true
} else if(this.objData.type === 'music') {
this.dialogThumbVisible = true
}
},
closeMaterial() {
this.dialogNewsVisible = false
this.dialogImageVisible = false
this.dialogVoiceVisible = false
this.dialogVideoVisible = false
this.dialogThumbVisible = false
},
deleteObj() {
if (this.objData.type === 'news') {
this.$delete(this.objData, 'articles');
} else if(this.objData.type === 'image') {
this.objData.mediaId = null
this.$delete(this.objData, 'url');
this.objData.name = null
} else if(this.objData.type === 'voice') {
this.objData.mediaId = null
this.$delete(this.objData, 'url');
this.objData.name = null
} else if(this.objData.type === 'video') {
this.objData.mediaId = null
this.$delete(this.objData, 'url');
this.objData.name = null
this.objData.title = null
this.objData.description = null
} else if(this.objData.type === 'music') {
this.objData.thumbMediaId = null
this.objData.thumbMediaUrl = null
this.objData.title = null
this.objData.description = null
this.objData.musicUrl = null
this.objData.hqMusicUrl = null
} else if(this.objData.type === 'text') {
this.objData.content = null
}
// 覆盖缓存
this.tempObj.set(this.objData.type, Object.assign({}, this.objData));
},
/**
* 输入时,缓存每次 objData 到 tempObj 中
*
* why不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式
*/
inputContent(str) {
// 覆盖缓存
this.tempObj.set(this.objData.type, Object.assign({}, this.objData));
}
}
};
</script>
<style lang="scss" scoped>
.public-account-management{
.el-input{
width: 70%;
margin-right: 2%;
}
}
.pagination{
text-align: right;
margin-right: 25px;
}
.select-item{
width: 280px;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.select-item2{
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.ope-row{
padding-top: 10px;
text-align: center;
}
.item-name{
font-size: 12px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
text-align: center;
}
.el-form-item__content{
line-height:unset!important;
}
.col-select{
border: 1px solid rgb(234, 234, 234);
padding: 50px 0px;
height: 160px;
width: 49.5%;
}
.col-select2{
border: 1px solid rgb(234, 234, 234);
padding: 50px 0px;
height: 160px;
}
.col-add{
border: 1px solid rgb(234, 234, 234);
padding: 50px 0px;
height: 160px;
width: 49.5%;
float: right
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 100px!important;
height: 100px!important;
line-height: 100px!important;
text-align: center;
}
.material-img {
width: 100%;
}
.thumb-div{
display: inline-block;
text-align: center;
}
.item-infos{
width: 30%;
margin: auto
}
</style>

View File

@@ -1,91 +0,0 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 视频
芋道源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放
2重构后的做法后端接收到微信公众号的视频消息后将视频消息的 media_id 的文件内容保存到文件服务器中这样前端可以直接使用 URL 播放
体验优化弹窗关闭后自动暂停视频的播放
-->
<template>
<div>
<!-- 提示 -->
<div @click="playVideo()">
<i class="el-icon-video-play" style="font-size: 40px!important;" ></i>
<p>点击播放视频</p>
</div>
<!-- 弹窗播放 -->
<el-dialog title="视频播放" :visible.sync="dialogVideo" width="40%" append-to-body @close="closeDialog">
<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>
// 引入 videoPlayer 相关组件。教程https://juejin.cn/post/6923056942281654285
import { videoPlayer } from 'vue-video-player'
require('video.js/dist/video-js.css')
require('vue-video-player/src/custom-theme.css')
export default {
name: "wxVideoPlayer",
props: {
url: { // 视频地址例如说https://www.iocoder.cn/xxx.mp4
type: String,
required: true
},
},
components: {
videoPlayer
},
data() {
return {
dialogVideo:false,
playerOptions: {
playbackRates: [0.5, 1.0, 1.5, 2.0], // 播放速度
autoplay: false, // 如果 true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: 'auto', // 建议浏览器在 <video> 加载元素后是否应该开始下载视频数据。auto 浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"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 //全屏按钮
}
}
}
},
methods: {
playVideo(){
this.dialogVideo = true
// 设置地址
this.$set(this.playerOptions.sources[0], 'src', this.url)
},
closeDialog(){
// 暂停播放
this.$refs.videoPlayer.player.pause()
},
onPlayerPlay(player) {
},
onPlayerPause(player) {
},
}
};
</script>

View File

@@ -1,98 +0,0 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 语音
芋道源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放
2重构后的做法后端接收到微信公众号的视频消息后将视频消息的 media_id 的文件内容保存到文件服务器中这样前端可以直接使用 URL 播放
代码优化 props 中的 objData 调成为 data 中对应的属性并补充相关注释
-->
<template>
<div class="wx-voice-div" @click="playVoice">
<i :class="playing !== true ? 'el-icon-video-play': 'el-icon-video-pause'">
<span class="amr-duration" v-if="duration">{{ duration }} </span>
</i>
<div v-if="content">
<el-tag type="success" size="mini">语音识别</el-tag>
{{ content }}
</div>
</div>
</template>
<script>
// 因为微信语音是 amr 格式,所以需要用到 amr 解码器https://www.npmjs.com/package/benz-amr-recorder
const BenzAMRRecorder = require('benz-amr-recorder')
export default {
name: "wxVoicePlayer",
props: {
url: { // 语音地址例如说https://www.iocoder.cn/xxx.amr
type: String,
required: true
},
content: { // 语音文本
type: String,
required: false
}
},
data() {
return {
amr: undefined, // BenzAMRRecorder 对象
playing: false, // 是否在播放中
duration: undefined, // 播放时长
}
},
methods:{
playVoice() {
debugger
// 情况一:未初始化,则创建 BenzAMRRecorder
if (this.amr === undefined){
this.amrInit();
return;
}
if (this.amr.isPlaying()) {
this.amrStop();
} else {
this.amrPlay();
}
},
amrInit() {
const amr = new BenzAMRRecorder();
this.amr = amr;
// 设置播放
const that = this
amr.initWithUrl(this.url).then(function() {
that.amrPlay()
that.duration = amr.getDuration();
})
// 监听暂停
amr.onEnded(function() {
that.playing = false;
})
},
amrPlay() {
this.playing = true;
this.amr.play()
},
amrStop() {
this.playing = false;
this.amr.stop()
},
}
};
</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>

View File

@@ -1,700 +0,0 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
优化代码和项目的代码保持一致
清理冗余代码保证代码整洁
增加注释提升可读性
-->
<template>
<div class="app-container">
<doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:draft:create']">新增
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<div class="waterfall" v-loading="loading">
<div v-if="item.content && item.content.newsItem" class="waterfall-item" v-for="item in list"
:key='item.articleId'>
<wx-news :articles="item.content.newsItem" />
<!-- 操作按钮 -->
<el-row class="ope-row">
<el-button type="success" circle @click="handlePublish(item)" v-hasPermi="['mp:free-publish:submit']">发布</el-button>
<el-button type="primary" icon="el-icon-edit" circle @click="handleUpdate(item)" v-hasPermi="['mp:draft:update']" />
<el-button type="danger" icon="el-icon-delete" circle @click="handleDelete(item)" v-hasPermi="['mp:draft:delete']" />
</el-row>
</div>
</div>
<!-- 分页记录 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改草稿对话框 -->
<el-dialog :title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
append-to-body width="80%" top="20px" :visible.sync="dialogNewsVisible"
:before-close="dialogNewsClose" :close-on-click-modal="false">
<div class="left">
<div class="select-item">
<div v-for="(news, index) in articlesAdd" :key='news.id'>
<div class="news-main father" v-if="index === 0" :class="{'activeAddNews': isActiveAddNews === index}"
@click="activeNews(index)">
<div class="news-content">
<img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl"/>
<div class="news-content-title">{{news.title}}</div>
</div>
<div class="child" v-if="articlesAdd.length>1">
<el-button type="mini" icon="el-icon-sort-down" @click="downNews(index)">下移</el-button>
<el-button v-if="operateMaterial === 'add'" type="mini" icon="el-icon-delete"
@click="minusNews(index)">删除
</el-button>
</div>
</div>
<div class="news-main-item father" v-if="index>0" :class="{'activeAddNews': isActiveAddNews === index}"
@click="activeNews(index)">
<div class="news-content-item">
<div class="news-content-item-title ">{{news.title}}</div>
<div class="news-content-item-img">
<img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" height="100%"/>
</div>
</div>
<div class="child">
<el-button v-if="articlesAdd.length > index+1" type="mini" icon="el-icon-sort-down"
@click="downNews(index)">下移
</el-button>
<el-button type="mini" icon="el-icon-sort-up" @click="upNews(index)">上移</el-button>
<el-button v-if="operateMaterial=== 'add'" type="mini" icon="el-icon-delete"
@click="minusNews(index)">删除
</el-button>
</div>
</div>
</div>
<div class="news-main-plus" @click="plusNews" v-if="articlesAdd.length<8 && operateMaterial==='add'">
<i class="el-icon-circle-plus icon-plus"></i>
</div>
</div>
</div>
<div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
<br /> <br /> <br /> <br />
<!-- 标题作者原文地址 -->
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" />
<el-input v-model="articlesAdd[isActiveAddNews].author" placeholder="请输入作者" style="margin-top: 5px;" />
<el-input v-model="articlesAdd[isActiveAddNews].contentSourceUrl" placeholder="请输入原文地址" style="margin-top: 5px;" />
<!-- 封面和摘要 -->
<div class="input-tt">封面和摘要</div>
<div>
<div class="thumb-div">
<img class="material-img" v-if="articlesAdd[isActiveAddNews].thumbUrl"
:src="articlesAdd[isActiveAddNews].thumbUrl" :class="isActiveAddNews === 0 ? 'avatar':'avatar1'">
<i v-else class="el-icon-plus avatar-uploader-icon"
:class="isActiveAddNews === 0 ? 'avatar':'avatar1'"></i>
<div class="thumb-but">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeThumbImageUpload" :on-success="handleUploadSuccess">
<el-button slot="trigger" size="mini" type="primary">本地上传</el-button>
<el-button size="mini" type="primary" @click="openMaterial" style="margin-left: 5px">素材库选择</el-button>
<div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</div>
</el-upload>
</div>
<el-dialog title="选择图片" :visible.sync="dialogImageVisible" width="80%" append-to-body>
<wx-material-select ref="materialSelect" :objData="{type: 'image', accountId: this.queryParams.accountId}"
@selectMaterial="selectMaterial" />
</el-dialog>
</div>
<el-input :rows="8" type="textarea" v-model="articlesAdd[isActiveAddNews].digest" placeholder="请输入摘要"
class="digest" maxlength="120" style="float: right" />
</div>
<!--富文本编辑器组件-->
<el-row>
<wx-editor v-model="articlesAdd[isActiveAddNews].content" :account-id="this.uploadData.accountId"
v-if="hackResetEditor"/>
</el-row>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogNewsVisible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import { getAccessToken } from '@/utils/auth'
import {createDraft, deleteDraft, getDraftPage, updateDraft} from "@/api/mp/draft";
import { getSimpleAccounts } from "@/api/mp/account";
import {deleteFreePublish, submitFreePublish} from "@/api/mp/freePublish";
export default {
name: 'MpDraft',
components: {
WxEditor,
WxNews,
WxMaterialSelect
},
data() {
return {
// 遮罩层
loading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 数据列表
list: [],
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: undefined,
},
// ========== 文件上传 ==========
actionUrl: process.env.VUE_APP_BASE_API + "/admin-api/mp/material/upload-permanent", // 上传永久素材的地址
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
fileList: [],
uploadData: {
"type": 'image',
// "accountId": 1,
},
// ========== 草稿新建 or 修改 ==========
dialogNewsVisible: false,
addMaterialLoading: false, // 添加草稿的 loading 标识
articlesAdd: [],
isActiveAddNews: 0,
dialogImageVisible: false,
operateMaterial: 'add',
articlesMediaId: '',
hackResetEditor: false,
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id);
}
// 加载数据
this.getList();
})
},
methods: {
// ======================== 列表查询 ========================
/** 设置账号编号 */
setAccountId(accountId) {
this.queryParams.accountId = accountId;
this.uploadData.accountId = accountId;
},
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询草稿箱')
return false
}
this.loading = true
getDraftPage(this.queryParams).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
// 默认选中第一个
if (this.queryParams.accountId) {
this.setAccountId(this.queryParams.accountId)
}
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id)
}
this.handleQuery()
},
// ======================== 新增/修改草稿 ========================
/** 新增按钮操作 */
handleAdd() {
this.resetEditor();
this.reset();
// 打开表单,并设置初始化
this.operateMaterial = 'add'
this.dialogNewsVisible = true
},
/** 更新按钮操作 */
handleUpdate(item){
this.resetEditor();
this.reset();
this.articlesMediaId = item.mediaId
this.articlesAdd = JSON.parse(JSON.stringify(item.content.newsItem))
// 打开表单,并设置初始化
this.operateMaterial = 'edit'
this.dialogNewsVisible = true
},
/** 提交按钮 */
submitForm() {
this.addMaterialLoading = true
if (this.operateMaterial === 'add') {
createDraft(this.queryParams.accountId, this.articlesAdd).then(response => {
this.$modal.msgSuccess("新增成功");
this.dialogNewsVisible = false;
this.getList()
}).finally(() => {
this.addMaterialLoading = false
})
} else {
updateDraft(this.queryParams.accountId, this.articlesMediaId, this.articlesAdd).then(response => {
this.$modal.msgSuccess("更新成功");
this.dialogNewsVisible = false;
this.getList()
}).finally(() => {
this.addMaterialLoading = false
})
}
},
// 关闭弹窗
dialogNewsClose(done) {
this.$modal.confirm('修改内容可能还未保存,确定关闭吗?').then(() => {
this.reset()
this.resetEditor()
done()
}).catch(() => {})
},
// 表单重置
reset() {
this.isActiveAddNews = 0
this.articlesAdd = [this.buildEmptyArticle()]
},
// 表单 Editor 重置
resetEditor() {
this.hackResetEditor = false // 销毁组件
this.$nextTick(() => {
this.hackResetEditor = true // 重建组件
})
},
// 将图文向下移动
downNews(index) {
let temp = this.articlesAdd[index]
this.articlesAdd[index] = this.articlesAdd[index+1]
this.articlesAdd[index + 1] = temp
this.isActiveAddNews = index + 1
},
// 将图文向上移动
upNews(index) {
let temp = this.articlesAdd[index]
this.articlesAdd[index] = this.articlesAdd[index - 1]
this.articlesAdd[index - 1] = temp
this.isActiveAddNews = index - 1
},
// 选中指定 index 的图文
activeNews(index) {
this.resetEditor();
this.isActiveAddNews = index
},
// 删除指定 index 的图文
minusNews(index) {
this.$modal.confirm('确定删除该图文吗?').then(() => {
this.articlesAdd.splice(index,1);
if (this.isActiveAddNews === index) {
this.isActiveAddNews = 0
}
}).catch(() => {})
},
// 添加一个图文
plusNews() {
this.articlesAdd.push(this.buildEmptyArticle())
this.isActiveAddNews = this.articlesAdd.length - 1
},
// 创建空的 article
buildEmptyArticle() {
return {
"title": '',
"thumbMediaId": '',
"author": '',
"digest": '',
"showCoverPic": '',
"content": '',
"contentSourceUrl": '',
"needOpenComment":'',
"onlyFansCanComment":'',
"thumbUrl":''
}
},
// ======================== 文件上传 ========================
beforeThumbImageUpload(file) {
this.addMaterialLoading = true
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!')
this.addMaterialLoading = false
return false;
}
const isLt = file.size / 1024 / 1024 < 2
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!')
this.addMaterialLoading = false
return false;
}
// 校验通过
return true;
},
handleUploadSuccess(response, file, fileList) {
this.addMaterialLoading = false // 关闭 loading
if (response.code !== 0) {
this.$message.error('上传出错:' + response.msg)
return false;
}
// 重置上传文件的表单
this.fileList = []
// 设置草稿的封面字段
this.articlesAdd[this.isActiveAddNews].thumbMediaId = response.data.mediaId
this.articlesAdd[this.isActiveAddNews].thumbUrl = response.data.url
},
// 选择 or 上传完素材,设置回草稿
selectMaterial(item) {
this.dialogImageVisible = false
this.articlesAdd[this.isActiveAddNews].thumbMediaId = item.mediaId
this.articlesAdd[this.isActiveAddNews].thumbUrl = item.url
},
// 打开素材选择
openMaterial() {
this.dialogImageVisible = true
try {
this.$refs['materialSelect'].queryParams.accountId = this.queryParams.accountId // 强制设置下 accountId避免二次查询不对
this.$refs['materialSelect'].handleQuery(); // 刷新列表,失败也无所谓
} catch (e) {}
},
// ======================== 草稿箱发布 ========================
handlePublish(item) {
const accountId = this.queryParams.accountId;
const mediaId = item.mediaId;
const content = '你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。';
this.$modal.confirm(content).then(function() {
return submitFreePublish(accountId, mediaId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("发布成功");
}).catch(() => {});
},
handleDelete(item) {
const accountId = this.queryParams.accountId;
const mediaId = item.mediaId;
this.$modal.confirm('此操作将永久删除该草稿, 是否继续?').then(function() {
return deleteDraft(accountId, mediaId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
/*新增图文*/
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #165dff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
/*新增图文*/
/*瀑布流样式*/
.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;
}
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;
}
}
/*瀑布流样式*/
.news-main {
background-color: #FFFFFF;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #FFFFFF;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #FFFFFF;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,395 +0,0 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
优化代码和项目的代码保持一致
-->
<template>
<div class="app-container">
<doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 列表 -->
<div class="waterfall" v-loading="loading">
<div v-if="item.content && item.content.newsItem" class="waterfall-item" v-for="item in list"
:key='item.articleId'>
<wx-news :articles="item.content.newsItem" />
<!-- 操作 -->
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="handleDelete(item)"
v-hasPermi="['mp:free-publish:delete']" />
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</div>
</template>
<script>
import { getFreePublishPage, deleteFreePublish } from "@/api/mp/freePublish";
import { getSimpleAccounts } from "@/api/mp/account";
import WxNews from '@/views/mp/components/wx-news/main.vue';
export default {
name: 'MpFreePublish',
components: {
WxNews
},
data() {
return {
// 遮罩层
loading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 已发表列表
list: [],
// 查询参数
queryParams: {
total: 0, // 总页数
currentPage: 1, // 当前页数
queryParamsSize: 10 // 每页显示多少条
},
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询已发表图文')
return false
}
this.loading = true;
getFreePublishPage(this.queryParams).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery();
},
/** 删除按钮操作 */
handleDelete(item){
const articleId = item.articleId;
const accountId = this.queryParams.accountId;
this.$modal.confirm('删除后用户将无法访问此页面,确定删除?').then(function() {
return deleteFreePublish(accountId, articleId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
/*新增图文*/
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #165dff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
/*新增图文*/
/*瀑布流样式*/
.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;
}
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;
}
}
/*瀑布流样式*/
.news-main {
background-color: #FFFFFF;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #FFFFFF;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #FFFFFF;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,440 +0,0 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
清理冗余 css 内容清理冗余 data 变量
美化样式支持播放提升使用体验
优化代码特别是方法名和变量提升可读性
-->
<template>
<div class="app-container">
<doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="type" @tab-click="handleClick">
<!-- tab 1图片 -->
<el-tab-pane name="image">
<span slot="label"><i class="el-icon-picture"></i> 图片</span>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
<el-button size="mini" type="primary">点击上传</el-button>
<sapn slot="tip" class="el-upload__tip" style="margin-left: 5px">支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</sapn>
</el-upload>
</div>
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key='item.id'>
<a target="_blank" :href="item.url">
<img class="material-img" :src="item.url">
<div class="item-name">{{item.name}}</div>
</a>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="handleDelete(item)"
v-hasPermi="['mp:material:delete']"/>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</el-tab-pane>
<!-- tab 2语音 -->
<el-tab-pane name="voice">
<span slot="label"><i class="el-icon-microphone"></i> 语音</span>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:on-success="handleUploadSuccess" :before-upload="beforeVoiceUpload">
<el-button size="mini" type="primary">点击上传</el-button>
<span slot="tip" class="el-upload__tip" style="margin-left: 5px">格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s</span>
</el-upload>
</div>
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px;">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="语音" align="center">
<template v-slot="scope">
<wx-voice-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button type="text" icon="el-icon-download" size="small" plain @click="handleDownload(scope.row)">下载</el-button>
<el-button type="text" icon="el-icon-delete" size="small" plain @click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</el-tab-pane>
<!-- tab 3视频 -->
<el-tab-pane name="video">
<span slot="label"><i class="el-icon-video-play"></i> 视频</span>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-button size="mini" type="primary" @click="handleAddVideo">新建视频</el-button>
</div>
<!-- 新建视频的弹窗 -->
<el-dialog title="新建视频" :visible.sync="dialogVideoVisible" append-to-body width="600px"
v-loading="addMaterialLoading">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeVideoUpload" :on-success="handleUploadSuccess"
ref="uploadVideo" :auto-upload="false">
<el-button slot="trigger" size="mini" type="primary">选择视频</el-button>
<span class="el-upload__tip" style="margin-left: 10px;">格式支持 MP4文件大小不超过 10MB</span>
</el-upload>
<el-form :model="uploadData" :rules="uploadRules" ref="uploadForm" label-width="80px">
<el-row>
<el-form-item label="标题" prop="title">
<el-input v-model="uploadData.title" placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题" />
</el-form-item>
</el-row>
<el-row>
<el-form-item label="描述" prop="introduction">
<el-input :rows="3" type="textarea" v-model="uploadData.introduction"
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容" />
</el-form-item>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancelVideo"> </el-button>
<el-button type="primary" @click="submitVideo"> </el-button>
</div>
</el-dialog>
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px;">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="介绍" align="center" prop="introduction" />
<el-table-column label="视频" align="center">
<template v-slot="scope">
<wx-video-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button type="text" icon="el-icon-download" size="small" plain @click="handleDownload(scope.row)">下载</el-button>
<el-button type="text" icon="el-icon-delete" size="small" plain @click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import { getSimpleAccounts } from "@/api/mp/account";
import { getMaterialPage, deletePermanentMaterial } from "@/api/mp/material";
import { getAccessToken } from '@/utils/auth'
export default {
name: 'MpMaterial',
components: {
WxVoicePlayer,
WxVideoPlayer
},
data() {
return {
type: 'image',
// 遮罩层
loading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 数据列表
list: [],
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: undefined,
permanent: true,
},
actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-permanent',
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
fileList:[],
uploadData: {
"type": 'image',
"title":'',
"introduction":''
},
// === 视频上传,独有变量 ===
dialogVideoVisible: false,
addMaterialLoading: false,
uploadRules: { // 视频上传的校验规则
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }],
},
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id);
}
// 加载数据
this.getList();
})
},
methods: {
// ======================== 列表查询 ========================
/** 设置账号编号 */
setAccountId(accountId) {
this.queryParams.accountId = accountId;
this.uploadData.accountId = accountId;
},
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询草稿箱')
return false
}
this.loading = true
getMaterialPage({
...this.queryParams,
type: this.type
}).then(response => {
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
// 默认选中第一个
if (this.queryParams.accountId) {
this.setAccountId(this.queryParams.accountId)
}
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id)
}
this.handleQuery()
},
handleClick(tab, event) {
// 设置 type
this.uploadData.type = tab.name
// 从第一页开始查询
this.handleQuery();
},
// ======================== 文件上传 ========================
beforeImageUpload(file) {
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!')
return false;
}
const isLt = file.size / 1024 / 1024 < 2
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!')
return false;
}
this.loading = true
return true;
},
beforeVoiceUpload(file){
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) {
this.$message.error('上传语音格式不对!')
return false;
}
if (!isLt) {
this.$message.error('上传语音大小不能超过 2M!')
return false;
}
this.loading = true
return true;
},
beforeVideoUpload(file){
const isType = file.type === 'video/mp4'
if (!isType) {
this.$message.error('上传视频格式不对!')
return false;
}
const isLt = file.size / 1024 / 1024 < 10
if (!isLt) {
this.$message.error('上传视频大小不能超过 10M!')
return false
}
this.addMaterialLoading = true
return true
},
handleUploadSuccess(response, file, fileList) {
this.loading = false
this.addMaterialLoading = false
if (response.code !== 0) {
this.$message.error('上传出错:' + response.msg)
return false;
}
// 清空上传时的各种数据
this.dialogVideoVisible = false
this.fileList = []
this.uploadData.title = ''
this.uploadData.introduction = ''
// 加载数据
this.getList()
},
// 下载文件
handleDownload(row) {
window.open(row.url,'_blank')
},
// 提交 video 新建的表单
submitVideo() {
this.$refs['uploadForm'].validate((valid) => {
if (!valid) {
return false;
}
this.$refs.uploadVideo.submit()
})
},
handleAddVideo() {
this.resetVideo();
this.dialogVideoVisible = true
},
/** 取消按钮 */
cancelVideo() {
this.dialogVideoVisible = false;
this.resetVideo();
},
/** 表单重置 */
resetVideo() {
this.fileList = []
this.uploadData.title = ''
this.uploadData.introduction = ''
this.resetForm("uploadForm");
},
// ======================== 其它操作 ========================
handleDelete(item) {
const id = item.id
this.$modal.confirm('此操作将永久删除该文件, 是否继续?').then(function() {
return deletePermanentMaterial(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>
<style lang="scss" scoped>
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap:10px;
column-count: 5;
margin-top: 10px; /* 芋道源码:增加 10px避免顶着上面 */
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
.material-img {
width: 100%;
}
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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,714 +0,0 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
less 切到 scss减少对 less less-loader 的依赖
-->
<template>
<div class="app-container">
<doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
<!-- 搜索工作栏 -->
<el-form ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="public-account-management clearfix" v-loading="loading">
<!--左边配置菜单-->
<div class="left">
<div class="weixin-hd">
<div class="weixin-title">{{ name }}</div>
</div>
<div class="weixin-menu menu_main clearfix">
<div class="menu_bottom" v-for="(item, i) of menuList" :key="i" >
<!-- 一级菜单 -->
<div @click="menuClick(i, item)" class="menu_item el-icon-s-fold" :class="{'active': isActive === i}">{{item.name}}</div>
<!-- 以下为二级菜单-->
<div class="submenu" v-if="isSubMenuFlag === i">
<div class="subtitle menu_bottom" v-if="item.children" v-for="(subItem, k) in item.children" :key="k">
<div class="menu_subItem" :class="{'active': isSubMenuActive === i + '' + k}" @click="subMenuClick(subItem, i, k)">
{{subItem.name}}
</div>
</div>
<!-- 二级菜单加号 当长度 小于 5 才显示二级菜单的加号 -->
<div class="menu_bottom menu_addicon" v-if="!item.children || item.children.length < 5" @click="addSubMenu(i,item)">
<i class="el-icon-plus" />
</div>
</div>
</div>
<!-- 一级菜单加号 -->
<div class="menu_bottom menu_addicon" v-if="this.menuList.length < 3" @click="addMenu"><i class="el-icon-plus"></i></div>
</div>
<div class="save_div">
<el-button class="save_btn" type="success" size="small" @click="handleSave" v-hasPermi="['mp:menu:save']">保存并发布菜单</el-button>
<el-button class="save_btn" type="danger" size="small" @click="handleDelete" v-hasPermi="['mp:menu:delete']">清空菜单</el-button>
</div>
</div>
<!--右边配置-->
<div v-if="showRightFlag" class="right">
<div class="configure_page">
<div class="delete_btn">
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteMenu(tempObj)">删除当前菜单</el-button>
</div>
<div>
<span>菜单名称</span>
<el-input class="input_width" v-model="tempObj.name" placeholder="请输入菜单名称" :maxlength="nameMaxLength" clearable />
</div>
<div v-if="showConfigureContent">
<div class="menu_content">
<span>菜单标识</span>
<el-input class="input_width" v-model="tempObj.menuKey" placeholder="请输入菜单 KEY" clearable />
</div>
<div class="menu_content">
<span>菜单内容</span>
<el-select v-model="tempObj.type" clearable placeholder="请选择" class="menu_option">
<el-option v-for="item in menuOptions" :label="item.label" :value="item.value" :key="item.value" />
</el-select>
</div>
<div class="configur_content" v-if="tempObj.type === 'view'">
<span>跳转链接</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable />
</div>
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
<div class="applet">
<span>小程序的 appid </span>
<el-input class="input_width" v-model="tempObj.miniProgramAppId" placeholder="请输入小程序的appid" clearable />
</div>
<div class="applet">
<span>小程序的页面路径</span>
<el-input class="input_width" v-model="tempObj.miniProgramPagePath"
placeholder="请输入小程序的页面路径pages/index" clearable />
</div>
<div class="applet">
<span>小程序的备用网页</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable />
</div>
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟</p>
</div>
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
<el-row>
<div class="select-item" v-if="tempObj && tempObj.replyArticles">
<wx-news :articles="tempObj.replyArticles" />
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteMaterial" />
</el-row>
</div>
<div v-else>
<el-row>
<el-col :span="24" style="text-align: center">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
</el-col>
</el-row>
</div>
<el-dialog title="选择图文" :visible.sync="dialogNewsVisible" width="90%">
<wx-material-select :objData="{type: 'news', accountId: this.accountId}" @selectMaterial="selectMaterial" />
</el-dialog>
</el-row>
</div>
<div class="configur_content" v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'">
<wx-reply-select :objData="tempObj.reply" v-if="hackResetWxReplySelect" />
</div>
</div>
</div>
</div>
<!-- 一进页面就显示的默认页面当点击左边按钮的时候就不显示了-->
<div v-else class="right">
<p>请选择菜单配置</p>
</div>
</div>
</div>
</template>
<script>
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import { deleteMenu, getMenuList, saveMenu } from "@/api/mp/menu";
import { getSimpleAccounts } from "@/api/mp/account";
export default {
name: 'MpMenu',
components: {
WxReplySelect,
WxNews,
WxMaterialSelect
},
data(){
return {
// ======================== 列表查询 ========================
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 查询参数
accountId: undefined,
name:'', // 公众号名
menuList: {
children: [],
},
// ======================== 菜单操作 ========================
isActive: -1,// 一级菜单点中样式
isSubMenuActive: -1, // 一级菜单点中样式
isSubMenuFlag: -1, // 二级菜单显示标志
// ======================== 菜单编辑 ========================
showRightFlag: false, // 右边配置显示默认详情还是配置详情
nameMaxLength: 0, // 菜单名称最大长度1 级是 4 字符2 级是 7 字符;
showConfigureContent: true, // 是否展示配置内容;如果有子菜单,就不显示配置内容
hackResetWxReplySelect: false, // 重置 WxReplySelect 组件
tempObj: {}, // 右边临时变量,作为中间值牵引关系
tempSelfObj: { // 一些临时值放在这里进行判断,如果放在 tempObj由于引用关系menu 也会多了多余的参数
},
dialogNewsVisible: false, // 跳转图文时的素材选择弹窗
menuOptions: [{
value: 'view',
label: '跳转网页'
}, {
value: 'miniprogram',
label: '跳转小程序'
}, {
value: 'click',
label: '点击回复'
}, {
value: 'article_view_limited',
label: '跳转图文消息'
}, {
value: 'scancode_push',
label: '扫码直接返回结果'
}, {
value: 'scancode_waitmsg',
label: '扫码回复'
}, {
value: 'pic_sysphoto',
label: '系统拍照发图'
}, {
value: 'pic_photo_or_album',
label: '拍照或者相册'
}, {
value: 'pic_weixin',
label: '微信相册'
}, {
value: 'location_select',
label: '选择地理位置'
}],
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id);
}
// 加载数据
this.getList();
})
},
methods: {
// ======================== 列表查询 ========================
/** 设置账号编号 */
setAccountId(accountId) {
this.accountId = accountId;
this.name = this.accounts.find(item => item.id === accountId)?.name;
},
getList() {
this.loading = false;
getMenuList(this.accountId).then(response => {
response.data = this.convertMenuList(response.data);
this.menuList = this.handleTree(response.data, "id");
}).finally(() => {
this.loading = false;
})
},
/** 搜索按钮操作 */
handleQuery() {
this.resetForm();
// 默认选中第一个
if (this.accountId) {
this.setAccountId(this.accountId)
}
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm();
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id)
}
this.handleQuery()
},
// 将后端返回的 menuList转换成前端的 menuList
convertMenuList(list) {
const menuList = [];
list.forEach(item => {
const menu = {
...item,
};
if (item.type === 'click' || item.type === 'scancode_waitmsg') {
this.$delete(menu, 'replyMessageType');
this.$delete(menu, 'replyContent');
this.$delete(menu, 'replyMediaId');
this.$delete(menu, 'replyMediaUrl');
this.$delete(menu, 'replyDescription');
this.$delete(menu, 'replyArticles');
menu.reply = {
type: item.replyMessageType,
accountId: item.accountId,
content: item.replyContent,
mediaId: item.replyMediaId,
url: item.replyMediaUrl,
title: item.replyTitle,
description: item.replyDescription,
thumbMediaId: item.replyThumbMediaId,
thumbMediaUrl: item.replyThumbMediaUrl,
articles: item.replyArticles,
musicUrl: item.replyMusicUrl,
hqMusicUrl: item.replyHqMusicUrl,
}
}
menuList.push(menu);
});
return menuList;
},
// 重置表单,清空表单数据
resetForm() {
// 菜单操作
this.isActive = -1;
this.isSubMenuActive = -1;
this.isSubMenuFlag = -1;
// 菜单编辑
this.showRightFlag = false;
this.nameMaxLength = 0;
this.showConfigureContent = 0;
this.hackResetWxReplySelect = true;
this.hackResetWxReplySelect = false;
this.tempObj = {};
this.tempSelfObj = {};
this.dialogNewsVisible = false;
},
// ======================== 菜单操作 ========================
// 一级菜单点击事件
menuClick(i, item) {
// 右侧的表单相关
this.resetEditor();
this.showRightFlag = true; // 右边菜单
this.tempObj = item; // 这个如果放在顶部flag 会没有。因为重新赋值了。
this.tempSelfObj.grand = "1"; // 表示一级菜单
this.tempSelfObj.index = i; // 表示一级菜单索引
this.nameMaxLength = 4
this.showConfigureContent = !(item.children && item.children.length > 0); // 有子菜单,就不显示配置内容
// 左侧的选中
this.isActive = i; // 一级菜单选中样式
this.isSubMenuFlag = i; // 二级菜单显示标志
this.isSubMenuActive = -1; // 二级菜单去除选中样式
},
// 二级菜单点击事件
subMenuClick(subItem, index, k) {
// 右侧的表单相关
this.resetEditor();
this.showRightFlag = true; // 右边菜单
this.tempObj = subItem; // 将点击的数据放到临时变量,对象有引用作用
this.tempSelfObj.grand = "2"; // 表示二级菜单
this.tempSelfObj.index = index; // 表示一级菜单索引
this.tempSelfObj.secondIndex = k; // 表示二级菜单索引
this.nameMaxLength = 7
this.showConfigureContent = true;
// 左侧的选中
this.isActive = -1; // 一级菜单去除样式
this.isSubMenuActive = index + "" + k; // 二级菜单选中样式
},
// 添加横向一级菜单
addMenu() {
const menuKeyLength = this.menuList.length;
const addButton = {
name: "菜单名称",
children: [],
reply: { // 用于存储回复内容
'type': 'text',
'accountId': this.accountId // 保证组件里,可以使用到对应的公众号
}
}
this.$set(this.menuList, menuKeyLength, addButton)
this.menuClick(this.menuKeyLength - 1, addButton)
},
// 添加横向二级菜单item 表示要操作的父菜单
addSubMenu(i, item) {
// 清空父菜单的属性,因为它只需要 name 属性即可
if (!item.children || item.children.length <= 0) {
this.$set( item, 'children',[])
this.$delete( item, 'type')
this.$delete( item, 'menuKey')
this.$delete( item, 'miniProgramAppId')
this.$delete( item, 'miniProgramPagePath')
this.$delete( item, 'url')
this.$delete( item, 'reply')
this.$delete( item, 'articleId')
this.$delete( item, 'replyArticles')
// 关闭配置面板
this.showConfigureContent = false
}
let subMenuKeyLength = item.children.length; // 获取二级菜单key长度
let addButton = {
name: "子菜单名称",
reply: { // 用于存储回复内容
'type': 'text',
'accountId': this.accountId // 保证组件里,可以使用到对应的公众号
}
}
this.$set(item.children, subMenuKeyLength, addButton);
this.subMenuClick(item.children[subMenuKeyLength], i, subMenuKeyLength)
},
// 删除当前菜单
deleteMenu(item) {
this.$modal.confirm('确定要删除吗?').then(() => {
// 删除数据
if (this.tempSelfObj.grand === "1") { // 一级菜单的删除方法
this.menuList.splice(this.tempSelfObj.index, 1);
} else if (this.tempSelfObj.grand === "2") { // 二级菜单的删除方法
this.menuList[this.tempSelfObj.index].children.splice(this.tempSelfObj.secondIndex, 1);
}
// 提示
this.$modal.msgSuccess("删除成功");
// 处理菜单的选中
this.tempObj = {};
this.showRightFlag = false;
this.isActive = -1;
this.isSubMenuActive = -1;
}).catch(() => {});
},
// ======================== 菜单编辑 ========================
handleSave() {
this.$modal.confirm('确定要保证并发布该菜单吗?').then(() => {
this.loading = true
return saveMenu(this.accountId, this.convertMenuFormList());
}).then(() => {
this.getList();
this.$modal.msgSuccess("发布成功");
}).finally(() => {
this.loading = false
});
},
// 表单 Editor 重置
resetEditor() {
this.hackResetWxReplySelect = false // 销毁组件
this.$nextTick(() => {
this.hackResetWxReplySelect = true // 重建组件
})
},
handleDelete() {
this.$modal.confirm('确定要清空所有菜单吗?').then(() => {
this.loading = true
return deleteMenu(this.accountId);
}).then(() => {
this.handleQuery();
this.$modal.msgSuccess("清空成功");
}).catch(() => {}).finally(() => {
this.loading = false
});
},
// 将前端的 menuList转换成后端接收的 menuList
convertMenuFormList() {
const menuList = [];
this.menuList.forEach(item => {
let menu = this.convertMenuForm(item);
menuList.push(menu);
// 处理子菜单
if (!item.children || item.children.length <= 0) {
return;
}
menu.children = [];
item.children.forEach(subItem => {
menu.children.push(this.convertMenuForm(subItem))
})
})
return menuList;
},
// 将前端的 menu转换成后端接收的 menu
convertMenuForm(menu) {
let result = {
...menu,
children: undefined, // 不处理子节点
reply: undefined, // 稍后复制
}
if (menu.type === 'click' || menu.type === 'scancode_waitmsg') {
result.replyMessageType = menu.reply.type;
result.replyContent = menu.reply.content;
result.replyMediaId = menu.reply.mediaId;
result.replyMediaUrl = menu.reply.url;
result.replyTitle = menu.reply.title;
result.replyDescription = menu.reply.description;
result.replyThumbMediaId = menu.reply.thumbMediaId;
result.replyThumbMediaUrl = menu.reply.thumbMediaUrl;
result.replyArticles = menu.reply.articles;
result.replyMusicUrl = menu.reply.musicUrl;
result.replyHqMusicUrl = menu.reply.hqMusicUrl;
}
return result;
},
// ======================== 菜单编辑(素材选择) ========================
openMaterial() {
this.dialogNewsVisible = true
},
selectMaterial(item) {
const articleId = item.articleId;
const articles = item.content.newsItem;
// 提示,针对多图文
if (articles.length > 1) {
this.$alert('您选择的是多图文,将默认跳转第一篇', '提示', {
confirmButtonText: '确定'
})
}
this.dialogNewsVisible = false
// 设置菜单的回复
this.tempObj.articleId = articleId;
this.tempObj.replyArticles = [];
articles.forEach(article => {
this.tempObj.replyArticles.push({
title: article.title,
description: article.digest,
picUrl: article.picUrl,
url: article.url,
})
})
},
deleteMaterial() {
this.$delete(this.tempObj,'articleId')
this.$delete(this.tempObj,'replyArticles')
},
},
}
</script>
<!--本组件样式-->
<style lang="scss" scoped="scoped">
/* 公共颜色变量 */
.clearfix{*zoom:1;}
.clearfix::after{content: "";display: table; clear: both;}
div{
text-align: left;
}
.weixin-hd{
color: #fff;
text-align: center;
position: relative;
bottom: 426px;
left:0px;
width: 300px;
height:64px;
background: transparent url("assets/menu_head.png") no-repeat 0 0;
background-position: 0 0;
background-size: 100%
}
.weixin-title{
color:#fff;
font-size:14px;
width:100%;
text-align: center;
position:absolute;
top: 33px;
left: 0px;
}
.weixin-menu{
background: transparent url("assets/menu_foot.png") no-repeat 0 0;
padding-left: 43px;
font-size: 12px
}
.menu_option{
width: 40%!important;
}
.public-account-management{
min-width: 1200px;
width: 1200px;
margin: 0 auto;
.left{
float: left;
display: inline-block;
width: 350px;
height: 715px;
background: url("assets/iphone_backImg.png") no-repeat;
background-size: 100% auto;
padding: 518px 25px 88px;
position: relative;
box-sizing: border-box;
/*第一级菜单*/
.menu_main{
.menu_bottom{
position: relative;
float: left;
display: inline-block;
box-sizing: border-box;
width: 85.5px;
text-align: center;
border: 1px solid #ebedee;
background-color: #fff;
cursor: pointer;
&.menu_addicon{
height: 46px;
line-height: 46px;
}
.menu_item{
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
width: 100%;
&.active{
border: 1px solid #2bb673;
}
}
.menu_subItem{
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
&.active{
border: 1px solid #2bb673;
}
}
}
i{
color:#2bb673;
}
/*第二级菜单*/
.submenu{
position: absolute;
width: 85.5px;
bottom: 45px;
.subtitle{
background-color: #fff;
box-sizing: border-box;
}
}
}
.save_div{
margin-top: 15px;
text-align: center;
.save_btn{
bottom: 20px;
left: 100px;
}
}
}
/*右边菜单内容*/
.right {
float: left;
width: 63%;
background-color: #e8e7e7;
padding: 20px;
margin-left: 20px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
.configure_page {
.delete_btn {
text-align: right;
margin-bottom: 15px;
}
.menu_content {
margin-top: 20px;
}
.configur_content {
margin-top: 20px;
background-color: #fff;
padding: 20px 10px;
border-radius: 5px
}
.blue {
color:#29b6f6;
margin-top: 10px;
}
.applet{
margin-bottom: 20px;
span{
width: 20%;
}
}
.input_width {
width: 40%;
}
.material{
.input_width{
width: 30%;
}
.el-textarea{
width: 80%
}
}
}
}
.el-input {
width: 70%;
margin-right: 2%;
}
}
</style>
<!--素材样式-->
<style lang="scss" scoped>
.pagination {
text-align: right;
margin-right: 25px;
}
.select-item {
width: 280px;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.select-item2 {
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.ope-row {
padding-top: 10px;
text-align: center;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
text-align: center;
}
</style>

View File

@@ -1,238 +0,0 @@
<template>
<div class="app-container">
<doc-alert title="公众号消息" url="https://doc.iocoder.cn/mp/message/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<!-- TODO 等待处理 -->
<el-form-item label="消息类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input v-model="queryParams.openid" placeholder="请输入用户标识" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="发送时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="消息类型" align="center" prop="type" width="80"/>
<el-table-column label="发送方" align="center" prop="sendFrom" width="80">
<template v-slot="scope">
<el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
<el-tag v-else type="info">公众号</el-tag>
</template>
</el-table-column>
<el-table-column label="用户标识" align="center" prop="openid" width="300" />
<el-table-column label="内容" prop="content">
<template v-slot="scope">
<!-- 事件区域 -->
<div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'">
<el-tag type="success" size="mini">关注</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'">
<el-tag type="danger" size="mini">取消关注</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'">
<el-tag size="mini">点击菜单</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'">
<el-tag size="mini">点击菜单链接</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'">
<el-tag size="mini">扫码结果</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'">
<el-tag size="mini">扫码结果</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'">
<el-tag size="mini">系统拍照发图</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_photo_or_album'">
<el-tag size="mini">拍照或者相册</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'">
<el-tag size="mini">微信相册</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'">
<el-tag size="mini">选择地理位置</el-tag>
</div>
<div v-else-if="scope.row.type === 'event'">
<el-tag type="danger" size="mini">未知事件类型</el-tag>
</div>
<!-- 消息区域 -->
<div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div>
<div v-else-if="scope.row.type === 'voice'">
<wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" />
</div>
<div v-else-if="scope.row.type === 'image'">
<a target="_blank" :href="scope.row.mediaUrl">
<img :src="scope.row.mediaUrl" style="width: 100px">
</a>
</div>
<div v-else-if="scope.row.type === 'video' || scope.row.type === 'shortvideo'">
<wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" />
</div>
<div v-else-if="scope.row.type === 'link'">
<el-tag size="mini">链接</el-tag>
<a :href="scope.row.url" target="_blank">{{scope.row.title}}</a>
</div>
<div v-else-if="scope.row.type === 'location'">
<wx-location :label="scope.row.label" :location-y="scope.row.locationY" :location-x="scope.row.locationX" />
</div>
<div v-else-if="scope.row.type === 'music'">
<wx-music :title="scope.row.title" :description="scope.row.description" :thumb-media-url="scope.row.thumbMediaUrl"
:music-url="scope.row.musicUrl" :hq-music-url="scope.row.hqMusicUrl" />
</div>
<div v-else-if="scope.row.type === 'news'">
<wx-news :articles="scope.row.articles" />
</div>
<div v-else>
<el-tag type="danger" size="mini">未知消息类型</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleSend(scope.row)"
v-hasPermi="['mp:message:send']">消息
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 发送消息的弹窗 -->
<el-dialog title="粉丝消息列表" :visible.sync="open" width="50%">
<wx-msg :user-id="userId" v-if="open" />
</el-dialog>
</div>
</template>
<script>
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxMsg from '@/views/mp/components/wx-msg/main.vue';
import WxLocation from '@/views/mp/components/wx-location/main.vue';
import WxMusic from '@/views/mp/components/wx-music/main.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import { getMessagePage } from "@/api/mp/message";
import { getSimpleAccounts } from "@/api/mp/account";
export default {
name: "MpMessage",
components: {
WxVideoPlayer,
WxVoicePlayer,
WxMsg,
WxLocation,
WxMusic,
WxNews
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 粉丝消息列表
list: [],
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
openid: null,
accountId: null,
type: null,
createTime: []
},
// 操作的用户编号
userId: 0,
// 公众号账号列表
accounts: []
};
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询消息')
return false
}
this.loading = true;
// 执行查询
getMessagePage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery();
},
handleSend(row) {
this.userId = row.userId;
this.open = true;
},
}
};
</script>

View File

@@ -1,364 +0,0 @@
<template>
<div class="app-container">
<doc-alert title="公众号统计" url="https://doc.iocoder.cn/mp/statistics/" />
<!-- 搜索工作栏 -->
<el-form ref="queryForm" size="small" :inline="true" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="accountId" @change="getSummary">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="时间范围" prop="date">
<el-date-picker v-model="date" style="width: 260px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:picker-options="datePickerOptions" :default-time="['00:00:00', '23:59:59']"
@change="getSummary">
</el-date-picker>
</el-form-item>
</el-form>
<!-- 图表 -->
<el-row>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>用户增减数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="userSummaryChart" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>累计用户数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="userCumulateChart" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>消息概况数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="upstreamMessageChart" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>接口分析数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="interfaceSummaryChart" style="height: 420px" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
// 引入基本模板
import * as echarts from 'echarts'
// 引入柱状图组件
require('echarts/lib/chart/bar')
// 引入柱拆线组件
require('echarts/lib/chart/line')
// 引入提示框和title组件
require('echarts/lib/component/tooltip')
require('echarts/lib/component/title')
require('echarts/lib/component/legend')
import { getInterfaceSummary, getUserSummary, getUserCumulate, getUpstreamMessage} from '@/api/mp/statistics'
import { datePickerOptions } from "@/utils/constants";
import {addTime, beginOfDay, betweenDay, endOfDay, formatDate} from "@/utils/dateUtils";
import { getSimpleAccounts } from "@/api/mp/account";
export default {
name: 'MpStatistics',
data() {
return {
date : [beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), // -7 天
endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24))], // -1 天
accountId: undefined,
accounts: [],
xAxisDate: [], // X 轴的日期范围
userSummaryOption: { // 用户增减数据
color: ['#67C23A', '#e5323e'],
legend: {
data: ['新增用户','取消关注的用户']
},
tooltip: {},
xAxis: {
data: [] // X 轴的日期范围
},
yAxis: {
minInterval: 1
},
series: [{
name: '新增用户',
type: 'bar',
label: {
normal: {
show: true
}
},
barGap: 0,
data: [] // 新增用户的数据
}, {
name: '取消关注的用户',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 取消关注的用户的数据
}]
},
userCumulateOption: { // 累计用户数据
legend: {
data: ['累计用户量']
},
xAxis: {
type: 'category',
data: []
},
yAxis: {
minInterval: 1
},
series: [{
name:'累计用户量',
data: [], // 累计用户量的数据
type: 'line',
smooth: true,
label: {
normal: {
show: true
}
}
}]
},
upstreamMessageOption: { // 消息发送概况数据
color: ['#67C23A', '#e5323e'],
legend: {
data: ['用户发送人数', '用户发送条数']
},
tooltip: {},
xAxis: {
data: [] // X 轴的日期范围
},
yAxis: {
minInterval: 1
},
series: [{
name: '用户发送人数',
type: 'line',
smooth: true,
label: {
normal: {
show: true
}
},
data: [] // 用户发送人数的数据
}, {
name: '用户发送条数',
type: 'line',
smooth: true,
label: {
normal: {
show: true
}
},
data: [] // 用户发送条数的数据
}]
},
interfaceSummaryOption: { // 接口分析况数据
color: ['#67C23A', '#e5323e', '#E6A23C', '#409EFF'],
legend: {
data: ['被动回复用户消息的次数','失败次数', '最大耗时','总耗时']
},
tooltip: {},
xAxis: {
data: [] // X 轴的日期范围
},
yAxis: {},
series: [{
name: '被动回复用户消息的次数',
type: 'bar',
label: {
normal: {
show: true
}
},
barGap: 0,
data: [] // 被动回复用户消息的次数的数据
}, {
name: '失败次数',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 失败次数的数据
}, {
name: '最大耗时',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 最大耗时的数据
}, {
name: '总耗时',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 总耗时的数据
}]
},
// 静态变量
datePickerOptions: datePickerOptions,
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.accountId = this.accounts[0].id;
}
// 加载数据
this.getSummary();
})
},
methods: {
getSummary() {
// 如果没有选中公众号账号,则进行提示。
if (!this.accountId) {
this.$message.error('未选中公众号,无法统计数据')
return false
}
// 必须选择 7 天内,因为公众号有时间跨度限制为 7
if (betweenDay(this.date[0], this.date[1]) >= 7) {
this.$message.error('时间间隔 7 天以内,请重新选择')
return false
}
this.xAxisDate = []
const days = betweenDay(this.date[0], this.date[1]) // 相差天数
for(let i = 0; i <= days; i++){
this.xAxisDate.push(formatDate(addTime(this.date[0], 3600 * 1000 * 24 * i), 'yyyy-MM-dd'));
}
// 初始化图表
this.initUserSummaryChart();
this.initUserCumulateChart();
this.initUpstreamMessageChart();
this.interfaceSummaryChart();
},
initUserSummaryChart() {
this.userSummaryOption.xAxis.data = [];
this.userSummaryOption.series[0].data = [];
this.userSummaryOption.series[1].data = [];
getUserSummary({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.userSummaryOption.xAxis.data = this.xAxisDate;
// 处理数据
this.xAxisDate.forEach((date, index) => {
response.data.forEach((item) => {
// 匹配日期
const refDate = formatDate(new Date(item.refDate), 'yyyy-MM-dd');
if (refDate.indexOf(date) === -1) {
return;
}
// 设置数据到对应的位置
this.userSummaryOption.series[0].data[index] = item.newUser;
this.userSummaryOption.series[1].data[index] = item.cancelUser;
})
})
// 绘制图表
const userSummaryChart = echarts.init(this.$refs.userSummaryChart);
userSummaryChart.setOption(this.userSummaryOption)
}).catch(() => {})
},
initUserCumulateChart() {
this.userCumulateOption.xAxis.data = [];
this.userCumulateOption.series[0].data = [];
// 发起请求
getUserCumulate({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.userCumulateOption.xAxis.data = this.xAxisDate;
// 处理数据
response.data.forEach((item, index) => {
this.userCumulateOption.series[0].data[index] = item.cumulateUser;
})
// 绘制图表
const userCumulateChart = echarts.init(this.$refs.userCumulateChart);
userCumulateChart.setOption(this.userCumulateOption)
}).catch(() => {})
},
initUpstreamMessageChart() {
this.upstreamMessageOption.xAxis.data = [];
this.upstreamMessageOption.series[0].data = [];
this.upstreamMessageOption.series[1].data = [];
// 发起请求
getUpstreamMessage({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.upstreamMessageOption.xAxis.data = this.xAxisDate;
// 处理数据
response.data.forEach((item, index) => {
this.upstreamMessageOption.series[0].data[index] = item.messageUser;
this.upstreamMessageOption.series[1].data[index] = item.messageCount;
})
// 绘制图表
const upstreamMessageChart = echarts.init(this.$refs.upstreamMessageChart);
upstreamMessageChart.setOption(this.upstreamMessageOption);
}).catch(() => {})
},
interfaceSummaryChart() {
this.interfaceSummaryOption.xAxis.data = [];
this.interfaceSummaryOption.series[0].data = [];
this.interfaceSummaryOption.series[1].data = [];
this.interfaceSummaryOption.series[2].data = [];
this.interfaceSummaryOption.series[3].data = [];
// 发起请求
getInterfaceSummary({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.interfaceSummaryOption.xAxis.data = this.xAxisDate;
// 处理数据
response.data.forEach((item, index) => {
this.interfaceSummaryOption.series[0].data[index] = item.callbackCount;
this.interfaceSummaryOption.series[1].data[index] = item.failCount;
this.interfaceSummaryOption.series[2].data[index] = item.maxTimeCost;
this.interfaceSummaryOption.series[3].data[index] = item.totalTimeCost;
})
// 绘制图表
const interfaceSummaryChart = echarts.init(this.$refs.interfaceSummaryChart);
interfaceSummaryChart.setOption(this.interfaceSummaryOption);
}).catch(() => {})
}
}
}
</script>

View File

@@ -1,243 +0,0 @@
<template>
<div class="app-container">
<doc-alert title="公众号标签" url="https://doc.iocoder.cn/mp/tag/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="标签名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入标签名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:tag:create']">新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-refresh" size="mini" @click="handleSync"
v-hasPermi="['mp:tag:sync']">同步
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id"/>
<el-table-column label="标签名称" align="center" prop="name"/>
<el-table-column label="粉丝数" align="center" prop="count"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:tag:update']">修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['mp:tag:delete']">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="标签名称" prop="name">
<el-input v-model="form.name" placeholder="请输入标签名称"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
createTag,
updateTag,
deleteTag,
getTag,
getTagPage,
syncTag,
} from '@/api/mp/tag'
import { getSimpleAccounts} from '@/api/mp/account'
export default {
name: 'MpTag',
components: {},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 公众号标签列表
list: [],
// 弹出层标题
title: '',
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
accountId: null,
name: null,
},
// 表单参数
form: {
accountId: undefined,
name: undefined,
},
// 表单校验
rules: {
name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]
},
// 公众号账号列表
accounts: []
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询标签')
return false
}
this.loading = false
// 处理查询参数
let params = {...this.queryParams}
// 执行查询
getTagPage(params).then(response => {
this.list = response.data.list
this.total = response.data.total
this.loading = false
})
},
/** 取消按钮 */
cancel() {
this.open = false
this.reset()
},
/** 表单重置 */
reset() {
this.form = {
accountId: undefined,
name: undefined,
}
this.resetForm('form')
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery()
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = '添加公众号标签'
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const id = row.id
getTag(id).then(response => {
this.form = response.data
this.open = true
this.title = '修改公众号标签'
})
},
/** 提交按钮 */
submitForm() {
this.$refs['form'].validate(valid => {
if (!valid) {
return
}
this.form.accountId = this.queryParams.accountId;
// 修改的提交
if (this.form.id != null) {
updateTag(this.form).then(response => {
this.$modal.msgSuccess('修改成功')
this.open = false
this.getList()
})
return
}
// 添加的提交
createTag(this.form).then(response => {
this.$modal.msgSuccess('新增成功')
this.open = false
this.getList()
})
})
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id
this.$modal.confirm('是否确认删除公众号标签编号为"' + id + '"的数据项?').then(function () {
return deleteTag(id)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {
})
},
/** 同步标签 */
handleSync() {
const accountId = this.queryParams.accountId
this.$modal.confirm('是否确认同步标签?').then(function () {
return syncTag(accountId)
}).then(() => {
this.$modal.msgSuccess('同步标签成功')
}).catch(() => {
})
},
}
}
</script>

View File

@@ -1,236 +0,0 @@
<template>
<div class="app-container">
<doc-alert title="公众号粉丝" url="https://doc.iocoder.cn/mp/user/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input v-model="queryParams.openid" placeholder="请输入用户标识" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="queryParams.nickname" placeholder="请输入昵称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-refresh" size="mini" @click="handleSync"
v-hasPermi="['mp:user:sync']">同步
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="用户标识" align="center" prop="openid" width="260" />
<el-table-column label="昵称" align="center" prop="nickname" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="标签" align="center" prop="tagIds" width="200">
<template v-slot="scope">
<span v-for="(tagId, index) in scope.row.tagIds" :key="index">
<el-tag>{{ tags.find(tag => tag.tagId === tagId)?.name }} </el-tag>&nbsp;
</span>
</template>
</el-table-column>
<el-table-column label="订阅状态" align="center" prop="subscribeStatus">
<template v-slot="scope">
<el-tag v-if="scope.row.subscribeStatus === 0" type="success">已订阅</el-tag>
<el-tag v-else type="danger">未订阅</el-tag>
</template>
</el-table-column>
<el-table-column label="订阅时间" align="center" prop="subscribeTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.subscribeTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:user:update']">修改</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="昵称" prop="nickname">
<el-input v-model="form.nickname" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="标签" prop="tagIds">
<el-select v-model="form.tagIds" multiple clearable placeholder="请选择标签">
<el-option v-for="item in tags" :key="parseInt(item.tagId)" :label="item.name" :value="parseInt(item.tagId)" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { updateUser, getUser, getUserPage, syncUser } from "@/api/mp/mpuser";
import { getSimpleAccounts } from "@/api/mp/account";
import { getSimpleTags } from "@/api/mp/tag";
export default {
name: "MpUser",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 微信公众号粉丝列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: null,
openid: null,
nickname: null,
},
// 表单参数
form: {},
// 表单校验
rules: {},
// 公众号账号列表
accounts: [],
// 公众号标签列表
tags: [],
};
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
// 加载标签
getSimpleTags().then(response => {
this.tags = response.data;
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询用户')
return false
}
this.loading = true;
// 处理查询参数
let params = {...this.queryParams};
// 执行查询
getUserPage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: [],
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery();
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getUser(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改公众号粉丝";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 修改的提交
if (this.form.id != null) {
updateUser(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
}
});
},
/** 同步标签 */
handleSync() {
const accountId = this.queryParams.accountId
this.$modal.confirm('是否确认同步粉丝?').then(function () {
return syncUser(accountId)
}).then(() => {
this.$modal.msgSuccess('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
}).catch(() => {
})
},
}
};
</script>

View File

@@ -55,7 +55,6 @@
:has-files="true"
:disabled="editMode === 'detail'"
:rows="computedRows" />
<!-- :has-files="['files', 'files2']" -->
</base-dialog>
<!-- 设备 详情 - 编辑 -->
@@ -134,7 +133,7 @@ export default {
components: {
Editor,
EquipmentDrawer,
BaseDialog: BaseDialogWrapper
BaseDialog: BaseDialogWrapper,
},
mixins: [basicPageMixin],
data() {
@@ -161,13 +160,6 @@ export default {
: undefined,
].filter((v) => v),
tableProps: [
// {
// prop: 'createTime',
// label: '添加时间',
// fixed: true,
// width: 180,
// filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
// },
{ prop: 'name', label: '设备名称' },
{ width: 256, prop: 'code', label: '设备编码' },
{ prop: 'location', label: '位置' },
@@ -413,34 +405,16 @@ export default {
this.$nextTick(() => {
this.$refs['drawer'].init();
});
// this.reset();
// this.showUploadComponents = false;
// this.editMode = 'edit';
// const id = row.id;
// getEquipment(id).then((response) => {
// this.form = response.data;
// this.open = true;
// this.title = '修改设备';
// });
},
handleDetail(row, mode='detail') {
const {id} = row;
handleDetail(row, mode = 'detail') {
const { id } = row;
this.form.id = id;
// 打开抽屉
this.editMode = mode;
this.editVisible = true;
this.$nextTick(() => {
this.$refs['drawer'].init();
})
// this.reset();
// this.showUploadComponents = false;
// const id = row.id;
// this.editMode = 'detail';
// getEquipment(id).then((response) => {
// this.form = response.data;
// this.open = true;
// this.title = '查看详情';
// });
});
},
/** 提交按钮 */
submitForm() {
@@ -514,33 +488,6 @@ export default {
this.$refs['drawer'].init();
});
},
// overwrite basicPageMixin 里的 处理表格按钮 方法
// handleTableBtnClick({ data, type }) {
// switch (type) {
// case 'edit':
// this.reset();
// this.editMode = 'edit';
// this.showUploadComponents = true;
// this.form.id = data.id;
// this.editVisible = true;
// this.$nextTick(() => {
// this.$refs['drawer'].init();
// });
// break;
// case 'delete':
// this.handleDelete(data);
// break;
// case 'detail':
// const { id } = data;
// this.viewDetail(id);
// break;
// }
// },
},
};
</script>
<style scoped lang="scss">
.SpecialEquipmentForFireFighting {
}
</style>

View File

@@ -177,7 +177,7 @@ export default {
</script>
<style scoped>
@import './iconfont/iconfont.css';
@import '../../../styles/iconfont/iconfont.css';
.delete-color {
color: #ff5454;
}

View File

@@ -73,7 +73,7 @@
<el-date-picker
v-model="dataForm.planCheckTime"
type="datetime"
placeholder="请选择计划开始时间"
placeholder="请选择巡检时间"
value-format="timestamp"></el-date-picker>
</el-form-item>
</el-col>
@@ -107,6 +107,9 @@ export default {
name: [
{ required: true, message: '巡检单名称不能为空', trigger: 'blur' },
],
planCheckTime: [
{ required: true, message: '巡检时间不能为空', trigger: 'blur' },
],
},
equipmentOptions: [],
groupOptions: [],

View File

@@ -145,7 +145,7 @@ export default {
equipmentId: null,
},
attrFormVisible: false,
attrRows: [
attrRows: [
[
{
select: true,
@@ -157,16 +157,8 @@ export default {
],
bind: {
clearable: true,
filterable: true
}
},
{
input: true,
label: '巡检项目',
prop: 'program',
rules: [
{ required: true, message: '巡检项目不能为空', trigger: 'blur' },
],
filterable: true,
},
},
],
[

View File

@@ -20,7 +20,9 @@
<div class="drawer-body flex">
<div class="drawer-body__content">
<div class="form-part" style="margin-bottom: 32px">
<div
class="form-part"
style="margin-bottom: 32px">
<!-- <el-skeleton v-if="!showForm" animated /> -->
<el-form
class="equipment-info-form"
@@ -49,7 +51,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="部门" prop="departmentId">
<el-form-item
label="部门"
prop="departmentId">
<el-select
v-model="form.departmentId"
filterable
@@ -66,7 +70,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="班次" prop="groupClass">
<el-form-item
label="班次"
prop="groupClass">
<el-select
v-model="form.groupClass"
filterable
@@ -85,7 +91,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="巡检人" prop="checkPerson">
<el-form-item
label="巡检人"
prop="checkPerson">
<!-- :rules="[
{
required: true,
@@ -110,7 +118,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="巡检时间" prop="planCheckTime">
<el-form-item
label="巡检时间"
prop="planCheckTime">
<el-date-picker
v-model="form.planCheckTime"
type="datetime"
@@ -142,7 +152,9 @@
<div
v-if="!disableEdit && !disableModifyDetail"
style="position: absolute; top: -40px; right: 0">
<el-button @click="handleAddAttr" type="text">
<el-button
@click="handleAddAttr"
type="text">
<i class="el-icon-plus"></i>
添加内容
</el-button>
@@ -173,7 +185,9 @@
</div>
<div class="drawer-body__footer">
<el-button v-if="!disableEdit" @click="handleCancel">
<el-button
v-if="!disableEdit"
@click="handleCancel">
{{ disableEdit ? '返回' : '取消' }}
</el-button>
<el-button
@@ -254,7 +268,7 @@ export default {
bind: {
filterable: true,
clearable: true,
}
},
},
],
[

View File

@@ -39,7 +39,9 @@
@close="cancel"
@cancel="cancel"
@confirm="handleSubmit">
<add ref="add" @refreshDataList="successSubmit" />
<add
ref="add"
@refreshDataList="successSubmit" />
</base-dialog>
<edit

View File

@@ -1,539 +0,0 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -1,38 +0,0 @@
@font-face {
font-family: "iconfont"; /* Project id 3821755 */
src: url('iconfont.eot?t=1689233106339'); /* IE9 */
src: url('iconfont.eot?t=1689233106339#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAARgAAsAAAAACcAAAAQUAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDKAqGSIUsATYCJAMYCw4ABCAFhGcHVBtUCMiuMG7hiaIkox1FaBhxvd1fpwAN4YKo1v7tWTr8hBRkBQgO7yqKUJKPUKzDjkixjn7/q0vlANCkEHCJtsMvx2eQn04h5rHEE8593TK2y1SC5nbvNG5gqgZBNEWTqFUQLGVFNnVu1TjyMm3SafEsC3y0myW12KdEh2x+deP/zzHTJeG3AVBm4lrnAxpQN0US7s3ijuSE6Jbh1UGv08cgQEivMg5z6m49Qj77TVxDEZww6gXicNkviSbkxgswNeGCj+CgMw1yFRpAnUNegSv+8+UfU/igsBpjnRV3rRV9+Su8Fk1Agv6CW4eAeyvAAhronJlI1tcrVnIMHZ0cCcsUTrRTnQ0CP50B4KfgEub9Lw80BougQBwGI8bYJU4LPwUbBw0/HXAw8NORUM90wsT9JeIMFACux9QckHyQ9FDYTobcGi+FLSjwMxDe3L5s+w9THFYeEs8H303FWMnZ1JuBWFgNPh3bd5wOvJmKI9aTqXeD8eJ66HnfnV1dJPE8cy4EgqT+s/SbdSWbp9Pn24NbZ1MnGw7CDbvT6+lL1CXVh++hcH56umDT9vOviL/grQ6srHjE57uLHdQn+dtO9x2v94WN6+eXm0HWlZOzta3tUDp1JWjymsiD8r0ViWGkC+n4GeH5aODSM+9COnilpdfdZsRIvMerLmS0uICTplrOnb99LT3NkJNeajPfpDznblq6bXH8efBjlPIUxhQcX+U0qEdt4bjxy6rMdOkbEn0/3mlvrXZL60ysxdwcn/qKLJ/82pnzGS61z5rBeGmrxf33ZXN/IBWUvb8KSvnttbwwq+YTkweZHue0M0+c0ssZGY/Rs+aVF8+3GaW07CkvLC/KcBvP8c2uGpsdm0THlazwlPE22Kdi9ZK4Hdsmtw9eHVQwPjG0p33Q7tGMJ6TUJ2VK1MMw6ftI6qPPtruCvEPmLZqiHcj1Aw3Rup/sdzj+lt/w3f5TrAHgx++nD7A+8xaD4CL8m60Q+KWYO7CgUY3gTFl+OwUBHKteh00BoWmGDIYm301frAJwKAg+BTEoPCpA41OPNNh2sET0g8NnCkI61M6PKKBzREwANBtGIOS6BEU2D0CT6xvSYP+Apdg/cOT6DyErI68W0RzbqhmYOFixuRNLFqoRi839Zh+A2KqYWJH1CTC7hM04My0j664HDVgXY+xtYhbnBBNGVVzH7geKQrHOqA0sPK2Fc70oPZ2UPSTNQlVUjQEmHLDCzDphEgtKQ9zCPP16DwBRK4UJAyKfyBg7jaeaxzKlyQAg6xkaKO9M2tu1EWXhOEL3IxhKhdWpMqCYDgrTy7vZAAsuTUsLm65IOlWIQBlp47PUS9wAhBgHOFGixYgVJx7+dtqutZuY1eXTvXYncJOkOF1Wicd+CnBACA==') format('woff2'),
url('iconfont.woff?t=1689233106339') format('woff'),
url('iconfont.ttf?t=1689233106339') format('truetype'),
url('iconfont.svg?t=1689233106339#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-downward:before {
content: "\e604";
}
.icon-upward:before {
content: "\e605";
}
.icon-detail:before {
content: "\e601";
}
.icon-edit:before {
content: "\e602";
}
.icon-delete:before {
content: "\e603";
}

File diff suppressed because one or more lines are too long

View File

@@ -1,44 +0,0 @@
{
"id": "3821755",
"name": "component",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "封装的组件中的icon",
"glyphs": [
{
"icon_id": "36426261",
"name": "downward",
"font_class": "downward",
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "36426301",
"name": "upward",
"font_class": "upward",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "33347867",
"name": "detail",
"font_class": "detail",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "33347918",
"name": "edit",
"font_class": "edit",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "33347930",
"name": "delete",
"font_class": "delete",
"unicode": "e603",
"unicode_decimal": 58883
}
]
}

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Created by iconfont</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024">
<font-face
font-family="iconfont"
font-weight="400"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="downward" unicode="&#58884;" d="M556.942222 144.099556l363.064889 401.806222c24.860444 21.617778 24.860444 56.263111 0 77.880889a68.437333 68.437333 0 0 1-44.942222 16.156444c-16.896 0-33.052444-5.859556-44.885333-16.156444L512 260.949333l-318.236444 362.951111a68.437333 68.437333 0 0 1-44.942223 16.099556c-16.896 0-33.109333-5.802667-44.942222-16.156444-24.746667-21.617778-24.746667-56.206222 0-77.824l363.121778-401.749334c5.973333-5.290667 13.141333-9.443556 21.048889-12.231111 23.722667-8.305778 51.029333-3.527111 68.892444 12.060445z" horiz-adv-x="1024" />
<glyph glyph-name="upward" unicode="&#58885;" d="M556.942222 623.900444l363.064889-401.806222c24.860444-21.617778 24.860444-56.263111 0-77.880889a68.437333 68.437333 0 0 0-44.942222-16.156444c-16.896 0-33.052444 5.859556-44.885333 16.156444L512 507.050667l-318.236444-362.951111a68.437333 68.437333 0 0 0-44.942223-16.099556c-16.896 0-33.109333 5.802667-44.942222 16.156444-24.746667 21.617778-24.746667 56.206222 0 77.824L467.057778 623.729778c5.973333 5.290667 13.141333 9.443556 21.048889 12.231111 23.722667 8.305778 51.029333 3.527111 68.892444-12.060445z" horiz-adv-x="1024" />
<glyph glyph-name="detail" unicode="&#58881;" d="M902.428444 705.251556H332.401778a7.964444 7.964444 0 0 1-7.793778-8.021334v-56.206222a7.964444 7.964444 0 0 1 7.793778-8.078222h570.026666A7.964444 7.964444 0 0 1 910.222222 641.024v56.206222a7.964444 7.964444 0 0 1-7.793778 8.021334z m0-285.127112H332.401778a7.964444 7.964444 0 0 1-7.793778-7.964444v-56.32a7.964444 7.964444 0 0 1 7.793778-7.964444h570.026666a7.964444 7.964444 0 0 1 7.793778 7.964444v56.32a7.964444 7.964444 0 0 1-7.793778 7.964444z m0-285.070222H332.401778a7.964444 7.964444 0 0 1-7.793778-8.078222v-56.206222a7.964444 7.964444 0 0 1 7.793778-8.021334h570.026666a7.964444 7.964444 0 0 1 7.793778 8.021334v56.206222a7.964444 7.964444 0 0 1-7.793778 8.078222zM113.777778 669.127111c0-20.081778 10.410667-38.684444 27.306666-48.696889a53.361778 53.361778 0 0 1 54.670223 0 56.547556 56.547556 0 0 1 27.306666 48.696889 56.547556 56.547556 0 0 1-27.306666 48.696889 53.361778 53.361778 0 0 1-54.613334 0A56.547556 56.547556 0 0 1 113.777778 669.127111zM113.777778 384c0-20.081778 10.410667-38.684444 27.306666-48.696889a53.361778 53.361778 0 0 1 54.670223 0A56.547556 56.547556 0 0 1 223.061333 384a56.547556 56.547556 0 0 1-27.306666 48.696889 53.361778 53.361778 0 0 1-54.613334 0A56.547556 56.547556 0 0 1 113.777778 384z m0-285.127111c0-20.081778 10.410667-38.684444 27.306666-48.696889a53.361778 53.361778 0 0 1 54.670223 0 56.547556 56.547556 0 0 1 27.306666 48.696889 56.547556 56.547556 0 0 1-27.306666 48.696889 53.361778 53.361778 0 0 1-54.613334 0 56.547556 56.547556 0 0 1-27.363555-48.696889z" horiz-adv-x="1024" />
<glyph glyph-name="edit" unicode="&#58882;" d="M873.016889 395.264a32.824889 32.824889 0 0 0 65.649778 0v-273.806222a164.124444 164.124444 0 0 0-164.124445-164.124445h-525.084444A164.124444 164.124444 0 0 0 85.333333 121.457778v525.084444A164.124444 164.124444 0 0 0 249.457778 810.666667h312.32a32.824889 32.824889 0 1 0 0-65.649778h-312.32a98.417778 98.417778 0 0 1-98.474667-98.417778v-525.141333c0-54.385778 44.088889-98.474667 98.417778-98.474667h525.141333a98.417778 98.417778 0 0 1 98.474667 98.417778V395.320889z m-14.222222 362.097778a32.824889 32.824889 0 0 0 48.014222-44.771556L548.750222 328.533333a32.824889 32.824889 0 1 0-48.014222 44.771556l358.115556 384.056889z" horiz-adv-x="1024" />
<glyph glyph-name="delete" unicode="&#58883;" d="M601.024 146.24a29.632 29.632 0 0 0-29.696 29.696V503.04a29.632 29.632 0 1 0 59.456 0v-326.848a29.76 29.76 0 0 0-29.76-29.888z m-178.24 0a29.632 29.632 0 0 0-29.696 29.696V503.04a29.632 29.632 0 1 0 59.392 0v-326.848a29.76 29.76 0 0 0-29.696-29.888z m475.52 505.216h-148.544v59.456c0 49.152-39.616 89.088-88.512 89.088H363.392c-49.216 0-89.152-39.936-89.152-89.088v-59.456H125.696a29.632 29.632 0 1 1 0-59.392h772.608a29.632 29.632 0 1 1 0 59.392z m-564.608 59.456c0 16.256 13.44 29.696 29.696 29.696h297.856c16.32 0 29.056-13.12 29.056-29.696v-59.456H333.696v59.456zM720-32h-416a89.152 89.152 0 0 0-89.088 89.088V503.232a29.632 29.632 0 1 0 59.456 0v-446.08c0-16.512 13.44-29.76 29.696-29.76h416.064a29.632 29.632 0 0 1 29.696 29.696V502.144a29.632 29.632 0 1 0 59.456 0v-445.056A89.536 89.536 0 0 0 720-32z" horiz-adv-x="1024" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -20,8 +20,9 @@
<div class="drawer-body flex">
<div class="drawer-body__content">
<div class="form-part" style="margin-bottom: 32px">
<!-- <el-skeleton v-if="!showForm" animated /> -->
<div
class="form-part"
style="margin-bottom: 32px">
<el-form
class="equipment-info-form"
ref="form"
@@ -31,7 +32,9 @@
v-loading="formLoading">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="保养计划单号" prop="maintainOrderNumber">
<el-form-item
label="保养计划单号"
prop="maintainOrderNumber">
<!-- :rules="[
{
required: true,
@@ -47,7 +50,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="保养计划名称" prop="planName">
<el-form-item
label="保养计划名称"
prop="planName">
<el-input
v-model="form.planName"
placeholder="请输入保养计划名称"
@@ -56,10 +61,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="部门" prop="departmentId">
<!-- :rules="[
{ required: true, message: '请选择部门', trigger: 'blur' },
]" -->
<el-form-item
label="部门"
prop="departmentId">
<el-select
v-model="form.departmentId"
:placeholder="`请选择部门`"
@@ -76,7 +80,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="产线" prop="lineId">
<el-form-item
label="产线"
prop="lineId">
<!-- :rules="[
{ required: true, message: '请选择产线', trigger: 'blur' },
]" -->
@@ -96,7 +102,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="计划保养人员" prop="maintainer">
<el-form-item
label="计划保养人员"
prop="maintainer">
<el-select
v-model="form.planMaintainWorker"
placeholder="请选择计划保养人员"
@@ -107,7 +115,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="计划开始时间" prop="planStartTime">
<el-form-item
label="计划开始时间"
prop="planStartTime">
<el-date-picker
v-model="form.planStartTime"
type="datetime"
@@ -118,7 +128,9 @@
</el-col>
<el-col :span="8">
<el-form-item label="计划结束时间" prop="planEndTime">
<el-form-item
label="计划结束时间"
prop="planEndTime">
<el-date-picker
v-model="form.planEndTime"
type="datetime"
@@ -193,8 +205,12 @@
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" :placeholder="`请输入备注`" />
<el-form-item
label="备注"
prop="remark">
<el-input
v-model="form.remark"
:placeholder="`请输入备注`" />
</el-form-item>
</el-col>
</el-row>
@@ -223,13 +239,7 @@
:page="attrQuery?.params.pageNo || 1"
:limit="attrQuery?.params.pageSize || 10"
:table-data="attrList"
@emitFun="handleEmitFun">
<!-- <method-btn
slot="handleBtn"
label="操作"
:method-list="tableBtn"
@clickBtn="handleTableBtnClick" /> -->
</base-table>
@emitFun="handleEmitFun"></base-table>
<!-- 分页组件 -->
<pagination
@@ -242,15 +252,16 @@
</div>
<div class="drawer-body__footer">
<el-button style="" @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleConfirm">保存</el-button>
<!-- sections的第二项必须是 属性列表 -->
<!-- <el-button
v-if="sections[1].allowAdd"
type="primary"
@click="handleAddAttr">
添加属性
</el-button> -->
<el-button
style=""
@click="handleCancel">
取消
</el-button>
<el-button
type="primary"
@click="handleConfirm">
保存
</el-button>
</div>
</div>
@@ -385,12 +396,6 @@ export default {
label: '设备名称',
prop: 'equipmentId',
url: '/base/core-equipment/page?pageNo=1&pageSize=100&special=true',
// method: 'post',
// queryParams: {
// pageNo: 1,
// pageSize: 100,
// special: true,
// },
rules: [
{ required: true, message: '设备不能为空', trigger: 'blur' },
],
@@ -441,7 +446,6 @@ export default {
],
attrFormSubmitting: false,
attrListLoading: false,
// syncFileListFlag: null,
tableBtn: [
{
type: 'edit',
@@ -599,7 +603,6 @@ export default {
'/base/core-production-line/listAll',
'/base/core-department/listAll',
'/base/core-worker/workerList',
// '/base/core-worker/listAll',
];
let res;
switch (source) {
@@ -619,22 +622,6 @@ export default {
this.formLoading = false;
},
// 保存表单
handleSave() {
this.$refs.form.validate(async (valid) => {
if (valid) {
await this.$axios({
url: '/urlupdate', // this.sections[0][isEdit ? 'urlUpdate' : 'urlCreate'],
method: 'post', // isEdit ? 'put' : 'post',
data: this.form,
});
this.$modal.msgSuccess(`${isEdit ? '更新' : '创建'}成功`);
this.visible = false;
this.$emit('refreshDataList');
}
});
},
handleCancel() {
this.visible = false;
},

View File

@@ -20,17 +20,27 @@
type="selection"
:width="50"
:selectable="checkSelectable" />
<el-table-column prop="_pageIndex" width="80" align="center">
<el-table-column
prop="_pageIndex"
width="80"
align="center">
<template slot="header">
<el-popover placement="bottom-start" width="300" trigger="click">
<div class="setting-box" style="max-height: 400px; overflow-y: auto">
<el-popover
placement="bottom-start"
width="300"
trigger="click">
<div
class="setting-box"
style="max-height: 400px; overflow-y: auto">
<el-checkbox
v-for="(item, index) in tablePropsLabelList"
:key="'cb' + index"
v-model="selectedBox[index]"
:label="item.label" />
</div>
<i slot="reference" class="el-icon-s-tools" />
<i
slot="reference"
class="el-icon-s-tools" />
</el-popover>
</template>
</el-table-column>
@@ -39,17 +49,27 @@
width="128"
label="设备保养单号"
prop="maintainOrderNumber"></el-table-column>
<el-table-column v-if="selectedBox[1]" width="128" label="保养计划名称" prop="planName">
<el-table-column
v-if="selectedBox[1]"
width="128"
label="保养计划名称"
prop="planName">
<template slot-scope="scope">
{{ scope.row.planName || '---' }}
</template>
</el-table-column>
<el-table-column v-if="selectedBox[2]" label="部门" prop="departmentName">
<el-table-column
v-if="selectedBox[2]"
label="部门"
prop="departmentName">
<template slot-scope="scope">
{{ scope.row.departmentName || '---' }}
</template>
</el-table-column>
<el-table-column v-if="selectedBox[3]" label="产线名" prop="lineName">
<el-table-column
v-if="selectedBox[3]"
label="产线名"
prop="lineName">
<template slot-scope="scope">
{{ scope.row.lineName || '---' }}
</template>
@@ -108,15 +128,13 @@
{{ scope.row.relatePlan | relatePlanFilter }}
</template>
</el-table-column>
<!-- <el-table-column v-if="selectedBox[10]" width="60" label="详情">
<el-table-column
width="188"
label="操作">
<template slot-scope="scope">
<el-button type="text" @click="handelDetail(scope.row)">详情</el-button>
</template>
</el-table-column> -->
<!-- btns -->
<el-table-column width="188" label="操作">
<template slot-scope="scope">
<el-tooltip content="确认" placement="top">
<el-tooltip
content="确认"
placement="top">
<el-button
type="text"
style="margin: 5px 0; padding: 0"
@@ -127,7 +145,9 @@
</el-tooltip>
<!-- line -->
<span style="margin: 0 4px; font-size: 18px; color: #e5e7eb">|</span>
<el-tooltip content="查看详情" placement="top">
<el-tooltip
content="查看详情"
placement="top">
<el-button
type="text"
style="margin: 5px 0; padding: 0"
@@ -137,7 +157,9 @@
</el-tooltip>
<!-- line -->
<span style="margin: 0 4px; font-size: 18px; color: #e5e7eb">|</span>
<el-tooltip content="编辑" placement="top">
<el-tooltip
content="编辑"
placement="top">
<el-button
type="text"
style="margin: 5px 0; padding: 0"
@@ -147,7 +169,9 @@
</el-tooltip>
<!-- line -->
<span style="margin: 0 4px; font-size: 18px; color: #e5e7eb">|</span>
<el-tooltip content="删除" placement="top">
<el-tooltip
content="删除"
placement="top">
<el-button
type="text"
style="margin: 5px 0; padding: 0"
@@ -254,7 +278,7 @@ export default {
</script>
<style scoped>
@import './iconfont/iconfont.css';
@import '../../../styles/iconfont/iconfont.css';
.delete-color {
color: #ff5454;
}

View File

@@ -31,7 +31,9 @@
<div style="margin-top: 12px; position: relative">
<div style="position: absolute; top: -40px; right: 0">
<el-button @click="handleAddAttr" type="text">
<el-button
@click="handleAddAttr"
type="text">
<i class="el-icon-plus"></i>
添加项目
</el-button>
@@ -61,8 +63,16 @@
</div>
<div class="drawer-body__footer">
<el-button style="" @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleConfirm">保存</el-button>
<el-button
style=""
@click="handleCancel">
取消
</el-button>
<el-button
type="primary"
@click="handleConfirm">
保存
</el-button>
<!-- sections的第二项必须是 属性列表 -->
<!-- <el-button
v-if="sections[1].allowAdd"
@@ -165,12 +175,6 @@ export default {
label: '设备名称',
prop: 'equipmentId',
url: '/base/core-equipment/page?pageNo=1&pageSize=100&special=true',
// method: 'post',
// queryParams: {
// pageNo: 1,
// pageSize: 100,
// special: true,
// },
rules: [
{ required: true, message: '设备不能为空', trigger: 'blur' },
],

View File

@@ -1,539 +0,0 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -1,38 +0,0 @@
@font-face {
font-family: "iconfont"; /* Project id 3821755 */
src: url('iconfont.eot?t=1689233106339'); /* IE9 */
src: url('iconfont.eot?t=1689233106339#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAARgAAsAAAAACcAAAAQUAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDKAqGSIUsATYCJAMYCw4ABCAFhGcHVBtUCMiuMG7hiaIkox1FaBhxvd1fpwAN4YKo1v7tWTr8hBRkBQgO7yqKUJKPUKzDjkixjn7/q0vlANCkEHCJtsMvx2eQn04h5rHEE8593TK2y1SC5nbvNG5gqgZBNEWTqFUQLGVFNnVu1TjyMm3SafEsC3y0myW12KdEh2x+deP/zzHTJeG3AVBm4lrnAxpQN0US7s3ijuSE6Jbh1UGv08cgQEivMg5z6m49Qj77TVxDEZww6gXicNkviSbkxgswNeGCj+CgMw1yFRpAnUNegSv+8+UfU/igsBpjnRV3rRV9+Su8Fk1Agv6CW4eAeyvAAhronJlI1tcrVnIMHZ0cCcsUTrRTnQ0CP50B4KfgEub9Lw80BougQBwGI8bYJU4LPwUbBw0/HXAw8NORUM90wsT9JeIMFACux9QckHyQ9FDYTobcGi+FLSjwMxDe3L5s+w9THFYeEs8H303FWMnZ1JuBWFgNPh3bd5wOvJmKI9aTqXeD8eJ66HnfnV1dJPE8cy4EgqT+s/SbdSWbp9Pn24NbZ1MnGw7CDbvT6+lL1CXVh++hcH56umDT9vOviL/grQ6srHjE57uLHdQn+dtO9x2v94WN6+eXm0HWlZOzta3tUDp1JWjymsiD8r0ViWGkC+n4GeH5aODSM+9COnilpdfdZsRIvMerLmS0uICTplrOnb99LT3NkJNeajPfpDznblq6bXH8efBjlPIUxhQcX+U0qEdt4bjxy6rMdOkbEn0/3mlvrXZL60ysxdwcn/qKLJ/82pnzGS61z5rBeGmrxf33ZXN/IBWUvb8KSvnttbwwq+YTkweZHue0M0+c0ssZGY/Rs+aVF8+3GaW07CkvLC/KcBvP8c2uGpsdm0THlazwlPE22Kdi9ZK4Hdsmtw9eHVQwPjG0p33Q7tGMJ6TUJ2VK1MMw6ftI6qPPtruCvEPmLZqiHcj1Aw3Rup/sdzj+lt/w3f5TrAHgx++nD7A+8xaD4CL8m60Q+KWYO7CgUY3gTFl+OwUBHKteh00BoWmGDIYm301frAJwKAg+BTEoPCpA41OPNNh2sET0g8NnCkI61M6PKKBzREwANBtGIOS6BEU2D0CT6xvSYP+Apdg/cOT6DyErI68W0RzbqhmYOFixuRNLFqoRi839Zh+A2KqYWJH1CTC7hM04My0j664HDVgXY+xtYhbnBBNGVVzH7geKQrHOqA0sPK2Fc70oPZ2UPSTNQlVUjQEmHLDCzDphEgtKQ9zCPP16DwBRK4UJAyKfyBg7jaeaxzKlyQAg6xkaKO9M2tu1EWXhOEL3IxhKhdWpMqCYDgrTy7vZAAsuTUsLm65IOlWIQBlp47PUS9wAhBgHOFGixYgVJx7+dtqutZuY1eXTvXYncJOkOF1Wicd+CnBACA==') format('woff2'),
url('iconfont.woff?t=1689233106339') format('woff'),
url('iconfont.ttf?t=1689233106339') format('truetype'),
url('iconfont.svg?t=1689233106339#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-downward:before {
content: "\e604";
}
.icon-upward:before {
content: "\e605";
}
.icon-detail:before {
content: "\e601";
}
.icon-edit:before {
content: "\e602";
}
.icon-delete:before {
content: "\e603";
}

File diff suppressed because one or more lines are too long

View File

@@ -1,44 +0,0 @@
{
"id": "3821755",
"name": "component",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "封装的组件中的icon",
"glyphs": [
{
"icon_id": "36426261",
"name": "downward",
"font_class": "downward",
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "36426301",
"name": "upward",
"font_class": "upward",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "33347867",
"name": "detail",
"font_class": "detail",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "33347918",
"name": "edit",
"font_class": "edit",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "33347930",
"name": "delete",
"font_class": "delete",
"unicode": "e603",
"unicode_decimal": 58883
}
]
}

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Created by iconfont</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024">
<font-face
font-family="iconfont"
font-weight="400"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="downward" unicode="&#58884;" d="M556.942222 144.099556l363.064889 401.806222c24.860444 21.617778 24.860444 56.263111 0 77.880889a68.437333 68.437333 0 0 1-44.942222 16.156444c-16.896 0-33.052444-5.859556-44.885333-16.156444L512 260.949333l-318.236444 362.951111a68.437333 68.437333 0 0 1-44.942223 16.099556c-16.896 0-33.109333-5.802667-44.942222-16.156444-24.746667-21.617778-24.746667-56.206222 0-77.824l363.121778-401.749334c5.973333-5.290667 13.141333-9.443556 21.048889-12.231111 23.722667-8.305778 51.029333-3.527111 68.892444 12.060445z" horiz-adv-x="1024" />
<glyph glyph-name="upward" unicode="&#58885;" d="M556.942222 623.900444l363.064889-401.806222c24.860444-21.617778 24.860444-56.263111 0-77.880889a68.437333 68.437333 0 0 0-44.942222-16.156444c-16.896 0-33.052444 5.859556-44.885333 16.156444L512 507.050667l-318.236444-362.951111a68.437333 68.437333 0 0 0-44.942223-16.099556c-16.896 0-33.109333 5.802667-44.942222 16.156444-24.746667 21.617778-24.746667 56.206222 0 77.824L467.057778 623.729778c5.973333 5.290667 13.141333 9.443556 21.048889 12.231111 23.722667 8.305778 51.029333 3.527111 68.892444-12.060445z" horiz-adv-x="1024" />
<glyph glyph-name="detail" unicode="&#58881;" d="M902.428444 705.251556H332.401778a7.964444 7.964444 0 0 1-7.793778-8.021334v-56.206222a7.964444 7.964444 0 0 1 7.793778-8.078222h570.026666A7.964444 7.964444 0 0 1 910.222222 641.024v56.206222a7.964444 7.964444 0 0 1-7.793778 8.021334z m0-285.127112H332.401778a7.964444 7.964444 0 0 1-7.793778-7.964444v-56.32a7.964444 7.964444 0 0 1 7.793778-7.964444h570.026666a7.964444 7.964444 0 0 1 7.793778 7.964444v56.32a7.964444 7.964444 0 0 1-7.793778 7.964444z m0-285.070222H332.401778a7.964444 7.964444 0 0 1-7.793778-8.078222v-56.206222a7.964444 7.964444 0 0 1 7.793778-8.021334h570.026666a7.964444 7.964444 0 0 1 7.793778 8.021334v56.206222a7.964444 7.964444 0 0 1-7.793778 8.078222zM113.777778 669.127111c0-20.081778 10.410667-38.684444 27.306666-48.696889a53.361778 53.361778 0 0 1 54.670223 0 56.547556 56.547556 0 0 1 27.306666 48.696889 56.547556 56.547556 0 0 1-27.306666 48.696889 53.361778 53.361778 0 0 1-54.613334 0A56.547556 56.547556 0 0 1 113.777778 669.127111zM113.777778 384c0-20.081778 10.410667-38.684444 27.306666-48.696889a53.361778 53.361778 0 0 1 54.670223 0A56.547556 56.547556 0 0 1 223.061333 384a56.547556 56.547556 0 0 1-27.306666 48.696889 53.361778 53.361778 0 0 1-54.613334 0A56.547556 56.547556 0 0 1 113.777778 384z m0-285.127111c0-20.081778 10.410667-38.684444 27.306666-48.696889a53.361778 53.361778 0 0 1 54.670223 0 56.547556 56.547556 0 0 1 27.306666 48.696889 56.547556 56.547556 0 0 1-27.306666 48.696889 53.361778 53.361778 0 0 1-54.613334 0 56.547556 56.547556 0 0 1-27.363555-48.696889z" horiz-adv-x="1024" />
<glyph glyph-name="edit" unicode="&#58882;" d="M873.016889 395.264a32.824889 32.824889 0 0 0 65.649778 0v-273.806222a164.124444 164.124444 0 0 0-164.124445-164.124445h-525.084444A164.124444 164.124444 0 0 0 85.333333 121.457778v525.084444A164.124444 164.124444 0 0 0 249.457778 810.666667h312.32a32.824889 32.824889 0 1 0 0-65.649778h-312.32a98.417778 98.417778 0 0 1-98.474667-98.417778v-525.141333c0-54.385778 44.088889-98.474667 98.417778-98.474667h525.141333a98.417778 98.417778 0 0 1 98.474667 98.417778V395.320889z m-14.222222 362.097778a32.824889 32.824889 0 0 0 48.014222-44.771556L548.750222 328.533333a32.824889 32.824889 0 1 0-48.014222 44.771556l358.115556 384.056889z" horiz-adv-x="1024" />
<glyph glyph-name="delete" unicode="&#58883;" d="M601.024 146.24a29.632 29.632 0 0 0-29.696 29.696V503.04a29.632 29.632 0 1 0 59.456 0v-326.848a29.76 29.76 0 0 0-29.76-29.888z m-178.24 0a29.632 29.632 0 0 0-29.696 29.696V503.04a29.632 29.632 0 1 0 59.392 0v-326.848a29.76 29.76 0 0 0-29.696-29.888z m475.52 505.216h-148.544v59.456c0 49.152-39.616 89.088-88.512 89.088H363.392c-49.216 0-89.152-39.936-89.152-89.088v-59.456H125.696a29.632 29.632 0 1 1 0-59.392h772.608a29.632 29.632 0 1 1 0 59.392z m-564.608 59.456c0 16.256 13.44 29.696 29.696 29.696h297.856c16.32 0 29.056-13.12 29.056-29.696v-59.456H333.696v59.456zM720-32h-416a89.152 89.152 0 0 0-89.088 89.088V503.232a29.632 29.632 0 1 0 59.456 0v-446.08c0-16.512 13.44-29.76 29.696-29.76h416.064a29.632 29.632 0 0 1 29.696 29.696V502.144a29.632 29.632 0 1 0 59.456 0v-445.056A89.536 89.536 0 0 0 720-32z" horiz-adv-x="1024" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB