Merge remote-tracking branch 'origin/build' into build

This commit is contained in:
aixianling
2024-01-15 19:13:01 +08:00
12 changed files with 5659 additions and 6 deletions

View File

@@ -52,7 +52,7 @@
<el-table-column slot="options" label="操作" align="center" width="200px">
<template slot-scope="{ row }">
<el-button type="text" @click="toGroup(row)">群匹配</el-button>
<el-button type="text" @click="toAnnouce(row)">推送任务</el-button>
<!-- <el-button type="text" @click="toAnnouce(row)">推送任务</el-button> -->
</template>
</el-table-column>
</ai-table>
@@ -85,7 +85,7 @@
>
<el-form class="ai-form" :model="areaInfo" label-width="120px" ref="form">
<el-form-item prop="areaId" style="width: 100%;" label="所属地区" :rules="[{required: true, message: '请选择所属地区地区', trigger: 'change'}]">
<ai-area-get style="width: 400px;" placeholder="所属地区" :instance="instance" v-model="areaInfo.areaId" :name.sync="areaInfo.areaName" @change="selectArea" />
<ai-area-get style="width: 400px;" placeholder="所属地区" :instance="instance" v-model="areaInfo.areaId" :name.sync="areaInfo.areaName" />
</el-form-item>
</el-form>
</ai-dialog>
@@ -221,10 +221,6 @@ export default {
this.areaInfo = {...row}
this.$refs['form'].clearValidate();
},
selectArea(e) {
console.log(e[0].label)
this.areaInfo.areaName = e[0].label
},
areaConfirm() {
this.$refs['form'].validate((valid) => {
if (valid) {

View File

@@ -0,0 +1,121 @@
<template>
<div class="AppTaskAi">
<keep-alive :include="['List']">
<component
ref="component"
:is="component"
@change="onChange"
:params="params"
:instance="instance"
:dict="dict"
></component>
</keep-alive>
</div>
</template>
<script>
import List from "./components/List";
import AnnounceList from './components/AnnounceList'
import Add from './components/Add'
import Detail from './components/Detail'
import TaskList from './components/TaskList'
import TaskAdd from './components/TaskAdd'
import TaskDetail from './components/TaskDetail'
export default {
name: "AppTaskAi",
label: "任务管理",
props: {
instance: Function,
dict: Object,
},
data() {
return {
component: "List",
params: {},
include: [],
};
},
components: {
List,
AnnounceList,
Add,
Detail,
TaskList,
TaskDetail,
TaskAdd
},
mounted() {
if (this.$route.params.id) {
this.component = 'Detail'
this.params = {
id: this.$route.params.id
}
}
},
methods: {
onChange(data) {
if (data.type === "List") {
this.component = "List";
this.params = data.params;
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getTableData();
}
});
}
if (data.type === 'Add') {
this.component = 'Add'
this.params = data.params
}
if (data.type === 'Detail') {
this.component = 'Detail'
this.params = data.params
}
if (data.type === 'AnnounceList') {
this.component = 'AnnounceList'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
if (data.type === 'TaskAdd') {
this.component = 'TaskAdd'
this.params = data.params
}
if (data.type === 'TaskDetail') {
this.component = 'TaskDetail'
this.params = data.params
}
if (data.type === 'TaskList') {
this.component = 'TaskList'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
},
},
};
</script>
<style lang="scss">
.AppTaskAi {
height: 100%;
background: #f3f6f9;
overflow: auto;
}
</style>

View File

@@ -0,0 +1,990 @@
<template>
<ai-detail class="AppAnnounceAdd">
<template slot="title">
<ai-title :title="id ? '编辑任务' : '添加任务'" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<div class="AppAnnounceDetail-container">
<el-form ref="form" class="left" :model="form" label-width="110px" label-position="right">
<ai-card title="基本信息">
<template #content>
<div class="ai-form">
<el-form-item label="任务名称" prop="taskTitle" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入任务名称" v-model="form.taskTitle" :maxlength="15" show-word-limit></el-input>
</el-form-item>
<el-form-item label="发送范围" style="width: 100%;" prop="sendScope" :rules="[{ required: true, message: '请选择发送范围', trigger: 'change' }]">
<el-radio-group v-model="form.sendScope" @change="onScopeChange">
<el-radio label="0">全部居民群</el-radio>
<el-radio label="1">按部门选择</el-radio>
<el-radio label="2">按网格选择</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选择群主" v-if="form.sendScope !== '0'" prop="wxGroupsName" style="width: 100%;" :rules="[{ required: true, message: '请选择选择群主', trigger: 'change' }]" >
<ai-picker
:instance="instance"
multiple
:dialogTitle="form.sendScope === '2' ? '选择网格' : '选择部门'"
:ops="{label: form.sendScope === '2' ? 'girdName' : 'name'}"
:pageTitle="form.sendScope === '2' ? '网格' : '部门'"
:action="form.sendScope === '1' ? `/app/wxcp/wxdepartment/departList` : '/app/appgirdinfo/girdList'"
v-model="form.filterCriteria"
@pick="onPick"
@change="onSelcetChange">
<div class="AppAnnounceDetail-select">
<el-input size="small" class="AppAnnounceDetail-select__input" placeholder="请选择..." disabled v-model="form.wxGroupsName"></el-input>
<div class="select-left" v-if="form.wxGroups && form.wxGroups.length">
<span v-for="(item, index) in form.wxGroups" :key="index" v-if="index < 9">{{ item.groupOwnerName }}</span>
<em v-if="form.wxGroups.length > 9">{{ form.wxGroups.length }}</em>
</div>
<i v-else>请选择</i>
<div class="select-right">{{ form.filterCriteria.length ? '重新选择' : '选择' }}</div>
</div>
</ai-picker>
<div class="tips">
<p>消息预计送达居民群数</p>
<span>{{ groupLen }}</span>
<!-- <el-tooltip
placement="top"
content="将由指定群主发送给TA作为群主的所有的群由于企业微信限制当超过1000个时将只发送到最近活跃的1000个群">
<i class="iconfont iconModal_Warning"></i>
</el-tooltip> -->
</div>
</el-form-item>
<el-form-item label="发送内容" prop="content" style="width: 100%;" :rules="[{ required: true, message: '请输入发送内容', trigger: 'blur' }]">
<el-input size="small" type="textarea" :rows="6" maxlength="1300" show-word-limit placeholder="请输入文本内容..." v-model="form.content"></el-input>
<div class="add">
<div class="fileList" v-if="fileList && fileList.length">
<div class="add-item" v-for="(item, index) in fileList" :key="index">
<div class="left">
<img :src="mapIcon(item.msgType)"/>
<span>{{ item.mpTitle || item.name || item.linkTitle }}</span>
</div>
<i @click="removeFile(index)">删除</i>
</div>
</div>
<el-popover
placement="top"
width="340"
offset="0"
trigger="hover">
<div class="add-item" slot="reference" style="width: max-content;">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/add.png"/>
<span style="color: #2266FF; font-size: 12px;">添加附件类型</span>
</div>
<div class="AppAnnounceDetail-content-wrapper">
<el-upload
ref="upload"
multiple
:file-list="fileList"
:show-file-list="false"
:before-upload="v => handleChange(v, 10, '.jpg,.png,.jpeg')"
:limit="9"
action="/app/wxcp/upload/uploadFile"
accept=".jpg,.png,.jpeg"
:on-exceed="onExceed"
:http-request="v => submitUpload(v, '1')">
<div class="content-item" trigger>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/big-img.png"/>
<p>图片</p>
</div>
</el-upload>
<!-- <el-upload
ref="upload"
multiple
:file-list="fileList"
:show-file-list="false"
:before-upload="v => handleChange(v, 10, '.mp4')"
:limit="9"
action="/app/wxcp/upload/uploadFile"
accept=".mp4"
:on-exceed="onExceed"
:http-request="v => submitUpload(v, '2')">
<div class="content-item" trigger>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/big-video.png"/>
<p>视频</p>
</div>
</el-upload> -->
<!-- <el-upload
ref="upload"
multiple
:file-list="fileList"
:show-file-list="false"
:before-upload="v => handleChange(v, 20, '.zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt')"
:limit="9"
:on-exceed="onExceed"
action="/app/wxcp/upload/uploadFile"
accept=".zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt"
:http-request="v => submitUpload(v, '3')">
<div class="content-item" trigger>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/folder.png"/>
<p>文件</p>
</div>
</el-upload> -->
<div class="content-item" @click="isShowAddLink = true">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/site.png"/>
<p>网页</p>
</div>
<!-- <div class="content-item" @click="isShowAddMiniapp = true">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png"/>
<p>小程序</p>
</div> -->
</div>
</el-popover>
<!-- <div class="add-material add-item" @click="$refs.ChooseMaterial.open()">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/add.png"/>
<span style="color: #2266FF; font-size: 12px;">从素材库选择</span>
</div> -->
</div>
<div class="tips">
<em>从本地上传图片最大支持10MB支持JPG,PNG格式视频最大支持10MB支持MP4格式文件最大支持20MB</em>
</div>
</el-form-item>
<!-- <el-form-item label="标签" style="width: 100%;" prop="markTag">
<el-checkbox-group v-model="form.markTag">
<el-checkbox
v-for="(item, index) in dict.getDict('mstTag')"
:key="index"
:label="item.dictValue">
{{ item.dictName }}
</el-checkbox>
</el-checkbox-group>
</el-form-item> -->
<el-form-item label="结束时间" style="width: 100%;" prop="taskEndTime" :rules="[{ required: true, message: '请选择结束时间', trigger: 'change' }]">
<el-date-picker
style="width: 100%;"
v-model="form.taskEndTime"
type="datetime"
size="small"
:picker-options="pickerOptions"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择结束时间">
</el-date-picker>
</el-form-item>
<!-- <el-form-item label="宣发审批" prop="enableExamine" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
<el-switch
v-model="form.enableExamine"
active-value="1"
inactive-value="0"
active-text="开启后创建的群发任务需要审批人进行审批">
</el-switch>
</el-form-item> -->
<el-form-item v-if="form.enableExamine === '1'" label="审批人员" prop="examines" style="width: 100%;" :rules="[{ required: true, message: '请选择审批人员', trigger: 'change' }]">
<ai-user-selecter :instance="instance" v-model="form.examines" @change="onUserChange">
<div class="AppAnnounceDetail-select">
<el-input class="AppAnnounceDetail-select__input" size="small" placeholder="请选择..." v-model="form.examinesName"></el-input>
<div class="select-left" v-if="form.examines && form.examines.length">
<span v-for="(item, index) in form.examines" :key="index">{{ item.name }}</span>
</div>
<i v-if="!form.examines.length">请选择</i>
<div class="select-right">{{ form.examines.length ? '重新选择' : '选择' }}</div>
</div>
</ai-user-selecter>
</el-form-item>
</div>
</template>
</ai-card>
</el-form>
<div class="right">
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="false" :content="form.content" :fileList="fileList"></Phone>
</div>
<ai-dialog
:visible.sync="isShowAddLink"
width="920px"
title="链接消息"
@close="onClose"
@onConfirm="onLinkConfirm">
<el-form ref="linkForm" :model="linkForm" label-width="110px" label-position="right">
<div class="ai-form">
<el-form-item label="标题" style="width: 100%;" prop="linkTitle" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入标题"
maxlength="42"
show-word-limit
v-model="linkForm.linkTitle">
</el-input>
</el-form-item>
<el-form-item label="链接" style="width: 100%;" prop="linkUrl" :rules="[{ required: true, message: '请输入链接', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入链接"
maxlength="682"
show-word-limit
v-model="linkForm.linkUrl">
</el-input>
</el-form-item>
<el-form-item label="描述" style="width: 100%;" prop="linkDesc">
<el-input
size="small"
placeholder="请输入描述"
maxlength="170"
show-word-limit
v-model="linkForm.linkDesc">
</el-input>
</el-form-item>
<el-form-item label="封面图" prop="linkPicUrl" style="width: 100%;">
<ai-uploader :instance="instance" v-model="linkForm.linkPicUrl" :limit="1"></ai-uploader>
</el-form-item>
</div>
</el-form>
</ai-dialog>
<ai-dialog
:visible.sync="isShowAddMiniapp"
width="920px"
title="小程序消息"
@close="onClose"
@onConfirm="onMiniAppForm">
<el-form ref="miniAppForm" :model="miniAppForm" label-width="130px" label-position="right">
<div class="ai-form">
<el-form-item label="小程序appid" style="width: 100%;" prop="mpAppid" :rules="[{ required: true, message: '小程序appid', trigger: 'blur' }]">
<el-input
size="small"
placeholder="小程序appid"
v-model="miniAppForm.mpAppid">
</el-input>
</el-form-item>
<el-form-item label="小程序page路径" style="width: 100%;" prop="mpPage" :rules="[{ required: true, message: '请输入小程序page路径', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入小程序page路径"
v-model="miniAppForm.mpPage">
</el-input>
</el-form-item>
<el-form-item label="标题" style="width: 100%;" prop="mpTitle" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入标题"
maxlength="20"
show-word-limit
v-model="miniAppForm.mpTitle">
</el-input>
</el-form-item>
<el-form-item label="封面图" prop="media" style="width: 100%;" :rules="[{ required: true, message: '请上传封面图', trigger: 'change' }]">
<ai-uploader url="/app/wxcp/upload/uploadFile?type=image" :instance="instance" isWechat v-model="miniAppForm.media" :limit="1"></ai-uploader>
</el-form-item>
</div>
</el-form>
</ai-dialog>
<ai-dialog
:visible.sync="isShowDate"
width="590px"
title="定时发送"
customFooter>
<el-form ref="dateForm" :model="dateForm" label-width="130px" label-position="right">
<div class="ai-form">
<el-form-item label="定时发送时间" style="width: 100%;" prop="choiceTime" :rules="[{ required: true, message: '请选择定时发送时间', trigger: 'change' }]">
<el-date-picker
style="width: 100%;"
v-model="dateForm.choiceTime"
type="datetime"
size="small"
:picker-options="pickerOptions"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择定时发送时间">
</el-date-picker>
</el-form-item>
</div>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="onClose">取消</el-button>
<el-button @click="onDateForm" type="primary" :loading="isLoading2" style="width: 92px;">确认</el-button>
</div>
</ai-dialog>
<!-- <ChooseMaterial ref="ChooseMaterial" :instance="instance" @change="onChooseChange"></ChooseMaterial> -->
</div>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm(0)" :loading="isLoading1" style="width: 120px;">立即执行</el-button>
<el-button type="primary" @click="confirm(1)">定时任务</el-button>
</template>
</ai-detail>
</template>
<script>
import Phone from './Phone'
import ChooseMaterial from './ChooseMaterial.vue'
import { mapActions, mapState } from 'vuex'
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
components: {
Phone,
ChooseMaterial
},
data() {
return {
info: {},
department: [],
isShowChoose: false,
isLoading1: false,
isLoading2: false,
fileList: [],
isShowAddLink: false,
isShowAddMiniapp: false,
isShowDate: false,
isLoading: false,
linkForm: {
linkPicUrl: [],
linkDesc: '',
linkTitle: '',
linkUrl: ''
},
dateForm: {
choiceTime: ''
},
miniAppForm: {
mpAppid: '',
mpPage: '',
mpTitle: '',
media: []
},
form: {
content: '',
choiceTime: '',
contents: [],
enableExamine: '0',
taskEndTime: '',
examines: [],
wxGroups: [],
markTag: [],
wxGroupsName: '',
sendScope: '1',
sendType: 0,
name: '',
filterCriteria: [],
taskTitle: '',
examinesName: ''
},
girdNames: '',
id: '',
tagsList: [],
pickerOptions: {
disabledDate: e => {
return e.getTime() < (Date.now() - 60 * 1000 * 60 * 24)
}
}
}
},
computed: {
...mapState(['user']),
groupLen() {
let i = 0
if(this.form.wxGroups && this.form.wxGroups.length) {
this.form.wxGroups.forEach(v => {
i = i + v.groupIds.split(',').length
})
}
return i
}
},
created() {
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
this.dict.load('mstTag')
} else {
this.getWxGroups()
this.dict.load('mstTag')
}
},
methods: {
...mapActions(['initOpenData', 'transCanvas']),
onChooseChange (e) {
this.form.content = e.filter(v => v.msgType === '0').map(v => v.content).join(' ')
this.fileList = this.fileList.concat(e.filter(v => v.msgType !== '0'))
},
getInfo(id) {
this.instance.post(`/app/appmasssendingtaskxbot/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = {
...this.form,
...res.data,
wxGroupsName: '1',
filterCriteria: res.data.filterCriteria.split(','),
// markTag: res.data.markTag.split(',')
}
if (res.data.girdNames) {
this.girdNames = res.data.girdNames.split(',')
}
this.dateForm.choiceTime = ''
if (res.data.examines && res.data.examines.length) {
this.form.examines = res.data.examines.map(v => {
return {
...v,
wxOpenUserId: v.examineUserId,
id: v.examineUserId,
name: v.examineUserName
}
})
this.form.examinesName = '1'
}
const content = res.data.contents.filter(v => v.msgType === '0')
if (content.length) {
this.$set(this.form, 'content', content[0].content)
}
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
return {
...v,
...v.sysFile
}
})
}
})
},
onUserChange(e) {
if (e.length) {
this.form.examinesName = '1'
} else {
this.form.wxGroupsName = ''
}
},
onScopeChange(e) {
this.form.filterCriteria = []
this.form.wxGroups = []
this.girdNames = ''
if (e === '0') {
this.getWxGroups()
} else {
this.form.filterCriteria = []
}
},
onPick(e) {
if (this.form.sendScope === '2' && e.length) {
this.girdNames = e.map(v => v.girdName)
}
},
onSelcetChange(e) {
if (e.length) {
this.form.wxGroupsName = '1'
this.$nextTick(() => {
this.getWxGroups()
})
} else {
this.form.wxGroupsName = ''
this.form.wxGroups = []
}
},
getWxGroups() {
this.instance.post(`/app/appmasssendingtaskxbot/queryWxGroups?sendScope=${this.form.sendScope}&clientInfoId=${this.params.clientInfoId}`, {
filterCriteria: this.form.filterCriteria.join(',')
}).then(res => {
if (res.code === 0) {
this.form.wxGroups = res.data
}
})
},
onLinkConfirm() {
this.$refs.linkForm.validate((valid) => {
if (valid) {
this.fileList.push({
...this.linkForm,
linkPicUrl: this.linkForm.linkPicUrl.length ? this.linkForm.linkPicUrl[0].url : '',
msgType: '4'
})
this.isShowAddLink = false
}
})
},
onMiniAppForm() {
this.$refs.miniAppForm.validate((valid) => {
if (valid) {
this.fileList.push({
...this.miniAppForm,
msgType: '5',
...this.miniAppForm.media[0],
mediaId: this.miniAppForm.media[0].media.mediaId,
sysFileId: this.miniAppForm.media[0].id
})
this.isShowAddMiniapp = false
}
})
},
onClose() {
this.linkForm.linkPicUrl = []
this.linkForm.linkDesc = ''
this.linkForm.linkTitle = ''
this.linkForm.linkUrl = ''
this.miniAppForm.mpAppid = ''
this.miniAppForm.mpPage = ''
this.miniAppForm.mpTitle = ''
this.dateForm.choiceTime = ''
this.isShowDate = false
},
removeFile(index) {
this.fileList.splice(index, 1)
},
mapIcon(type) {
return {
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
}[type]
},
onBeforeUpload(event) {
return this.onOverSize(event)
},
getExtension(name) {
return name.substring(name.lastIndexOf('.'))
},
handleChange(e, size, accept) {
const isLt10M = e.size / 1024 / 1024 < size
const suffixName = this.getExtension(e.name)
const suffixNameList = accept.split(',')
if (suffixNameList.indexOf(`${suffixName.toLowerCase()}`) === -1) {
this.$message.error(`不支持该格式`)
return false
}
if (!isLt10M) {
this.$message.error(`大小不超过${10}MB!`)
return false
}
return true
},
onExceed() {
this.$message.error(`最多上传9个附件`)
},
submitUpload(file, type) {
const fileType = {
'1': 'image',
'2': 'video',
'3': 'file'
}[type]
let formData = new FormData()
formData.append('file', file.file)
formData.append('type', fileType)
let loading = this.$loading()
this.instance.post(`/app/wxcp/upload/uploadFile`, formData, {
withCredentials: false
}).then(res => {
if (res.code == 0) {
this.fileList.push({
...res.data.file,
media: res.data.media,
msgType: type,
sysFileId: res.data.file.id,
imgPicUrl: res.data.file.url,
mediaId: res.data.media.mediaId
})
this.$message.success('上传成功')
}
}).finally(() => loading.close())
},
onDateForm() {
this.$refs.dateForm.validate((valid) => {
if (valid) {
if (new Date(this.dateForm.choiceTime).getTime() < Date.now()) {
return this.$message.error('定时发送时间不得早于当前时间')
}
if (this.params.sendChannel === '1' && new Date(this.dateForm.choiceTime).getTime() > new Date(this.form.taskEndTime).getTime()) {
return this.$message.error('定时发送时间不得晚于结束时间')
}
this.confirm(1)
}
})
},
confirm(sendType) {
this.$refs.form.validate((valid) => {
if (valid) {
if (!this.form.wxGroups.length) {
return this.$message.error('居民群数量不能为0')
}
if (this.params.sendChannel === '1' && new Date(this.form.taskEndTime).getTime() < Date.now()) {
return this.$message.error('结束时间不得早于当前时间')
}
if (sendType === 1 && !this.dateForm.choiceTime) {
this.isShowDate = true
return false
}
const contents = [
{
content: this.form.content,
msgType: '0'
},
...this.fileList
]
if (sendType === 0) {
this.isLoading1 = true
} else {
this.isLoading2 = true
}
this.instance.post(`/app/appmasssendingtaskxbot/addOrUpdate`, {
clientInfoId: this.params.clientInfoId,
...this.form,
id: this.params.id,
wxGroups: this.form.wxGroups,
contents,
sendType,
// markTag: this.form.markTag.join(','),
sendChannel: this.params.sendChannel,
choiceTime: this.dateForm.choiceTime,
filterCriteria: this.form.filterCriteria.join(','),
examines: this.form.examines.length ? this.form.examines.map(v => {
return {
...v,
examineUserId: v.id,
examineUserName: v.name
}
}) : [],
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
} else {
this.isLoading1 = false
this.isLoading2 = false
}
}).catch(() => {
this.isLoading1 = false
this.isLoading2 = false
})
}
})
},
selectDept() {
return this.$message.error(`您未在系统中关联‘主部门’,请联系管理员处理`)
},
cancel(isRefresh) {
this.$emit('change', {
type: 'AnnounceList',
isRefresh: !!isRefresh,
params: {id: this.params.clientInfoId, status: this.params.status}
})
}
}
}
</script>
<style lang="scss">
.el-tooltip__popper.is-dark {
max-width: 240px;
}
.AppAnnounceDetail-content-wrapper {
display: flex;
align-items: center;
.content-item {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 64px;
height: 64px;
line-height: 1;
margin-right: 4px;
text-align: center;
background: #F9F9F9;
border-radius: 2px;
cursor: pointer;
&:hover {
opacity: 0.6;
}
&:last-child {
margin-right: 0;
}
img {
width: 32px;
height: 32px;
margin-bottom: 4px;
}
p {
color: #222;
font-size: 12px;
}
}
}
.AppAnnounceAdd {
.ai-detail__content {
.ai-detail__content--wrapper {
position: relative;
max-width: 100%;
margin: 0;
height: 100%;
overflow: hidden;
}
}
.ai-form {
textarea {
border-radius: 4px 4px 0 0!important;
}
}
* {
box-sizing: border-box;
}
.add {
display: flex;
position: relative;
flex-direction: column;
padding: 14px 16px;
background: #F9F9F9;
border-radius: 0px 0px 4px 4px;
border: 1px solid #D0D4DC;
border-top: none;
.add-material {
position: absolute;
bottom: 14px;
left: 16px;
transform: translateX(120%);
}
.add-item {
display: flex;
align-items: center;
line-height: 1;
cursor: pointer;
&:hover {
opacity: 0.6;
}
img {
width: 20px;
height: 20px;
margin-right: 2px;
}
span {
color: #222;
font-size: 14px;
}
}
.fileList {
margin-bottom: 12px;
.add-item {
justify-content: space-between;
margin-bottom: 8px;
.left {
display: flex;
align-items: center;
}
i {
font-size: 14px;
cursor: pointer;
font-style: normal;
color: red;
&:hover {
opacity: 0.6;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
.AppAnnounceDetail-container {
display: flex;
position: relative;
height: 100%;
padding: 0 20px;
overflow: auto;
overflow: overlay;
.left {
flex: 1;
margin-right: 20px;
}
.right {
position: sticky;
top: 0;
}
.AppAnnounceDetail-select {
display: flex;
align-items: center;
min-height: 32px;
line-height: 1;
background: #F5F5F5;
border-radius: 4px;
border: 1px solid #D0D4DC;
cursor: pointer;
overflow: hidden;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
& > i {
flex: 1;
height: 100%;
line-height: 32px;
padding: 0 12px;
color: #888888;
font-size: 14px;
font-style: normal;
border-right: 1px solid #D0D4DC;
background: #fff;
}
.AppAnnounceDetail-select__input {
position: absolute;
left: 0;
top: 0;
z-index: -1;
opacity: 0;
height: 100%;
}
.select-right {
height: 100%;
padding: 0 12px;
color: #222222;
font-size: 12px;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.5;
}
}
.select-left {
display: flex;
flex-wrap: wrap;
flex: 1;
padding: 5px 0 0px 12px;
border-right: 1px solid #D0D4DC;
border-radius: 4px 0 0 4px;
background: #fff;
em {
height: 22px;
line-height: 22px;
margin: 0 4px 5px 0;
color: #222222;
font-size: 12px;
font-style: normal;
}
span {
height: 22px;
line-height: 22px;
margin: 0 4px 5px 0;
padding: 0 8px;
font-size: 12px;
color: #222222;
background: #F3F4F7;
border-radius: 2px;
border: 1px solid #D0D4DC;
}
}
}
}
.tips {
display: flex;
align-items: center;
font-size: 14px;
color: #222222;
span {
margin: 0 3px;
color: #2266FF;
}
i {
color: #8899bb;
}
em {
line-height: 20px;
margin-top: 8px;
color: #888888;
font-size: 12px;
font-style: normal;
}
}
.select-div {
width: 100%;
height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #ddd;
padding: 0 15px;
box-sizing: border-box;
font-size: 14px;
color: #999;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,438 @@
<template>
<ai-list class="AppAnnounce">
<template slot="title">
<ai-title title="推送任务" isShowBack isShowBottomBorder @onBackClick="cancel(true)">
<template #sub>
<span>设置居民助手的推送任务可定时发送也可立即发送</span>
</template>
</ai-title>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="toAdd('')" :disabled="params.status != 1">创建任务</el-button>
<ai-select
v-model="search.status"
@change="search.current = 1, getList()"
placeholder="任务状态"
:selectList="dict.getDict('mstXbotStatus')">
</ai-select>
<el-date-picker
v-model="search.startTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发开始日期">
</el-date-picker>
<el-date-picker
v-model="search.endTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发结束日期">
</el-date-picker>
<ai-user-selecter :instance="instance" @change="onUserChange" :isMultiple="false" v-model="userList" :actions="{tree: `/app/wxcp/wxdepartment/listAll?wxMainDepartmentId=${user.info.wxMainDepartmentId}`}" >
<div class="userSelcet">
<span style="color: #606266;" v-if="search.createUserId">{{ name }}</span>
<span v-else>创建人</span>
<i class="el-icon-arrow-up" v-if="!search.createUserId"></i>
<i class="el-icon-circle-close" v-if="search.createUserId" @click.stop="userList = [], search.createUserId = '', name = '', search.current = 1, getList()"></i>
</div>
</ai-user-selecter>
</template>
<template slot="right">
<el-input
v-model="search.taskTitle"
size="small"
v-throttle="() => { search.current = 1, getList() }"
placeholder="请输入任务名称"
clearable
@clear="search.current = 1, search.taskTitle = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
v-loading="loading"
style="margin-top: 6px; width: 100%;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="user" width="140px" label="创建人" align="center">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.createUserName }}</span>
<span style="color: #999">{{ row.createUserDeptName }}</span>
</div>
</template>
</el-table-column>
<el-table-column slot="options" width="140px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<!-- <el-button type="text" @click="remindExamine(row.id)" v-if="['0'].includes(row.status)">催办</el-button> -->
<el-button type="text" @click="remove(row.id)" v-if="['0'].includes(row.status)">删除</el-button>
<el-button type="text" @click="close(row.id)" v-if="['0'].includes(row.status)">关闭</el-button>
<!-- <el-button type="text" @click="cancel(row.id)" v-if="['0'].includes(row.status)">撤回</el-button> -->
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
<el-button type="text" @click="toAdd(row.id)" v-if="['1', '3'].includes(row.status)">编辑</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object,
params: Object
},
data() {
return {
search: {
current: 1,
size: 10,
status: '',
createUserId: '',
taskTitle: '',
startTime: '',
endTime: '',
markTag: ''
},
currIndex: '1',
name: '',
isShow: false,
userList: [],
tableData: [],
loading: false,
total: 0,
colConfigs: [
{ prop: 'taskTitle', label: '任务名称' },
// { prop: 'typeName', label: '群发类型', align: 'center' },
{ slot: 'user', label: '创建人', openType: 'userName', align: 'center' },
{ prop: 'choiceTime', label: '群发时间', align: 'center' },
{ prop: 'taskEndTime', label: '群发结束时间', align: 'center' },
{
prop: 'status',
align: 'center',
label: '状态',
render: (h, {row}) => {
return h('span', {
style: {
color: this.dict.getColor('mstXbotStatus', row.status)
}
}, this.dict.getLabel('mstXbotStatus', row.status))
}
},
{ prop: 'completionRate', label: '任务进度', align: 'center', format: v => v ? v === '0.0' ? '0%' : `${v}%` : '-' }
]
}
},
computed: {
...mapState(['user']),
},
mounted () {
this.dict.load('mstXbotStatus', 'mstSendType', 'mstTag').then(() => {
this.getList()
})
},
methods: {
onUserChange (e) {
if (e.length) {
this.name = e[0].name
this.search.createUserId = e[0].id
} else {
this.search.createUserId = ''
this.name = ''
}
this.search.current = 1
this.getList()
},
getList() {
this.loading = true
this.instance.post(`/app/appmasssendingtaskxbot/list`, null, {
params: {
...this.search,
clientInfoId: this.params.id,
markTag: this.search.markTag ? this.search.markTag.join(',') : ''
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records.map(v => {
return {
...v,
typeName: '群发居民群'
}
})
this.total = res.data.total
this.$nextTick(() => {
this.loading = false
})
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
remindExamine (id) {
this.$confirm('确认再次通知任务审核人员?').then(() => {
this.instance.post(`/app/appmasssendingtaskxbot/remindExamine?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('催办成功!')
this.getList()
}
})
})
},
close (id) {
this.$confirm('确认关闭该群发任务?').then(() => {
this.instance.post(`/app/appmasssendingtaskxbot/closeTask?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('关闭成功!')
this.getList()
}
})
})
},
cancel (id) {
this.$confirm('确认撤回该群发任务?').then(() => {
this.instance.post(`/app/appmasssendingtaskxbot/cancel?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('撤回成功!')
this.getList()
}
})
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appmasssendingtaskxbot/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
id: id || '',
clientInfoId: this.params.id,
sendChannel: 0,
status: this.params.status
}
})
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id,
status: this.params.status
}
})
},
cancel(isRefresh) {
this.$emit('change', {
type: 'List',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style lang="scss" scoped>
.AppAnnounce {
height: 100%;
.dialog {
position: fixed;
left: 0;
top: 0;
z-index: 111;
width: 100%;
height: 100%;
.mask {
position: fixed;
left: 0;
top: 0;
z-index: 1;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.2);
}
.dialog-wrapper {
position: absolute;
left: 50%;
top: 50%;
z-index: 2;
width: 600px;
height: 532px;
padding: 40px 64px;
box-sizing: border-box;
background: #FFFFFF;
border-radius: 4px;
text-align: center;
transform: translate(-50%, -50%);
& > img {
position: absolute;
right: 16px;
top: 16px;
z-index: 2;
width: 16px;
height: 16px;
cursor: pointer;
}
h2 {
margin-bottom: 12px;
font-size: 24px;
color: #222;
}
& > p {
margin-bottom: 40px;
font-size: 12px;
color: #888888;
}
.dialog-list {
// display: flex;
// justify-content: space-between;
margin-bottom: 40px;
& > div {
// flex: 1;
width: 216px;
margin-left: 120px;
height: 280px;
padding: 24px;
background: #FFFFFF;
border: 1px solid #D0D4DC;
border-radius: 8px;
text-align: left;
cursor: pointer;
transition: all ease 0.3s;
&.active, &:hover {
background: #F7FAFF;
border: 1px solid #2266FF;
}
img {
width: 80px;
height: 80px;
margin-bottom: 32px;
}
p {
line-height: 22px;
font-size: 14px;
color: #888;
text-align: center;
}
h3 {
margin-bottom: 8px;
font-size: 18px;
color: #222;
font-weight: 600;
text-align: center;
}
&:first-child {
margin-right: 40px;
}
}
}
}
}
.userinfo {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: $placeholderColor;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
:deep .AiTitle .iconBack_Large {
line-height: 22px!important;
}
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<div id="ChooseMaterial">
<ai-dialog
:visible.sync="isShow"
width="890px"
@onConfirm="onConfirm"
title="选择素材">
<div class="AppMaterialLibrary-title">
<span
v-for="(item, index) in typeList"
:key="index"
:class="[currIndex === index ? 'active' : '']" @click="currIndex = index, search.current = 1, getList()">
{{ item }}
</span>
</div>
<ai-search-bar class="search-bar">
<template #left>
</template>
<template slot="right">
<el-input
v-model="search.title"
size="small"
v-throttle="() => { search.current = 1, getList() }"
placeholder="请输入标题、话术内容、添加人"
clearable
@clear="search.current = 1, search.title = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
v-if="isShow"
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px; width: 100%;"
:current.sync="search.current"
:size.sync="search.size"
@selection-change="onSelectChange"
@getList="getList">
</ai-table>
</ai-dialog>
</div>
</template>
<script>
export default {
name: 'ChooseMaterial',
props: {
instance: Function
},
data() {
return {
search: {
current: 1,
size: 10,
title: '',
},
ids: [],
isShow: false,
id: '',
typeList: ['话术', '图片', '小程序', '文件', '视频', '网页'],
currIndex: 0,
tableData: [],
total: 0,
value: []
}
},
computed: {
mpTitle () {
return {
'0': '话术标题',
'1': '图片名称',
'2': '小程序标题',
'3': '文件名称',
'4': '视频名称',
'5': '网页名称'
}[this.currIndex]
},
colConfigs () {
if (this.currIndex === 0) {
return [
{ type: 'selection' },
{ prop: 'title', label: this.mpTitle },
{ prop: 'content', label: '话术内容', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
if (this.currIndex === 2) {
return [
{ type: 'selection' },
{ prop: 'mpTitle', label: this.mpTitle },
{ prop: 'mpAppid', label: '小程序APPID', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
if (this.currIndex === 5) {
return [
{ type: 'selection' },
{ prop: 'linkTitle', label: this.mpTitle },
{ prop: 'linkUrl', label: '外链网页', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
return [
{ type: 'selection' },
{ prop: 'title', label: this.mpTitle },
{ prop: 'fileSizeStr', label: '文件大小', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
},
created () {
this.getList()
},
methods: {
getList() {
this.instance.post(`/app/appmaterialinfo/listByMST`, null, {
params: {
...this.search,
mstType: 0,
type: this.currIndex
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
onSelectChange (e) {
this.value = e
},
onConfirm () {
if (!this.value.length) {
return this.$message.error('请选择素材')
}
if (this.value.length > 9) {
return this.$message.error('素材不能超过9个')
}
this.$emit('change', this.value)
this.isShow = false
},
open () {
this.value = []
this.isShow = true
},
close () {
this.isShow = false
}
}
}
</script>
<style lang="scss" scoped>
#ChooseMaterial {
:deep( .ai-list__content--right-wrapper ) {
padding: 0 20px!important;
}
:deep( .el-dialog__header ) {
display: none;
}
:deep( .el-dialog__body ) {
padding-top: 0!important;
}
.AppMaterialLibrary-title {
display: flex;
align-items: center;
margin-bottom: 20px;
border-bottom: 1px solid #eee;
span {
height: 100%;
line-height: 56px;
margin-right: 32px;
color: #888888;
font-size: 16px;
font-weight: 600;
transition: all ease 0.3s;
border-bottom: 3px solid transparent;
cursor: pointer;
user-select: none;
&:hover {
color: #222;
}
&:last-child {
margin-right: 0;
}
&.active {
color: #222222;
border-bottom: 3px solid #2266FF;
}
}
}
}
</style>

View File

@@ -0,0 +1,784 @@
<template>
<ai-detail class="AppAnnounceDetail">
<template slot="title">
<ai-title title="任务详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基础信息">
<template #right>
<div class="right-tips" v-if="info.status === '4'">
<el-tooltip
placement="top"
content="任务开始后3天内15分钟更新1次3天后访问页面时触发更新1小时最多刷新1次">
<i class="iconfont iconDetails"></i>
</el-tooltip>
<span>数据更新于{{ info.dataUpdateTime }}</span>
</div>
</template>
<template #content>
<ai-wrapper>
<ai-info-item label="任务名称" isLine :value="info.taskTitle"></ai-info-item>
<ai-info-item label="任务状态" isLine>
<span :style="{ color: dict.getColor('mstXbotStatus', info.status) }">{{ dict.getLabel('mstXbotStatus', info.status) }}</span>
</ai-info-item>
<ai-info-item label="创建人" isLine>
<div class="user">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ info.createUserName }}</span>
<!-- <span>{{ info.createUserDeptName }}</span> -->
</div>
</ai-info-item>
<ai-info-item label="审批人" isLine v-if="info.enableExamine === '1'">
<div class="user-wrapper">
<div class="user" v-for="(item, index) in info.examines" :key="index">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ item.examineUserName }}</span>
</div>
</div>
</ai-info-item>
<!-- <ai-info-item label="发送方式">
<span>{{ info.sendChannel === '1' ? '通知员工转发' : '成员一键群发' }}</span>
</ai-info-item> -->
<!-- <ai-info-item label="标签" :value="info.markTag">
</ai-info-item> -->
<ai-info-item label="创建时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="群发时间" :value="info.choiceTime"></ai-info-item>
<ai-info-item label="结束时间" :value="info.taskEndTime" v-if="info.sendChannel === '1'"></ai-info-item>
<ai-info-item label="群发范围" isLine>
<div class="text">
<span>{{ info.sendScope === '0' ? '全部' : '按条件筛选的' }}</span>
<i>{{ groups.length }}</i>
<span>个居民群</span>
<em @click="isShowGroups = true">详情</em>
</div>
</ai-info-item>
<ai-info-item label="消息内容" isLine>
<div class="msg">
<p>{{ content }}</p>
<div class="msg-bottom">
<div class="left" v-if="fileList.length">
<img :src="mapIcon(fileList[0].msgType)" />
<span>{{ mapType(fileList[0].msgType) }}{{ fileList[0].mpTitle || fileList[0].name || fileList[0].linkTitle }} </span>
<i>{{ fileList.length }}</i>
<span>个附件</span>
</div>
<div class="left" v-else>
<span>暂无附件</span>
</div>
<div class="right" @click="isShowPhone = true">预览消息</div>
</div>
</div>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<!-- <ai-card>
<template #title>
<div class="AppAnnounceDetail-title">
<span :class="[currIndex === 0 ? 'active' : '']" @click="currIndex = 0">成员统计</span>
<span :class="[currIndex === 1 ? 'active' : '']" @click="currIndex = 1">居民群统计</span>
</div>
</template>
<template #content>
<div class="content-item" v-if="currIndex === 0">
<div class="top">
<div class="top-item">
<div class="top-item__title">
<h3>计划执行成员</h3>
</div>
<p>{{ memberInfo.planCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>未执行成员</h3>
</div>
<p>{{ memberInfo.unExecutedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>已执行成员</h3>
</div>
<p>{{ memberInfo.executedCount || 0 }}</p>
</div>
<div class="top-item" v-if="info.sendChannel === '0'">
<div class="top-item__title">
<h3>无法执行成员</h3>
<el-tooltip
placement="top"
content="由于员工不在可见范围、离职、客户群接收已达到上限等原因,无法执行群发任务的成员总数">
<i class="iconfont iconDetails"></i>
</el-tooltip>
</div>
<p>{{ memberInfo.cannotExecuteCount || 0 }}</p>
</div>
</div>
<div class="bottom">
<div class="bottom-search">
<div class="left">
<el-radio-group v-model="search1.sendStatus" size="small" @change="search1.current = 1, getMemberInfo()">
<el-radio-button size="small" label="0">未执行</el-radio-button>
<el-radio-button size="small" label="1">已执行</el-radio-button>
<el-radio-button size="small" label="2" v-if="info.sendChannel === '0'">无法执行</el-radio-button>
</el-radio-group>
<ai-picker
dialogTitle="选择部门"
action="/app/wxcp/wxdepartment/departList"
:instance="instance"
@pick="e => onUserChange(e, 'search1')" :multiple="false" v-model="user1">
<div class="userSelcet">
<span style="color: #606266;" v-if="search1.deptartId">{{ name1 }}</span>
<span v-else>部门</span>
<i class="el-icon-arrow-up" v-if="!search1.deptartId"></i>
<i class="el-icon-circle-close" v-if="search1.deptartId" @click.stop="user1 = [], search1.deptartId = '', search1.current = 1, getMemberInfo()"></i>
</div>
</ai-picker>
</div>
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(0)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
</div>
<ai-table
:tableData="tableData1"
:col-configs="colConfigs1"
:total="total1"
border
tableSize="small"
:current.sync="search1.current"
:size.sync="search1.size"
@getList="getMemberInfo">
<el-table-column slot="user" label="成员" align="left">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.groupOwnerName }}</span>
<span style="color: #999">{{ row.mainDepartmentName }}</span>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</div>
<div class="content-item" v-if="currIndex === 1">
<div class="top">
<div class="top-item">
<div class="top-item__title">
<h3>计划送达居民群</h3>
</div>
<p>{{ groupInfo.planCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>未送达居民群</h3>
</div>
<p>{{ groupInfo.unExecutedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>已送达居民群</h3>
</div>
<p>{{ groupInfo.executedCount || 0 }}</p>
</div>
<div class="top-item" v-if="info.sendChannel === '0'">
<div class="top-item__title">
<h3>无法送达居民群</h3>
</div>
<p>{{ groupInfo.cannotExecuteCount || 0 }}</p>
</div>
</div>
<div class="bottom">
<div class="bottom-search">
<div class="left">
<el-radio-group v-model="search2.sendStatus" size="small" @change="search2.current = 1, getGroupInfo()">
<el-radio-button size="small" label="0">未送达</el-radio-button>
<el-radio-button size="small" label="1">已送达</el-radio-button>
<el-radio-button size="small" label="2" v-if="info.sendChannel === '0'">无法送达</el-radio-button>
</el-radio-group>
<ai-picker
dialogTitle="选择部门"
action="/app/wxcp/wxdepartment/departList"
:instance="instance"
@pick="e => onUserChange(e, 'search2')" :multiple="false" v-model="user2">
<div class="userSelcet">
<span style="color: #606266;" v-if="search2.deptartId">{{ name2 }}</span>
<span v-else>部门</span>
<i class="el-icon-arrow-up" v-if="!search2.deptartId"></i>
<i class="el-icon-circle-close" v-if="search2.deptartId" @click.stop="user1 = [], search2.deptartId = '', search2.current = 1, getGroupInfo()"></i>
</div>
</ai-picker>
</div>
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(1)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
</div>
<ai-table
:tableData="tableData2"
:col-configs="colConfigs2"
:total="total2"
border
tableSize="small"
:current.sync="search2.current"
:size.sync="search2.size"
@getList="getGroupInfo">
<el-table-column slot="user" label="群主" align="center">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.groupOwnerName }}</span>
<span style="color: #999">{{ row.mainDepartmentName }}</span>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</div>
</template>
</ai-card> -->
<ai-dialog
:visible.sync="isShowGroups"
width="890px"
title="群发范围"
customFooter
@onConfirm="isShowGroups = false">
<ai-table
:tableData="info.wxGroups"
:col-configs="colConfigs3"
border
tableSize="small"
:isShowPagination="false"
@getList="() => {}">
</ai-table>
<div class="dialog-footer" slot="footer">
<el-button @click="isShowGroups = false">关闭</el-button>
</div>
</ai-dialog>
<div class="detail-phone" v-if="isShowPhone">
<div class="mask"></div>
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="true" :content="content" :fileList="fileList"></Phone>
</div>
</template>
</ai-detail>
</template>
<script>
import { mapState } from 'vuex'
import Phone from './Phone'
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
components: {
Phone
},
data () {
return {
total1: 0,
isShowGroups: false,
isShowPhone: false,
total2: 0,
user1: [],
user2: [],
name1: '',
name2: '',
radio1: '未执行',
search1: {
current: 1,
size: 10,
deptartId: '',
type: 0,
sendStatus: '0'
},
search2: {
current: 1,
size: 10,
deptartId: '',
type: 1,
sendStatus: '0'
},
memberInfo: {},
groupInfo: {},
tableData1: [],
fileList: [],
tableData2: [],
info: {},
content: '',
currIndex: 0,
colConfigs3: [
{ prop: 'groupOwnerName', label: '群主' },
{ prop: 'groupNames', label: '群名称' }
],
colConfigs1: [
{ slot: 'user', label: '成员' },
{ prop: 'groupCount', label: '预计送达居民群', align: 'center' }
],
colConfigs2: [
{ prop: 'groupName', label: '居民群' },
{ prop: 'memberCount', label: '群人数', align: 'center' },
{ slot: 'user', label: '群主', align: 'center' },
],
groups: [],
timer: null,
min: 60,
isDisabled: false,
rejecterId: ''
}
},
computed: {
...mapState(['user'])
},
created () {
this.getInfo(this.params.id)
// this.getMemberInfo()
// this.getGroupInfo()
},
destroyed () {
clearInterval(this.timer)
},
methods: {
getMemberInfo () {
this.instance.post(`/app/appmasssendingtaskxbot/detailStatistics`, null, {
params: {
...this.search1,
taskId: this.params.id
}
}).then(res => {
if (res.code === 0) {
this.tableData1 = res.data.executedList.records
this.total1 = res.data.executedList.total
this.memberInfo = res.data
}
})
},
onUserChange (e, search) {
if (e.length) {
search === 'search1' ? this.name1 = e[0].name : this.name2 = e[0].name
this[search].deptartId = e[0].id
} else {
this[search].deptartId = ''
search === 'search1' ? this.name1 = '' : this.name2 = ''
}
this[search].current = 1
if (search === 'search1') {
this.getMemberInfo()
} else {
this.getGroupInfo()
}
},
sendMsg () {
this.instance.post(`/app/appmasssendingtaskxbot/remindSend?id=${this.params.id}`).then(res => {
if (res.code === 0) {
this.$message.success('提醒成功')
this.getInfo(this.params.id)
}
})
},
getGroupInfo () {
this.instance.post(`/app/appmasssendingtaskxbot/detailStatistics`, null, {
params: {
...this.search2,
taskId: this.params.id
}
}).then(res => {
if (res.code === 0) {
this.tableData2 = res.data.executedList.records.map(v => {
return {
...v,
groupName: v.groupName || '未命名群聊'
}
})
this.total2 = res.data.executedList.total
this.groupInfo = res.data
}
})
},
countdown () {
this.timer = setInterval(() => {
const nowTime = this.$moment(new Date())
const min = nowTime.diff(this.info.remindTime, 'minute')
this.min = (60 - min)
if (this.min <= 0) {
this.isDisabled = false
clearInterval(this.timer)
} else {
this.isDisabled = true
}
}, 1000)
},
getInfo (id) {
this.instance.post(`/app/appmasssendingtaskxbot/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
if (res.data.status === '4' && res.data.remindTime) {
this.countdown()
}
const content = res.data.contents.filter(v => v.msgType === '0')
if (content.length) {
this.content = content[0].content
}
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
return {
...v,
...v.sysFile
}
})
this.info.wxGroups = res.data.wxGroups.map(v => {
this.groups.push(...v.groupIds.split(','))
return {
...v,
groupIds: v.groupIds.split(',')
}
})
if (res.data.examines && res.data.examines.length) {
const user = res.data.examines.filter(v => v.examineStatus === '2')
if (user.length) {
this.rejecterId = user[0].examineUserId
}
}
}
})
},
mapType (type) {
return {
1: '图片',
2: '视频',
3: '文件',
4: '网站',
5: '小程序'
}[type]
},
mapIcon (type) {
return {
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
}[type]
},
cancel (isRefresh) {
this.$emit('change', {
type: 'AnnounceList',
isRefresh: !!isRefresh,
params: {id: this.params.clientInfoId, status: this.params.status}
})
}
}
}
</script>
<style scoped lang="scss">
.AppAnnounceDetail {
position: relative;
.user-wrapper {
display: flex;
flex-wrap: wrap;
}
.detail-phone {
position: fixed;
left: 0%;
top: 0%;
z-index: 11;
width: 100%;
height: 100%;
.mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
background: rgba($color: #000000, $alpha: 0.6);
}
:deep( .phone-container ){
position: absolute;
left: 50%;
top: 50%;
z-index: 11;
transform: translate(-50%, -50%);
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
margin-left: 12px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
.userinfo {
display: flex;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.user {
display: flex;
align-items: center;
line-height: 1;
margin-right: 8px;
img {
width: 16px;
height: 16px;
margin-right: 2px;
}
span {
position: relative;
top: 2px;
color: #222222;
font-size: 12px;
}
}
.text {
display: flex;
align-items: center;
i {
color: #2266FF;
font-style: normal;
}
em {
margin-left: 8px;
color: #2266FF;
font-size: 12px;
font-style: normal;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.6;
}
}
}
.msg {
background: #F9F9F9;
border-radius: 2px;
border: 1px solid #D0D4DC;
p {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
line-height: 38px;
padding: 0px 12px;
overflow: hidden;
}
.msg-bottom {
display: flex;
align-items: center;
justify-content: space-between;
height: 38px;
padding: 0 16px;
border-top: 1px solid #D0D4DC;
.left {
display: flex;
align-items: center;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
span {
color: #222222;
font-size: 14px;
}
i {
color: #2266FF;
font-size: 14px;
font-style: normal;
}
}
.right {
color: #2266FF;
font-size: 12px;
cursor: pointer;
&:hover {
opacity: 0.6;
}
}
}
}
:deep( .AppAnnounceDetail-title ){
display: flex;
align-items: center;
span {
height: 100%;
line-height: 56px;
margin-right: 32px;
color: #888888;
font-size: 16px;
font-weight: 600;
transition: all ease 0.3s;
border-bottom: 3px solid transparent;
cursor: pointer;
user-select: none;
&:hover {
color: #222;
}
&:last-child {
margin-right: 0;
}
&.active {
color: #222222;
border-bottom: 3px solid #2266FF;
}
}
}
.content-item {
.top {
display: flex;
align-items: center;
margin-bottom: 16px;
.top-item {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
height: 90px;
margin-right: 16px;
padding: 0 16px;
background: #F9F9F9;
border-radius: 2px;
&:last-child {
margin-right: 0;
}
.top-item__title {
display: flex;
align-items: center;
margin-bottom: 8px;
i {
margin-left: 4px;
color: #8899bb;
font-size: 16px;
}
}
h3 {
color: #222222;
font-size: 14px;
font-weight: 700;
}
p {
color: #2266FF;
font-size: 24px;
font-weight: 700;
}
}
}
.bottom-search {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.left {
display: flex;
align-items: center;
}
}
}
:deep( .right-tips ){
display: flex;
align-items: center;
i {
margin-right: 4px;
color: #8899bb;
font-size: 16px;
}
span {
color: #888888;
font-size: 12px;
}
}
}
</style>

View File

@@ -0,0 +1,243 @@
<template>
<section class="GroupList">
<ai-list>
<template slot="title">
<ai-title title="群匹配" isShowBottomBorder isShowBack @onBackClick="cancel(false)"></ai-title>
</template>
<template #content>
<ai-search-bar>
<template #left>
<ai-select v-model="search.matchStatus" :selectList="dict.getDict('xbotGroupMatchStatus')" placeholder="请选择是否匹配" @change="search.current = 1, getTableData()"/>
<ai-select v-model="search.status" :selectList="dict.getDict('xbotGroupStatus')" placeholder="请选择群状态" @change="search.current = 1, getTableData()"/>
<el-button type="primary" @click="match">自动匹配</el-button>
</template>
<template #right>
<el-input
v-model="search.nickname"
size="small"
placeholder="群名称"
clearable
v-throttle="() => {search.current = 1, getTableData()}"
@clear="search.current = 1, search.nickname = '', getTableData()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="total" :current.sync="search.current" :size.sync="search.size"
@getList="getTableData()" :col-configs="colConfigs">
<el-table-column slot="avatar" label="群聊头像" align="left">
<template slot-scope="{ row }">
<img :src="row.avatar ? row.avatar : 'https://cdn.cunwuyun.cn/dvcp/group-img.png'" alt="" class="group-avatar">
</template>
</el-table-column>
<el-table-column slot="option" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="edit(row)">编辑</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog
:visible.sync="dialog"
width="800px"
@close="dialog=false"
title="群关联信息"
@onConfirm="onConfirm">
<el-form class="ai-form" label-width="120px" :model="editInfo" ref="editInfo">
<el-form-item label="群名称" style="width: 100%;">
<div>{{editInfo.nickname}}</div>
</el-form-item>
<el-form-item label="群主ID" style="width: 100%;">
<div>{{editInfo.managerWxid}}</div>
</el-form-item>
<el-form-item label="人员关联" prop="managerTpUserName" style="width: 100%;" :rules="[{ required: true, message: '请选择人员' }]">
<el-input disabled size="small" v-model="editInfo.managerTpUserName" clearable placeholder="请选择人员">
<template slot="append">
<ai-user-selecter refs="addTags" :isMultiple="false" :instance="instance" v-model="editInfo.user" @change="onChooseUser">
<el-button size="small">选择人员</el-button>
</ai-user-selecter>
</template>
</el-input>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "GroupList",
props: {
instance: Function,
dict: Object,
params: Object
},
data() {
return {
search: {
current: 1,
size: 10,
matchStatus: '',
status: '',
nickname: ''
},
tableData: [],
total: 0,
dialog: false,
editInfo: {}
}
},
created() {
this.dict.load('xbotGroupMatchStatus', 'xbotGroupStatus').then(() => {
this.getTableData()
})
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{ slot: "avatar", label: '群聊头像'},
{ prop: "nickname", label: '群名称', align: 'center'},
{ prop: "managerNickname", label: '群主昵称', align: 'center'},
{ prop: "status", label: '群状态', align: 'center', dict: 'xbotGroupStatus'},
{ prop: "managerTpUserName", label: '关联人员', align: 'center'},
{ prop: "managerTpUserDeptName", label: '主部门', align: 'center'},
{ slot: "option"},
// { prop: "status", label: '状态'},
]
},
},
methods: {
getTableData() {
this.instance.post(`/app/xbotCallback/groupMatchList`,null,{
params: {
...this.search,
managerWxid: this.params.loginUserId
}
}).then(res => {
if(res?.data) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
showDialog(row) {
this.dialog = true
this.groupInfo = row
this.getTableDataGroup()
},
getTableDataGroup() {
this.instance.post(`/app/xbotCallback/groupList`,null,{
params: {
// ...this.searchGroup,
current: 1,
managerWxid: this.groupInfo.loginUserId
}
}).then(res => {
if(res?.data) {
this.tableDataGroup = JSON.parse(res.data)
this.tableDataGroup.map((item) => {
item.num = item.member_list.length
})
// this.totalGroup = res.data.total
}
})
},
cancel(isRefresh) {
this.$emit('change', {
type: 'List',
isRefresh: !!isRefresh
})
},
match() {
this.instance.post(`/app/xbotCallback/automaticMatch`,null,{
params: {
managerWxid: this.params.loginUserId
}
}).then(res => {
if(res.code == 0) {
this.$message.success('匹配成功!')
this.getTableData()
}
})
},
edit(row) {
this.dialog = true
this.editInfo = {...row}
this.editInfo.user = [{id: row.managerTpwxid, name: row.managerTpUserName}]
},
onChooseUser(v) {
this.editInfo.wxUserId = v[0].id
this.editInfo.managerTpUserName = v[0].name
this.editInfo.user = v
},
onConfirm() {
this.$refs.editInfo.validate(v => {
if (v) {
this.instance.post('/app/xbotCallback/manualMatch', null, {
params: {
wxUserId: this.editInfo.wxUserId,
wxid: this.editInfo.wxid
}
}).then(res => {
if (res?.code == 0) {
this.dialog = false
this.getTableData()
this.$message.success('编辑成功!')
}
})
}
})
}
},
}
</script>
<style lang="scss" scoped>
.GroupList {
height: 100%;
.time-select {
padding: 0 16px;
height: 32px;
line-height: 32px;
border: 1px solid #d0d4dc;
border-radius: 4px;
display: flex;
justify-content: space-between;
cursor: pointer;
.el-icon-arrow-down {
line-height: 32px;
}
}
:deep .is-error {
.time-select {
border: 1px solid #f46!important;
}
}
.tips {
display: inline-block;
color: #999;
font-size: 14px;
margin-left: 16px;
}
.group-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
}
:deep .ai-dialog__content--wrapper {
// height: 1000px;
// overflow-y: scroll;
.ai-table {
// height: 1000px;
}
}
}
</style>

View File

@@ -0,0 +1,327 @@
<template>
<section class="List">
<ai-list>
<template slot="title">
<ai-title title="任务管理" isShowBottomBorder></ai-title>
</template>
<template #content>
<ai-search-bar>
<template #left>
<el-select v-model="configInfo.id" filterable placeholder="请选择机位" size="small" @change="configChange" clearable>
<el-option
v-for="item in configList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<ai-area-get
style="width: 180px;"
placeholder="所属地区"
:instance="instance"
v-model="search.areaId"
@change="getListInit"/>
<span class="tips">当前机位1有效期为{{configInfo.validity}}&nbsp;&nbsp;&nbsp;&nbsp;ip地址为{{configInfo.xbotIp}}</span>
</template>
<template #right>
<el-button size="small" v-if="configInfo.validity" :type="configInfo.status != 1 ? 'info' : 'primary'" @click="openWechat()" :disabled="configInfo.status != 1">{{configInfo.status != 1 ? '已启动' : '启动'}}</el-button>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="total" :current.sync="search.current" :size.sync="search.size"
@getList="getTableData()" :col-configs="colConfigs">
<el-table-column slot="avatar" label="头像 " align="left" width="200">
<template slot-scope="{ row }">
<img :src="row.avatar ? row.avatar : 'https://cdn.cunwuyun.cn/dvcp/group-img.png'" alt="" class="group-avatar">
</template>
</el-table-column>
<el-table-column slot="groupCount" label="监控群聊" align="center">
<template slot-scope="{ row }">
<div>{{row.monitorGroupCount}}</div>
</template>
</el-table-column>
<el-table-column slot="areaName" label="所属地区" align="center">
<template slot-scope="{ row }">
<div @click="changeArea(row)" style="cursor: pointer;color:#26f">{{row.areaName || '请选择'}}</div>
</template>
</el-table-column>
<el-table-column slot="num" label="群匹配" align="center">
<template slot-scope="{ row }">
<div>{{row.matchGroupCount}}/{{row.groupCount}}</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" align="center" width="200px">
<template slot-scope="{ row }">
<!-- <el-button type="text" @click="toGroup(row)">群匹配</el-button> -->
<el-button type="text" @click="toTask(row)">调用任务</el-button>
<el-button type="text" @click="toAnnouce(row)">推送任务</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog
title="监控群聊"
:visible.sync="dialog"
:destroyOnClose="true"
width="720px"
:customFooter="true">
<ai-table :tableData="tableDataGroup" :isShowPagination="false"
@getList="getTableDataGroup()" :col-configs="colConfigsGroup">
<el-table-column slot="avatar" label="群聊头像" align="left">
<template slot-scope="{ row }">
<img :src="row.avatar ? row.avatar : 'https://cdn.cunwuyun.cn/dvcp/group-img.png'" alt="" class="group-avatar">
</template>
</el-table-column>
</ai-table>
<div class="dialog-footer" slot="footer">
<el-button @click="dialog=false">关闭</el-button>
</div>
</ai-dialog>
<ai-dialog
title="所属地区设置"
:visible.sync="showArea"
:destroyOnClose="true"
width="720px"
@onConfirm="areaConfirm"
>
<el-form class="ai-form" :model="areaInfo" label-width="120px" ref="form">
<el-form-item prop="areaId" style="width: 100%;" label="所属地区" :rules="[{required: true, message: '请选择所属地区地区', trigger: 'change'}]">
<ai-area-get style="width: 400px;" placeholder="所属地区" :instance="instance" v-model="areaInfo.areaId" :name.sync="areaInfo.areaName" />
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "List",
props: {
instance: Function,
dict: Object
},
data() {
return {
search: {
areaId: '',
current: 1,
size: 10,
},
tableData: [],
total: 0,
dialog: false,
configList: [],
configId: '',
configInfo: {id: ''},
groupInfo: {},
searchGroup: {
current: 1,
size: 10,
},
tableDataGroup: [],
totalGroup: 0,
showArea: false,
areaInfo: {}
}
},
created() {
this.dict.load('yesOrNo', 'deviceStatus').then(() => {
this.getConfigList()
})
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{ slot: "avatar"},
{ prop: "xbotName", label: '机位'},
{ slot: "loginUserId", label: '用户id'},
{ prop: "loginUserName", label: '姓名', align: "left"},
{ prop: "phone", label: '手机号'},
// { prop: "girdNames", label: '管辖区域'},
{ slot: "groupCount", label: '监控群聊'},
{ slot: "areaName"},
{ slot: "num", label: '群匹配'},
{ prop: "status", label: '状态', dict: 'deviceStatus'},
{ slot: "option"},
]
},
colConfigsGroup() {
return [
{ slot: "avatar", label: '群聊头像'},
{ prop: "nickname", label: '群聊名称', align: 'center'},
{ prop: "is_manager", label: '是否管理员', dict: 'yesOrNo', align: 'center'},
{ prop: "num", label: '群聊成员(人数)', align: 'center'},
// { prop: "phone", label: '群主'},
]
},
},
methods: {
getConfigList() {
this.instance.post(`/app/appxbotconfig/list?size=1000`).then(res => {
if(res?.data) {
res.data.records.map((item) => {
item.label = item.xbotName
item.value = item.id
})
this.configList = res.data.records
// this.configInfo = this.configList[0]
this.getTableData()
}
})
},
configChange(e) {
if(e) {
this.configList.map((item) => {
if(item.id == e) {
this.configInfo = {...item}
}
})
}else {
this.configInfo = {id: ''}
}
this.search.current = 1
this.getTableData()
},
openWechat() {
this.instance.post(`/app/appxbotconfig/openWechat?id=${this.configInfo.id}`).then(res => {
if(res.code === 0) {
this.configInfo.status = 0
this.$message.success('启动成功!')
}
})
},
getListInit() {
this.search.current = 1
this.getTableData()
},
getTableData() {
this.instance.post(`/app/xbotCallback/list`,null,{
params: {
...this.search,
xbotId: this.configInfo.id ? this.configInfo.id : ''
}
}).then(res => {
if(res?.data) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
showDialog(row) {
if(row.status == 1) {
this.dialog = true
this.groupInfo = row
this.getTableDataGroup()
}
},
changeArea(row) {
this.showArea = true
this.areaInfo = {...row}
this.$refs['form'].clearValidate();
},
areaConfirm() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.instance.post(`app/xbotCallback/updateArea?id=${this.areaInfo.id}&areaId=${this.areaInfo.areaId}&areaName=${this.areaInfo.areaName}`).then(res => {
if (res.code == 0) {
this.$message.success('所属地区设置成功')
this.showArea = false
this.getListInit()
}
})
} else {
return false
}
});
},
getTableDataGroup() {
this.tableDataGroup = []
this.instance.post(`/app/xbotCallback/groupList`,null,{
params: {
// ...this.searchGroup,
current: 1,
managerWxid: this.groupInfo.loginUserId,
xbotId: this.groupInfo.xbotId
}
}).then(res => {
if(res?.data) {
var tableDataGroup = JSON.parse(res.data)
tableDataGroup.map((item) => {
item.num = item.member_list.length
if(item.total_member > 2) {
this.tableDataGroup.push(item)
}
})
}
})
},
toGroup(row) {
this.$emit('change', {
type: 'GroupList',
params:row
})
},
toAnnouce(row) {
this.$emit('change', {
type: 'AnnounceList',
params: row,
isRefresh: true
})
},
toTask(row) {
this.$emit('change', {
type: 'TaskList',
params: row,
isRefresh: true
})
},
},
}
</script>
<style lang="scss" scoped>
.List {
height: 100%;
.time-select {
padding: 0 16px;
height: 32px;
line-height: 32px;
border: 1px solid #d0d4dc;
border-radius: 4px;
display: flex;
justify-content: space-between;
cursor: pointer;
.el-icon-arrow-down {
line-height: 32px;
}
}
:deep .is-error {
.time-select {
border: 1px solid #f46!important;
}
}
.tips {
display: inline-block;
color: #999;
font-size: 14px;
margin-left: 16px;
}
.group-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
}
:deep .ai-dialog__content--wrapper {
// height: 1000px;
// overflow-y: scroll;
.ai-table {
// height: 1000px;
}
}
}
</style>

View File

@@ -0,0 +1,344 @@
<template>
<div class="phone-container">
<img class="close" @click="$emit('close')" v-if="isShowClose" src="https://cdn.cunwuyun.cn/dvcp/announce/close.png" />
<img class="phone" src="https://cdn.cunwuyun.cn/dvcp/announce/phone.png" />
<img class="phone-wrapper" src="https://cdn.cunwuyun.cn/dvcp/announce/phone-wrapper.png" />
<div class="right-content">
<div class="msg-list">
<div class="msg-item" v-if="content">
<div class="msg-item__left">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
</div>
<div class="msg-item__right">
<div class="msg-wrapper msg-text">
<p>{{ content }}</p>
</div>
</div>
</div>
<div class="msg-item" v-for="item in fileList" :key="item.id">
<div class="msg-item__left">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
</div>
<div class="msg-item__right" :class="[['1', '2'].indexOf(item.msgType) !== -1 ? 'left-border' : '']">
<div class="msg-wrapper msg-img" v-if="item.msgType === '1'">
<img :src="item.imgPicUrl" />
</div>
<div class="msg-wrapper msg-video" v-if="item.msgType === '2'">
<video controls :src="item.url || item.fileUrl"></video>
</div>
<div class="msg-wrapper msg-file" v-if="item.msgType === '3'">
<div class="msg-left">
<h2>{{ item.name || item.title }}</h2>
<p>{{ item.fileSizeStr }}</p>
</div>
<img :src="mapIcon(item.name || item.fileUrl)" />
</div>
<div class="msg-wrapper msg-link" v-if="item.msgType === '4'">
<h2>{{ item.linkTitle }}</h2>
<div class="msg-right">
<p>{{ item.linkDesc }}</p>
<img :src="item.linkPicUrl || 'https://cdn.cunwuyun.cn/dvcp/announce/html.png'" />
</div>
</div>
<div class="msg-wrapper msg-miniapp" v-if="item.msgType === '5'">
<h2>{{ item.mpTitle }}</h2>
<img :src="item.url || item.pictureUrl" />
<div class="msg-bottom">
<i>小程序</i>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['fileList', 'avatar', 'content', 'isShowClose'],
watch: {
fileList (v) {
if (v.length) {
setTimeout(() => {
document.querySelector('.right-content').scrollTo(0, 999999)
}, 800)
}
}
},
methods: {
mapIcon (fileName) {
if (['.zip', '.rar'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/zip.png'
}
if (['.doc', '.docx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/world.png'
}
if (['.xls', '.xlsx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/xls.png'
}
if (['.txt'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/txt.png'
}
if (['.pdf'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/pdf.png'
}
if (['.ppt', '.pptx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/ppt.png'
}
},
getExtension(name) {
return name.substring(name.lastIndexOf('.'))
}
}
}
</script>
<style lang="scss" scoped>
.phone-container {
width: 338px;
height: 675px;
padding: 80px 15px 100px 32px;
.phone {
position: absolute;
left: 13px;
top: 4px;
z-index: 1;
width: 314px;
height: 647px;
}
.close {
position: absolute;
top: 0;
right: 0;
z-index: 111;
width: 60px;
height: 60px;
cursor: pointer;
transition: all ease 0.5s;
transform: translate(100%, -50%);
&:hover {
opacity: 0.7;
}
}
.phone-wrapper {
position: absolute;
left: 0;
top: 0;
z-index: 2;
width: 338px;
height: 675px;
}
.right-content {
position: relative;
z-index: 11;
height: 100%;
overflow-y: auto;
.msg-item {
display: flex;
margin-bottom: 20px;
.msg-item__left {
width: 42px;
height: 42px;
margin-right: 16px;
border-radius: 4px;
flex-shrink: 1;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.msg-item__right {
position: relative;
flex: 1;
&::after {
position: absolute;
top: 16px;
left: 0;
z-index: 1;
width: 0;
height: 0;
border-right: 6px solid #fff;
border-left: 6px solid transparent;
border-bottom: 6px solid transparent;
border-top: 6px solid transparent;
content: " ";
transform: translate(-100%, 0%);
}
&.left-border::after {
display: none;
}
.msg-img img {
max-width: 206px;
max-height: 200px;
}
.msg-video video {
max-width: 206px;
max-height: 200px;
}
.msg-text {
max-width: 206px;
width: max-content;
line-height: 1.3;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
word-break: break-all;
font-size: 14px;
color: #222222;
}
.msg-miniapp {
width: 206px;
padding: 0 12px;
text-align: justify;
font-size: 0;
background: #FFFFFF;
border-radius: 5px;
font-size: 14px;
color: #222222;
h2 {
line-height: 1.2;
padding: 8px 0;
border-bottom: 1px solid #eee;
color: #222222;
font-size: 14px;
}
& > img {
width: 100%;
height: 120px;
margin-bottom: 8px;
}
.msg-bottom {
display: flex;
align-items: center;
line-height: 1;
padding: 4px 0;
border-top: 1px solid #eee;
i {
margin-right: 4px;
font-size: 12px;
font-style: normal;
color: #999;
}
img {
width: 16px;
height: 16px;
border-radius: 50%;
}
}
}
.msg-file {
display: flex;
align-items: center;
width: 206px;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
.msg-left {
flex: 1;
margin-right: 18px;
h2 {
display: -webkit-box;
flex: 1;
line-height: 16px;
margin-bottom: 4px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
overflow: hidden;
color: #222222;
font-size: 14px;
width: 120px;
}
p {
color: #888888;
font-size: 12px;
}
}
img {
width: 44px;
height: 44px;
border-radius: 2px;
}
}
.msg-link {
width: 206px;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
h2 {
margin-bottom: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #222222;
font-size: 14px;
font-weight: normal;
}
.msg-right {
display: flex;
align-items: center;
p {
display: -webkit-box;
flex: 1;
line-height: 16px;
margin-right: 10px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
text-overflow: ellipsis;
overflow: hidden;
color: #888;
font-size: 12px;
}
img {
width: 50px;
height: 50px;
border-radius: 4px;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,951 @@
<template>
<ai-detail class="AppAnnounceAdd">
<template slot="title">
<ai-title :title="id ? '编辑任务' : '添加任务'" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<div class="AppAnnounceDetail-container">
<el-form ref="form" class="left" :model="form" label-width="110px" label-position="right">
<ai-card title="基本信息">
<template #content>
<div class="ai-form">
<el-form-item label="任务名称" prop="taskTitle" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入任务名称" v-model="form.taskTitle" :maxlength="15" show-word-limit></el-input>
</el-form-item>
<el-form-item label="调用" style="width: 100%;" prop="sendScope" :rules="[{ required: true, message: '请选择发送范围', trigger: 'change' }]">
<el-radio-group v-model="form.sendScope" @change="onScopeChange">
<el-radio label="0">全部居民群</el-radio>
<el-radio label="1">按部门选择</el-radio>
<el-radio label="2">按网格选择</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选择群主" v-if="form.sendScope !== '0'" prop="wxGroupsName" style="width: 100%;" :rules="[{ required: true, message: '请选择选择群主', trigger: 'change' }]" >
<ai-picker
:instance="instance"
multiple
:dialogTitle="form.sendScope === '2' ? '选择网格' : '选择部门'"
:ops="{label: form.sendScope === '2' ? 'girdName' : 'name'}"
:pageTitle="form.sendScope === '2' ? '网格' : '部门'"
:action="form.sendScope === '1' ? `/app/wxcp/wxdepartment/departList` : '/app/appgirdinfo/girdList'"
v-model="form.filterCriteria"
@pick="onPick"
@change="onSelcetChange">
<div class="AppAnnounceDetail-select">
<el-input size="small" class="AppAnnounceDetail-select__input" placeholder="请选择..." disabled v-model="form.wxGroupsName"></el-input>
<div class="select-left" v-if="form.wxGroups && form.wxGroups.length">
<span v-for="(item, index) in form.wxGroups" :key="index" v-if="index < 9">{{ item.groupOwnerName }}</span>
<em v-if="form.wxGroups.length > 9">{{ form.wxGroups.length }}</em>
</div>
<i v-else>请选择</i>
<div class="select-right">{{ form.filterCriteria.length ? '重新选择' : '选择' }}</div>
</div>
</ai-picker>
<div class="tips">
<p>消息预计送达居民群数</p>
<span>{{ groupLen }}</span>
<!-- <el-tooltip
placement="top"
content="将由指定群主发送给TA作为群主的所有的群由于企业微信限制当超过1000个时将只发送到最近活跃的1000个群">
<i class="iconfont iconModal_Warning"></i>
</el-tooltip> -->
</div>
</el-form-item>
<el-form-item label="调用内容" prop="fileList" style="width: 100%;" :rules="[{ required: true, message: '请上传发送内容', trigger: 'blur' }]">
<!-- <ai-uploader :instance="instance" fileType="file" v-model="form.fileList" :limit="1" @change="onChange" accept=".csv, .CSV"></ai-uploader> -->
<el-upload action="/" :on-change="onChange" :on-remove="handleRemove" :auto-upload="false" accept=".csv, .CSV" :limit="1">
<el-button size="small" type="primary" class="upload-bom uploadBtn">
<span class="iconXlSX-sty-test">上传附件</span>
</el-button>
</el-upload>
</el-form-item>
<el-form-item label="日调用次数" prop="dayLimit" style="width: 50%;" :rules="[{ required: true, message: '请输入日调用次数', trigger: 'blur' }]">
<el-input-number size="small" v-model="form.dayLimit" :min="1" :max="50000" label="请输入日调用次数" @change="handleChangeCount"></el-input-number>
</el-form-item>
<el-form-item label="预计执行天数" prop="days" style="width: 50%;" :rules="[{ required: true, message: '请输入预计执行天数', trigger: 'blur' }]">
<el-input-number size="small" v-model="form.days" :min="1" :max="50" label="请输入预计执行天数" @change="handleChangeCount"></el-input-number>
</el-form-item>
<el-form-item label="累计调用" prop="totalCount" style="width: 50%;">
<el-input size="small" placeholder="自动生成" v-model="form.totalCount" disabled></el-input>
</el-form-item>
<!-- <el-form-item label="标签" style="width: 100%;" prop="markTag">
<el-checkbox-group v-model="form.markTag">
<el-checkbox
v-for="(item, index) in dict.getDict('mstTag')"
:key="index"
:label="item.dictValue">
{{ item.dictName }}
</el-checkbox>
</el-checkbox-group>
</el-form-item> -->
<el-form-item label="结束时间" style="width: 100%;" prop="taskEndTime" :rules="[{ required: true, message: '请选择结束时间', trigger: 'change' }]">
<el-date-picker
style="width: 100%;"
v-model="form.taskEndTime"
type="datetime"
size="small"
:picker-options="pickerOptions"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择结束时间">
</el-date-picker>
</el-form-item>
<!-- <el-form-item label="宣发审批" prop="enableExamine" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
<el-switch
v-model="form.enableExamine"
active-value="1"
inactive-value="0"
active-text="开启后创建的群发任务需要审批人进行审批">
</el-switch>
</el-form-item> -->
<el-form-item v-if="form.enableExamine === '1'" label="审批人员" prop="examines" style="width: 100%;" :rules="[{ required: true, message: '请选择审批人员', trigger: 'change' }]">
<ai-user-selecter :instance="instance" v-model="form.examines" @change="onUserChange">
<div class="AppAnnounceDetail-select">
<el-input class="AppAnnounceDetail-select__input" size="small" placeholder="请选择..." v-model="form.examinesName"></el-input>
<div class="select-left" v-if="form.examines && form.examines.length">
<span v-for="(item, index) in form.examines" :key="index">{{ item.name }}</span>
</div>
<i v-if="!form.examines.length">请选择</i>
<div class="select-right">{{ form.examines.length ? '重新选择' : '选择' }}</div>
</div>
</ai-user-selecter>
</el-form-item>
</div>
</template>
</ai-card>
</el-form>
<!-- <div class="right">
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="false" :content="form.content" :fileList="fileList"></Phone>
</div> -->
<ai-dialog
:visible.sync="isShowAddLink"
width="920px"
title="链接消息"
@close="onClose"
@onConfirm="onLinkConfirm">
<el-form ref="linkForm" :model="linkForm" label-width="110px" label-position="right">
<div class="ai-form">
<el-form-item label="标题" style="width: 100%;" prop="linkTitle" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入标题"
maxlength="42"
show-word-limit
v-model="linkForm.linkTitle">
</el-input>
</el-form-item>
<el-form-item label="链接" style="width: 100%;" prop="linkUrl" :rules="[{ required: true, message: '请输入链接', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入链接"
maxlength="682"
show-word-limit
v-model="linkForm.linkUrl">
</el-input>
</el-form-item>
<el-form-item label="描述" style="width: 100%;" prop="linkDesc">
<el-input
size="small"
placeholder="请输入描述"
maxlength="170"
show-word-limit
v-model="linkForm.linkDesc">
</el-input>
</el-form-item>
<el-form-item label="封面图" prop="linkPicUrl" style="width: 100%;">
<ai-uploader :instance="instance" v-model="linkForm.linkPicUrl" :limit="1"></ai-uploader>
</el-form-item>
</div>
</el-form>
</ai-dialog>
<ai-dialog
:visible.sync="isShowAddMiniapp"
width="920px"
title="小程序消息"
@close="onClose"
@onConfirm="onMiniAppForm">
<el-form ref="miniAppForm" :model="miniAppForm" label-width="130px" label-position="right">
<div class="ai-form">
<el-form-item label="小程序appid" style="width: 100%;" prop="mpAppid" :rules="[{ required: true, message: '小程序appid', trigger: 'blur' }]">
<el-input
size="small"
placeholder="小程序appid"
v-model="miniAppForm.mpAppid">
</el-input>
</el-form-item>
<el-form-item label="小程序page路径" style="width: 100%;" prop="mpPage" :rules="[{ required: true, message: '请输入小程序page路径', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入小程序page路径"
v-model="miniAppForm.mpPage">
</el-input>
</el-form-item>
<el-form-item label="标题" style="width: 100%;" prop="mpTitle" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入标题"
maxlength="20"
show-word-limit
v-model="miniAppForm.mpTitle">
</el-input>
</el-form-item>
<el-form-item label="封面图" prop="media" style="width: 100%;" :rules="[{ required: true, message: '请上传封面图', trigger: 'change' }]">
<ai-uploader url="/app/wxcp/upload/uploadFile?type=image" :instance="instance" isWechat v-model="miniAppForm.media" :limit="1"></ai-uploader>
</el-form-item>
</div>
</el-form>
</ai-dialog>
<ai-dialog
:visible.sync="isShowDate"
width="590px"
title="定时发送"
customFooter>
<el-form ref="dateForm" :model="dateForm" label-width="130px" label-position="right">
<div class="ai-form">
<el-form-item label="定时发送时间" style="width: 100%;" prop="choiceTime" :rules="[{ required: true, message: '请选择定时发送时间', trigger: 'change' }]">
<el-date-picker
style="width: 100%;"
v-model="dateForm.choiceTime"
type="datetime"
size="small"
:picker-options="pickerOptions"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择定时发送时间">
</el-date-picker>
</el-form-item>
</div>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="onClose">取消</el-button>
<el-button @click="onDateForm" type="primary" :loading="isLoading2" style="width: 92px;">确认</el-button>
</div>
</ai-dialog>
<!-- <ChooseMaterial ref="ChooseMaterial" :instance="instance" @change="onChooseChange"></ChooseMaterial> -->
</div>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm(0)" :loading="isLoading1" style="width: 120px;">立即执行</el-button>
<el-button type="primary" @click="confirm(1)">定时任务</el-button>
</template>
</ai-detail>
</template>
<script>
import Phone from './Phone'
import ChooseMaterial from './ChooseMaterial.vue'
import { mapActions, mapState } from 'vuex'
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
components: {
Phone,
ChooseMaterial
},
data() {
return {
info: {},
department: [],
isShowChoose: false,
isLoading1: false,
isLoading2: false,
fileList: [],
isShowAddLink: false,
isShowAddMiniapp: false,
isShowDate: false,
isLoading: false,
linkForm: {
linkPicUrl: [],
linkDesc: '',
linkTitle: '',
linkUrl: ''
},
dateForm: {
choiceTime: ''
},
miniAppForm: {
mpAppid: '',
mpPage: '',
mpTitle: '',
media: []
},
form: {
content: '',
choiceTime: '',
contents: [],
enableExamine: '0',
taskEndTime: '',
examines: [],
wxGroups: [],
markTag: [],
wxGroupsName: '',
sendScope: '1',
sendType: 0,
name: '',
filterCriteria: [],
taskTitle: '',
examinesName: '',
fileList: []
},
girdNames: '',
id: '',
tagsList: [],
pickerOptions: {
disabledDate: e => {
return e.getTime() < (Date.now() - 60 * 1000 * 60 * 24)
}
},
fileContentList: []
}
},
computed: {
...mapState(['user']),
groupLen() {
let i = 0
if(this.form.wxGroups && this.form.wxGroups.length) {
this.form.wxGroups.forEach(v => {
i = i + v.groupIds.split(',').length
})
}
return i
}
},
created() {
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
this.dict.load('mstTag')
} else {
this.getWxGroups()
this.dict.load('mstTag')
}
},
methods: {
...mapActions(['initOpenData', 'transCanvas']),
onChooseChange (e) {
this.form.content = e.filter(v => v.msgType === '0').map(v => v.content).join(' ')
this.fileList = this.fileList.concat(e.filter(v => v.msgType !== '0'))
},
getInfo(id) {
this.instance.post(`/app/appmasssendingtaskbaidu/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = {
...this.form,
...res.data,
wxGroupsName: '1',
filterCriteria: res.data.filterCriteria.split(','),
// markTag: res.data.markTag.split(',')
}
if (res.data.girdNames) {
this.girdNames = res.data.girdNames.split(',')
}
this.dateForm.choiceTime = ''
if (res.data.examines && res.data.examines.length) {
this.form.examines = res.data.examines.map(v => {
return {
...v,
wxOpenUserId: v.examineUserId,
id: v.examineUserId,
name: v.examineUserName
}
})
this.form.examinesName = '1'
}
const content = res.data.contents.filter(v => v.msgType === '0')
if (content.length) {
this.$set(this.form, 'content', content[0].content)
}
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
return {
...v,
...v.sysFile
}
})
}
})
},
onUserChange(e) {
if (e.length) {
this.form.examinesName = '1'
} else {
this.form.wxGroupsName = ''
}
},
onScopeChange(e) {
this.form.filterCriteria = []
this.form.wxGroups = []
this.girdNames = ''
if (e === '0') {
this.getWxGroups()
} else {
this.form.filterCriteria = []
}
},
onPick(e) {
if (this.form.sendScope === '2' && e.length) {
this.girdNames = e.map(v => v.girdName)
}
},
onSelcetChange(e) {
if (e.length) {
this.form.wxGroupsName = '1'
this.$nextTick(() => {
this.getWxGroups()
})
} else {
this.form.wxGroupsName = ''
this.form.wxGroups = []
}
},
getWxGroups() {
this.instance.post(`/app/appmasssendingtaskbaidu/queryWxGroups?sendScope=${this.form.sendScope}&clientInfoId=${this.params.clientInfoId}`, {
filterCriteria: this.form.filterCriteria.join(',')
}).then(res => {
if (res.code === 0) {
this.form.wxGroups = res.data
}
})
},
onLinkConfirm() {
this.$refs.linkForm.validate((valid) => {
if (valid) {
this.fileList.push({
...this.linkForm,
linkPicUrl: this.linkForm.linkPicUrl.length ? this.linkForm.linkPicUrl[0].url : '',
msgType: '4'
})
this.isShowAddLink = false
}
})
},
onMiniAppForm() {
this.$refs.miniAppForm.validate((valid) => {
if (valid) {
this.fileList.push({
...this.miniAppForm,
msgType: '5',
...this.miniAppForm.media[0],
mediaId: this.miniAppForm.media[0].media.mediaId,
sysFileId: this.miniAppForm.media[0].id
})
this.isShowAddMiniapp = false
}
})
},
onClose() {
this.linkForm.linkPicUrl = []
this.linkForm.linkDesc = ''
this.linkForm.linkTitle = ''
this.linkForm.linkUrl = ''
this.miniAppForm.mpAppid = ''
this.miniAppForm.mpPage = ''
this.miniAppForm.mpTitle = ''
this.dateForm.choiceTime = ''
this.isShowDate = false
},
removeFile(index) {
this.fileList.splice(index, 1)
},
mapIcon(type) {
return {
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
}[type]
},
onBeforeUpload(event) {
return this.onOverSize(event)
},
getExtension(name) {
return name.substring(name.lastIndexOf('.'))
},
handleChange(e, size, accept) {
const isLt10M = e.size / 1024 / 1024 < size
const suffixName = this.getExtension(e.name)
const suffixNameList = accept.split(',')
if (suffixNameList.indexOf(`${suffixName.toLowerCase()}`) === -1) {
this.$message.error(`不支持该格式`)
return false
}
if (!isLt10M) {
this.$message.error(`大小不超过${10}MB!`)
return false
}
return true
},
onExceed() {
this.$message.error(`最多上传9个附件`)
},
submitUpload(file, type) {
const fileType = {
'1': 'image',
'2': 'video',
'3': 'file'
}[type]
let formData = new FormData()
formData.append('file', file.file)
formData.append('type', fileType)
let loading = this.$loading()
this.instance.post(`/app/wxcp/upload/uploadFile`, formData, {
withCredentials: false
}).then(res => {
if (res.code == 0) {
this.fileList.push({
...res.data.file,
media: res.data.media,
msgType: type,
sysFileId: res.data.file.id,
imgPicUrl: res.data.file.url,
mediaId: res.data.media.mediaId
})
this.$message.success('上传成功')
}
}).finally(() => loading.close())
},
onDateForm() {
this.$refs.dateForm.validate((valid) => {
if (valid) {
if (new Date(this.dateForm.choiceTime).getTime() < Date.now()) {
return this.$message.error('定时发送时间不得早于当前时间')
}
if (this.params.sendChannel === '1' && new Date(this.dateForm.choiceTime).getTime() > new Date(this.form.taskEndTime).getTime()) {
return this.$message.error('定时发送时间不得晚于结束时间')
}
this.confirm(1)
}
})
},
confirm(sendType) {
this.$refs.form.validate((valid) => {
if (valid) {
if (!this.form.wxGroups.length) {
return this.$message.error('居民群数量不能为0')
}
if (this.params.sendChannel === '1' && new Date(this.form.taskEndTime).getTime() < Date.now()) {
return this.$message.error('结束时间不得早于当前时间')
}
if (sendType === 1 && !this.dateForm.choiceTime) {
this.isShowDate = true
return false
}
// const contents = [
// {
// content: this.form.content,
// msgType: '0'
// },
// ...this.fileList
// ]
if (sendType === 0) {
this.isLoading1 = true
} else {
this.isLoading2 = true
}
this.instance.post(`/app/appmasssendingtaskbaidu/addOrUpdate`, {
clientInfoId: this.params.clientInfoId,
...this.form,
id: this.params.id,
wxGroups: this.form.wxGroups,
contents: this.fileContentList,
sendType,
// markTag: this.form.markTag.join(','),
sendChannel: this.params.sendChannel,
choiceTime: this.dateForm.choiceTime,
filterCriteria: this.form.filterCriteria.join(','),
examines: this.form.examines.length ? this.form.examines.map(v => {
return {
...v,
examineUserId: v.id,
examineUserName: v.name
}
}) : [],
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
} else {
this.isLoading1 = false
this.isLoading2 = false
}
}).catch(() => {
this.isLoading1 = false
this.isLoading2 = false
})
}
})
},
selectDept() {
return this.$message.error(`您未在系统中关联‘主部门’,请联系管理员处理`)
},
handleChangeCount() {
if(this.form.days > 0 && this.form.dayLimit > 0) {
this.form.totalCount = this.form.days * this.form.dayLimit
}
},
onChange(file, fileList) {
console.log(file, fileList);
var reader = new FileReader();
if(typeof reader == 'undefined') {
this.$message.error("您的浏览器暂不支持该功能")
return;
}
reader.readAsText(file.raw, 'gbk')
reader.onload = (e) => {
var list = e.target.result.split('\r\n')
list.map((item) => {
if(item) {
this.fileContentList.push(item)
}
})
this.form.fileList = fileList
}
},
handleRemove(file, fileList) {
this.form.fileList = fileList
this.fileContentList = []
},
cancel(isRefresh) {
this.$emit('change', {
type: 'TaskList',
isRefresh: !!isRefresh,
params: {id: this.params.clientInfoId, status: this.params.status}
})
}
}
}
</script>
<style lang="scss">
.el-tooltip__popper.is-dark {
max-width: 240px;
}
.AppAnnounceDetail-content-wrapper {
display: flex;
align-items: center;
.content-item {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 64px;
height: 64px;
line-height: 1;
margin-right: 4px;
text-align: center;
background: #F9F9F9;
border-radius: 2px;
cursor: pointer;
&:hover {
opacity: 0.6;
}
&:last-child {
margin-right: 0;
}
img {
width: 32px;
height: 32px;
margin-bottom: 4px;
}
p {
color: #222;
font-size: 12px;
}
}
}
.AppAnnounceAdd {
.ai-detail__content {
.ai-detail__content--wrapper {
position: relative;
max-width: 100%;
margin: 0;
height: 100%;
overflow: hidden;
}
}
.ai-form {
textarea {
border-radius: 4px 4px 0 0!important;
}
}
* {
box-sizing: border-box;
}
.add {
display: flex;
position: relative;
flex-direction: column;
padding: 14px 16px;
background: #F9F9F9;
border-radius: 0px 0px 4px 4px;
border: 1px solid #D0D4DC;
border-top: none;
.add-material {
position: absolute;
bottom: 14px;
left: 16px;
transform: translateX(120%);
}
.add-item {
display: flex;
align-items: center;
line-height: 1;
cursor: pointer;
&:hover {
opacity: 0.6;
}
img {
width: 20px;
height: 20px;
margin-right: 2px;
}
span {
color: #222;
font-size: 14px;
}
}
.fileList {
margin-bottom: 12px;
.add-item {
justify-content: space-between;
margin-bottom: 8px;
.left {
display: flex;
align-items: center;
}
i {
font-size: 14px;
cursor: pointer;
font-style: normal;
color: red;
&:hover {
opacity: 0.6;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
.AppAnnounceDetail-container {
display: flex;
position: relative;
height: 100%;
padding: 0 20px;
overflow: auto;
overflow: overlay;
.left {
flex: 1;
margin-right: 20px;
}
.right {
position: sticky;
top: 0;
}
.AppAnnounceDetail-select {
display: flex;
align-items: center;
min-height: 32px;
line-height: 1;
background: #F5F5F5;
border-radius: 4px;
border: 1px solid #D0D4DC;
cursor: pointer;
overflow: hidden;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
& > i {
flex: 1;
height: 100%;
line-height: 32px;
padding: 0 12px;
color: #888888;
font-size: 14px;
font-style: normal;
border-right: 1px solid #D0D4DC;
background: #fff;
}
.AppAnnounceDetail-select__input {
position: absolute;
left: 0;
top: 0;
z-index: -1;
opacity: 0;
height: 100%;
}
.select-right {
height: 100%;
padding: 0 12px;
color: #222222;
font-size: 12px;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.5;
}
}
.select-left {
display: flex;
flex-wrap: wrap;
flex: 1;
padding: 5px 0 0px 12px;
border-right: 1px solid #D0D4DC;
border-radius: 4px 0 0 4px;
background: #fff;
em {
height: 22px;
line-height: 22px;
margin: 0 4px 5px 0;
color: #222222;
font-size: 12px;
font-style: normal;
}
span {
height: 22px;
line-height: 22px;
margin: 0 4px 5px 0;
padding: 0 8px;
font-size: 12px;
color: #222222;
background: #F3F4F7;
border-radius: 2px;
border: 1px solid #D0D4DC;
}
}
}
}
.tips {
display: flex;
align-items: center;
font-size: 14px;
color: #222222;
span {
margin: 0 3px;
color: #2266FF;
}
i {
color: #8899bb;
}
em {
line-height: 20px;
margin-top: 8px;
color: #888888;
font-size: 12px;
font-style: normal;
}
}
.select-div {
width: 100%;
height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #ddd;
padding: 0 15px;
box-sizing: border-box;
font-size: 14px;
color: #999;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,786 @@
<template>
<ai-detail class="AppAnnounceDetail">
<template slot="title">
<ai-title title="任务详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基础信息">
<template #right>
<div class="right-tips" v-if="info.status === '4'">
<el-tooltip
placement="top"
content="任务开始后3天内15分钟更新1次3天后访问页面时触发更新1小时最多刷新1次">
<i class="iconfont iconDetails"></i>
</el-tooltip>
<span>数据更新于{{ info.dataUpdateTime }}</span>
</div>
</template>
<template #content>
<ai-wrapper>
<ai-info-item label="任务名称" isLine :value="info.taskTitle"></ai-info-item>
<ai-info-item label="任务状态" isLine>
<span :style="{ color: dict.getColor('mstXbotStatus', info.status) }">{{ dict.getLabel('mstXbotStatus', info.status) }}</span>
</ai-info-item>
<ai-info-item label="创建人" isLine>
<div class="user">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ info.createUserName }}</span>
<!-- <span>{{ info.createUserDeptName }}</span> -->
</div>
</ai-info-item>
<ai-info-item label="审批人" isLine v-if="info.enableExamine === '1'">
<div class="user-wrapper">
<div class="user" v-for="(item, index) in info.examines" :key="index">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ item.examineUserName }}</span>
</div>
</div>
</ai-info-item>
<!-- <ai-info-item label="发送方式">
<span>{{ info.sendChannel === '1' ? '通知员工转发' : '成员一键群发' }}</span>
</ai-info-item> -->
<!-- <ai-info-item label="标签" :value="info.markTag">
</ai-info-item> -->
<ai-info-item label="创建时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="开始时间" :value="info.choiceTime"></ai-info-item>
<ai-info-item label="结束时间" :value="info.taskEndTime" v-if="info.sendChannel === '1'"></ai-info-item>
<ai-info-item label="群发范围" isLine>
<div class="text">
<span>{{ info.sendScope === '0' ? '全部' : '按条件筛选的' }}</span>
<i>{{ groups.length }}</i>
<span>个居民群</span>
<em @click="isShowGroups = true">详情</em>
</div>
</ai-info-item>
<!-- <ai-info-item label="消息内容" isLine>
<div class="msg">
<p>{{ content }}</p>
<div class="msg-bottom">
<div class="left" v-if="fileList.length">
<img :src="mapIcon(fileList[0].msgType)" />
<span>{{ mapType(fileList[0].msgType) }}{{ fileList[0].mpTitle || fileList[0].name || fileList[0].linkTitle }} </span>
<i>{{ fileList.length }}</i>
<span>个附件</span>
</div>
<div class="left" v-else>
<span>暂无附件</span>
</div>
<div class="right" @click="isShowPhone = true">预览消息</div>
</div>
</div>
</ai-info-item> -->
<ai-info-item label="日调用次数" :value="info.dayLimit"></ai-info-item>
<ai-info-item label="预计执行天数" :value="info.days"></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<!-- <ai-card>
<template #title>
<div class="AppAnnounceDetail-title">
<span :class="[currIndex === 0 ? 'active' : '']" @click="currIndex = 0">成员统计</span>
<span :class="[currIndex === 1 ? 'active' : '']" @click="currIndex = 1">居民群统计</span>
</div>
</template>
<template #content>
<div class="content-item" v-if="currIndex === 0">
<div class="top">
<div class="top-item">
<div class="top-item__title">
<h3>计划执行成员</h3>
</div>
<p>{{ memberInfo.planCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>未执行成员</h3>
</div>
<p>{{ memberInfo.unExecutedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>已执行成员</h3>
</div>
<p>{{ memberInfo.executedCount || 0 }}</p>
</div>
<div class="top-item" v-if="info.sendChannel === '0'">
<div class="top-item__title">
<h3>无法执行成员</h3>
<el-tooltip
placement="top"
content="由于员工不在可见范围、离职、客户群接收已达到上限等原因,无法执行群发任务的成员总数">
<i class="iconfont iconDetails"></i>
</el-tooltip>
</div>
<p>{{ memberInfo.cannotExecuteCount || 0 }}</p>
</div>
</div>
<div class="bottom">
<div class="bottom-search">
<div class="left">
<el-radio-group v-model="search1.sendStatus" size="small" @change="search1.current = 1, getMemberInfo()">
<el-radio-button size="small" label="0">未执行</el-radio-button>
<el-radio-button size="small" label="1">已执行</el-radio-button>
<el-radio-button size="small" label="2" v-if="info.sendChannel === '0'">无法执行</el-radio-button>
</el-radio-group>
<ai-picker
dialogTitle="选择部门"
action="/app/wxcp/wxdepartment/departList"
:instance="instance"
@pick="e => onUserChange(e, 'search1')" :multiple="false" v-model="user1">
<div class="userSelcet">
<span style="color: #606266;" v-if="search1.deptartId">{{ name1 }}</span>
<span v-else>部门</span>
<i class="el-icon-arrow-up" v-if="!search1.deptartId"></i>
<i class="el-icon-circle-close" v-if="search1.deptartId" @click.stop="user1 = [], search1.deptartId = '', search1.current = 1, getMemberInfo()"></i>
</div>
</ai-picker>
</div>
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(0)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
</div>
<ai-table
:tableData="tableData1"
:col-configs="colConfigs1"
:total="total1"
border
tableSize="small"
:current.sync="search1.current"
:size.sync="search1.size"
@getList="getMemberInfo">
<el-table-column slot="user" label="成员" align="left">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.groupOwnerName }}</span>
<span style="color: #999">{{ row.mainDepartmentName }}</span>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</div>
<div class="content-item" v-if="currIndex === 1">
<div class="top">
<div class="top-item">
<div class="top-item__title">
<h3>计划送达居民群</h3>
</div>
<p>{{ groupInfo.planCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>未送达居民群</h3>
</div>
<p>{{ groupInfo.unExecutedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>已送达居民群</h3>
</div>
<p>{{ groupInfo.executedCount || 0 }}</p>
</div>
<div class="top-item" v-if="info.sendChannel === '0'">
<div class="top-item__title">
<h3>无法送达居民群</h3>
</div>
<p>{{ groupInfo.cannotExecuteCount || 0 }}</p>
</div>
</div>
<div class="bottom">
<div class="bottom-search">
<div class="left">
<el-radio-group v-model="search2.sendStatus" size="small" @change="search2.current = 1, getGroupInfo()">
<el-radio-button size="small" label="0">未送达</el-radio-button>
<el-radio-button size="small" label="1">已送达</el-radio-button>
<el-radio-button size="small" label="2" v-if="info.sendChannel === '0'">无法送达</el-radio-button>
</el-radio-group>
<ai-picker
dialogTitle="选择部门"
action="/app/wxcp/wxdepartment/departList"
:instance="instance"
@pick="e => onUserChange(e, 'search2')" :multiple="false" v-model="user2">
<div class="userSelcet">
<span style="color: #606266;" v-if="search2.deptartId">{{ name2 }}</span>
<span v-else>部门</span>
<i class="el-icon-arrow-up" v-if="!search2.deptartId"></i>
<i class="el-icon-circle-close" v-if="search2.deptartId" @click.stop="user1 = [], search2.deptartId = '', search2.current = 1, getGroupInfo()"></i>
</div>
</ai-picker>
</div>
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(1)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
</div>
<ai-table
:tableData="tableData2"
:col-configs="colConfigs2"
:total="total2"
border
tableSize="small"
:current.sync="search2.current"
:size.sync="search2.size"
@getList="getGroupInfo">
<el-table-column slot="user" label="群主" align="center">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.groupOwnerName }}</span>
<span style="color: #999">{{ row.mainDepartmentName }}</span>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</div>
</template>
</ai-card> -->
<ai-dialog
:visible.sync="isShowGroups"
width="890px"
title="群发范围"
customFooter
@onConfirm="isShowGroups = false">
<ai-table
:tableData="info.wxGroups"
:col-configs="colConfigs3"
border
tableSize="small"
:isShowPagination="false"
@getList="() => {}">
</ai-table>
<div class="dialog-footer" slot="footer">
<el-button @click="isShowGroups = false">关闭</el-button>
</div>
</ai-dialog>
<div class="detail-phone" v-if="isShowPhone">
<div class="mask"></div>
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="true" :content="content" :fileList="fileList"></Phone>
</div>
</template>
</ai-detail>
</template>
<script>
import { mapState } from 'vuex'
import Phone from './Phone'
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
components: {
Phone
},
data () {
return {
total1: 0,
isShowGroups: false,
isShowPhone: false,
total2: 0,
user1: [],
user2: [],
name1: '',
name2: '',
radio1: '未执行',
search1: {
current: 1,
size: 10,
deptartId: '',
type: 0,
sendStatus: '0'
},
search2: {
current: 1,
size: 10,
deptartId: '',
type: 1,
sendStatus: '0'
},
memberInfo: {},
groupInfo: {},
tableData1: [],
fileList: [],
tableData2: [],
info: {},
content: '',
currIndex: 0,
colConfigs3: [
{ prop: 'groupOwnerName', label: '群主' },
{ prop: 'groupNames', label: '群名称' }
],
colConfigs1: [
{ slot: 'user', label: '成员' },
{ prop: 'groupCount', label: '预计送达居民群', align: 'center' }
],
colConfigs2: [
{ prop: 'groupName', label: '居民群' },
{ prop: 'memberCount', label: '群人数', align: 'center' },
{ slot: 'user', label: '群主', align: 'center' },
],
groups: [],
timer: null,
min: 60,
isDisabled: false,
rejecterId: ''
}
},
computed: {
...mapState(['user'])
},
created () {
this.getInfo(this.params.id)
// this.getMemberInfo()
// this.getGroupInfo()
},
destroyed () {
clearInterval(this.timer)
},
methods: {
getMemberInfo () {
this.instance.post(`/app/appmasssendingtaskbaidu/detailStatistics`, null, {
params: {
...this.search1,
taskId: this.params.id
}
}).then(res => {
if (res.code === 0) {
this.tableData1 = res.data.executedList.records
this.total1 = res.data.executedList.total
this.memberInfo = res.data
}
})
},
onUserChange (e, search) {
if (e.length) {
search === 'search1' ? this.name1 = e[0].name : this.name2 = e[0].name
this[search].deptartId = e[0].id
} else {
this[search].deptartId = ''
search === 'search1' ? this.name1 = '' : this.name2 = ''
}
this[search].current = 1
if (search === 'search1') {
this.getMemberInfo()
} else {
this.getGroupInfo()
}
},
sendMsg () {
this.instance.post(`/app/appmasssendingtaskbaidu/remindSend?id=${this.params.id}`).then(res => {
if (res.code === 0) {
this.$message.success('提醒成功')
this.getInfo(this.params.id)
}
})
},
getGroupInfo () {
this.instance.post(`/app/appmasssendingtaskbaidu/detailStatistics`, null, {
params: {
...this.search2,
taskId: this.params.id
}
}).then(res => {
if (res.code === 0) {
this.tableData2 = res.data.executedList.records.map(v => {
return {
...v,
groupName: v.groupName || '未命名群聊'
}
})
this.total2 = res.data.executedList.total
this.groupInfo = res.data
}
})
},
countdown () {
this.timer = setInterval(() => {
const nowTime = this.$moment(new Date())
const min = nowTime.diff(this.info.remindTime, 'minute')
this.min = (60 - min)
if (this.min <= 0) {
this.isDisabled = false
clearInterval(this.timer)
} else {
this.isDisabled = true
}
}, 1000)
},
getInfo (id) {
this.instance.post(`/app/appmasssendingtaskbaidu/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
if (res.data.status === '4' && res.data.remindTime) {
this.countdown()
}
// const content = res.data.contents.filter(v => v.msgType === '0')
// if (content.length) {
// this.content = content[0].content
// }
// this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
// return {
// ...v,
// ...v.sysFile
// }
// })
this.info.wxGroups = res.data.wxGroups.map(v => {
this.groups.push(...v.groupIds.split(','))
return {
...v,
groupIds: v.groupIds ? v.groupIds.split(',') : []
}
})
if (res.data.examines && res.data.examines.length) {
const user = res.data.examines.filter(v => v.examineStatus === '2')
if (user.length) {
this.rejecterId = user[0].examineUserId
}
}
}
})
},
mapType (type) {
return {
1: '图片',
2: '视频',
3: '文件',
4: '网站',
5: '小程序'
}[type]
},
mapIcon (type) {
return {
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
}[type]
},
cancel (isRefresh) {
this.$emit('change', {
type: 'TaskList',
isRefresh: !!isRefresh,
params: {id: this.params.clientInfoId, status: this.params.status}
})
}
}
}
</script>
<style scoped lang="scss">
.AppAnnounceDetail {
position: relative;
.user-wrapper {
display: flex;
flex-wrap: wrap;
}
.detail-phone {
position: fixed;
left: 0%;
top: 0%;
z-index: 11;
width: 100%;
height: 100%;
.mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
background: rgba($color: #000000, $alpha: 0.6);
}
:deep( .phone-container ){
position: absolute;
left: 50%;
top: 50%;
z-index: 11;
transform: translate(-50%, -50%);
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
margin-left: 12px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
.userinfo {
display: flex;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.user {
display: flex;
align-items: center;
line-height: 1;
margin-right: 8px;
img {
width: 16px;
height: 16px;
margin-right: 2px;
}
span {
position: relative;
top: 2px;
color: #222222;
font-size: 12px;
}
}
.text {
display: flex;
align-items: center;
i {
color: #2266FF;
font-style: normal;
}
em {
margin-left: 8px;
color: #2266FF;
font-size: 12px;
font-style: normal;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.6;
}
}
}
.msg {
background: #F9F9F9;
border-radius: 2px;
border: 1px solid #D0D4DC;
p {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
line-height: 38px;
padding: 0px 12px;
overflow: hidden;
}
.msg-bottom {
display: flex;
align-items: center;
justify-content: space-between;
height: 38px;
padding: 0 16px;
border-top: 1px solid #D0D4DC;
.left {
display: flex;
align-items: center;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
span {
color: #222222;
font-size: 14px;
}
i {
color: #2266FF;
font-size: 14px;
font-style: normal;
}
}
.right {
color: #2266FF;
font-size: 12px;
cursor: pointer;
&:hover {
opacity: 0.6;
}
}
}
}
:deep( .AppAnnounceDetail-title ){
display: flex;
align-items: center;
span {
height: 100%;
line-height: 56px;
margin-right: 32px;
color: #888888;
font-size: 16px;
font-weight: 600;
transition: all ease 0.3s;
border-bottom: 3px solid transparent;
cursor: pointer;
user-select: none;
&:hover {
color: #222;
}
&:last-child {
margin-right: 0;
}
&.active {
color: #222222;
border-bottom: 3px solid #2266FF;
}
}
}
.content-item {
.top {
display: flex;
align-items: center;
margin-bottom: 16px;
.top-item {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
height: 90px;
margin-right: 16px;
padding: 0 16px;
background: #F9F9F9;
border-radius: 2px;
&:last-child {
margin-right: 0;
}
.top-item__title {
display: flex;
align-items: center;
margin-bottom: 8px;
i {
margin-left: 4px;
color: #8899bb;
font-size: 16px;
}
}
h3 {
color: #222222;
font-size: 14px;
font-weight: 700;
}
p {
color: #2266FF;
font-size: 24px;
font-weight: 700;
}
}
}
.bottom-search {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.left {
display: flex;
align-items: center;
}
}
}
:deep( .right-tips ){
display: flex;
align-items: center;
i {
margin-right: 4px;
color: #8899bb;
font-size: 16px;
}
span {
color: #888888;
font-size: 12px;
}
}
}
</style>

View File

@@ -0,0 +1,452 @@
<template>
<ai-list class="AppAnnounce">
<template slot="title">
<ai-title title="调用任务" isShowBack isShowBottomBorder @onBackClick="cancelBack(true)">
<template #sub>
<span>设置居民助手的调用任务可定时发送也可立即发送</span>
</template>
</ai-title>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<!-- :disabled="params.status != 1" -->
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="toAdd('')" >创建任务</el-button>
<ai-select
v-model="search.status"
@change="search.current = 1, getList()"
placeholder="任务状态"
:selectList="dict.getDict('mstXbotStatus')">
</ai-select>
<el-date-picker
v-model="search.startTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发开始日期">
</el-date-picker>
<el-date-picker
v-model="search.endTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发结束日期">
</el-date-picker>
<ai-user-selecter :instance="instance" @change="onUserChange" :isMultiple="false" v-model="userList" :actions="{tree: `/app/wxcp/wxdepartment/listAll?wxMainDepartmentId=${user.info.wxMainDepartmentId}`}" >
<div class="userSelcet">
<span style="color: #606266;" v-if="search.createUserId">{{ name }}</span>
<span v-else>创建人</span>
<i class="el-icon-arrow-up" v-if="!search.createUserId"></i>
<i class="el-icon-circle-close" v-if="search.createUserId" @click.stop="userList = [], search.createUserId = '', name = '', search.current = 1, getList()"></i>
</div>
</ai-user-selecter>
</template>
<template slot="right">
<el-input
v-model="search.taskTitle"
size="small"
v-throttle="() => { search.current = 1, getList() }"
placeholder="请输入任务名称"
clearable
@clear="search.current = 1, search.taskTitle = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
v-loading="loading"
style="margin-top: 6px; width: 100%;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="user" width="140px" label="创建人" align="center">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.createUserName }}</span>
<span style="color: #999">{{ row.createUserDeptName }}</span>
</div>
</template>
</el-table-column>
<el-table-column slot="successCount" width="140px" label="已调用" align="center">
<template slot-scope="{ row }">
<div>
<span>{{ row.successCount + row.failCount }}</span>
<el-tooltip
placement="top"
:content="`成功${row.successCount}条,失败${row.failCount}条`">
<i class="iconfont iconModal_Warning"></i>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column slot="options" width="140px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<!-- <el-button type="text" @click="remindExamine(row.id)" v-if="['0'].includes(row.status)">催办</el-button> -->
<el-button type="text" @click="remove(row.id)" v-if="['0'].includes(row.status)">删除</el-button>
<el-button type="text" @click="close(row.id)" v-if="['0', '1'].includes(row.status)">关闭</el-button>
<!-- <el-button type="text" @click="cancel(row.id)" v-if="['0'].includes(row.status)">撤回</el-button> -->
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
<!-- <el-button type="text" @click="toAdd(row.id)" v-if="['1', '3'].includes(row.status)">编辑</el-button> -->
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object,
params: Object
},
data() {
return {
search: {
current: 1,
size: 10,
status: '',
createUserId: '',
taskTitle: '',
startTime: '',
endTime: '',
markTag: ''
},
currIndex: '1',
name: '',
isShow: false,
userList: [],
tableData: [],
loading: false,
total: 0,
colConfigs: [
{ prop: 'taskTitle', label: '任务名称' },
// { prop: 'typeName', label: '群发类型', align: 'center' },
{ slot: 'user', label: '创建人', openType: 'userName', align: 'center' },
{ prop: 'choiceTime', label: '任务开始时间', align: 'center' },
{ prop: 'taskEndTime', label: '任务结束时间', align: 'center' },
{
prop: 'status',
align: 'center',
label: '状态',
render: (h, {row}) => {
return h('span', {
style: {
color: this.dict.getColor('mstXbotStatus', row.status)
}
}, this.dict.getLabel('mstXbotStatus', row.status))
}
},
{ slot: 'successCount'},
{ prop: 'totalCount', label: '预计调用', align: 'center' }
]
}
},
computed: {
...mapState(['user']),
},
mounted () {
this.dict.load('mstXbotStatus', 'mstSendType', 'mstTag').then(() => {
this.getList()
})
},
methods: {
onUserChange (e) {
if (e.length) {
this.name = e[0].name
this.search.createUserId = e[0].id
} else {
this.search.createUserId = ''
this.name = ''
}
this.search.current = 1
this.getList()
},
getList() {
this.loading = true
this.instance.post(`/app/appmasssendingtaskbaidu/list`, null, {
params: {
...this.search,
clientInfoId: this.params.id,
markTag: this.search.markTag ? this.search.markTag.join(',') : ''
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records.map(v => {
return {
...v,
typeName: '群发居民群'
}
})
this.total = res.data.total
this.$nextTick(() => {
this.loading = false
})
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
remindExamine (id) {
this.$confirm('确认再次通知任务审核人员?').then(() => {
this.instance.post(`/app/appmasssendingtaskbaidu/remindExamine?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('催办成功!')
this.getList()
}
})
})
},
close (id) {
this.$confirm('确认关闭该群发任务?').then(() => {
this.instance.post(`/app/appmasssendingtaskbaidu/closeTask?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('关闭成功!')
this.getList()
}
})
})
},
cancel (id) {
this.$confirm('确认撤回该群发任务?').then(() => {
this.instance.post(`/app/appmasssendingtaskxbot/cancel?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('撤回成功!')
this.getList()
}
})
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appmasssendingtaskbaidu/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'TaskAdd',
params: {
id: id || '',
clientInfoId: this.params.id,
sendChannel: 0,
status: this.params.status
}
})
},
toDetail (id) {
this.$emit('change', {
type: 'TaskDetail',
params: {
id,
status: this.params.status
}
})
},
cancelBack(isRefresh) {
this.$emit('change', {
type: 'List',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style lang="scss" scoped>
.AppAnnounce {
height: 100%;
.dialog {
position: fixed;
left: 0;
top: 0;
z-index: 111;
width: 100%;
height: 100%;
.mask {
position: fixed;
left: 0;
top: 0;
z-index: 1;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.2);
}
.dialog-wrapper {
position: absolute;
left: 50%;
top: 50%;
z-index: 2;
width: 600px;
height: 532px;
padding: 40px 64px;
box-sizing: border-box;
background: #FFFFFF;
border-radius: 4px;
text-align: center;
transform: translate(-50%, -50%);
& > img {
position: absolute;
right: 16px;
top: 16px;
z-index: 2;
width: 16px;
height: 16px;
cursor: pointer;
}
h2 {
margin-bottom: 12px;
font-size: 24px;
color: #222;
}
& > p {
margin-bottom: 40px;
font-size: 12px;
color: #888888;
}
.dialog-list {
// display: flex;
// justify-content: space-between;
margin-bottom: 40px;
& > div {
// flex: 1;
width: 216px;
margin-left: 120px;
height: 280px;
padding: 24px;
background: #FFFFFF;
border: 1px solid #D0D4DC;
border-radius: 8px;
text-align: left;
cursor: pointer;
transition: all ease 0.3s;
&.active, &:hover {
background: #F7FAFF;
border: 1px solid #2266FF;
}
img {
width: 80px;
height: 80px;
margin-bottom: 32px;
}
p {
line-height: 22px;
font-size: 14px;
color: #888;
text-align: center;
}
h3 {
margin-bottom: 8px;
font-size: 18px;
color: #222;
font-weight: 600;
text-align: center;
}
&:first-child {
margin-right: 40px;
}
}
}
}
}
.userinfo {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: $placeholderColor;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
:deep .AiTitle .iconBack_Large {
line-height: 22px!important;
}
}
</style>