目录代码整合

This commit is contained in:
aixianling
2022-05-10 20:02:37 +08:00
parent 71049f7f65
commit 036ee91533
324 changed files with 4 additions and 8321 deletions

View File

@@ -0,0 +1,66 @@
<template>
<div class="doc-circulation ailist-wrapper">
<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 Add from './components/Add'
export default {
name: 'AppBuddyMessage',
label: '好友欢迎语',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
Add,
List
},
mounted () {
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
this.params = data.params
}
if (data.type === 'list') {
this.component = 'List'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
}
}
}
</script>
<style lang="scss">
.doc-circulation {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -0,0 +1,378 @@
<template>
<ai-detail>
<template slot="title">
<ai-title title="新建欢迎语" isShowBack isShowBottomBorder @onBackClick="cancel(false)"></ai-title>
</template>
<template slot="content">
<ai-card>
<template #title>
<div class="ai-card__title">
<h2>基本信息</h2>
<span>*一个成员如果被设置了多个欢迎语将会使用最新设置或修改的欢迎语</span>
</div>
</template>
<template #content>
<el-form class="ai-form" :rules="rules" ref="userForm" :model="form" label-width="100px" label-position="right">
<el-form-item label="使用成员" prop="users" style="width: 100%">
<el-input size="small" placeholder="请选择..." disabled v-model="users">
<ai-wechat-selecter slot="append" :instance="instance" v-model="form.users">
<el-button type="info">选择</el-button>
</ai-wechat-selecter>
</el-input>
</el-form-item>
</el-form>
</template>
</ai-card>
<ai-card title="发送欢迎语">
<template #content>
<el-form class="ai-form" ref="form" :model="form" label-width="110px" label-position="right">
<el-form-item class="el-form-item__textarea" label="文本内容" prop="explain" style="width: 100%">
<span @click="insertNickname" class="el-form-item__btn" type="text">[插入居民昵称]</span>
<el-input type="textarea" placeholder="请输入…" v-model="form.content" maxlength="1000" :rows="5" show-word-limit></el-input>
</el-form-item>
<el-form-item label="其他类型" prop="explain" style="width: 100%">
<el-radio-group v-model="form.type" @change="onTypeChange">
<el-radio label="image">图片</el-radio>
<el-radio label="link">链接</el-radio>
<el-radio label="video">视频</el-radio>
<el-radio label="miniapp">小程序</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="图片" prop="files" style="width: 100%" v-if="form.type === 'image'">
<ai-uploader :instance="instance" isWechat v-model="form.files" :limit="1" url="/app/wxcp/upload/uploadFile?type=image"></ai-uploader>
</el-form-item>
<el-form-item label="链接" prop="linkUrl" style="width: 100%" v-if="form.type === 'link'">
<el-input placeholder="链接地址请以http或https开头" :rows="2" type="textarea" v-model="form.linkUrl"></el-input>
</el-form-item>
<el-form-item label="链接图片地址" prop="picUrl" style="width: 100%" v-if="form.type === 'link'">
<ai-uploader :instance="instance" v-model="form.picUrl" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item label="链接标题" prop="title" style="width: 100%" v-if="form.type === 'link'">
<el-input placeholder="请输入链接标题" v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="视频" prop="files" style="width: 100%" v-if="form.type === 'video'">
<ai-uploader :instance="instance" fileType="file" isWechat acceptType=".mp4" v-model="form.files" :limit="1" url="/app/wxcp/upload/uploadFile?type=video"></ai-uploader>
</el-form-item>
<!-- <el-form-item label="小程序" prop="applets" style="width: 100%" v-if="form.type === 'miniapp'">
<el-radio-group v-model="form.applets">
<div class="appletss">
<div class="applets-item" @click="form.applets = '0'" :class="[form.applets === '0' ? 'applets-active' : '']">
<el-radio label="0"></el-radio>
<img src="http://www.9665.com/uploadfile/2018/0607/20180607042142312.png">
<span>小程序</span>
</div>
<div class="applets-item" @click="form.applets = '1'" :class="[form.applets === '1' ? 'applets-active' : '']">
<el-radio label="1"></el-radio>
<img src="http://www.9665.com/uploadfile/2018/0607/20180607042142312.png">
<span>小程序</span>
</div>
<div class="applets-item" @click="form.applets = '2'" :class="[form.applets === '2' ? 'applets-active' : '']">
<el-radio label="2"></el-radio>
<img src="http://www.9665.com/uploadfile/2018/0607/20180607042142312.png">
<span>小程序</span>
</div>
</div>
</el-radio-group>
</el-form-item> -->
<el-form-item label="小程序标题" prop="title" style="width: 100%" v-if="form.type === 'miniapp'">
<el-input placeholder="请输入小程序标题" v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="小程序APPID" prop="appid" style="width: 100%" v-if="form.type === 'miniapp'">
<el-input placeholder="请输入小程序APPID" v-model="form.appid"></el-input>
</el-form-item>
<el-form-item label="小程序跳转页面" prop="page" style="width: 100%" v-if="form.type === 'miniapp'">
<el-input placeholder="如pages/home/Home" v-model="form.page"></el-input>
</el-form-item>
<el-form-item label="小程序图片" prop="files" style="width: 100%" v-if="form.type === 'miniapp'">
<ai-uploader :instance="instance" v-model="form.files" isWechat :limit="1" url="/app/wxcp/upload/uploadFile?type=image"></ai-uploader>
</el-form-item>
</el-form>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm">提交</el-button>
</template>
</ai-detail>
</template>
<script>
const validateUser = (rule, value, callback) => {
if (!value.length) {
callback(new Error('请选择使用成员'))
} else {
callback()
}
}
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
return {
isShow: false,
info: {},
form: {
users: [],
appId: '',
page: '',
title: '',
miniappImg: [],
picUrl: [],
content: '',
files: [],
linkUrl: '',
isRemind: true,
type: 'text'
},
rules: {
users: [
{ required: true, message: '请选择使用成员', trigger: 'change' },
{ validator: validateUser, trigger: 'change' }
]
}
}
},
computed: {
users () {
return this.form.users.map(v => v.name).join(',')
}
},
created () {
},
methods: {
getInfo (id) {
this.instance.post(`/app/appleavemessage/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
this.info.appLeaveMessageReplyList = res.data.appLeaveMessageReplyList.map(item => {
item.images = JSON.parse(item.images).map(item => {
return {
...item,
url: item.accessUrl
}
})
return item
})
}
})
},
validateUser (rule, value, callback) {
if (!value.length) {
callback(new Error('请选择使用成员'))
} else {
callback()
}
},
onClose () {
this.form.explain = ''
},
onTypeChange () {
this.form.files = []
this.form.picUrl = []
this.form.appId = ''
this.form.page = ''
this.form.title = ''
this.form.miniappImg = []
this.form.linkUrl = ''
},
insertNickname () {
this.form.content = this.form.content + '[用户昵称]'
},
onChange () {
},
confirm () {
this.$refs.userForm.validate((valid) => {
if (valid) {
if (this.form.type === 'text' && !this.form.content) {
return this.$message.error('请输入消息内容')
}
if (this.form.type === 'image' && !this.form.files.length) {
return this.$message.error('请上传图片')
}
// if (this.form.type === 'file' && !this.form.files.length) {
// return this.$message.error('请上传附件')
// }
if (this.form.type === 'video' && !this.form.files.length) {
return this.$message.error('请上传视频')
}
if (this.form.type === 'link' && !this.form.linkUrl) {
return this.$message.error('请输入链接')
}
if (this.form.type === 'link' && !this.form.picUrl.length) {
return this.$message.error('请输入链接图片')
}
if (this.form.type === 'link' && !this.form.title) {
return this.$message.error('请输入链接标题')
}
if (this.form.type === 'miniapp' && !this.form.title) {
return this.$message.error('请输入小程序标题')
}
if (this.form.type === 'miniapp' && !this.form.appid) {
return this.$message.error('请输入小程序appid')
}
if (this.form.type === 'miniapp' && !this.form.page) {
return this.$message.error('请输入小程序page')
}
if (this.form.type === 'miniapp' && !this.form.files.length) {
return this.$message.error('请上传小程序图片')
}
this.instance.post(`/app/wxcp/wxwelcomeword/add`, {
type: '1',
content: this.form.content || '',
isNotify: '0',
media: {
createdAt: this.form.files.length ? this.form.files[0].media.createdAt : '',
file: this.form.files.length ? this.form.files[0] : {},
mediaId: this.form.files.length ? this.form.files[0].media.mediaId : '',
sysFileId: this.form.files.length ? this.form.files[0].id : '',
type: this.form.type,
linkUrl: this.form.type === 'link' && this.form.linkUrl ? this.form.linkUrl : '',
title: this.form.title,
appId: this.form.appid,
page: this.form.page,
picUrl: this.form.type === 'link' ? this.form.picUrl[0].url : ''
},
users: this.form.users.map(item => {
return {
type: 0,
objectId: item.id,
objectName: item.name
}
})
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
}
})
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: isRefresh ? true : false
})
}
}
}
</script>
<style scoped lang="scss">
.ai-card__title {
display: flex;
align-items: center;
h2 {
margin-right: 20px;
color: #222222;
font-size: 16px;
font-weight: 700;
}
span {
color: #888888;
font-size: 14px;
}
}
.appletss {
display: flex;
flex-wrap: wrap;
.applets-item {
display: flex;
align-items: center;
width: 400px;
height: 60px;
margin-right: 8px;
margin-bottom: 8px;
padding: 0 17px;
background: #FFFFFF;
border-radius: 2px;
border: 1px solid #D0D4DC;
cursor: pointer;
&:hover {
border-color: #2266FF;
}
::v-deep {
.el-radio__label {
display: none;
}
}
img {
width: 40px;
height: 40px;
margin: 0 8px 0 13px;
}
span {
color: #222222;
font-size: 12px;
}
.el-radio {
margin: 0;
}
&.applets-active {
border-color: #2266FF;
}
}
}
.tips {
position: relative;
top: 2px;
padding-left: 8px;
color: #222222;
font-size: 14px;
}
.el-form-item-item__textarea {
position: relative;
}
.el-form-item__btn {
position: absolute;
bottom: 12px;
left: 12px;
line-height: 1;
z-index: 1;
color: #2266FF;
font-size: 14px;
user-select: none;
background: #fff;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,235 @@
<template>
<ai-list class="message">
<template slot="title">
<ai-title title="好友欢迎语" isShowBottomBorder></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">添加好友欢迎语</el-button>
</template>
<template slot="right">
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="type" width="240px" label="消息内容" align="center">
<template slot-scope="{ row }">
<el-popover
placement="bottom"
width="400"
:visible-arrow="false"
popper-class="wechat-message__container"
trigger="hover">
<div class="count" slot="reference">{{ row.count }}</div>
<div class="message-info">
<h2 v-if="row.content" :style="{marginBottom: row.media ? '16px' : '0'}">{{ row.content }}</h2>
<div class="message-info__wrapper" v-if="row.media && (row.media.file || row.media.type === 'link')">
<img v-if="row.media.type === 'image' || row.media.type === 'miniapp'" :src="row.media.file.url">
<video v-if="row.media.type === 'video'" :src="row.media.file.url"></video>
<img v-if="row.media.type === 'link'" :src="row.media.picUrl">
<div class="message-info__wrapper--right">
<h3 v-if="row.media.type === 'miniapp'">{{ row.media.title }}</h3>
<h3 v-if="row.media.type === 'image'">{{ row.media.file.name }}</h3>
<h3 v-if="row.media.type === 'link'">{{ row.media.linkUrl }}</h3>
<h3 v-if="row.media.type === 'video'">{{ row.media.file.name }}</h3>
<p v-if="row.media.type === 'image'">{{ row.media.file.fileSizeStr }}</p>
<p v-if="row.media.type === 'link'">{{ row.media.title }}</p>
<p v-if="row.media.type === 'video'">{{ row.media.file.fileSizeStr }}</p>
</div>
</div>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column slot="options" width="120px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<!-- <el-button type="text" disabled @click="toAdd(row.id)" title="编辑">编辑</el-button> -->
<el-button type="text" @click="remove(row.id)" title="删除">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10,
content: ''
},
currIndex: 0,
total: 10,
colConfigs: [
{ prop: 'type', label: '类型', align: 'left', width: '160',
render: (h, params) => {
let str = ''
if (params.row.content) {
str = '文字'
}
if (params.row.media && params.row.media.type === 'link') {
str += this.dict.getLabel('wxMsgType', params.row.media.type) ? (params.row.content ? '+' : '') + this.dict.getLabel('wxMsgType', params.row.media.type) : ''
}
if (params.row.media && params.row.media.file) {
str += this.dict.getLabel('wxMsgType', params.row.media.type) ? (params.row.content ? '+' : '') + this.dict.getLabel('wxMsgType', params.row.media.type) : ''
}
return h('span', {
style: {
}
}, str)
}
},
{ slot: 'type' },
{ prop: 'users', label: '使用成员', align: 'left', formart: v => v.join(';') },
{ prop: 'createUser', label: '创建人' },
{ prop: 'createTime', label: '编辑时间' },
{ slot: 'options', label: '操作' }
],
tableData: []
}
},
mounted () {
this.dict.load(['wxMsgType']).then(() => {
this.getList()
})
},
methods: {
getList() {
this.instance.post(`/app/wxcp/wxwelcomeword/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records.map(item => {
let count = 0
if (item.content) {
count = count + 1
}
if (item.media && item.media.type !== 'text') {
count = count + 1
}
item.count = count
return item
})
this.total = res.data.total
}
})
},
remove (id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxwelcomeword/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd (id) {
this.$emit('change', {
type: 'Add',
params: {
id
}
})
}
}
}
</script>
<style lang="scss" scoped>
.message {
.count {
cursor: pointer;
color: #2266FF;
font-size: 14px;
&:hover {
opacity: 0.6;
}
}
}
.message-info {
padding: 8px;
min-height: 116px;
h2 {
color: #222222;
font-weight: 500;
font-size: 14px;
}
.message-info__wrapper {
display: flex;
align-items: center;
width: 368px;
height: 60px;
padding: 10px;
background: #FFFFFF;
border-radius: 2px;
border: 1px solid #D0D4DC;
.message-info__wrapper--right {
flex: 1;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
h3 {
width: 100%;
color: #222222;
font-size: 14px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
font-weight: normal;
}
img, video {
width: 40px;
height: 40px;
margin-right: 10px;
object-fit: cover;
}
p {
margin-top: 6px;
font-size: 14px;
color: #888888;
}
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<ai-list v-if="!isShowDetail">
<template slot="title">
<ai-title title="客户群发" :isShowBottomBorder="true"></ai-title>
</template>
<template slot="tabs">
<component :ref="activeName" :is="activeName" @change="change" :instance="instance" :dict="dict"
:permissions="permissions"/>
</template>
</ai-list>
<component v-else :is="activeName" :params="params" @change="change" :instance="instance" :dict="dict" :permissions="permissions"/>
</template>
<script>
import TableList from './components/TableList.vue'
import NewClientMass from './components/NewClientMass.vue'
import {mapState} from 'vuex'
export default {
name: 'AppClientMassTextin',
label: '客户群发',
components: {
TableList,
NewClientMass,
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
},
data() {
return {
activeName: 'TableList',
params: {},
isShowDetail: false,
}
},
methods: {
change(val) {
console.log(val);
if (val.type) {
this.activeName = val.type
switch (val.type) {
case "NewClientMass":
this.isShowDetail = true
this.params = val.row
break;
case "TableList":
this.isShowDetail = false
this.$nextTick(() => {
this.$refs[this.activeName].getList()
})
break;
}
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,342 @@
<template>
<div class="new-client-mass">
<ai-detail>
<template #title>
<ai-title :title="params.isAdd?'新建群发':'群发详情'" isShowBottomBorder :isShowBack="true" @onBackClick="onBack"
:isShowBottomBorder="true"></ai-title>
</template>
<template #content>
<ai-card title="基本信息" v-if="params.isAdd && !isEditInfo">
<template #content>
<ai-wrapper>
<el-form ref="form"
:model="form"
:rules="formRules"
size="small"
style="width: 100%;"
label-width="120px">
<el-form-item label="群发账号" prop="documentName">
<el-row type="flex">
<div class="input"></div>
<ai-person-select :instance="instance" url="/app/appvillagecadres/list" :isMultiple="true"
@selectPerson="getSelect" btnText="选择" dialogTitle="选择">
<template name="option" v-slot:option="{ item }">
<span class="iconfont iconProlife">{{ item.name }}</span>
<ai-id mode="show" :show-eyes="false" :value="item.idNumber"/>
</template>
</ai-person-select>
</el-row>
</el-form-item>
<el-row type="flex" justify="space-between">
<el-form-item label="群发时间" prop="documentName">
<el-radio-group>
<el-radio :label="1">立即发送</el-radio>
<el-radio :label="2">定时发送</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="发送时间" prop="documentName">
<el-time-picker
class="select-width"
:picker-options="{ selectableRange: '18:30:00 - 20:30:00'}"
placeholder="请选择...">
</el-time-picker>
</el-form-item>
</el-row>
<el-form-item label="选择居民" prop="documentName">
<el-radio-group>
<el-radio :label="1">全部居民</el-radio>
<el-radio :label="2">筛选居民</el-radio>
</el-radio-group>
</el-form-item>
<el-row type="flex" justify="space-between">
<el-form-item label="最早添加日期" prop="documentName">
<el-time-picker
class="select-width"
:picker-options="{ selectableRange: '18:30:00 - 20:30:00'}"
placeholder="请选择...">
</el-time-picker>
</el-form-item>
</el-form-item>
<el-form-item label="最晚添加日期" prop="documentName">
<el-time-picker
class="select-width"
:picker-options="{ selectableRange: '18:30:00 - 20:30:00'}"
placeholder="请选择...">
</el-time-picker>
</el-form-item>
</el-row>
<el-form-item label="居民公共标签" prop="documentName">
<el-row type="flex">
<div class="input"></div>
<el-button class="person-select" @click="dialog=true">选择</el-button>
</el-row>
</el-form-item>
<el-form-item label="排除居民" prop="documentName">
<el-row type="flex">
<div class="input"></div>
<ai-person-select :instance="instance" url="/app/appvillagecadres/list" :isMultiple="true"
@selectPerson="getSelect" btnText="选择" dialogTitle="选择">
<template name="option" v-slot:option="{ item }">
<span class="iconfont iconProlife">{{ item.name }}</span>
<ai-id mode="show" :show-eyes="false" :value="item.idNumber"/>
</template>
</ai-person-select>
</el-row>
</el-form-item>
</el-form>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="基本信息" v-if="!params.isAdd && !isEditInfo">
<template #right>
<el-button icon="iconfont iconjdq_led_edit" size="mini" @click="editInfo()">编辑</el-button>
</template>
<template #content>
<ai-wrapper label-width="96px">
<ai-info-item label="群发账号" isLine>刘仕伟郭麒麟陶瑞武</ai-info-item>
<ai-info-item label="群发时间">定时发送</ai-info-item>
<ai-info-item label="发送时间">2021-05-12 18:00</ai-info-item>
<ai-info-item label="选择居民" isLine>筛选居民</ai-info-item>
<ai-info-item label="最早添加日期">2021-05-12 18:00</ai-info-item>
<ai-info-item label="最晚添加日期">2021-05-12 18:00</ai-info-item>
<ai-info-item label="居民公共标签" isLine>2021-05-12 18:00</ai-info-item>
<ai-info-item label="排除居民" isLine>陶瑞武</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="群发消息内容" class="msg-title" v-if="params.isAdd && !isEditMsg">
<template #right>
*居民每个月最多接收来自同一企业管理员的4条群发消息4条消息可在同一天发送
</template>
<template #content>
<ai-wrapper>
<el-form ref="form"
style="width: 100%;"
:model="form"
:rules="formRules"
size="small"
label-width="120px">
<el-form-item label="文本内容">
<el-input
type="textarea"
:rows="5"
placeholder="请输入回复内容">
</el-input>
</el-form-item>
<el-form-item label="其他类型">
<el-radio-group v-model="form.type">
<el-radio label="none"></el-radio>
<el-radio label="img">图片</el-radio>
<el-radio label="link">链接</el-radio>
<el-radio label="file">附件</el-radio>
<el-radio label="video">视频</el-radio>
<el-radio label="miniProgrom">小程序</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="comp.label" v-for="(comp,index) in msgConnect" :key="index">
<ai-uploader :instance="instance" :limit="1" v-if="comp.type=='img'"></ai-uploader>
<el-input placeholder="链接地址请以http或https开头" v-if="comp.type=='link'">
<template slot="prepend">Http://</template>
</el-input>
<ai-uploader :instance="instance" fileType="file" :limit="9" v-if="comp.type=='file'"></ai-uploader>
<el-radio-group v-if="comp.type=='miniProgrom'" class="radio-group-wrap">
<div class="radio-wrap" v-for="(item,index) in 5" :key="index">
<el-radio label="none">
<span class="iconfont iconwenmingxiangfeng"></span>
小程序名称
</el-radio>
</div>
</el-radio-group>
</el-form-item>
</el-form>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="群发消息内容" v-if="!params.isAdd && !isEditMsg">
<template #right>
<el-button icon="iconfont iconjdq_led_edit" size="mini">编辑</el-button>
</template>
<template #content>
<ai-wrapper label-width="96px">
<ai-info-item label="文本内容" isLine>内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容</ai-info-item>
<ai-info-item label="小程序" isLine>
<div class="radio-group-wrap">
<div class="radio-wrap" v-for="(item,index) in 5" :key="index">
<span class="iconfont iconwenmingxiangfeng"></span>
<span style="margin-left: 8px;">小程序名称</span>
</div>
</div>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
</template>
<template slot="footer" v-if="params.isAdd">
<el-button style="width:120px">取消</el-button>
<el-button type="primary" style="width:120px"> </el-button>
</template>
</ai-detail>
<ai-dialog
title="选择标签"
width="800px"
:visible.sync="dialog"
:destroyOnClose="true"
@onConfirm="onConfirm">
<el-form ref="form" label-width="80px">
<el-form-item label="标签规则">
<el-radio-group>
<el-radio :label="0">以下标签满足其一</el-radio>
<el-radio :label="1">以下标签同时满足</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</ai-dialog>
</div>
</template>
<script>
export default {
name: "NewClientMass",
data() {
return {
form: {
type: "0"
},
isAdd: true,
isEditInfo:false,
isEditMsg:false,
dialog: false,
}
},
props: {
instance: Function,
dict: Object,
permissions: Function,
params:Object
},
computed: {
formRules() {
return {
documentName: [{required: true, message: "请输入公文名称", trigger: 'change'}],
documentCode: [{required: true, message: "请输入公文编号", trigger: 'change'}],
documentType: [{required: true, message: "请选择公文类型", trigger: 'change'}],
readType: [{required: true, message: "请选择阅示类型", trigger: 'change'}],
}
},
msgConnect() {
return [
{type: 'none', label: ''},
{type: 'img', label: '图片'},
{type: 'link', label: '链接'},
{type: 'file', label: "附件"},
{type: 'video', label: "视频"},
{type: 'miniProgrom', label: "小程序"},
].filter(e => e.type == this.form.type)
}
},
create() {
},
methods: {
editInfo(){
this.$emit("change",{
type:"NewClientMass",
row:{row:this.params.row,isAdd:true}
})
this.isEditInfo=true
},
onConfirm(){
},
getSelect() {
},
onBack() {
this.$emit("change", {
type: 'TableList'
})
}
}
}
</script>
<style lang="scss" scoped>
.new-client-mass {
height: 100%;
overflow: auto;
background: #f3f6f9;
.input {
width: 100%;
min-height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #d0d4dc;
color: #666;
display: inline-block;
font-size: inherit;
cursor: pointer;
&:after {
content: "请选择...";
color: #888888;
padding-left: 10px;
}
}
.select-width {
width: 328px;
}
.radio-group-wrap {
display: flex;
flex-wrap: wrap;
gap: 8px;
.radio-wrap {
display: flex;
align-items: center;
padding: 0 17px;
width: 400px;
height: 60px;
background: #FFFFFF;
border-radius: 2px;
border: 1px solid #D0D4DC;
}
}
::v-deep .AiPersonSelect {
& > button {
background: #F5F5F5;
border-radius: 0px 2px 2px 0px;
border: 1px solid #D0D4DC;
color: #222222;
}
}
.person-select {
background: #F5F5F5;
border-radius: 0px 2px 2px 0px;
border: 1px solid #D0D4DC;
color: #222222;
}
::v-deep .msg-title {
& > .aibar {
justify-content: flex-start;
& > .aibar-right {
margin-left: 64px;
font-size: 14px;
color: #888888 !important;
}
}
}
}
</style>

View File

@@ -0,0 +1,141 @@
<template>
<ai-list isTabs>
<template slot="content">
<ai-search-bar class="search-bar" bottomBorder>
<template slot="left">
<el-button type="primary" icon="iconfont iconAdd" @click="newClient">新建群发</el-button>
</template>
<template slot="right">
<el-input
v-model="search.name"
class="search-input"
size="small"
v-throttle="() => {search.current = 1, getList()}"
placeholder="请输入标题或编号"
clearable
@clear="search.current = 1, search.name = '', getList()"
suffix-icon="iconfont iconSearch" />
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:stripe="true"
:total="total"
ref="aitableex"
style="margin-top: 20px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="identityNumber" label="群发内容" align="center">
<template slot-scope="{ row }">
<el-popover
placement="right"
width="400"
trigger="hover">
<div>您可以查询到本村()的基本情况小微权力公开情况,并进行留言评价您自己操作一下看看,是不是很简单?</div>
<!-- <ai-file-list :fileList="fileList" :fileOps="{name: 'name', size: 'fileSizeStr'}"></ai-file-list>-->
<span style="cursor: pointer;" slot="reference">{{row.identityNumber}}</span>
</el-popover>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" align="center">
<template slot-scope="{ row }">
<span class="table-btn" title="提醒发送" @click="remind(row.id)">提醒发送</span>
<span class="table-btn" title="详情" @click="toDetail(row)">详情</span>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
export default {
name: 'TableList',
props: {
instance: Function,
dict: Object,
permissions:Function
},
data () {
return {
fileList:[],
search: {
current: 1,
size: 10,
name: ''
},
total: 0,
colConfigs: [
{ prop: 'name', label: '群发类型', align: 'center', width: '200' },
{ prop: 'phone', label: '类型', align: 'center' },
{ slot: 'identityNumber', label: '群发内容', align: 'center'},
{ prop: 'registTime', label: '发送时间', align: 'center' },
{ prop: 'recordUser', label: '已发送成员', align: 'center' },
{ prop: 'recordUser', label: '未发送成员', align: 'center' },
{ prop: 'recordUser', label: '已送达成员', align: 'center' },
{ prop: 'recordUser', label: '未送达成员', align: 'center' },
{ slot: 'options', label: '操作', align: 'center' }
],
tableData: [],
}
},
mounted () {
this.getList()
},
methods: {
toDetail(row){
this.$emit("change",{
type:"NewClientMass",
row:{row,isAdd:false}
})
},
newClient(){
this.$emit("change",{
type:"NewClientMass",
row:{row:"",isAdd:true}
})
},
getList() {
this.instance.post(`/app/apppetition/list`, null, {
params: {
...this.search,
status: 1
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
remind (id) {
},
onAdd () {
this.$emit('change', {
type: 'add'
})
}
}
}
</script>
<style lang="scss" scoped>
.table-btn{
font-size: 14px;
color: #2266FF;
cursor: pointer;
&:nth-child(1){
margin-right: 16px;
}
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<ai-list v-if="showList">
<template slot="title">
<ai-title title="消息推送" :isShowBottomBorder="false"> </ai-title>
</template>
<template slot="tabs">
<el-tabs v-model="currIndex">
<el-tab-pane
v-for="(tab, i) in tabs"
:key="i"
:label="tab.label"
:name="String(i)"
>
<component
:is="tab.comp"
v-if="currIndex === String(i)"
:ref="currIndex"
:instance="instance"
:dict="dict"
@change="changeDetail"
></component>
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<component
v-else
@change="changeDetail"
:is="componentName"
:params="params"
:instance="instance"
:dict="dict"
></component>
</template>
<script>
import commonList from './components/commonList.vue'
import systemList from './components/systemList.vue'
import Detail from './components/Detail'
import detailSystem from './components/detailSystem'
export default {
label: '消息推送',
name: 'AppMsgTemplate',
// 组件
components: {
commonList,
systemList,
Detail,
detailSystem
},
props: {
instance: Function,
dict: Object
},
data() {
return {
currIndex: '0',
showList: true,
componentName: '',
params: {}
}
},
// 计算
computed: {
tabs() {
return [
{
label: '公共模板',
name: 'commonList',
comp: commonList
},
{
label: '系统模板',
name: 'systemList',
comp: systemList
}
]
}
},
// 监听
watch: {},
// 实例创建后
created() {},
// 实例渲染后
mounted() {},
// 方法
methods: {
changeDetail(data) {
if (data.type === 'detail') {
this.showList = false
this.componentName = 'Detail'
this.params = data.params
}
if (data.type === 'detailSystem') {
this.showList = false
this.componentName = 'detailSystem'
this.params = data.params
}
if (data.type === 'list') {
this.showList = true
this.$nextTick(() => {
if (data.isRefresh) {
}
})
}
}
}
}
</script>
<style lang="scss">
.appmsgtemplate {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,194 @@
<template>
<!-- <div> -->
<ai-detail class="detail">
<template slot="title">
<ai-title
title="详情"
:isShowBack="true"
@onBackClick="onBack"
isShowBottomBorder
>
</ai-title>
<ai-wrapper label-width="80px" :columnsNumber="1">
<!-- 消息类型 -->
<ai-info-item label="消息类型">
<span v-for="(item, index) in types" :key="index">
<span v-if="item.label == infoList.msgtype">
{{ item.name }}
</span>
</span>
</ai-info-item>
<!-- 名称 -->
<ai-info-item label="名称">
<span>{{ infoList.name }}</span>
</ai-info-item>
<!-- 文本 -->
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'text'">
<span>{{ infoList.content }}</span>
</ai-info-item>
<!-- 图片 -->
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'image'">
<img
:src="infoList.media.file.accessUrl"
alt=""
style="width:100px;height:100px;"
/>
</ai-info-item>
<!-- 视频 -->
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'video'">
<video
:src="infoList.media.file.accessUrl"
alt=""
style="width:100px;height:100px;"
/>
</ai-info-item>
<!-- 音频 -->
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'voice'">
<ai-audio
:src="infoList.media.file.accessUrl"
alt=""
style="width:100px;height:100px;"
></ai-audio>
</ai-info-item>
<ai-info-item
label="标题"
v-if="
infoList.msgtype == 'video' ||
infoList.msgtype == 'voice' ||
infoList.msgtype == 'news'
"
>
<span>{{ infoList.title }}</span>
</ai-info-item>
<ai-info-item
label="文件描述"
v-if="
infoList.msgtype != 'text' &&
infoList.msgtype != 'image' &&
infoList.msgtype != 'voice'
"
>
<span>{{ infoList.description }}</span>
</ai-info-item>
<ai-info-item label="跳转地址" v-if="infoList.msgtype == 'news'">
<span>{{ infoList.url }}</span>
</ai-info-item>
<ai-info-item label="创建人">
<span>{{ infoList.createUserName }}</span>
</ai-info-item>
<ai-info-item label="创建时间">
<span>{{ infoList.createTime }}</span>
</ai-info-item>
<ai-info-item label="图片链接" v-if="infoList.msgtype == 'news'">
<span>{{ infoList.picurl }}</span>
</ai-info-item>
<ai-info-item label="是否启用">
<span>{{
this.dict.getLabel('wxAppMsgTemplateStatus', infoList.status)
}}</span>
</ai-info-item>
</ai-wrapper>
</template>
</ai-detail>
<!-- </div> -->
</template>
<script>
export default {
name: 'Detail',
// 组件
components: {},
props: {
instance: Function,
dict: Object,
params: Object
},
data() {
return {
infoList: []
}
},
// 计算
computed: {
types() {
return [
{ name: '文本', label: 'text' },
{ name: '图片', label: 'image' },
{ name: '视频', label: 'video' },
// { name: '附件', label: 'file' },
{ name: '音频', label: 'voice' },
{ name: '文本卡片', label: 'textcard' },
{ name: '图文', label: 'news' }
// { name: '图文消息', label: 'mpnews' }
]
}
},
// 监听
watch: {},
// 实例创建后
onShow() {},
created() {
this.dict.load('wxAppMsgTemplateStatus').then(() => {
this.getList()
})
},
// 实例渲染后
mounted() {},
// 方法
methods: {
getList(id) {
this.instance
.post(`/app/wxcp/wxapplicationmsgtemplate/detail?id=${this.params.id}`)
.then(res => {
if (res.code === 0) {
this.infoList = res.data
// this.grooupTableData = res.data.groupInfos
}
})
},
onBack() {
this.$emit('change', {
type: 'list'
})
}
}
}
</script>
<style scoped lang="scss">
.detail {
.AiTitle {
::v-deep.ai-detail__title {
// padding: 10px 0 0 10px;
background-color: #fff;
}
}
.ai-wrapper {
margin-top: 10px;
padding-top: 20px;
background-color: #fff;
.ai-info-item {
.ai-info-item__right {
video {
width: 85px;
height: 150px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,511 @@
<template class="commonList">
<section>
<ai-list>
<template slot="content">
<el-button class="btn" style="margin-bottom: 10px;" type="primary" icon="iconfont iconAdd" size="small" @click="showAdd">添加公共模板</el-button>
<ai-table :tableData="tableData" :col-configs="colConfigs" :total="page.total" :current.sync="page.current" :size.sync="page.size" @getList="getList" :dict="dict" style="height: 610px;overflow: auto;">
<!-- 消息类型 -->
<el-table-column slot="msgtype" label="消息类型" align="center">
<template slot-scope="{ row }">
<span v-for="(item, index) in types" :key="index">
<span v-if="item.label == row.msgtype">
{{ item.name }}
</span>
</span>
</template>
</el-table-column>
<!-- 消息内容 -->
<el-table-column slot="content" label="消息内容" align="center">
<template slot-scope="{ row }">
<div class="table-left__wrapper">
<img :src="row.media.file && row.media.file.accessUrl" v-if="row.media && (row.msgtype == 'image' || row.msgtype == 'news')" class="media" style="width: 40px;height: 40px;" />
<video :src="row.media.file && row.media.file.accessUrl" v-if="row.media && row.msgtype == 'video'" class="media" style="width: 40px;height: 40px;"></video>
<ai-audio :src="row.media.file && row.media.file.accessUrl" v-if="row.media && row.msgtype == 'voice'" class="media" style="width: 40px;height: 40px;"></ai-audio>
<div class="table-left__wrapper--right">
<el-tooltip class="item" effect="dark" :content="row.content" placement="top">
<div v-if="row.msgtype != 'news'">
{{ row.content }}
</div>
</el-tooltip>
</div>
</div>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column slot="options" label="操作" align="center" fixed="right" width="200">
<template slot-scope="{ row }">
<div class="table-options">
<ai-wechat-selecter style="display:inline-block;margin-right:8px ;" v-if="row.status == 1" :instance="instance" @change="(h) => release(h, row)">
<el-button type="text">发送</el-button>
</ai-wechat-selecter>
<el-button type="text" @click="remove(row.id)">删除</el-button>
<el-button type="text" @click="toEdit(row)">编辑</el-button>
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<!-- 新增的弹窗 -->
<ai-dialog :title="dialog.title" :visible.sync="visible" @onCancel="onCancel" @onConfirm="addConfirm" width="800px">
<div class="form_div">
<el-form ref="ruleForm" :model="dialogInfo" :rules="formRules" size="small" label-suffix="" label-width="100px">
<!-- 新增名称 -->
<el-form-item label="名称" prop="name">
<el-input clearable placeholder="请输入名称" v-model="dialogInfo.name" show-word-limit :maxlength="128"></el-input>
</el-form-item>
<!-- 新增状态 -->
<el-form-item label="状态" prop="status">
<ai-select v-model="dialogInfo.status" placeholder="请选择状态" :selectList="$dict.getDict('wxAppMsgTemplateStatus')"></ai-select>
</el-form-item>
<!-- 新增消息类型 -->
<el-form-item label="消息类型" prop="msgtype">
<el-radio-group v-model="dialogInfo.msgtype" @change="onChange">
<el-radio :label="item.label" v-for="(item, index) in types" :key="index">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<!-- 新增图片 -->
<el-form-item :label="compLabel" prop="sysFileId" v-if="dialogInfo.msgtype == 'image' || dialogInfo.msgtype == 'video' || dialogInfo.msgtype == 'voice' || dialogInfo.msgtype == 'news'">
<ai-uploader :instance="instance" v-model="fileList" :acceptType="acceptType" :url="'/app/wxcp/upload/uploadFile?type=' + fileTypeList" isWechat :fileType="fileType" :limit="1"></ai-uploader>
</el-form-item>
<!-- 新增消息类型对应的消息内容 -->
<el-form-item label="消息内容" prop="content" v-if="dialogInfo.msgtype == 'text'">
<el-input type="textarea" placeholder="请输入消息内容" :maxlength="2048" show-word-limit v-model="dialogInfo.content"> </el-input>
</el-form-item>
<!-- 新增标题 -->
<el-form-item label="标题" prop="title" v-if="dialogInfo.msgtype != 'text' && dialogInfo.msgtype != 'image' && dialogInfo.msgtype != 'voice'">
<el-input clearable placeholder="请输入标题" v-model="dialogInfo.title" show-word-limit :maxlength="128"></el-input>
</el-form-item>
<!-- 新增文件描述 -->
<el-form-item label="文件描述" prop="description" v-if="dialogInfo.msgtype != 'text' && dialogInfo.msgtype != 'image' && dialogInfo.msgtype != 'voice'">
<el-input clearable placeholder="请输入文件描述" v-model="dialogInfo.description" show-word-limit :maxlength="512" />
</el-form-item>
<!-- 新增跳转地址 -->
<el-form-item label="跳转地址" prop="url" v-if="dialogInfo.msgtype == 'news' || dialogInfo.msgtype == 'textcard'">
<el-input clearable placeholder="请输入要跳转的链接地址http/https" v-model="dialogInfo.url" show-word-limit :maxlength="128" />
</el-form-item>
<!-- 新增图片链接 -->
<el-form-item label="图片链接" prop="picurl" v-if="dialogInfo.msgtype != 'text' && dialogInfo.msgtype != 'video' && dialogInfo.msgtype != 'textcard' && dialogInfo.msgtype != 'voice' && dialogInfo.msgtype != 'image'">
<el-input clearable placeholder="请输入图片链接" v-model="dialogInfo.picurl" show-word-limit :maxlength="128" />
</el-form-item>
</el-form>
</div>
<div class="dialog-footer" slot="footer">
<el-button @click="onCancel" size="medium">取消</el-button>
<el-button @click="onConfirm('ruleForm')" type="primary" size="medium">确认</el-button>
</div>
</ai-dialog>
</section>
</template>
<script>
export default {
name: 'commonList',
props: {
instance: Function,
dict: Object,
},
data() {
var checkAge = (rule, value, callback) => {
if (this.dialogInfo.msgtype == 'image' && this.fileList.length == 0) {
return callback(new Error('请上传图片'))
} else if (this.dialogInfo.msgtype == 'file' && this.fileList.length == 0) {
return callback(new Error('请上传文件'))
} else if (this.dialogInfo.msgtype == 'video' && this.fileList.length == 0) {
return callback(new Error('请上传视频'))
} else if (this.dialogInfo.msgtype == 'voice' && this.fileList.length == 0) {
return callback(new Error('请上传音频'))
} else if (this.dialogInfo.msgtype == 'news' && this.fileList.length == 0) {
return callback(new Error('请上传图片'))
} else {
callback()
}
}
return {
visible: false,
total: 10,
colConfigs: [
{
prop: 'name',
label: '名称',
},
{
slot: 'msgtype',
label: '类型',
},
// {
// slot: 'content',
// label: '消息内容'
// },
// {
// prop: 'title',
// label: '标题'
// },
{
prop: 'createUserName',
label: '创建人',
'show-overflow-tooltip': true,
},
{ prop: 'createTime', label: '创建时间' },
{
prop: 'status',
label: '状态',
dict: 'wxAppMsgTemplateStatus',
},
{ slot: 'options', label: '操作', align: 'center' },
],
tableData: [],
page: {
size: 10,
current: 1,
total: 0,
},
dialog: {
title: '',
visible: false,
},
dialogInfo: {
name: '',
title: '',
msgtype: 'text',
content: '',
createUserName: '',
createTime: '',
status: '',
id: '',
isSystem: 0,
mediaId: '',
sysFileId: '',
media: {},
description: '',
url: '',
picurl: '',
},
fileList: [],
formRules: {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
content: [{ required: true, message: '请输入消息内容', trigger: 'blur' }],
sysFileId: [{ required: true, validator: checkAge, trigger: 'change' }],
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
description: [{ required: true, message: '请输入文件描述或链接', trigger: 'blur' }],
url: [
{
required: true,
message: '请输入要跳转的链接地址http/https',
trigger: 'blur',
},
],
picurl: [{ required: true, message: '请输入图片链接', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'blur' }],
},
}
},
computed: {
types() {
return [
{ name: '文本', label: 'text' },
{ name: '图片', label: 'image' },
{ name: '视频', label: 'video' },
// { name: '附件', label: 'file' },
// { name: '音频', label: 'voice' },
{ name: '文本卡片', label: 'textcard' },
{ name: '图文', label: 'news' },
// { name: '图文消息', label: 'mpnews' }
]
},
compLabel() {
return this.types.find((e) => e.label == this.dialogInfo.msgtype)?.name
},
fileTypeList() {
var type = ''
if (this.dialogInfo.msgtype == 'text' || this.dialogInfo.msgtype == 'textcard') {
type = 'text'
} else if (this.dialogInfo.msgtype == 'image' || this.dialogInfo.msgtype == 'news') {
type = 'image'
} else if (this.dialogInfo.msgtype == 'video') {
type = 'video'
} else if (this.dialogInfo.msgtype == 'voice') {
type = 'voice'
}
return type
},
fileType() {
// return this.dialogInfo.msgtype == 'image' ? 'img' : 'file'
var type = ''
if (this.dialogInfo.msgtype == 'image' || this.dialogInfo.msgtype == 'news') {
type = 'img'
} else {
type = 'file'
}
return type
},
acceptType() {
return {
image: '.jpg,.png,.jpeg',
video: '.mp4',
voice: '.AMR', // 无效的设置
}[this.dialogInfo.msgtype]
},
},
created() {
this.dict.load('wxAppMsgTemplateStatus').then(() => {
this.getList()
})
},
methods: {
getList() {
this.instance
.post(`/app/wxcp/wxapplicationmsgtemplate/list`, null, {
params: {
...this.page,
isSystem: 0,
},
})
.then((res) => {
if (res.code == 0) {
this.tableData = res.data.records
this.page.total = res.data.total
}
})
},
// 增加按钮
showAdd() {
this.dialog.title = '添加'
this.dialogInfo.msgtype = 'text'
this.visible = true
this.getList()
},
// 确定增加
addConfirm() {
var media = null
if (this.dialogInfo.msgtype != 'text' && this.fileList.length) {
media = {
mediaId: this.fileList[0].media.mediaId,
createdAt: this.fileList[0].media.createdAt,
sysFileId: this.fileList[0].id,
type: this.dialogInfo.msgtype,
url: this.fileList[0].url,
}
}
this.$refs['ruleForm'].validate((valid) => {
if (valid) {
this.instance
.post(`/app/wxcp/wxapplicationmsgtemplate/addOrUpdate`, {
createTime: this.dialogInfo.createTime,
createUserName: this.dialogInfo.createUserName,
status: this.dialogInfo.status,
id: this.dialogInfo.id,
isSystem: 0,
msgtype: this.dialogInfo.msgtype,
// mediaId: this.fileList[0].media.mediaId,
// sysFileId: this.fileList[0].id
name: this.dialogInfo.name,
title: this.dialogInfo.title,
content: this.dialogInfo.content,
description: this.dialogInfo.description,
url: this.dialogInfo.url,
picurl: this.dialogInfo.picurl,
media,
})
.then((res) => {
if (res.code == 0) {
this.$message.success('操作成功')
this.getList()
this.visible = false
this.infoInit()
}
})
.catch((err) => {})
}
})
},
// 新增完成后初始化页面
infoInit() {
for (let key in this.dialogInfo) {
this.dialogInfo[key] = ''
}
this.dialogInfo.msgtype = 'text'
this.dialogInfo.media = {}
this.dialogInfo.isSystem = '0'
this.fileList = []
},
// 删除
remove(id) {
this.$confirm('删除后不可恢复,是否要删除该事项?', {
type: 'error',
}).then(() => {
this.instance.post(`/app/wxcp/wxapplicationmsgtemplate/delete?id=${id}`).then((res) => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
// 编辑
toEdit(row) {
const info = JSON.parse(JSON.stringify(row))
this.dialog.title = '编辑'
this.visible = true
this.dialogInfo = { ...info }
this.fileList = [
{
accessUrl: '',
url: '',
id: '',
media: {
mediaId: '',
createdAt: '',
sysFileId: '',
},
},
]
if (this.dialogInfo.msgtype != 'text' && this.dialogInfo.msgtype != 'textcard') {
this.dialogInfo.media == info.media
this.fileList[0].accessUrl = info.media.file.accessUrl
this.fileList[0].url = info.media.file.url
this.fileList[0].id = info.media.sysFileId
this.fileList[0].media.mediaId = info.media.mediaId
this.fileList[0].media.createdAt = info.media.createdAt
this.fileList[0].media.sysFileId = info.media.sysFileId
}
},
// 详情
toDetail(id) {
this.$emit('change', {
type: 'detail',
params: {
type: 'commonListDetail',
id: id,
},
})
},
// 发送
release(person, row) {
// let typeList = ['templateId', 'toParty', 'toTag']
// person[0].type==0?'toParty':'templateId'
let typeList = ['toUser', 'toParty']
var type = typeList[person[0].type]
// console.log(type)
// 姓名id
const nameList = []
person.map((item) => {
if (item.type == 0) {
nameList.push(item.id)
}
})
var toUser = nameList.join('|')
// console.log(toUser)
// 部门id
const typesList = []
person.map((item) => {
if (item.type == 1) {
typesList.push(item.name)
}
})
var typeType = typesList.join('|')
// console.log(typeType)
this.instance
.post('/app/wxcp/wxapplicationmsgtemplate/sendByTemplate', {
type: typesList.join('|'), // 部门id如果只发送给个人则此处为空
toUser: nameList.join('|'),
templateId: row.id,
})
.then((res) => {
if (res?.code == 0) {
this.$message.success('发送成功!')
this.getList()
}
})
},
// radio多选框切换
onChange() {
this.$refs['ruleForm'].clearValidate()
this.dialogInfo.name = ''
this.dialogInfo.content = ''
this.dialogInfo.title = ''
this.dialogInfo.description = ''
this.dialogInfo.url = ''
this.dialogInfo.picurl = ''
this.dialogInfo.status = ''
this.fileList = []
},
// 新增弹窗的取消按钮
onCancel() {
this.dialogInfo.name = ''
this.dialogInfo.content = ''
this.dialogInfo.title = ''
this.dialogInfo.description = ''
this.dialogInfo.url = ''
this.dialogInfo.picurl = ''
this.dialogInfo.status = ''
this.fileList = []
},
},
}
</script>
<style lang="scss">
.commonList {
// overflow: hidden;
// white-space: nowrap;
// text-overflow: ellipsis;
height: 100%;
background: #f3f6f9;
::v-deep.btn {
margin-bottom: 10px;
}
::v-deep.cell {
width: 40px;
height: 40px;
::v-deep.table-left__wrapper {
::v-deep .img {
// object-fit: fill;
width: 40px;
height: 40px;
// vertical-align: middle;
// box-sizing: content-box;
}
}
}
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<ai-detail class="detail">
<template slot="title">
<ai-title
title="详情"
:isShowBack="true"
@onBackClick="onBack"
isShowBottomBorder
>
</ai-title>
<ai-wrapper label-width="80px" :columnsNumber="1">
<ai-info-item label="消息类型">
<span v-for="(item, index) in types" :key="index">
<span v-if="item.label == infoList.msgtype">
{{ item.name }}
</span>
</span>
</ai-info-item>
<ai-info-item label="名称">
<span>{{ infoList.name }}</span>
</ai-info-item>
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'text'">
<span>{{ infoList.content }}</span>
</ai-info-item>
<!-- 图片 -->
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'image'">
<img
:src="infoList.media.file.accessUrl"
alt=""
style="width:100px;height:100px;"
/>
</ai-info-item>
<!-- 视频 -->
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'video'">
<video
:src="infoList.media.file.accessUrl"
alt=""
style="width:100px;height:100px;"
/>
</ai-info-item>
<!-- 音频 -->
<ai-info-item label="消息内容" v-if="infoList.msgtype == 'voice'">
<ai-audio
:src="infoList.media.file.accessUrl"
alt=""
style="width:100px;height:100px;"
></ai-audio>
</ai-info-item>
<ai-info-item
label="标题"
v-if="
infoList.msgtype == 'video' ||
infoList.msgtype == 'voice' ||
infoList.msgtype == 'news'
"
>
<span>{{ infoList.title }}</span>
</ai-info-item>
<ai-info-item
label="文件描述"
v-if="
infoList.msgtype != 'text' &&
infoList.msgtype != 'image' &&
infoList.msgtype != 'voice'
"
>
<span>{{ infoList.description }}</span>
</ai-info-item>
<ai-info-item label="跳转地址" v-if="infoList.msgtype == 'news'">
<span>{{ infoList.url }}</span>
</ai-info-item>
<ai-info-item label="创建人">
<span>{{ infoList.createUserName }}</span>
</ai-info-item>
<ai-info-item label="创建时间">
<span>{{ infoList.createTime }}</span>
</ai-info-item>
<ai-info-item label="图片链接" v-if="infoList.msgtype == 'news'">
<span>{{ infoList.picurl }}</span>
</ai-info-item>
<ai-info-item label="标识">
<span>{{ infoList.uniqueId }}</span>
</ai-info-item>
<ai-info-item label="是否启用">
<span>{{
this.dict.getLabel('wxAppMsgTemplateStatus', infoList.status)
}}</span>
</ai-info-item>
</ai-wrapper>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Detail',
// 组件
components: {},
props: {
instance: Function,
dict: Object,
params: Object
},
data() {
return {
infoList: []
}
},
// 计算
computed: {
types() {
return [
{ name: '文本', label: 'text' },
{ name: '图片', label: 'image' },
{ name: '视频', label: 'video' },
// { name: '附件', label: 'file' },
{ name: '音频', label: 'voice' },
{ name: '文本卡片', label: 'textcard' },
{ name: '图文', label: 'news' }
// { name: '图文消息', label: 'mpnews' }
]
}
},
// 监听
watch: {},
// 实例创建后
onShow() {},
created() {
this.dict.load('wxAppMsgTemplateStatus').then(() => {
this.getList()
})
},
// 实例渲染后
mounted() {},
// 方法
methods: {
getList(id) {
this.instance
.post(`/app/wxcp/wxapplicationmsgtemplate/detail?id=${this.params.id}`)
.then(res => {
if (res.code === 0) {
this.infoList = res.data
// this.grooupTableData = res.data.groupInfos
}
})
},
onBack() {
this.$emit('change', {
type: 'list'
})
}
}
}
</script>
<style scoped lang="scss">
.detail {
.AiTitle {
::v-deep.ai-detail__title {
// padding: 10px 0 0 10px;
background-color: #fff;
}
}
.ai-wrapper {
margin-top: 10px;
padding-top: 20px;
background-color: #fff;
.ai-info-item {
.ai-info-item__right {
video {
width: 85px;
height: 150px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,733 @@
<template>
<div class="systemList">
<section>
<ai-list>
<template slot="content">
<el-alert
class="el-alert"
title="系统模板为内置模板,如非对业务熟悉,请勿修改或删除"
:closable="false"
show-icon
>
</el-alert>
<el-button
class="btn"
style="margin-bottom: 10px;"
type="primary"
icon="iconfont iconAdd"
size="small"
@click="showAdd"
>添加系统模板</el-button
>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
ref="aitableex"
:total="page.total"
:current.sync="page.current"
:size.sync="page.size"
@getList="getList"
:dict="dict"
style="height: 610px;overflow: auto;"
>
<!-- 消息类型 -->
<el-table-column slot="msgtype" label="消息类型" align="center">
<template slot-scope="{ row }">
<span v-for="(item, index) in types" :key="index">
<span v-if="item.label == row.msgtype">
{{ item.name }}
</span>
</span>
</template>
</el-table-column>
<!-- 消息内容 -->
<el-table-column slot="content" label="消息内容" align="center">
<template slot-scope="{ row }">
<div class="table-left__wrapper">
<img
:src="row.media.file && row.media.file.accessUrl"
v-if="
row.media &&
(row.msgtype == 'image' || row.msgtype == 'news')
"
class="media"
style="width: 40px;height: 40px;"
/>
<video
:src="row.media.file && row.media.file.accessUrl"
v-if="row.media && row.msgtype == 'video'"
class="media"
style="width: 40px;height: 40px;"
></video>
<audio
:src="row.media.file && row.media.file.accessUrl"
v-if="row.media && row.msgtype == 'voice'"
class="media"
style="width: 40px;height: 40px;"
></audio>
<div class="table-left__wrapper--right">
<el-tooltip
class="item"
effect="dark"
:content="row.content"
placement="top"
>
<div v-if="row.msgtype != 'news'">
{{ row.content }}
</div>
</el-tooltip>
</div>
</div>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column
slot="options"
label="操作"
align="center"
fixed="right"
width="200"
>
<template slot-scope="{ row }">
<div class="table-options">
<ai-wechat-selecter
style="display:inline-block;margin-right:8px ;"
v-if="row.status == 1"
:instance="instance"
@change="h => release(h, row)"
>
<el-button type="text">发送</el-button>
</ai-wechat-selecter>
<el-button type="text" @click="remove(row.id)"
>删除</el-button
>
<el-button type="text" @click="toEdit(row)">编辑</el-button>
<el-button type="text" @click="toDetail(row.id)"
>详情</el-button
>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<!-- 新增的弹窗 -->
<ai-dialog
:title="dialog.title"
:visible.sync="visible"
@onCancel="onCancel"
@onConfirm="addConfirm"
width="800px"
>
<div class="form_div">
<el-form
ref="ruleForm"
:model="dialogInfo"
:rules="formRules"
size="small"
label-suffix=""
label-width="100px"
>
<!-- 新增名称 -->
<el-form-item label="名称" prop="name">
<el-input
clearable
placeholder="请输入名称"
v-model="dialogInfo.name"
show-word-limit
:maxlength="128"
></el-input>
</el-form-item>
<!-- 新增标识uniqueId -->
<el-form-item label="标识" prop="uniqueId">
<el-input
clearable
placeholder="请输入标识"
v-model="dialogInfo.uniqueId"
show-word-limit
:maxlength="128"
/>
</el-form-item>
<!-- 新增状态 -->
<el-form-item label="状态" prop="status">
<ai-select
v-model="dialogInfo.status"
placeholder="请选择状态"
:selectList="$dict.getDict('wxAppMsgTemplateStatus')"
></ai-select>
</el-form-item>
<!-- 新增消息类型 -->
<el-form-item label="消息类型" prop="msgtype">
<el-radio-group v-model="dialogInfo.msgtype" @change="onChange">
<el-radio
:label="item.label"
v-for="(item, index) in types"
:key="index"
>{{ item.name }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item
:label="compLabel"
prop="sysFileId"
v-if="
dialogInfo.msgtype != 'text' && dialogInfo.msgtype != 'textcard'
"
>
<ai-uploader
:instance="instance"
v-model="fileList"
:acceptType="acceptType"
:url="'/app/wxcp/upload/uploadFile?type=' + fileTypeList"
isWechat
:fileType="fileType"
:limit="1"
></ai-uploader>
</el-form-item>
<!-- 新增消息类型对应的消息内容 -->
<el-form-item
label="消息内容"
prop="content"
v-if="dialogInfo.msgtype == 'text'"
>
<el-input
type="textarea"
placeholder="请输入消息内容"
:maxlength="2048"
show-word-limit
v-model="dialogInfo.content"
>
</el-input>
</el-form-item>
<!-- 新增标题 -->
<el-form-item
label="标题"
prop="title"
v-if="
dialogInfo.msgtype != 'text' &&
dialogInfo.msgtype != 'image' &&
dialogInfo.msgtype != 'voice'
"
>
<el-input
clearable
placeholder="请输入标题"
v-model="dialogInfo.title"
show-word-limit
:maxlength="128"
/>
</el-form-item>
<!-- 新增文件描述 -->
<el-form-item
label="文件描述"
prop="description"
v-if="
dialogInfo.msgtype != 'text' &&
dialogInfo.msgtype != 'image' &&
dialogInfo.msgtype != 'voice'
"
>
<el-input
clearable
placeholder="请输入文件描述"
v-model="dialogInfo.description"
show-word-limit
:maxlength="512"
/>
</el-form-item>
<!-- 新增跳转地址 -->
<el-form-item
label="跳转地址"
prop="url"
v-if="
dialogInfo.msgtype == 'news' || dialogInfo.msgtype == 'textcard'
"
>
<el-input
clearable
placeholder="请输入要跳转的链接地址http/https"
v-model="dialogInfo.url"
show-word-limit
:maxlength="128"
/>
</el-form-item>
<!-- 新增图片链接 -->
<el-form-item
label="图片链接"
prop="picurl"
v-if="
dialogInfo.msgtype != 'text' &&
dialogInfo.msgtype != 'video' &&
dialogInfo.msgtype != 'textcard' &&
dialogInfo.msgtype != 'voice' &&
dialogInfo.msgtype != 'image'
"
>
<el-input
clearable
placeholder="请输入图片链接"
v-model="dialogInfo.picurl"
show-word-limit
:maxlength="128"
/>
</el-form-item>
</el-form>
</div>
<div class="dialog-footer" slot="footer">
<el-button @click="onCancel" size="medium">取消</el-button>
<el-button @click="onConfirm('ruleForm')" type="primary" size="medium"
>确认</el-button
>
</div>
</ai-dialog>
</section>
</div>
</template>
<script>
export default {
name: 'systemList',
props: {
instance: Function,
dict: Object
},
data() {
var checkAge = (rule, value, callback) => {
if (this.dialogInfo.msgtype == 'image' && this.fileList.length == 0) {
return callback(new Error('请上传图片'))
} else if (
this.dialogInfo.msgtype == 'file' &&
this.fileList.length == 0
) {
return callback(new Error('请上传文件'))
} else if (
this.dialogInfo.msgtype == 'video' &&
this.fileList.length == 0
) {
return callback(new Error('请上传视频'))
} else if (
this.dialogInfo.msgtype == 'voice' &&
this.fileList.length == 0
) {
return callback(new Error('请上传音频'))
} else if (
this.dialogInfo.msgtype == 'news' &&
this.fileList.length == 0
) {
return callback(new Error('请上传图片'))
} else {
callback()
}
}
return {
visible: false,
total: 10,
colConfigs: [
{
prop: 'name',
label: '名称'
},
{
slot: 'msgtype',
label: '类型'
},
// {
// slot: 'content',
// label: '消息内容'
// },
// {
// prop: 'title',
// label: '标题'
// },
{
prop: 'uniqueId',
label: '标识'
},
{
prop: 'createUserName',
label: '创建人',
'show-overflow-tooltip': true
},
{ prop: 'createTime', label: '创建时间' },
{
prop: 'status',
label: '状态',
dict: 'wxAppMsgTemplateStatus'
},
{ slot: 'options', label: '操作', align: 'center' }
],
tableData: [],
page: {
size: 10,
current: 1,
total: 0
},
dialog: {
title: '',
visible: false
},
dialogInfo: {
title: '',
msgtype: 'text',
content: '',
createUserName: '',
createTime: '',
status: '',
id: '',
isSystem: 1,
mediaId: '',
sysFileId: '',
media: {},
description: '',
url: '',
picurl: '',
uniqueId: ''
},
fileList: [],
formRules: {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
content: [
{ required: true, message: '请输入消息内容', trigger: 'blur' }
],
sysFileId: [{ required: true, validator: checkAge, trigger: 'change' }],
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
description: [
{ required: true, message: '请输入文件描述或链接', trigger: 'blur' }
],
uniqueId: [{ required: true, message: '请输入标识', trigger: 'blur' }],
url: [
{
required: true,
message: '请输入要跳转的链接地址http/https',
trigger: 'blur'
}
],
picurl: [
{ required: true, message: '请输入图片链接', trigger: 'blur' }
],
status: [{ required: true, message: '请选择状态', trigger: 'blur' }]
}
}
},
computed: {
types() {
return [
{ name: '文本', label: 'text' },
{ name: '图片', label: 'image' },
{ name: '视频', label: 'video' },
// { name: '附件', label: 'file' },
// { name: '音频', label: 'voice' },
{ name: '文本卡片', label: 'textcard' },
{ name: '图文', label: 'news' }
// { name: '图文消息', label: 'mpnews' }
]
},
compLabel() {
return this.types.find(e => e.label == this.dialogInfo.msgtype)?.name
},
fileTypeList() {
var type = ''
if (
this.dialogInfo.msgtype == 'text' ||
this.dialogInfo.msgtype == 'textcard'
) {
type = 'text'
} else if (
this.dialogInfo.msgtype == 'image' ||
this.dialogInfo.msgtype == 'news'
) {
type = 'image'
} else if (this.dialogInfo.msgtype == 'video') {
type = 'video'
} else if (this.dialogInfo.msgtype == 'voice') {
type = 'voice'
}
return type
},
fileType() {
// return this.dialogInfo.msgtype == 'image' ? 'img' : 'file'
var type = ''
if (
this.dialogInfo.msgtype == 'image' ||
this.dialogInfo.msgtype == 'news'
) {
type = 'img'
} else {
type = 'file'
}
return type
},
acceptType() {
return {
image: '.jpg,.png,.jpeg',
video: '.mp4',
voice: '.AMR' // 无效的设置
}[this.dialogInfo.msgtype]
}
},
created() {
this.dict.load(['wxAppMsgTemplateStatus']).then(() => {
this.getList()
})
},
methods: {
getList() {
this.instance
.post(`/app/wxcp/wxapplicationmsgtemplate/list`, null, {
params: {
...this.page,
isSystem: 1
}
})
.then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.page.total = res.data.total
}
})
},
// 新增按钮
showAdd() {
this.dialog.title = '添加'
this.dialogInfo.msgtype = 'text'
this.visible = true
this.getList()
},
// 确定新增
addConfirm() {
var media = null
if (this.dialogInfo.msgtype != 'text' && this.fileList.length) {
media = {
mediaId: this.fileList[0].media.mediaId,
createdAt: this.fileList[0].media.createdAt,
sysFileId: this.fileList[0].id,
type: this.dialogInfo.msgtype,
title: this.dialogInfo.title,
url: this.dialogInfo.url
}
}
this.$refs['ruleForm'].validate(valid => {
if (valid) {
this.instance
.post(`/app/wxcp/wxapplicationmsgtemplate/addOrUpdate`, {
createTime: this.dialogInfo.createTime,
createUserName: this.dialogInfo.createUserName,
status: this.dialogInfo.status,
id: this.dialogInfo.id,
isSystem: 1,
msgtype: this.dialogInfo.msgtype,
title: this.dialogInfo.title,
name: this.dialogInfo.name,
content: this.dialogInfo.content,
description: this.dialogInfo.description,
url: this.dialogInfo.url,
picurl: this.dialogInfo.picurl,
uniqueId: this.dialogInfo.uniqueId,
media
})
.then(res => {
if (res.code == 0) {
this.$message.success('操作成功')
this.getList()
this.visible = false
this.dialogInfo = {}
this.infoInit()
}
})
.catch(err => {})
}
})
},
// 新增完成后初始化页面
infoInit() {
for (let key in this.dialogInfo) {
this.dialogInfo[key] = ''
}
this.dialogInfo.msgtype = 'text'
this.dialogInfo.media = {}
this.dialogInfo.isSystem = '0'
this.fileList = []
},
// 删除
remove(id) {
this.$confirm('删除后不可恢复,是否要删除该事项?', {
type: 'error'
}).then(() => {
this.instance
.post(`/app/wxcp/wxapplicationmsgtemplate/delete?id=${id}`)
.then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
// 编辑
toEdit(row) {
const info = JSON.parse(JSON.stringify(row))
this.dialog.title = '编辑'
this.visible = true
this.dialogInfo = { ...info }
this.fileList = [
{
accessUrl: '',
url: '',
id: '',
media: {
mediaId: '',
createdAt: '',
sysFileId: ''
}
}
]
if (
this.dialogInfo.msgtype != 'text' &&
this.dialogInfo.msgtype != 'textcard'
) {
this.dialogInfo.media == info.media
this.fileList[0].accessUrl = info.media.file.accessUrl
this.fileList[0].url = info.media.file.url
this.fileList[0].id = info.media.sysFileId
this.fileList[0].media.mediaId = info.media.mediaId
this.fileList[0].media.createdAt = info.media.createdAt
this.fileList[0].media.sysFileId = info.media.sysFileId
}
},
// 查看详情
toDetail(id) {
this.$emit('change', {
type: 'detailSystem',
params: {
type: 'detailSystemList',
id: id
}
})
},
// 发送
release(person, row) {
// let typeList = ['templateId', 'toParty', 'toTag']
let typeList = ['toUser', 'toParty']
var type = typeList[person[0].type]
// console.log(type)
// 姓名id
const nameList = []
person.map(item => {
if (item.type == 0) {
nameList.push(item.id)
}
})
var toUser = nameList.join('|')
// console.log(toUser)
// 部门id
const typesList = []
person.map(item => {
if (item.type == 1) {
typesList.push(item.name)
}
})
var typeType = typesList.join('|')
// console.log(typeType)
this.instance
.post('/app/wxcp/wxapplicationmsgtemplate/sendByTemplate', {
type: typesList.join('|'), // 部门id如果只发送给个人则此处为空
toUser: nameList.join('|'),
templateId: row.id
})
.then(res => {
if (res?.code == 0) {
this.$message.success('发送成功!')
this.getList()
}
})
},
// radio多选框切换
onChange() {
this.$refs['ruleForm'].clearValidate()
this.dialogInfo.name = ''
this.dialogInfo.content = ''
this.dialogInfo.title = ''
this.dialogInfo.description = ''
this.dialogInfo.url = ''
this.dialogInfo.picurl = ''
this.dialogInfo.status = ''
this.dialogInfo.uniqueId = ''
this.fileList = []
},
// 新增弹窗的取消按钮
onCancel() {
this.dialogInfo.name = ''
this.dialogInfo.content = ''
this.dialogInfo.title = ''
this.dialogInfo.description = ''
this.dialogInfo.url = ''
this.dialogInfo.picurl = ''
this.dialogInfo.status = ''
this.dialogInfo.uniqueId = ''
this.fileList = []
}
}
}
</script>
<style scoped lang="scss">
.systemList {
// overflow: hidden;
// white-space: nowrap;
// text-overflow: ellipsis;
height: 100%;
.el-alert {
margin-bottom: 10px;
// ::v-deep.el-alert__content {
// color: red;
// }
::v-deep.el-alert__closebtn {
color: #8d96a9;
}
}
.btn {
margin-bottom: 15px;
}
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<section style="height: 100%;">
<ai-list v-if="!showDetail">
<template slot="title">
<ai-title title="通知公告"></ai-title>
</template>
<template slot="tabs">
<el-tabs v-model="currIndex">
<el-tab-pane v-for="(tab,i) in tabs" :key="i" :label="tab.label" :name="String(i)">
<component :is="tab.comp" v-if="currIndex==i" :ref="currIndex" :instance="instance" :dict="dict"
@goPage="goPage"/>
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<component v-else :is="currentComp" :instance="instance" :dict="dict" :detail="detailRow" @gotoEdit="gotoAdd" ></component>
</section>
</template>
<script>
import add from './components/add';
import detail from './components/detail'
import manageDetail from './components/manageDetail'
import recentNotice from './components/recentNotice'
import noticeManage from './components/noticeManage'
export default {
name: 'AppNotification',
label: "通知公告",
components: {add, detail, recentNotice,noticeManage,manageDetail},
provide() {
return {
top: this
}
},
props: {
instance: Function,
dict: Object,
},
data() {
return {
currIndex: "0",
currentComp: "",
showDetail: false,
detailRow: {},
}
},
computed: {
tabs() {
return [
{label: "最新公告", name: "recentNotice", comp: recentNotice, detail: detail},
{label: "公告管理", name: "noticeManage", comp: noticeManage, detail: manageDetail},
]
},
},
methods: {
goPage(params) {
params.row && (this.detailRow = params.row)
this.currentComp = params.comp
if (params.comp == 'detail' || params.comp == 'add' || params.comp == "manageDetail") {
this.showDetail = true
}
},
goBack() {
this.showDetail = false;
},
gotoAdd(obj) {
this.showDetail = true
this.detailRow = obj
this.currentComp = 'add'
},
},
}
</script>
<style lang="scss" scoped>
::v-deep .ai-list__content--right-wrapper {
background-color: transparent !important;
box-shadow: none !important;
}
</style>

View File

@@ -0,0 +1,186 @@
<template>
<ai-detail>
<template slot="title">
<ai-title :title="detail.id ? '编辑公告' : '创建公告'" isShowBack isShowBottomBorder
@onBackClick="$parent.goBack"></ai-title>
</template>
<template #content>
<ai-card title="基本信息">
<template #content>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="公告标题" prop="title">
<el-input v-model="form.title" size="small" placeholder="请输入" show-word-limit maxlength="30"></el-input>
</el-form-item>
<el-form-item label="公告内容" prop="content">
<ai-editor v-model="form.content" :instance="instance"/>
</el-form-item>
<el-form-item label="附件">
<ai-uploader :instance="instance" v-model="form.files" fileType="file" isShowTip></ai-uploader>
</el-form-item>
<el-form-item label="发送对象" prop="persons">
<el-row type="flex" align="middle">
<div class="text-area">
<span v-text="item.name"/>
<span v-if="persons.length">{{ persons.length }}</span>
</div>
<ai-wechat-selecter v-model="form.persons" :instance="instance" @change="onChange">
<el-button type="info">选择</el-button>
</ai-wechat-selecter>
</el-row>
</el-form-item>
<el-row type="flex">
<el-form-item label="发送时间" prop="type">
<el-radio-group v-model="form.type" @change="form.releaseTime = null">
<el-radio :label="0">立即发送</el-radio>
<el-radio :label="1">定时发送</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="releaseTime" class="picker" v-if="form.type==1">
<el-date-picker
v-model="form.releaseTime"
style="margin-left: 10px;"
value-format="yyyy-MM-dd HH:mm:ss"
size="small"
type="datetime"
placeholder="请选择">
</el-date-picker>
</el-form-item>
</el-row>
</el-form>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="$parent.goBack">取消</el-button>
<el-button type="primary" @click="confim(0)">保存</el-button>
<el-button type="primary" @click="confim(1)">发布</el-button>
</template>
</ai-detail>
</template>
<script>
export default {
name: "add",
props: {
instance: Function,
dict: Object,
detail: Object,
},
data() {
return {
form: {
id: null,
title: "",
content: "",
files: [],
persons: [],
type: 0,
releaseTime: null,
},
persons: [],
}
},
computed: {
rules() {
return {
title: [
{required: true, message: '请输入公告标题'},
],
content: [
{required: true, message: '请输入公告内容'},
],
persons: [
{required: true, message: '请选择发送对象'},
],
type: [
{required: true},
],
releaseTime: [
{required: true, message: '请选择发送时间'},
],
};
}
},
methods: {
onChange(e) {
this.form.persons = e;
this.persons = e;
this.$refs["form"].validateField("persons");
},
confim(e) {
this.$refs["form"].validate(v => {
if (v) {
if (this.form.releaseTime && (new Date(this.form.releaseTime).getTime() <= Date.now())) {
return this.$message.error("发送时间要大于当前时间")
}
this.instance.post("/app/appannouncement/addOrUpdate", {
...this.form,
status: e
}).then(res => {
if (res.code == 0) {
this.$message.success(e == 0 ? "保存成功" : "发布成功");
this.$parent.goBack();
}
})
}
})
},
getDetail() {
this.instance.post("/app/appannouncement/detail", null, {
params: {
id: this.detail.id
}
}).then(res => {
if (res && res.data) {
Object.keys(this.form).map(e => this.form[e] = res.data[e]);
this.form.type = res.data.releaseTime ? 1 : 0;
}
})
},
},
mounted() {
if (this.detail?.id) {
this.getDetail();
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .picker {
width: 300px;
.el-form-item__content {
margin-left: 0 !important;
}
}
.text-area {
width: 995px;
height: 32px;
background-color: #F5F5F5;
border: 1px solid #d0d4dc;
border-radius: 2px;
display: flex;
align-items: center;
box-sizing: border-box;
padding: 0 6px;
color: #666666;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
::v-deep .AiOpenData {
height: auto !important;
&:after {
content: "、";
}
&:nth-child(2):after {
content: "";
}
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<ai-detail>
<template slot="title">
<ai-title title="公告详情" isShowBack isShowBottomBorder @onBackClick="$parent.goBack"></ai-title>
</template>
<template #content>
<ai-card :title="detailObj.title" class="title">
<template #content>
<el-row type="flex" justify="space-between" class="info">
<span>时间{{ detailObj.releaseTime }}</span>
<span style="display:flex">发布单位
<span v-text="detailObj.unitName"/>
</span>
<span style="display:flex">发布人
<span v-text="detailObj.releaseUserName"/>
</span>
</el-row>
<div v-html="detailObj.content" style="margin: 20px 0;"></div>
</template>
</ai-card>
<ai-card title="附件" v-if="detailObj.files && detailObj.files.length">
<template #content>
<el-row type="flex" justify="space-between" class="file" v-for="(item,index) in detailObj.files" :key="index"
@click.native="open(item)">
<span>{{ item.fileName }}</span>
<span>{{ (item.size / 1024).toFixed(2) }}KB</span>
</el-row>
</template>
</ai-card>
</template>
</ai-detail>
</template>
<script>
export default {
name: "detail",
props: {
instance: Function,
dict: Object,
detail: Object
},
data() {
return {
detailObj: {},
}
},
methods: {
open(item) {
window.open(item.url);
}
},
mounted() {
this.instance.post("/app/appannouncement/detail", null, {
params: {
id: this.detail.id
}
}).then(res => {
if (res && res.data) {
this.detailObj = res.data;
}
})
}
}
</script>
<style lang="scss" scoped>
::v-deep .title {
.aibar-left {
width: 100%;
text-align: center;
}
}
.file {
height: 40px;
line-height: 40px;
padding: 0 8px;
font-size: 14px;
color: #333;
background: #fff;
border-radius: 4px;
border: 1px solid #d0d4dc;
margin-bottom: 16px;
cursor: pointer;
}
.info {
& > span {
font-size: 14px;
color: #333;
}
}
</style>

View File

@@ -0,0 +1,204 @@
<template>
<ai-detail>
<template slot="title">
<ai-title title="公告详情" isShowBack isShowBottomBorder @onBackClick="$parent.goBack"></ai-title>
</template>
<template #content>
<ai-sidebar v-model="index" :tabTitle="tabTitle" @change="onChange"></ai-sidebar>
<template v-if="index==0">
<ai-card :title="detailObj.title" class="title">
<template #content>
<el-row type="flex" justify="space-between" class="info">
<span>时间{{ detailObj.releaseTime }}</span>
<span style="display:flex">发布单位
<span v-text="detailObj.unitName"/>
</span>
<span style="display:flex">发布人
<span v-text="detailObj.releaseUserName"/>
</span>
</el-row>
<div v-html="detailObj.content" style="margin: 20px 0;"></div>
</template>
</ai-card>
<ai-card title="附件" v-if="detailObj.files && detailObj.files.length">
<template #right>
<span class="Edit" @click="downFileAll"><i class="iconfont iconDownload"></i>下载全部</span>
</template>
<template #content>
<ai-file-list :fileList="detailObj.files" :fileOps="{ name: 'fileName', size: 'size' }"></ai-file-list>
</template>
</ai-card>
</template>
<template v-else>
<ai-list>
<template #content>
<ai-search-bar>
<template #left>
<ai-select
v-model="search.readStatus"
@change="search.current=1,getList()"
placeholder="查阅状态"
:selectList="dict.getDict('announcementReadStatus')"
></ai-select>
</template>
<template #right>
<el-input v-model="search.readUserName" @keyup.enter.native="getList()" placeholder="姓名"
size="small" suffix-icon="iconfont iconSearch" clearable
@clear="search.current=1,getList()"></el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="readUserName" label="姓名" align="center">
<template slot-scope="{ row }">
<span v-text="row.readUserName"/>
</template>
</el-table-column>
<el-table-column slot="unitName" label="所属部门" align="center">
<template slot-scope="{ row }">
<span v-text="row.unitName"/>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
</template>
</ai-detail>
</template>
<script>
export default {
name: "manageDetail",
props: {
instance: Function,
dict: Object,
detail: Object
},
data() {
return {
index: 0,
tableData: [],
total: 0,
detailObj: {},
search: {
current: 1,
size: 10
}
}
},
computed: {
tabTitle() {
return ["公告详情", "查询情况"];
},
colConfigs() {
return [
{slot: "readUserName"},
{prop: "readUserPhone", label: "手机号", align: "center"},
{slot: "unitName"},
{
prop: "readStatus", label: "查阅状态", align: "center",
render: (h, {row}) => [<span
style={{color: this.dict.getColor("announcementReadStatus", row.readStatus)}}>{this.dict.getLabel("announcementReadStatus", row.readStatus)}</span>]
},
];
}
},
methods: {
downFileAll() {
if (this.detailObj.files.length > 0) {
this.instance.post('/app/appannouncement/downLoadAllFileForDetail', null, {
responseType: 'blob',
params: {
id: this.detailObj.id
}
}).then((res) => {
const link = document.createElement('a')
let blob = new Blob([res], {type: 'application/octet-stream'})
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
var num = ''
for (let i = 0; i < 10; i++) {
num += Math.ceil(Math.random() * 10)
}
link.setAttribute('download', '公告文件' + '.zip')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
} else {
this.$message.error('暂无附件提供下载')
}
},
onChange(val) {
if (val == 0) {
this.getDetail();
} else {
this.getList();
}
},
getDetail() {
this.instance.post("/app/appannouncement/detail", null, {
params: {
id: this.detail.id
}
}).then(res => {
if (res && res.data) {
this.detailObj = res.data;
}
})
},
getList() {
this.instance.post("/app/appannouncementreader/list", null, {
params: {
announcementId: this.detail.id,
...this.search
}
}).then(res => {
if (res && res.data) {
this.tableData = res.data.records;
this.total = res.data.total;
}
})
},
},
created() {
this.dict.load("announcementReadStatus").then(this.getDetail);
}
}
</script>
<style lang="scss" scoped>
::v-deep .title {
.aibar-left {
width: 100%;
text-align: center;
}
}
.file {
height: 40px;
line-height: 40px;
padding: 0 8px;
font-size: 14px;
color: #333;
background: #fff;
border-radius: 4px;
border: 1px solid #d0d4dc;
margin-bottom: 16px;
cursor: pointer;
}
.info {
& > span {
font-size: 14px;
color: #333;
}
}
</style>

View File

@@ -0,0 +1,267 @@
<template>
<section style="height: 100%;">
<ai-list isTabs>
<template slot="content">
<ai-search-bar>
<template #left>
<ai-select v-model="search.status" placeholder="发布状态" :selectList="dict.getDict('announcementStatus')"
@change="search.current = 1, getList()"></ai-select>
<el-date-picker
type="daterange"
size="small"
v-model="date"
@change="search.current = 1,getList()"
range-separator=""
value-format="yyyy-MM-dd"
start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>
</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-search-bar>
<template #left>
<el-button icon="iconfont iconAdd" type="primary" @click="add">创建公告</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="releaseUserName" label="发布人" align="center">
<template slot-scope="{ row }">
<span v-text="row.releaseUserName"/>
</template>
</el-table-column>
<el-table-column slot="unitName" label="发布部门" align="center">
<template slot-scope="{ row }">
<span v-text="row.unitName"/>
</template>
</el-table-column>
<el-table-column slot="options" width="250" label="操作" align="center">
<template slot-scope="{ row }">
<el-button type="text" @click="toDetail(row)">详情</el-button>
<el-button type="text" v-if="row.status==0" @click="publish(row,0)">发布</el-button>
<el-button type="text" v-if="row.status==1" @click="publish(row,1)">撤回</el-button>
<el-button type="text" @click="toEdit(row)" v-if="row.status==0 || row.status==3">编辑</el-button>
<el-button type="text" @click="handleDel(row)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog
title="查阅状态"
:visible.sync="visible"
@closed="row={},readList=[]"
:customFooter="true"
width="800px">
<template v-if="readObj.read && readObj.read.length">
<header>已读人员</header>
<div class="wrap">
<div class="item" v-for="(item,index) in readObj.read" :key="index">
<img :src="item.avatar" alt="">
<span v-text="item.name"/>
</div>
</div>
</template>
<template v-if="readObj.unRead && readObj.unRead.length">
<header>未读人员</header>
<div class="wrap">
<div class="item" v-for="(item,index) in readObj.unRead" :key="index">
<img :src="item.avatar" alt="">
<span v-text="item.name"/>
</div>
</div>
</template>
</ai-dialog>
</section>
</template>
<script>
export default {
name: "noticeManage",
props: {
instance: Function,
dict: Object
},
data() {
return {
search: {
title: "",
status: "",
size: 10,
current: 1,
},
date: [],
tableData: [],
total: 0,
visible: false,
row: {},
readObj: {},
}
},
computed: {
colConfigs() {
return [
{prop: 'title', label: '标题'},
{
prop: 'readNum', label: '查询状态', align: 'center',
render: (h, {row}) => [<span class='status'
onClick={this.showDialog.bind(this, row)}>{row.readNum}人已读</span>,
<span class='status' onClick={this.showDialog.bind(this, row)}>{row.unReadNum}人未读</span>]
},
{slot: 'releaseUserName'},
{slot: 'unitName'},
{prop: 'releaseTime', label: '发布时间', align: 'center'},
{
prop: 'status', label: '发布状态', align: 'center',
render: (h, {row}) => [<span
style={{color: this.dict.getColor("announcementStatus", row.status)}}>{this.dict.getLabel("announcementStatus", row.status)}</span>]
},
{slot: 'options'},
];
}
},
methods: {
showDialog(row) {
this.row = row;
this.getReadList();
},
toDetail(row) {
this.$emit('goPage', {
comp: 'manageDetail',
row
});
},
toEdit(row) {
this.$emit('goPage', {
comp: 'add',
row
});
},
publish(row, status) {
this.$confirm(`是否要${status == 0 ? '发布' : '撤回'}该公告?`).then(() => {
this.instance.post("/app/appannouncement/update-status", null, {
params: {
id: row.id
}
}).then(res => {
if (res.code == 0) {
this.$message.success(status == 0 ? '发布成功' : '撤回成功');
this.getList();
}
})
})
},
handleDel(row) {
this.$confirm("是否要删除该公布?").then(() => {
this.instance.post("/app/appannouncement/delete", null, {
params: {
ids: row.id
}
}).then(res => {
if (res.code == 0) {
this.$message.success("删除成功");
this.getList();
}
})
})
},
add() {
this.$emit('goPage', {
comp: 'add',
row: {},
});
},
getReadList() {
this.instance.post("/app/appannouncementreader/list-unread", null, {
params: {
id: this.row.id
}
}).then(res => {
if (res && res.data) {
this.readObj = res.data;
this.visible = true;
}
})
},
getList() {
this.instance.post("/app/appannouncement/list-mgr", null, {
params: {
...this.search,
startTime: this.date?.length ? this.date[0] : null,
endTime: this.date?.length ? this.date[1] : null,
}
}).then(res => {
if (res && res.data) {
this.tableData = res.data.records;
this.total = res.data.total;
}
})
}
},
created() {
this.dict.load("announcementStatus").then(this.getList)
}
}
</script>
<style lang="scss" scoped>
::v-deep .status {
color: rgba(41, 107, 251, 100);
cursor: pointer;
}
header {
font-size: 14px;
color: #333333;
margin-bottom: 10px;
}
::v-deep .wrap {
display: flex;
align-items: center;
flex-wrap: wrap;
.item {
width: 50px;
display: flex;
flex-direction: column;
justify-content: center;
margin-right: 22px;
margin-bottom: 22px;
img {
width: 100%;
height: 50px;
border-radius: 50%;
}
span {
font-size: 14px;
color: #333333;
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<ai-list isTabs>
<template #content>
<ai-search-bar>
<template #left>
<ai-select
v-model="search.readStatus"
@change="getList()"
placeholder="查阅状态"
:selectList="dict.getDict('announcementReadStatus')"
></ai-select>
<el-date-picker
v-model="date"
@change="search.current = 1,getList()"
type="daterange"
size="small"
value-format="yyyy-MM-dd"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>
</template>
<template #right>
<el-input v-model="search.title" @keyup.enter.native="getList()" placeholder="标题"
size="small" suffix-icon="iconfont iconSearch" clearable
@clear="search.current=1,getList()"></el-input>
</template>
</ai-search-bar>
<div class="body">
<ul v-if="tableData.length">
<li v-for="(item,index) in tableData" :key="index" @click="goDetail(item)">
<label>
<!-- <em v-if="item.readStatus==0"></em>-->
<div class="status" v-if="item.readStatus==0">未读</div>
<div class="status read" v-else>已读</div>
{{ item.title }}</label>
<el-row type="flex" justify="space-between" class="row">
<span style="display:flex">
<b>发布人</b>
<span v-text="item.releaseUserName"/>
</span>
<span style="display:flex">
<b>发布部门</b>
<span v-text="item.unitName"/>
</span>
<span>
<b>发布日期</b>
{{ item.releaseTime }}</span>
</el-row>
</li>
</ul>
<div class="no-data" style="height:160px;" v-else></div>
</div>
<div class="pagination" v-if="tableData.length>0">
<el-pagination
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
background
:current-page.sync="search.current"
:page-sizes="[5, 10, 50, 100,200]"
:page-size="search.size"
layout="slot,->,prev, pager, next,sizes,jumper"
:total="total">
<div class="page" style="text-align: left">
<em>{{ total }}</em>
条记录
</div>
</el-pagination>
</div>
</template>
</ai-list>
</template>
<script>
export default {
name: "recentNotice",
props: {
instance: Function,
dict: Object,
},
data() {
return {
search: {
readStatus: "",
title: "",
current: 1,
size: 10,
},
date: [],
tableData: [],
total: 0,
}
},
methods: {
pickerChange(e) {
console.log(e);
},
goDetail(item) {
this.$emit('goPage', {
row: item,
comp: 'detail'
});
},
getList() {
this.instance.post(`/app/appannouncement/list-latest`, null, {
params: {
...this.search,
startTime: this.date?.length ? this.date[0] : null,
endTime: this.date?.length ? this.date[1] : null,
}
}).then(res => {
if (res && res.data) {
this.tableData = res.data.records;
this.total = res.data.total;
}
});
},
handleCurrentChange(val) {
this.search.current = val;
this.getList();
},
handleSizeChange(val) {
this.search.size = val;
this.getList();
},
},
created() {
this.dict.load("announcementReadStatus").then(_ => this.getList())
}
}
</script>
<style lang="scss" scoped>
.body {
ul {
overflow: hidden;
padding: 0;
margin: 0;
li {
height: 86px;
background: #FFFFFF;
border-radius: 4px;
box-sizing: border-box;
padding: 16px 215px 16px 32px;
margin-top: 10px;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
label {
display: flex;
align-items: center;
position: relative;
font-size: 16px;
color: #222222;
.status {
width: 40px;
height: 20px;
background: #FEF4E5;
color: #F79300;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
margin-right: 4px;
}
.read {
background: #EEEEEE;
color: #999999;
}
em {
width: 8px;
height: 8px;
background: #ff4422;
border-radius: 50%;
position: absolute;
left: -16px;
top: 4px;
}
}
}
::v-deep .row {
margin-top: 10px;
span {
font-size: 14px;
color: #222222;
b {
font-size: 14px;
color: #888888;
}
}
}
}
}
::v-deep .page {
font-size: 12px;
color: #555555;
em {
font-style: normal;
color: rgb(34, 102, 255);
}
}
</style>

View File

@@ -0,0 +1,89 @@
<template>
<ai-list v-if="!isShowDetail">
<template slot="title">
<ai-title title="居民群管理" :isShowBottomBorder="false"></ai-title>
</template>
<template slot="tabs">
<el-tabs v-model="currIndex">
<el-tab-pane v-for="(tab,i) in tabs" :key="i" :label="tab.label">
<component :ref="String(i)" v-if="currIndex == i" :is="tab.comp" @change="onChange" lazy :instance="instance" :dict="dict" :permissions="permissions"/>
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<Detail v-else-if="componentName === 'Detail'" :params="params" :instance="instance" :dict="dict" :permissions="permissions" @change="onChange"></Detail>
</template>
<script>
import List from './components/List.vue'
import Statistics from './components/Statistics'
import Tags from './components/Tags'
import Detail from './components/Detail'
import { mapState } from 'vuex'
export default {
name: 'AppResidentGroupManage',
label: '居民群管理',
components: {
List,
Tags,
Detail,
Statistics
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
tabs () {
const tabList = [
{label: '居民群列表', name: 'List', comp: List, permission: ''},
{label: '居民群统计', name: 'Statistics', comp: Statistics, permission: ''},
{label: '居民群标签', name: 'Tags', comp: Tags, permission: ''}
].filter(item => {
return true
})
return tabList
}
},
data () {
return {
activeName: 'JoinEvent',
currIndex: '0',
componentName: '',
params: {},
isShowDetail: false
}
},
created () {
},
methods: {
onChange (data) {
if (data.type === 'list') {
this.componentName = 'List'
this.isShowDetail = false
this.params = data.params
}
if (data.type === 'detail') {
this.componentName = 'Detail'
this.isShowDetail = true
this.params = data.params
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,377 @@
<template>
<ai-detail class="AppResidentManage">
<template slot="title">
<ai-title title="居民群详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)"></ai-title>
</template>
<template slot="content">
<div class="detail-top">
<div class="detail-top__header">
<div class="header-left">
<img src="https://cdn.cunwuyun.cn/dvcp/h5/groupAvatar.png">
<div class="header-left__right">
<h2>{{ info.name }}</h2>
</div>
</div>
<div class="header-right">
<!-- <div class="header-right__item">-->
<!-- <span>成员总数</span>-->
<!-- <h3>{{ chartData.groupSum}}</h3>-->
<!-- </div>-->
<div class="header-right__item">
<span>成员总数</span>
<h3>{{chartData.today && chartData.today.total }}</h3>
</div>
<div class="header-right__item">
<span>今日新增</span>
<h3>{{ chartData.today && chartData.today.increase }}</h3>
</div>
<div class="header-right__item">
<span>今日流失</span>
<h3>{{chartData.today && chartData.today.decrease}}</h3>
</div>
</div>
</div>
<div class="detail-top__content">
<ai-wrapper
label-width="80px">
<ai-info-item label="群主" :value="info.ownerName"></ai-info-item>
<ai-info-item label="群公告" :value="info.notice" isLine></ai-info-item>
<ai-info-item label="群聊标签" isLine>
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in info.tagList" size="small" :key="index">{{ item.tagName }}
</el-tag>
</div>
</ai-info-item>
</ai-wrapper>
</div>
</div>
<ai-card title="图表数据">
<template slot="content">
<div id="lineChart"></div>
</template>
</ai-card>
<ai-card title="成员列表">
<template slot="content">
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
border
ref="aitableex"
@getList="getDynamicInfo"
:current.sync="search.current"
:size.sync="search.size">
<el-table-column slot="options" label="操作" width="100" align="center">
<template slot-scope="{ row }">
<el-button type="text" v-if="row.type==2 && row.avatar" @click="toDetail(row)">居民详情</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
</template>
</ai-detail>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
computed: {
colConfigs() {
return [
{
prop: 'memberName', label: '成员',render:(h,{row})=>[<img class="avatar" src={row.avatar || "https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png"} />,
<span>{row.memberName}</span>,
<span style={{color:row.customerType==1 ? '#2EA222' : '#3C7FC8',marginLeft:'8px'}}>{ row.customerType?(row.customerType==1 ? '@微信' : '@' + row.corpName):'' }</span>],
},
{prop: 'type', label: '类型',render:(h,{row})=>[<span>{this.dict.getLabel("wxGroupMemberType",row.type)}</span>]},
{prop: 'joinTime', label: '入群时间'},
{prop: 'joinScene', label: '入群方式',render:(h,{row})=>[<span>{this.dict.getLabel("wxGroupMemberJoinScene",row.joinScene)}</span>]},
{slot: "options"},
]
}
},
data() {
return {
isShow: false,
info: {},
search: {
current: 1,
size: 10
},
total: 0,
tableData: [],
chart: null,
chartData: {},
}
},
created() {
this.dict.load("wxGroupMemberJoinScene", "wxGroupMemberType")
},
mounted() {
if (this.params && this.params.id) {
this.getInfo()
this.getDynamicInfo()
this.getChart()
}
},
methods: {
getChart() {
this.instance.post(`/app/wxcp/wxgroup/groupStatistic`, null, {
params: {
id: this.params.id
}
}).then(res => {
if (res && res.data) {
this.chartData = res.data
this.initChart()
}
})
},
initChart() {
this.chart = echarts.init(document.getElementById('lineChart'))
this.setOptions()
},
setOptions() {
const x = Object.keys(this.chartData.list)
const y = Object.values(this.chartData.list)
this.chart.setOption({
tooltip: {
trigger: 'axis'
},
legend: {
type: "plain"
},
grid: {
left: '20px',
right: '38px',
bottom: '14px',
top: '30px',
containLabel: true
},
color: ['#2266FF', '#22AA99', '#F8B425'],
xAxis: {
type: 'category',
axisLabel: {
align: 'center',
padding: [2, 0, 0, 0],
interval: 0,
fontSize: 14,
color: '#666666'
},
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#E1E5EF'
}
},
data: x
},
yAxis: {
axisTick: {
length: 0,
show: false
},
splitLine: {
show: true,
lineStyle: {
color: ['#E1E5EF'],
width: 1,
type: 'solid'
}
},
nameTextStyle: {
color: '#666666',
align: 'left'
},
axisLine: {
show: false
},
axisLabel: {
color: '#666666'
},
type: 'value'
},
series: [
{
name: '成员总数',
type: 'line',
data: y.map(v => v.total)
},
{
name: '新增成员数',
type: 'line',
data: y.map(v => v.increase)
},
{
name: '流失成员数',
type: 'line',
data: y.map(v => v.decrease)
}
]
})
},
getInfo() {
this.instance.post(`/app/wxcp/wxgroup/getDetail?id=${this.params.id}`).then(res => {
if (res && res.data) {
this.info = res.data
}
})
},
getDynamicInfo() {
this.instance.post(`/app/wxcp/wxgroup/listMember?groupId=${this.params.id}`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
toDetail(row) {
this.$router.push({
name: '68',
query: {
id: row.userId,
type: 'Detail'
}
})
},
cancel(isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: isRefresh ? true : false
})
}
}
}
</script>
<style scoped lang="scss">
.AppResidentManage {
::v-deep .ai-detail__content--wrapper {
max-width: 100% !important;
padding: 20px;
}
h2, h3 {
margin: 0;
}
.detail-top {
padding: 30px 40px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
margin-bottom: 20px;
.detail-top__content {
padding-top: 32px;
}
.detail-top__header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 32px;
border-bottom: 1px solid #EEEEEE;
.header-right {
.header-right__item {
width: 120px;
margin-right: 8px;
text-align: center;
}
div {
text-align: center;
&:last-child {
margin-right: 0;
}
span {
display: block;
margin-bottom: 10px;
color: #888888;
}
}
.el-button {
height: 28px;
margin-left: 8px;
border-radius: 14px;
font-size: 12px;
padding: 7px 15px;
}
}
.header-left, .header-right {
display: flex;
align-items: center;
}
.header-left {
img {
width: 64px;
height: 64px;
margin-right: 16px;
}
h2 {
margin-bottom: 6px;
color: #222222;
font-size: 16px;
}
p {
color: #2EA222;
font-size: 14px;
}
}
}
}
#lineChart {
width: 100%;
height: 336px;
}
.table-tags {
.el-tag {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
}
::v-deep .avatar {
width: 40px;
height: 40px;
vertical-align: middle;
margin-right: 8px;
object-fit: fill;
}
}
</style>

View File

@@ -0,0 +1,401 @@
<template>
<ai-list class="AppPetitionManage" isTabs>
<template slot="content">
<ai-search-bar class="search-bar">
<template slot="left">
<!-- <ai-select-->
<!-- v-model="search.owner"-->
<!-- filterable-->
<!-- @change="search.current = 1, getList()"-->
<!-- placeholder="群主"-->
<!-- :selectList="userList">-->
<!-- </ai-select>-->
<ai-select
v-model="search.tagId"
@change="search.current = 1, getList()"
placeholder="选择标签"
:selectList="subTags">
</ai-select>
</template>
<template slot="right">
<el-input
v-model="search.name"
class="search-input"
size="small"
v-throttle="() => {search.current = 1, getList()}"
placeholder="请输入群主/群名称"
clearable
@change="getList"
@clear="search.current = 1, search.name = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-search-bar>
<template slot="left">
<el-button type="primary" icon="iconfont iconAdd" @click="isShow = true" :disabled="!ids.length">批量打群标签
</el-button>
</template>
<template slot="left">
<el-button type="primary" icon="iconfont iconDelete" @click="onConfirm(1)" :disabled="!ids.length">批量移除群标签
</el-button>
</template>
<template slot="right">
<el-button type="primary" icon="iconfont iconResetting" @click="update">更新数据</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
ref="aitableex"
:current.sync="search.current"
@selection-change="handleSelectionChange"
:size.sync="search.size"
v-loading="isLoading"
@getList="getList">
<el-table-column slot="avatar" label="" width="80" align="center">
<template slot-scope="{ row }">
<div class="avatar" style="text-align: right;justify-content: end;">
<img :src="row.avatar">
</div>
</template>
</el-table-column>
<el-table-column slot="userinfo" label="群名称" show-overflow-tooltip width="300px" align="left">
<template slot-scope="{ row }">
<div class="userinfo">
<div class="userinfo-right ellipsis">
<div class="userinfo-right__top">
<h3>{{ row.name || "群聊" }}</h3>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column slot="tags" label="群标签" align="left">
<template slot-scope="{ row }">
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in row.tagList" size="medium" :key="index">{{ item.tagName }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" width="100" fixed="right" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
width="800px"
title="批量打标签"
@close="chooseTags = []"
@onConfirm="onConfirm">
<div class="tags">
<div class="tag-item" v-for="(item, index) in tags" :key="index">
<h2>{{ item.name }}</h2>
<div class="tag-item__right">
<el-button
:type="chooseTags.indexOf(item.id) === -1 ? '' : 'primary'"
v-for="(item, index) in item.tagList"
@click="choose(item.id)"
:key="index">
{{ item.name }}
</el-button>
</div>
</div>
</div>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'List',
props: {
instance: Function, dict: Object
},
data() {
return {
search: {
current: 1,
size: 10,
name: '',
// tagId: '',
owner: ''
},
isLoading: false,
isShow: false,
ids: [],
total: 10,
chooseTags: [],
tags: [],
colConfigs: [
{type: 'selection'},
// {slot: 'avatar'},
{slot: 'userinfo'},
{prop: 'ownerName', label: '群主', align: 'center'},
{slot: 'tags'},
{
prop: 'personCount', label: '群人数', align: 'center'
},
{prop: 'increase', label: '当日入群人数', align: 'center'},
{prop: 'decrease', label: '当日退群人数', align: 'center'},
{
prop: 'createTime', label: '创建时间', align: 'left'
},
{slot: 'options', label: '操作', align: 'center'}],
tableData: [],
subTags: [],
userList: []
}
},
computed: {
...mapState(['user'])
},
mounted() {
this.getTags()
this.getSubTags()
// this.getWxUserList()
this.getList()
this.isLoading = true
},
methods: {
getWxUserList () {
this.instance.post(`/app/wxcp/wxuser/listByDepartId`, {
}).then(res => {
if (res.code == 0) {
this.userList = res.data.map(item => {
item.dictName = item.name
item.dictValue = item.id
return item
})
}
})
},
update() {
this.instance.post(`/app/wxcp/wxgroup/sync`).then(res => {
if (res.code == 0) {
this.$message.success('更新成功')
this.getList()
}
})
},
getSubTags() {
this.instance.post(`/app/wxcp/wxgroupchattag/listAllTag`).then(res => {
if (res.code == 0) {
this.subTags = res.data?.map(item => {
return {
dictName: item.name,
dictValue: item.id
}
})
}
})
},
getList() {
this.instance.post(`/app/wxcp/wxgroup/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
this.isLoading = false
}).catch(() => {
this.isLoading = false
})
},
choose(id) {
const index = this.chooseTags.indexOf(id)
if (index === -1) {
this.chooseTags.push(id)
} else {
this.chooseTags.splice(index, 1)
}
},
onConfirm(type = 0) {
if (type == 0 && !this.chooseTags.length) {
return this.$message.error('请选择标签')
}
this.instance.post(`/app/wxcp/wxgroupchattag/markTagForWeb`, {
tagIds: this.chooseTags, groupIds: this.ids.map(v => v.id), type,
}).then(res => {
if (res.code == 0) {
type == 0 ? (this.isShow = false) : false
this.$message.success(type == 0 ? "添加成功" : "删除成功")
this.getList()
}
})
},
getTags() {
this.instance.post(`/app/wxcp/wxgroupchattag/listAll?size=999`).then(res => {
if (res.code == 0) {
this.tags = res.data.records
}
})
},
onAdd() {
this.$emit('change', {
type: 'add'
})
},
handleSelectionChange(e) {
this.ids = e
},
removeAll() {
this.remove(this.ids.map(v => v.id).join(','))
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/apppetition/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toDetail(id) {
this.$emit('change', {
type: 'detail', params: {
id: id
}
})
},
onAdd() {
this.$emit('change', {
type: 'add'
})
}
}
}
</script>
<style lang="scss" scoped>
.table-tags {
.el-tag {
margin-right: 8px;
margin-bottom: 8px;
border: 1px solid #D0D4DC;
background: #F3F4F7;
border-radius: 4px;
font-size: 13px;
color: #222222;
&:last-child {
margin-right: 0;
}
}
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tags {
.tag-item {
display: flex;
align-items: center;
padding-bottom: 30px;
padding-top: 30px;
border-bottom: 1px solid #EEEEEE;
&:first-child {
padding-top: 0;
}
.el-tag {
margin-right: 8px;
color: #222222;
}
h2 {
width: 88px;
margin-right: 40px;
text-align: right;
color: #888888;
font-size: 14px;
}
}
}
.avatar {
text-align: right;
img {
position: relative;
top: 4px;
width: 40px;
height: 40px;
border-radius: 2px;
border: 1px solid #CCCCCC;
}
}
.userinfo {
display: flex;
align-items: center;
line-height: 1;
.userinfo-right__top {
display: flex;
align-items: center;
cursor: pointer;
white-space: nowrap;
}
.userinfo-right__bottom {
text-align: left;
}
i {
cursor: pointer;
font-style: normal;
color: #888888;
font-size: 14px;
}
h3 {
margin-top: 0;
margin-bottom: 0;
margin-right: 8px;
color: #222222;
font-weight: normal;
font-size: 14px;
}
span {
color: #2EA222;
white-space: nowrap;
}
}
</style>

View File

@@ -0,0 +1,207 @@
<template>
<ai-list class="statistics" isTabs style="width: 100%">
<template slot="content" v-loading="loading">
<div class="statistics-top">
<div class="statistics-top__item">
<span>群聊总数</span>
<h2 style="color: #2266FF;">{{ groupSum }}</h2>
</div>
<div class="statistics-top__item">
<span>群成员总人数</span>
<h2>{{ info.total }}</h2>
</div>
<div class="statistics-top__item">
<span>今日入群人数</span>
<h2 style="color: #22AA99;">{{ info.increase }}</h2>
</div>
<div class="statistics-top__item">
<span>今日退群人数</span>
<h2 style="color: #F8B425">{{ info.decrease }}</h2>
</div>
</div>
<ai-card title="趋势图">
<template #content>
<div class="chart" style="height: 340px; width: 100%;"></div>
</template>
</ai-card>
</template>
</ai-list>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'Statistics',
props: {
instance: Function,
dict: Object
},
data() {
return {
chart: null,
info: {},
chartWidth: '',
groupSum: "",
loading: false
}
},
mounted() {
this.loading = true
this.$nextTick(() => {
this.chart = echarts.init(document.querySelector('.chart'))
window.addEventListener('resize', this.onResize)
this.getInfo()
})
},
destroyed() {
window.removeEventListener('resize', this.onResize)
},
methods: {
onResize() {
this.chart.resize()
},
getInfo() {
this.instance.post(`/app/wxcp/wxgroup/groupStatistic`).then(res => {
if (res.code == 0) {
this.info = res.data.today
this.groupSum = res.data.groupSum
this.initChart(res.data.list)
this.loading = false
} else {
this.loading = false
}
})
},
initChart(data) {
const x = Object.keys(data)
const y = Object.values(data)
let option = {
tooltip: {
trigger: 'axis'
},
legend: {
type: "plain"
},
grid: {
left: '20px',
right: '38px',
bottom: '14px',
top: '30px',
containLabel: true
},
color: ['#2266FF', '#22AA99', '#F8B425'],
xAxis: {
type: 'category',
axisLabel: {
align: 'center',
padding: [2, 0, 0, 0],
interval: 0,
fontSize: 14,
color: '#666666'
},
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#E1E5EF'
}
},
data: x
},
yAxis: {
axisTick: {
length: 0,
show: false
},
splitLine: {
show: true,
lineStyle: {
color: ['#E1E5EF'],
width: 1,
type: 'solid'
}
},
nameTextStyle: {
color: '#666666',
align: 'left'
},
axisLine: {
show: false
},
axisLabel: {
color: '#666666'
},
type: 'value'
},
series: [
{
name: '群成员总数',
type: 'line',
data: y.map(v => v.total)
},
{
name: '新增入群人数',
type: 'line',
data: y.map(v => v.increase)
},
{
name: '退群人数',
type: 'line',
data: y.map(v => v.decrease)
}
]
}
this.chart.setOption(option)
}
}
}
</script>
<style scoped lang="scss">
.statistics {
::v-deep .ai-list__content--right-wrapper {
background: transparent !important;
box-shadow: none !important;
padding: 12px 0 12px !important;
}
.statistics-top {
display: flex;
align-items: center;
margin-bottom: 20px;
& > div {
flex: 1;
height: 96px;
line-height: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
h3 {
font-size: 24px;
}
span {
display: block;
margin-bottom: 16px;
color: #888888;
font-size: 16px;
}
}
}
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<ai-list class="AppPetitionManage" isTabs>
<template slot="content">
<ai-search-bar>
<template slot="left">
<el-button type="primary" icon="iconfont iconAdd" @click="id = '', form.name = '', form.tagList = [], isShow = true">添加群标签组</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
ref="aitableex"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="tags" label="群标签" align="left">
<template slot-scope="{ row }">
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in row.tagList" size="small" :key="index">{{ item.name }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" width="180" fixed="right" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<!-- <el-button type="text" @click="edit(row)">添加标签</el-button> -->
<el-button type="text" @click="edit(row)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
width="800px"
title="添加群标签组"
@onConfirm="onConfirm"
@onCancel="onCancel">
<el-form class="ai-form" ref="form" label-width="120px" :model="form">
<el-form-item style="width: 100%" label="群标签组名称" prop="name" :rules="[{ required: true, message: '请输入群标签组名称', trigger: 'blur' }]">
<el-input size="small" v-model.trim="form.name" :maxlength="15" show-word-limit placeholder="请输入群标签组名称"></el-input>
</el-form-item>
<el-form-item style="width: 100%" label="群标签" prop="tagList" :rules="[{ required: true, message: '请输入群标签组名称', trigger: 'blur' }]">
<div class="table-tags">
<el-tag type="info" color="#fff" closable @close="onClose(index)" v-for="(item, index) in form.tagList" :key="index">{{ item.name }}</el-tag>
<el-input
v-if="inputVisible"
v-model.trim="tagName"
size="small"
style="width: 100px;"
maxlength="30"
clearable
@keyup.enter.native="handleInputConfirm"
@blur="handleInputConfirm">
</el-input>
<el-button v-else size="small" icon="iconfont iconAdd" @click="inputVisible = true">添加</el-button>
</div>
</el-form-item>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
export default {
name: 'Tags',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10
},
tagName: '',
form: {
name: '',
tagList: []
},
inputVisible: false,
isShow: false,
ids: [],
total: 10,
colConfigs: [
{ prop: 'name', label: '群标签组名称', align: 'left', width: 160 },
{ slot: 'tags' },
{ slot: 'options', label: '操作', align: 'center' }
],
id: '',
tableData: [],
}
},
mounted () {
this.getList()
},
methods: {
getList() {
this.instance.post(`/app/wxcp/wxgroupchattag/listAll`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
onCancel () {
this.form.name = ''
this.form.tagList = []
this.id = ''
this.inputVisible = ''
this.isShow = false
},
edit (e) {
this.id = e.id
this.form = JSON.parse(JSON.stringify(e))
this.$nextTick(() => {
this.isShow = true
})
},
handleInputConfirm () {
if (!this.tagName) {
return
}
this.form.tagList.push({
name: this.tagName
})
this.$nextTick(() => {
this.tagName = ''
this.inputVisible = false
})
},
onClose (index) {
this.form.tagList.splice(index, 1)
},
onConfirm() {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(this.id ? '/app/wxcp/wxgroupchattag/update' : `/app/wxcp/wxgroupchattag/add`, {
name: this.form.name,
id: this.id || null,
tagList: this.form.tagList
}).then(res => {
if (res.code === 0) {
this.getList()
this.isShow = false
this.$message.success(`${this.id}` ? '编辑成功' : '提交成功')
}
})
}
})
},
remove (id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxgroupchattag/delete?id=${id}&type=0`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
}
}
}
</script>
<style lang="scss" scoped>
.table-tags {
.el-tag {
margin-right: 8px;
border: 1px solid #D0D4DC;
background: #F3F4F7;
border-radius: 4px;
font-size: 13px;
color: #222222;
&:last-child {
margin-right: 0;
}
}
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<ai-list v-if="!isShowDetail">
<template slot="title">
<ai-title title="居民信息管理" :isShowBottomBorder="false"></ai-title>
</template>
<template slot="tabs">
<el-tabs v-model="currIndex">
<el-tab-pane v-for="(tab,i) in tabs" :key="i" :label="tab.label">
<component :ref="String(i)" v-if="currIndex == i" :is="tab.comp" @change="onChange" lazy :instance="instance" :dict="dict" :permissions="permissions"/>
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<Detail v-else-if="componentName === 'Detail'" :params="params" :instance="instance" :dict="dict" :permissions="permissions" @change="onChange"></Detail>
</template>
<script>
import List from './components/List.vue'
import Statistics from './components/Statistics'
import Tags from './components/Tags'
import Detail from './components/Detail'
import { mapState } from 'vuex'
export default {
name: 'AppResidentManage',
label: '居民信息管理',
components: {
List,
Tags,
Detail,
Statistics
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
tabs () {
const tabList = [
{label: '居民列表', name: 'List', comp: List, permission: ''},
{label: '居民统计', name: 'Statistics', comp: Statistics, permission: ''},
{label: '居民标签', name: 'Tags', comp: Tags, permission: ''}
].filter(item => {
return true
})
return tabList
}
},
data () {
return {
activeName: 'JoinEvent',
currIndex: '0',
componentName: '',
params: {},
isShowDetail: false
}
},
created () {
if (this.$route.query.id) {
this.componentName = this.$route.query?.type
this.params = {id: this.$route.query?.id}
this.isShowDetail = true
}
},
methods: {
onChange (data) {
if (data.type === 'list') {
this.componentName = 'List'
this.isShowDetail = false
this.params = data.params
}
if (data.type === 'detail') {
this.componentName = 'Detail'
this.isShowDetail = true
this.params = data.params
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,465 @@
<template>
<ai-detail class="AppResidentManage">
<template slot="title">
<ai-title title="居民信息详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)"></ai-title>
</template>
<template slot="content">
<div class="detail-top">
<div class="detail-top__header">
<div class="header-left">
<img :src="info.avatar">
<div class="header-left__right">
<h2>{{ info.name }}</h2>
<p>{{ info.corpFullName }}</p>
</div>
</div>
<div class="header-right">
<div class="header-right__item">
<span>家庭积分</span>
<h3>{{ info.residentInfo ? (info.residentInfo.resident.familyIntegral || 0) : '0' }}</h3>
</div>
<div class="header-right__item">
<span>个人积分</span>
<h3>{{ info.residentInfo ? (info.residentInfo.resident.personalIntegral || 0) : '0' }}</h3>
</div>
<el-button type="primary" size="mini" v-if="info.realName" @click="toDetail">查看居民档案</el-button>
</div>
</div>
<div class="detail-top__content">
<ai-wrapper
label-width="80px">
<ai-info-item label="添加渠道" :value="dict.getLabel('wxCustomerAddWay', info.addWay)"></ai-info-item>
<ai-info-item label="添加时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="真实姓名" :value="info.realName"></ai-info-item>
<ai-info-item label="手机号码"
:value="info.residentInfo ? info.residentInfo.resident.phone : '-'"></ai-info-item>
<ai-info-item label="标签" isLine>
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in info.tags" size="small" :key="index">{{ item.tagName }}
</el-tag>
</div>
</ai-info-item>
</ai-wrapper>
</div>
</div>
<div class="detail-bottom">
<div class="detail-bottom__left">
<h2>居民动态</h2>
<div class="step-list">
<div class="step-item" v-for="(item, index) in logList" :key="index">
<i class="step-item__left"></i>
<div class="step-item__right">
<h2>{{ dict.getLabel('wxCustomerLogType', item.type) }}</h2>
<p>{{ item.createTime }}</p>
<div class="step-item__right--bottom" v-html="item.content"></div>
</div>
</div>
<div slot="empty" class="no-data" v-if="!logList.length" style="height:160px;"></div>
</div>
</div>
<div class="detail-bottom__right">
<div class="detail-table">
<h2>所属员工</h2>
<ai-table
class="detail-table__table"
:border="true"
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:stripe="false"
:current.sync="search.current"
:size.sync="search.size"
@getList="getDynamicInfo">
<el-table-column slot="userinfo" label="所属员工" width="268px" align="center">
<template slot-scope="{ row }">
<div class="userinfo">
<img :src="row.avatar">
<h3>{{ row.userName }}</h3>
</div>
</template>
</el-table-column>
</ai-table>
</div>
<div class="detail-table">
<h2>所在群聊</h2>
<ai-table
class="detail-table__table"
:border="true"
:tableData="grooupTableData"
:col-configs="groupColConfigs"
:total="grooupTableData.length"
:stripe="false"
:current.sync="groupSearch.current"
:size.sync="groupSearch.size"
@getList="getInfo">
</ai-table>
</div>
</div>
</div>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
data() {
return {
isShow: false,
tags: ['青年', '郑村村', '郑村村', '青年', '郑村村', '郑村村'],
info: {},
search: {
current: 1,
size: 10
},
groupSearch: {
current: 1,
size: 10
},
groupTotal: 0,
form: {
explain: '',
imgs: []
},
total: 0,
logList: [],
colConfigs: [
{slot: 'userinfo'},
{prop: 'addWay', label: '客户来源', align: 'center', formart: v => this.dict.getLabel('wxCustomerAddWay', v)},
{prop: 'createTime', label: '添加时间', align: 'center'}
],
groupColConfigs: [
{prop: 'name', label: '群名称', align: 'left'},
{prop: 'ownerName', label: '群主', align: 'center'},
{prop: 'personCount', label: '群人数', align: 'left'},
{prop: 'joinTime', label: '居民入群时间', align: 'left'}
],
grooupTableData: [],
tableData: []
}
},
created() {
if (this.params && this.params.id) {
this.getInfo()
this.dict.load(['wxCustomerLogType', 'wxCustomerAddWay']).then(() => {
this.getWxcustomerlog()
this.getDynamicInfo()
})
}
},
methods: {
getInfo(id) {
this.instance.post(`/app/wxcp/wxcustomer/queryCustomerById?id=${this.params.id}`).then(res => {
if (res.code === 0) {
this.info = res.data
this.grooupTableData = res.data.groupInfos
}
})
},
getDynamicInfo(id) {
this.instance.post(`/app/wxcp/wxcustomer/getDynamicInfoById?customerId=${this.params.id}`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
getWxcustomerlog() {
this.instance.post(`/app/wxcp/wxcustomerlog/listAll?customerId=${this.params.id}`).then(res => {
if (res.code === 0) {
this.logList = res.data
}
})
},
toDetail() {
this.$router.push({
name: '67',
query: {
id: this.info.residentInfo.resident.id,
type: '0'
}
})
},
onClose() {
this.form.explain = ''
},
confirm() {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(`/app/appgirdmemberevent/rejectEvent`, {
eventId: this.params.id,
explain: this.form.explain
}).then(res => {
if (res.code == 0) {
this.$message.success('驳回成功')
setTimeout(() => {
this.cancel()
}, 600)
}
})
}
})
},
cancel(isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: isRefresh ? true : false
})
}
}
}
</script>
<style scoped lang="scss">
.AppResidentManage {
::v-deep .ai-detail__content--wrapper {
max-width: 100% !important;
padding: 20px;
}
h2, h3 {
margin: 0;
}
.userinfo {
display: flex;
align-items: center;
padding-left: 16px;
img {
width: 40px;
height: 40px;
margin-right: 8px;
border-radius: 2px;
}
h3 {
font-weight: normal;
color: #222222;
}
}
.detail-bottom {
display: flex;
margin-top: 20px;
.step-list {
padding: 36px 40px 40px;
.step-item {
display: flex;
position: relative;
padding-bottom: 36px;
&:last-child {
padding-bottom: 0;
&::after {
display: none;
}
}
&::after {
position: absolute;
left: 3px;
top: 14px;
width: 2px;
height: 100%;
background: #EEEEEE;
content: ' ';
}
.step-item__right--bottom {
color: #555;
font-size: 14px;
b {
font-weight: normal;
}
::v-deep i {
font-style: normal;
color: #2266FF;
}
}
.step-item__right {
flex: 1;
h2 {
line-height: 22px;
color: #222222;
font-size: 16px;
}
p {
line-height: 22px;
margin: 2px 0 4px;
color: #888888;
font-size: 14px;
}
}
.step-item__left {
position: relative;
top: 4px;
margin-right: 20px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #2266FF;
}
}
}
.detail-bottom__left {
flex-shrink: 1;
width: 360px;
height: fit-content;
margin-right: 20px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
& > h2 {
height: 56px;
line-height: 56px;
padding: 0 16px;
color: #222222;
font-size: 16px;
font-weight: 700;
border: 1px solid #EEEEEE;
}
}
.detail-bottom__right {
flex: 1;
& > div {
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
&:last-child {
margin-top: 20px;
}
& > h2 {
height: 56px;
line-height: 56px;
padding: 0 16px;
color: #222222;
font-size: 16px;
font-weight: 700;
border: 1px solid #EEEEEE;
}
}
.detail-table__table {
padding: 16px;
}
}
}
.detail-top {
padding: 30px 40px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
.detail-top__content {
padding-top: 32px;
}
.detail-top__header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 32px;
border-bottom: 1px solid #EEEEEE;
.header-right {
.header-right__item {
width: 120px;
margin-right: 8px;
text-align: center;
}
div {
text-align: center;
&:last-child {
margin-right: 0;
}
span {
display: block;
margin-bottom: 10px;
color: #888888;
}
}
.el-button {
height: 28px;
margin-left: 8px;
border-radius: 14px;
font-size: 12px;
padding: 7px 15px;
}
}
.header-left, .header-right {
display: flex;
align-items: center;
}
.header-left {
img {
width: 64px;
height: 64px;
margin-right: 16px;
}
h2 {
margin-bottom: 6px;
color: #222222;
font-size: 16px;
}
p {
color: #2EA222;
font-size: 14px;
}
}
}
}
.table-tags {
.el-tag {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,457 @@
<template>
<ai-list class="AppPetitionManage" isTabs>
<template slot="content">
<ai-search-bar class="search-bar">
<template slot="left">
<ai-select
v-model="search.isRealName"
@change="search.current = 1, getList()"
placeholder="是否实名"
:selectList="realStatus">
</ai-select>
<ai-select
v-model="search.wxUserId"
filterable
@change="search.current = 1, getList()"
placeholder="所属员工"
:selectList="userList">
</ai-select>
<ai-select
v-model="search.tagId"
@change="search.current = 1, getList()"
placeholder="选择标签"
:selectList="subTags">
</ai-select>
</template>
<template slot="right">
<el-input
v-model="search.name"
class="search-input"
size="small"
v-throttle="() => {search.current = 1, getList()}"
placeholder="请输入备注、昵称、姓名"
clearable
@change="getList"
@clear="search.current = 1, search.name = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-search-bar>
<template slot="left">
<el-button type="primary" icon="iconfont iconAdd" @click="isRemove = false, isShow = true" :disabled="!ids.length">批量打标签</el-button>
<el-button type="primary" icon="iconfont iconDelete" @click="isRemove = true, isShow = true" :disabled="!ids.length">批量移除标签</el-button>
</template>
<template slot="right">
<el-button type="primary" icon="iconfont iconResetting" @click="update" :loading="btnLoading">更新数据</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
ref="aitableex"
:current.sync="search.current"
@selection-change="handleSelectionChange"
:size.sync="search.size"
v-loading="isLoading"
@getList="getList">
<el-table-column slot="avatar" label="" width="80" align="center">
<template slot-scope="{ row }">
<div class="avatar" style="text-align: right; justify-content: end;">
<img :src="row.avatar">
</div>
</template>
</el-table-column>
<el-table-column slot="userinfo" label="备注名" width="280px" align="left">
<template slot-scope="{ row }">
<div class="userinfo">
<div class="userinfo-right ellipsis">
<el-tooltip effect="dark" :content="row.corpFullName ? (row.remark || row.name) + '@' + row.corpFullName : (row.remark || row.name) + ''" placement="top">
<div class="userinfo-right__top">
<h3>{{ row.remark || row.name }}</h3>
<span class="ellipsis">{{ row.corpFullName ? '@' + row.corpFullName : '' }}</span>
</div>
</el-tooltip>
<div class="userinfo-right__bottom">
<el-tooltip effect="dark" :content="row.name" placement="top">
<i>昵称{{ row.name }}</i>
</el-tooltip>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column slot="tags" label="标签" align="left">
<template slot-scope="{ row }">
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in row.tags" size="medium" :key="index">{{ item.tagName }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" width="100" fixed="right" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
width="800px"
:title="isRemove ? '批量移除标签' : '批量打标签'"
@close="onClose"
@onConfirm="onConfirm">
<div class="tags">
<div class="tag-item" v-for="(item, index) in tags" :key="index">
<h2>{{ item.name }}</h2>
<div class="tag-item__right">
<el-button
:type="chooseTags.indexOf(item.id) === -1 ? '' : 'primary'"
v-for="(item, index) in item.tagList"
@click="choose(item.id)"
:key="index">
{{ item.name }}
</el-button>
</div>
</div>
</div>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10,
name: '',
tagId: '',
wxUserId: '',
isRealName: ''
},
isRemove: false,
btnLoading: false,
isLoading: false,
isShow: false,
ids: [],
total: 10,
chooseTags: [],
tags: [],
colConfigs: [
{ type: 'selection' },
{ slot: 'avatar' },
{ slot: 'userinfo' },
{ prop: 'realName', label: '真实姓名', align: 'center' },
{
prop: 'identityNumber', label: '是否实名', align: 'center',
render: (h, params) => {
return h('span', {
}, params.row.realName ? '是' : '否')
}
},
{ prop: 'wxUserNames', label: '所属员工', align: 'center' },
{ slot: 'tags' },
{ prop: 'createTime', label: '添加时间', align: 'left' },
{ prop: 'addWay', label: '添加渠道', align: 'center', formart: v => this.dict.getLabel('wxCustomerAddWay', v) },
{ slot: 'options', label: '操作', align: 'center' }
],
tableData: [],
realStatus: [{
dictName: '是',
dictValue: '1'
}, {
dictName: '否',
dictValue : '0'
}],
subTags: [],
userList: []
}
},
computed: {
...mapState(['user'])
},
mounted () {
this.getTags()
this.getSubTags()
this.getWxUserList()
this.isLoading = true
this.dict.load(['wxCustomerAddWay']).then(() => {
this.getList()
})
},
methods: {
getWxUserList () {
this.instance.post(`/app/wxcp/wxuser/listByDepartId`, {
}).then(res => {
if (res.code == 0) {
this.userList = res.data.map(item => {
item.dictName = item.name
item.dictValue = item.id
return item
})
}
})
},
update () {
this.btnLoading = true
this.instance.post(`/app/wxcp/wxusercustomer/sync`, null, {
timeout: 1000000
}).then(res => {
if (res.code == 0) {
this.$message.success('更新成功')
this.getList()
}
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
},
onClose () {
this.chooseTags = []
},
getSubTags () {
this.instance.post(`/app/wxcp/wxcorptag/listAllTags`).then(res => {
if (res.code == 0) {
this.subTags = res.data.map(item => {
return {
dictName: item.name,
dictValue: item.id
}
})
}
})
},
getList() {
this.instance.post(`/app/wxcp/wxcustomer/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
this.isLoading = false
} else {
this.isLoading = false
}
}).catch(() => {
this.isLoading = false
})
},
choose (id) {
const index = this.chooseTags.indexOf(id)
if (index === -1) {
this.chooseTags.push(id)
} else {
this.chooseTags.splice(index, 1)
}
},
onConfirm () {
if (!this.chooseTags.length) {
return this.$message.error('请选择标签')
}
if (this.isRemove) {
this.instance.post(`/app/wxcp/wxcorptag/markTagForWeb`, {
delTagIds: this.chooseTags,
customerIds: this.ids.map(v => v.id)
}).then(res => {
if (res.code == 0) {
this.isShow = false
this.$message.success('移除成功')
this.getList()
}
})
} else {
this.instance.post(`/app/wxcp/wxcorptag/markTagForWeb`, {
addTagIds: this.chooseTags,
customerIds: this.ids.map(v => v.id)
}).then(res => {
if (res.code == 0) {
this.isShow = false
this.getList()
this.$message.success('添加标签成功')
}
})
}
},
getTags () {
this.instance.post(`/app/wxcp/wxcorptag/listAll?size=100`).then(res => {
if (res.code == 0) {
this.tags = res.data.records
}
})
},
onAdd () {
this.$emit('change', {
type: 'add'
})
},
handleSelectionChange(e) {
this.ids = e
},
removeAll () {
this.remove(this.ids.map(v => v.id).join(','))
},
remove (id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/apppetition/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toDetail (id) {
this.$emit('change', {
type: 'detail',
params: {
id: id
}
})
},
onAdd () {
this.$emit('change', {
type: 'add'
})
}
}
}
</script>
<style lang="scss" scoped>
.table-tags {
.el-tag {
margin-right: 8px;
margin-bottom: 8px;
border: 1px solid #D0D4DC;
background: #F3F4F7;
border-radius: 4px;
font-size: 13px;
color: #222222;
&:last-child {
margin-right: 0;
}
}
}
.ellipsis {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
.tags {
.tag-item {
display: flex;
align-items: center;
padding-bottom: 30px;
padding-top: 30px;
border-bottom: 1px solid #EEEEEE;
&:first-child {
padding-top: 0;
}
.el-tag {
margin-right: 8px;
color: #222222;
}
h2 {
width: 88px;
margin-right: 40px;
text-align: right;
color: #888888;
font-size: 14px;
}
}
}
.avatar {
text-align: right;
img {
position: relative;
top: 4px;
width: 40px;
height: 40px;
border-radius: 2px;
border: 1px solid #CCCCCC;
}
}
.userinfo {
display: flex;
align-items: center;
line-height: 1;
.userinfo-right__top {
display: flex;
align-items: center;
margin-bottom: 10px;
cursor: pointer;
white-space: nowrap;
}
.userinfo-right__bottom {
text-align: left;
}
i {
cursor: pointer;
font-style: normal;
color: #888888;
font-size: 14px;
}
h3 {
margin-top: 0;
margin-bottom: 0;
margin-right: 8px;
color: #222222;
font-weight: normal;
font-size: 14px;
}
span {
color: #3C7FC8;
white-space: nowrap;
}
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<ai-list class="statistics" isTabs style="width: 100%">
<template slot="content" v-loading="loading">
<div class="statistics-top">
<div class="statistics-top__item">
<span>居民总数</span>
<h2 style="color: #2266FF;">{{ info.total }}</h2>
</div>
<div class="statistics-top__item">
<span>今日新增</span>
<h2 style="color: #22AA99;">{{ info.increase }}</h2>
</div>
<div class="statistics-top__item">
<span>今日流失</span>
<h2 style="color: #F8B425">{{ info.decrease }}</h2>
</div>
</div>
<ai-card title="趋势图">
<template #content>
<div class="chart" style="height: 340px; width: 100%;"></div>
<ai-empty v-if="false" style="height: 148px;"></ai-empty>
</template>
</ai-card>
</template>
</ai-list>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'Statistics',
props: {
instance: Function,
dict: Object
},
data () {
return {
chart: null,
info: {},
chartWidth: '',
loading: false
}
},
mounted () {
this.loading = true
this.$nextTick(() => {
this.chart = echarts.init(document.querySelector('.chart'))
window.addEventListener('resize', this.onResize)
this.getInfo()
})
},
destroyed () {
window.removeEventListener('resize', this.onResize)
},
methods: {
onResize () {
this.chart.resize()
},
getInfo () {
this.instance.post(`/app/wxcp/wxcustomerlog/customerStatistic`).then(res => {
if (res.code == 0) {
this.info = res.data.today
this.initChart(res.data.list)
this.loading = false
} else {
this.loading = false
}
})
},
initChart (data) {
const x = Object.keys(data)
const y = Object.values(data)
let option = {
tooltip: {
trigger: 'axis'
},
legend: {
type: "plain"
},
grid: {
left: '20px',
right: '38px',
bottom: '14px',
top: '30px',
containLabel: true
},
color: ['#2266FF', '#22AA99', '#F8B425'],
xAxis: {
type: 'category',
axisLabel: {
align: 'center',
padding: [2, 0, 0, 0],
interval: 0,
fontSize: 14,
color: '#666666'
},
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#E1E5EF'
}
},
data: x
},
yAxis: {
axisTick: {
length: 0,
show: false
},
splitLine: {
show: true,
lineStyle:{
color: ['#E1E5EF'],
width: 1,
type: 'solid'
}
},
nameTextStyle: {
color: '#666666',
align: 'left'
},
axisLine: {
show: false
},
axisLabel: {
color: '#666666'
},
type: 'value'
},
series: [
{
name: '居民总数',
type: 'line',
data: y.map(v => v.total)
},
{
name: '新增居民数',
type: 'line',
data: y.map(v => v.increase)
},
{
name: '流失居民数',
type: 'line',
data: y.map(v => v.decrease)
}
]
}
this.chart.setOption(option)
}
}
}
</script>
<style scoped lang="scss">
.statistics {
::v-deep .ai-list__content--right-wrapper {
background: transparent!important;
box-shadow: none!important;
padding: 12px 0 12px!important;
}
.statistics-top {
display: flex;
align-items: center;
margin-bottom: 20px;
& > div {
flex: 1;
height: 96px;
line-height: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
h3 {
font-size: 24px;
}
span {
display: block;
margin-bottom: 16px;
color: #888888;
font-size: 16px;
}
}
}
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<ai-list class="AppPetitionManage" isTabs>
<template slot="content">
<ai-search-bar>
<template slot="left">
<el-button type="primary" icon="iconfont iconAdd" @click="id = '', form.name = '', form.tagList = [], isShow = true">添加标签组</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
ref="aitableex"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="tags" label="标签" align="left">
<template slot-scope="{ row }">
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in row.tagList" size="small" :key="index">{{ item.name }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" width="180" fixed="right" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<!-- <el-button type="text" @click="edit(row)">添加标签</el-button> -->
<el-button type="text" @click="edit(row)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
width="800px"
title="添加标签组"
@onConfirm="onConfirm"
@onCancel="onCancel">
<el-form class="ai-form" ref="form" label-width="100px" :model="form">
<el-form-item style="width: 100%" label="标签组名称" prop="name" :rules="[{ required: true, message: '请输入标签组名称', trigger: 'blur' }]">
<el-input size="small" v-model.trim="form.name" :maxlength="15" show-word-limit placeholder="请输入标签组名称"></el-input>
</el-form-item>
<el-form-item style="width: 100%" label="标签" prop="tagList" :rules="[{ required: true, message: '请输入标签组名称', trigger: 'blur' }]">
<div class="table-tags">
<el-tag type="info" color="#fff" closable @close="onClose(index)" v-for="(item, index) in form.tagList" :key="index">{{ item.name }}</el-tag>
<el-input
v-if="inputVisible"
v-model.trim="tagName"
size="small"
style="width: 100px;"
maxlength="30"
clearable
@keyup.enter.native="handleInputConfirm"
@blur="handleInputConfirm">
</el-input>
<el-button v-else size="small" icon="iconfont iconAdd" @click="inputVisible = true">添加</el-button>
</div>
</el-form-item>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
export default {
name: 'Tags',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10
},
tagName: '',
form: {
name: '',
tagList: []
},
inputVisible: false,
isShow: false,
ids: [],
total: 10,
colConfigs: [
{ prop: 'name', label: '标签组', align: 'left', width: 160 },
{ slot: 'tags' },
{ slot: 'options', label: '操作', align: 'center' }
],
id: '',
tableData: [],
}
},
mounted () {
this.getList()
},
methods: {
getList() {
this.instance.post(`/app/wxcp/wxcorptag/listAll`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
onCancel () {
this.form.name = ''
this.form.tagList = []
this.id = ''
this.inputVisible = ''
this.isShow = false
},
edit (e) {
this.id = e.id
this.form = JSON.parse(JSON.stringify(e))
this.$nextTick(() => {
this.isShow = true
})
},
handleInputConfirm () {
if (!this.tagName) {
return
}
this.form.tagList.push({
name: this.tagName
})
this.$nextTick(() => {
this.tagName = ''
this.inputVisible = false
})
},
onClose (index) {
this.form.tagList.splice(index, 1)
},
onConfirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(this.id ? '/app/wxcp/wxcorptag/update' : `/app/wxcp/wxcorptag/add`, {
name: this.form.name,
id: this.id || '',
tagList: this.form.tagList
}).then(res => {
if (res.code === 0) {
this.getList()
this.isShow = false
this.$message.success(`${this.id}` ? '编辑成功' : '提交成功')
}
})
}
})
},
remove (id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxcorptag/delete?id=${id}&type=0`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
}
}
}
</script>
<style lang="scss" scoped>
.table-tags {
.el-tag {
margin-right: 8px;
border: 1px solid #D0D4DC;
background: #F3F4F7;
border-radius: 4px;
font-size: 13px;
color: #222222;
&:last-child {
margin-right: 0;
}
}
}
</style>

View File

@@ -0,0 +1,617 @@
<template>
<section style="height: 100%;">
<ai-list>
<template slot="title">
<ai-title title="话术库" :isShowBottomBorder="true"></ai-title>
</template>
<template #left>
<div class="left-tree">
<div class="tree-title">
<label>分组</label>
<el-button icon="iconfont iconAdd" style="height: 28px;line-height: 0;" @click="groupDialog = true">添加分组
</el-button>
</div>
<el-scrollbar style="height: calc(100% - 40px)">
<el-menu style="height: 100%;" default-active="0" active-text-color="#26f" text-color="#222"
mode="vertical">
<el-menu-item v-for="(item, index) in sortList" :key="index" class="menu-item" @click="currentMenu(item)"
:index="index.toString()">
<label class="item-title">{{ item.name }}</label>
<el-popover style="width: 30px" placement="left" trigger="click">
<div style="display: flex;flex-direction: column;align-items: center;">
<div style="cursor: pointer;" @click="editGroup(item, index)">编辑</div>
<div style="cursor: pointer;" @click="delteGroup(item, index)">删除</div>
</div>
<span class="iconfont iconMore" slot="reference"></span>
</el-popover>
</el-menu-item>
</el-menu>
</el-scrollbar>
</div>
</template>
<template slot="content">
<ai-search-bar>
<template slot="left">
<el-select v-model="search.contentType" placeholder="类型" size="small" clearable @change="getList()">
<el-option v-for="(item, index) in types" :key="index" :label="item.name" :value="item.value"></el-option>
</el-select>
</template>
<template slot="right">
<el-input v-model="search.title" class="search-input" size="small"
@keyup.enter.native=";(search.current = 1), getList()" placeholder="请输入标题或话术内容或创建人" clearable
@clear=";(search.current = 1), (search.title = ''), getList()" suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-search-bar>
<template slot="left">
<el-button icon="iconfont iconAdd" type="primary" @click="add">添加话术</el-button>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :col-configs="colConfigs" :stripe="true" :total="total" ref="aitableex"
style="margin-top: 8px;" :current.sync="search.current" :size.sync="search.size" @getList="getList">\
<el-table-column slot="content" label="话术内容" align="left" width="280">
<template slot-scope="{ row }">
<div class="table-left__wrapper">
<video :src="row.file && row.file.url" v-if="row.file && row.contentType == 'video'"
class="media"></video>
<img :src="row.file && row.file.url" v-if="row.file && row.contentType == 'image'" class="media"/>
<audio :src="row.file && row.file.url" v-if="row.file && row.contentType == 'voice'"
class="media"></audio>
<div class="table-left__wrapper--right">
<el-tooltip class="item" effect="dark" :content="row.content" placement="top">
<div class="table-left__wrapper--text" v-if="row.contentType == 'text'">{{ row.content }}</div>
</el-tooltip>
<div class="ellipsis" v-if="row.contentType !== 'text'">{{ row.file.name }}</div>
<div v-if="row.contentType !== 'text'">{{ row.file.size | size }}KB</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" width="160px" align="center" fixed="right">
<template slot-scope="{ row }">
<span class="table-btn" title="编辑" @click="editTrick(row)">编辑</span>
<span class="table-btn" title="删除" @click="deleteTrick(row)">删除</span>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog :visible.sync="groupDialog" @closed="sortForm = {},sortForm.type=1" width="800px" title="添加分组"
@onConfirm="onConfirm">
<el-form size="small" label-width="100px" ref="groupForm" :model="sortForm" :rules="sortRules">
<el-form-item label="分组名称" prop="name">
<el-input clearable v-model.trim="sortForm.name" placeholder="请输入..." :maxlength="10" show-word-limit/>
</el-form-item>
<!-- <el-form-item label="可见范围" prop="type">-->
<!-- <el-radio-group v-model="sortForm.type">-->
<!-- <el-radio label="0">个人话术</el-radio>-->
<!-- <el-radio label="1">公共话术</el-radio>-->
<!-- </el-radio-group>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="可用部门" prop="documentName">-->
<!-- <el-row type="flex">-->
<!-- <div class="input"></div>-->
<!-- <ai-person-select :instance="instance" url="/app/appvillagecadres/list" :isMultiple="true"-->
<!-- btnText="选择" dialogTitle="选择">-->
<!-- <template name="option" v-slot:option="{ item }">-->
<!-- <span class="iconfont iconProlife">{{ item.name }}</span>-->
<!-- <ai-id mode="show" :show-eyes="false" :value="item.idNumber"/>-->
<!-- </template>-->
<!-- </ai-person-select>-->
<!-- </el-row>-->
<!-- </el-form-item>-->
</el-form>
</ai-dialog>
<ai-dialog :visible.sync="trickDialog" @close="onClose" @onConfirm="trickConfirm" width="800px" title="添加话术">
<el-form size="small" label-width="100px" ref="trickFom" :model="trickForm" :rules="trickRules">
<!-- <el-form-item label="分组" prop="handleResult">-->
<!-- <ai-select-->
<!-- v-model="search.type"-->
<!-- @change="search.current = 1, getList()"-->
<!-- placeholder="类型"-->
<!-- :selectList="sortList"-->
<!-- ></ai-select>-->
<!-- </el-form-item>-->
<el-form-item label="标题" prop="title">
<el-input clearable placeholder="请输入..." v-model="trickForm.title" show-word-limit :maxlength="20"/>
</el-form-item>
<el-form-item label="话术类型" prop="contentType">
<el-radio-group v-model="trickForm.contentType" @change="onChange">
<el-radio :label="item.value" v-for="(item, index) in types" :key="index">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="文本内容" class="el-form-item__textarea" prop="content" v-if="trickForm.contentType == 'text'">
<span @click="insertNickname" class="el-form-item__btn" type="text">[插入用户昵称]</span>
<span @click="insertRemark" class="el-form-item__remark" type="text">[插入备注名]</span>
<el-input type="textarea" v-model="trickForm.content" placeholder="请输入..." :rows="5" show-word-limit
:maxlength="255"/>
</el-form-item>
<el-form-item :label="compLabel" prop="fileId" v-else>
<ai-uploader :instance="instance" v-model="fileList" :acceptType="acceptType"
:url="'/app/wxcp/upload/uploadFile?type=' + trickForm.contentType" isWechat :fileType="fileType"
:limit="1" @change="change"></ai-uploader>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'AppVerbalTrick',
label: '话术库',
props: {
instance: Function,
dict: Object,
permissions: Function,
},
data() {
return {
groupDialog: false,
trickDialog: false,
search: {
current: 1,
size: 10,
contentType: '',
},
sortForm: {
name: '',
type: '1',
},
trickForm: {
title: '',
contentType: 'text',
content: '',
fileId: '',
mediaId: '',
},
row: {},
current: {},
sortList: [],
total: 0,
tableData: [],
fileList: [],
}
},
computed: {
...mapState(['user']),
acceptType() {
return {
"image": ".jpg,.png,.jpeg",
"video": ".mp4"
}[this.trickForm.contentType]
},
compLabel() {
return this.types.find((e) => e.value == this.trickForm.contentType)?.name
},
fileType() {
return this.trickForm.contentType == 'image' ? 'img' : 'file'
},
types() {
return [
{name: '文本', value: 'text'},
{name: '图片', value: 'image'},
{name: '视频', value: 'video'},
// // {name: "附件", value: "file"},
// {name: "音频", value: "voice"},
]
},
trickRules() {
return {
title: [{required: true, message: '请输入标题', trigger: 'blur'}],
contentType: [{required: true, message: '请选择话术类型', trigger: 'change'}],
content: [{required: true, validator: (rule, value, callback)=>{
if(this.trickForm.contentType == 'text' && !value){
return callback("请输入文本内容")
}else {
callback()
}
}}],
fileId: [{required: true, validator: (rule, value, callback)=>{
if(this.trickForm.contentType != 'text' && !value){
return callback("请上传文件")
}else {
callback()
}
}}],
}
},
// message() {
// return (() => {
// if (this.trickForm.contentType == 'text') {
// return '请输入文本内容'
// } else if (this.trickForm.contentType == 'image') {
// return '请上传图片'
// } else if (this.trickForm.contentType == 'file') {
// return '请上传文件'
// } else if (this.trickForm.contentType == 'video') {
// return '请上传视频'
// } else if (this.trickForm.contentType == 'voice') {
// return '请上传音频'
// }
// })()
// },
sortRules() {
return {
name: [{required: true, message: '请输入分组名称', trigger: 'blur'}],
type: [{required: true, message: '请选择可见范围', trigger: 'change'}],
}
},
colConfigs() {
return [
{slot: 'type'},
{slot: 'content'},
{prop: 'title', label: '标题'},
{
prop: 'contentType',
label: '类型',
align: 'center',
render: (h, {row}) => {
return h('span', {}, this.types.find((e) => row.contentType == e.value)?.name)
},
},
{prop: 'userName', label: '创建人', align: 'center'},
{prop: 'createTime', label: '创建时间', align: 'center'},
{slot: 'options', label: '操作', align: 'center'},
]
},
},
created() {
this.getSortList()
},
filters: {
size(size) {
return (size / 1024).toFixed(1)
},
},
methods: {
insertNickname() {
this.trickForm.content += '[用户昵称]'
},
insertRemark() {
this.trickForm.content += '[备注名]'
},
onClose() {
this.trickForm.title = ''
this.trickForm.content = ''
this.trickForm.fileId = ''
this.trickForm.mediaId = ''
this.trickForm.createdAt = ''
this.trickForm.contentType = 'text'
this.fileList = []
},
onChange() {
this.trickForm.fileId = ''
this.trickForm.content = ''
this.trickForm.mediaId = ''
this.trickForm.createdAt = ''
this.fileList = []
},
add() {
if (this.sortList.length == 0) return this.$message.error('请先添加分组')
this.$nextTick(() => {
this.trickDialog = true
})
},
change(e) {
if(e.length){
this.trickForm.fileId = e[0].id
this.trickForm.mediaId = e[0].media.mediaId
this.trickForm.createdAt = e[0].media.createdAt
}else {
this.trickForm.fileId =""
this.trickForm.mediaId = ""
this.trickForm.createdAt = ""
}
},
editTrick(row) {
this.row = row
this.trickForm = {...row}
row.file && (this.trickForm.fileId = row.file.id)
this.$nextTick(() => {
this.trickDialog = true
})
this.fileList.push(row.file)
},
deleteTrick(row) {
this.$confirm('确认要删除吗?').then(() => {
this.instance
.post(`/app/wxcp/wxspeechtechnique/delete`, null, {
params: {
id: row.id,
},
})
.then((res) => {
if (res.code == 0) {
this.$message.success('删除成功')
this.getList()
}
})
})
},
trickConfirm() {
this.$refs['trickFom'].validate((valid) => {
if (valid) {
this.instance
.post(`/app/wxcp/wxspeechtechnique/addOrUpdate`, {
...this.trickForm,
category: this.current.id,
userId: this.user.info.id,
})
.then((res) => {
if (res.code == 0) {
this.trickDialog = false
this.$message.success(this.trickForm.id ? '编辑成功' : '添加成功')
this.getList()
}
})
}
})
},
currentMenu(item) {
this.current = item
this.getList()
},
delteGroup(item, index) {
this.$confirm('是否要删除?').then(() => {
this.instance
.post(`/app/wxcp/wxspeechtechniquecategory/delete`, null, {
params: {
id: item.id,
},
})
.then((res) => {
if (res.code == 0) {
this.$message.success('删除成功')
this.getSortList()
}
})
})
},
editGroup(item, index) {
this.sortForm = {...item}
this.groupDialog = true
},
onConfirm() {
this.$refs['groupForm'].validate((valid) => {
if (valid) {
this.instance
.post(`/app/wxcp/wxspeechtechniquecategory/addOrUpdate`, {
...this.sortForm,
})
.then((res) => {
if (res.code == 0) {
this.groupDialog = false
this.$message.success(this.sortForm.id ? '编辑成功' : '添加成功')
this.getSortList()
}
})
}
})
},
getSortList() {
this.instance
.post(`/app/wxcp/wxspeechtechniquecategory/listAll`, null, {
params: {
// type: 1,
},
})
.then((res) => {
if (res && res.data.length) {
this.sortList = res.data
this.current = res.data[0]
this.getList()
}
})
},
getList(item) {
this.instance
.post(`/app/wxcp/wxspeechtechnique/list`, null, {
params: {
category: this.current.id,
...this.search,
status: 1,
},
})
.then((res) => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
},
}
</script>
<style lang="scss" scoped>
.left-tree {
width: 264px;
background: #fafafb;
box-shadow: -1px 0px 0px 0px #e5e5e5;
border-radius: 2px 0px 0px 2px;
.tree-title {
height: 40px;
box-sizing: border-box;
padding: 0 8px 0 16px;
background: #eeeff1;
display: flex;
align-items: center;
justify-content: space-between;
& > label {
font-size: 14px;
font-weight: bold;
color: #222222;
}
}
.tree-item {
box-sizing: border-box;
padding: 8px 16px 8px 24px;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
border-right: 2px solid transparent;
}
.menu-item {
height: 40px;
align-items: center;
display: flex;
justify-content: space-between;
.item-title {
display: inline-block;
width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #222222;
}
.iconMore {
cursor: pointer;
}
}
::v-deep .el-scrollbar__wrap {
overflow-x: hidden;
}
}
::v-deep .ai-list__content--right {
.ai-list__content--right-wrapper {
min-height: 100%;
}
}
::v-deep .has-gutter tr {
height: 40px;
}
::v-deep .el-table__row td {
height: 68px;
}
// ::v-deep .el-table__row td .cell {
// width: 240px;
// height: 44px;
// }
.table-btn {
font-size: 14px;
color: #2266ff;
cursor: pointer;
&:nth-child(1) {
margin-right: 16px;
}
}
.input {
width: 100%;
min-height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #d0d4dc;
color: #666;
display: inline-block;
font-size: inherit;
cursor: pointer;
&:after {
content: '请选择...';
color: #888888;
padding-left: 10px;
}
}
::v-deep .media {
object-fit: fill;
width: 40px;
height: 40px;
vertical-align: middle;
box-sizing: content-box;
}
::v-deep .AiPersonSelect {
& > button {
background: #f5f5f5;
border-radius: 0px 2px 2px 0px;
border: 1px solid #d0d4dc;
color: #222222;
}
}
::v-deep .is-active {
border-right: 2px solid #2266ff;
}
</style>
<style scoped lang="scss">
.el-popper {
min-width: 76px !important;
}
.table-left__wrapper {
display: flex;
align-items: center;
.table-left__wrapper--text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.ellipsis {
width: 170px;
height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
img,
video {
margin-right: 8px;
}
}
.el-form-item__textarea {
position: relative;
.el-form-item__btn, .el-form-item__remark {
position: absolute;
bottom: 12px;
left: 12px;
line-height: 1;
z-index: 1;
color: #2266FF;
font-size: 14px;
user-select: none;
background: #fff;
cursor: pointer;
}
.el-form-item__remark {
left: 108px;
}
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div class="doc-circulation ailist-wrapper">
<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 Add from './components/Add'
export default {
name: 'AppVillageCode',
label: '一村一码',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
Add,
List
},
mounted () {
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
this.params = data.params
}
if (data.type === 'list') {
this.component = 'List'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
}
}
}
</script>
<style lang="scss">
.doc-circulation {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<ai-detail>
<template slot="title">
<ai-title title="添加二维码" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基本信息">
<template #content>
<el-form class="ai-form" ref="form" :model="form" label-width="110px" label-position="right">
<el-form-item label="地区" style="width: 100%;" prop="codeName">
<span style="color: #666;">{{ form.areaName }}</span>
</el-form-item>
<el-form-item label="二维码名称" prop="codeName" :rules="[{ required: true, message: '请输入二维码名称', trigger: 'blur' }]">
<el-input size="small" maxlength="30" show-word-limit placeholder="请输入二维码名称" style="width: 328px;" v-model="form.codeName"></el-input>
</el-form-item>
<el-form-item style="width: 100%;" label="二维码类型" prop="type" :rules="[{ required: true, message: '请选择二维码类型', trigger: 'change' }]">
<el-radio-group v-model="form.type">
<el-radio label="0">群二维码</el-radio>
<el-radio label="1">个人二维码</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传二维码" prop="codeUrl" style="width: 100%;" :rules="[{ required: true, message: '请上传二维码', trigger: 'change' }]">
<ai-uploader :instance="instance" v-model="form.codeUrl" :limit="1"></ai-uploader>
</el-form-item>
</el-form>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm">提交</el-button>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
return {
info: {},
form: {
areaId: '',
codeName: '',
areaName: '',
code: '',
codeUrl: [],
type: '',
},
id: ''
}
},
created () {
if (this.params && this.params.areaId && !this.params.id) {
this.form.areaId = this.params.areaId
this.form.areaName = this.params.areaName
}
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
}
},
methods: {
getInfo (id) {
this.instance.post(`/app/appeveryvillagecode/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = res.data
this.form.codeUrl = [{
url: res.data.codeUrl
}]
}
})
},
onClose () {
this.form.explain = ''
},
confirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(`/app/appeveryvillagecode/addOrUpdate`, {
...this.form,
codeUrl: this.form.codeUrl[0].url
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
}
})
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,413 @@
<template>
<ai-list class="villagecode">
<template slot="title">
<ai-title title="一村一码" isShowBottomBorder></ai-title>
</template>
<template #left>
<div class="villagecode-left">
<div class="villagecode-left__title">
<h2>村列表</h2>
</div>
<div class="addressBook-left__list">
<div class="addressBook-left__list--title">
<el-input
class="addressBook-left__list--search"
size="mini"
clearable
placeholder="请输入地区名称"
v-model="unitName"
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<el-tree
:filter-node-method="filterNode"
ref="tree"
:props="defaultProps"
node-key="id"
:data="areaTree"
highlight-current
:current-node-key="search.areaId"
:default-expanded-keys="defaultExpanded"
:default-checked-keys="defaultChecked"
@current-change="onTreeChange">
</el-tree>
</div>
</div>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" :disabled="isShowAdd" icon="iconfont iconAdd" @click="toAdd('')">添加活码</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="tags" label="标签">
<template slot-scope="{ row }">
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in row.tags" size="small" :key="index">{{ item }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column slot="options" width="180px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-popover
placement="bottom"
width="160"
:visible-arrow="false"
popper-class="wechat-message__container"
trigger="hover">
<el-button type="text" slot="reference">二维码</el-button>
<div style="font-size: 0;">
<img class="message-info__img" :src="row.codeUrl">
</div>
</el-popover>
<el-button type="text" @click="toAdd(row.id)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</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
},
data() {
return {
search: {
current: 1,
size: 10,
status: 0,
title: '',
areaId: ''
},
defaultExpanded: [],
defaultChecked: [],
areaTree: [],
defaultProps: {
children: 'children',
label: 'name'
},
currIndex: -1,
total: 10,
colConfigs: [
{prop: 'codeName', label: '名称', align: 'left'},
{prop: 'type', label: '二维码类型', align: 'left', formart: v => v === '0' ? '群二维码' : '个人二维码'},
{prop: 'createUserName', label: '创建人'},
{prop: 'createTime', label: '创建时间'},
{slot: 'options', label: '操作'}
],
areaName: '',
unitName: '',
tableData: []
}
},
computed: {
...mapState(['user']),
isShowAdd () {
const str = this.search.areaId.substr(this.search.areaId.length - 3)
return str === '000'
}
},
watch: {
unitName (val) {
this.$refs.tree.filter(val)
}
},
mounted() {
this.search.areaId = this.user.info.areaId
this.areaName = this.user.info.areaName
this.getTree()
this.getList()
this.$nextTick(() => {
})
},
methods: {
getList() {
this.instance.post(`/app/appeveryvillagecode/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
onTreeChange (e) {
this.search.areaId = e.id
this.areaName = e.name
this.search.current = 1
this.$nextTick(() => {
this.getList()
})
},
getTree () {
this.instance.post(`/admin/area/queryAllArea?id=${this.user.info.areaId}`).then(res => {
if (res.code === 0) {
let parent = res.data.map(v => {
v.label = v.name
v.children = []
return v
}).filter(e => !e.parentid)[0]
this.defaultExpanded = [parent.id]
this.defaultChecked = [parent.id]
this.search.areaId = parent.id
this.addChild(parent, res.data)
this.areaTree = [parent]
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(parent.id)
})
}
})
},
addChild (parent, list) {
for (let i = 0; i < list.length; i++) {
if (list[i].parentId === parent.id) {
parent.children.push(list[i])
}
}
if (list.length > 0) {
parent['children'].map(v => this.addChild(v, list))
}
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appeveryvillagecode/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
areaName: this.areaName,
id: id || '',
areaId: this.search.areaId
}
})
}
}
}
</script>
<style lang="scss" scoped>
.villagecode {
.table-tags {
.el-tag {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
}
.addressBook-left__list {
height: calc(100% - 40px);
padding: 8px 8px;
overflow: auto;
.addressBook-left__tags--item {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 8px 0 16px;
color: #222222;
&.addressBook-left__tags--item-active, &:hover {
background: #E8EFFF;
color: #2266FF;
i, span {
color: #2266FF;
}
}
span {
font-size: 14px;
}
i {
cursor: pointer;
color: #8e9ebf;
font-size: 16px;
}
}
.addressBook-left__list--title {
display: flex;
align-items: center;
margin-bottom: 8px;
.addressBook-left__list--search {
flex: 1;
::v-deep input {
width: 100%;
}
}
.el-button {
width: 84px;
flex-shrink: 1;
margin-right: 8px;
}
}
span {
color: #222222;
font-size: 14px;
}
::v-deep .el-tree {
background: transparent;
.el-tree-node__expand-icon.is-leaf {
color: transparent!important;
}
.el-tree-node__content > .el-tree-node__expand-icon {
padding: 4px;
}
.el-tree-node__content {
height: 32px;
}
.el-tree__empty-text {
color: #222;
font-size: 14px;
}
.el-tree-node__children .el-tree-node__content {
height: 32px;
}
.el-tree-node__content:hover {
background: #E8EFFF;
color: #222222;
border-radius: 2px;
}
.is-current > .el-tree-node__content {
&:hover {
background: #2266FF;
color: #fff;
}
background: #2266FF;
span {
color: #fff;
}
}
}
}
.villagecode-left {
width: 100%;
height: auto;
background: #FAFAFB;
.villagecode-left__title {
display: flex;
align-items: center;
height: 40px;
padding: 0 16px;
background: #E5E5E5;
h2 {
color: #222;
font-size: 14px;
}
}
.villagecode-left__list {
height: calc(100% - 40px);
padding: 8px 0;
overflow: auto;
span {
display: block;
height: 40px;
line-height: 40px;
padding: 0 24px;
color: #222222;
font-size: 14px;
cursor: pointer;
border-right: 2px solid transparent;
background: transparent;
&:hover {
color: #2266FF;
background: #E8EFFF;
}
&.left-active {
color: #2266FF;
border-color: #2266FF;
background: #E8EFFF;
}
}
}
}
::v-deep .ai-list__content--right {
.ai-list__content--right-wrapper {
min-height: 100%;
}
}
}
.message-info__img {
font-size: 0;
width: 144px;
height: 144px;
}
</style>