持续集成分支

This commit is contained in:
aixianling
2024-10-31 14:34:57 +08:00
parent 6a833be062
commit 8c56cf808b
2165 changed files with 4116 additions and 8716 deletions

View File

@@ -0,0 +1,472 @@
<template>
<section class="AppMessageNotification">
<div class="header">
<p>注意</p>
<p>每个用户每天可以接受10条群发消息不限企业发布的群发还是个人发布的群发</p>
<p>个人群发每天可以给用户发送10条群发消息</p>
</div>
<div class="select-user">
<div class="label color-666"><span class="tips">*</span>发送方式</div>
<div class="right">
<u-radio-group v-model="form.messageSource" @change="radioGroupChange">
<u-radio v-for="(item, index) in typeList" :key="index" :label="item.name" :name="item.type">{{ item.name }}</u-radio>
</u-radio-group>
</div>
</div>
<div class="select-user">
<div class="label color-666"><span class="tips">*</span>部门选择</div>
<div class="right">
<AiPagePicker type="custom" :selected.sync="deptUserList" nodeKey="id" :ops="{url:`./selectDeptUser`,label:'name'}" valueObj>
<span class="label" v-if="deptUserList.length">已选择</span>
<span v-else style="color:#999;">请选择</span>
<u-icon name="arrow-right" color="#999" size="24" style="margin-left:8px;"/>
</AiPagePicker>
</div>
</div>
<div class="select-user">
<div class="label color-666">发送地区</div>
<div class="right">
<AiAreaPicker v-model="areaIdList" multiple>
<span class="label" v-if="areaIdList.length">已选择</span>
<span v-else style="color:#999;">请选择</span>
<u-icon name="arrow-down" color="#666" size="24"/>
</AiAreaPicker>
</div>
</div>
<div class="content">
<p class="title fw500 mar-b32">群发消息设置</p>
<div class="mini-title color-666"><span class="tips">*</span>文本内容</div>
<div class="textarea">
<u-input v-model="form.content" type="textarea" :height="400" auto-height maxlength="1000" placeholder="请输入文本内容"/>
<div class="hint">{{ form.content.length }}/1000</div>
</div>
<div class="type-content">
<div class="flex">
<p class="label" style="width:40px;">图片</p>
<AiUploader type="image" :limit="9" multiple :def.sync="fileListImg" @data="changeImg"></AiUploader>
</div>
<div class="flex" >
<p class="label" style="width:40px;">视频</p>
<AiUploader type="video" :limit="9" multiple placeholder="上传视频" :def.sync="fileListVideo" @data="changeVideo"></AiUploader>
</div>
<div class="flex">
<p class="label" style="width:40px;">附件</p>
<AiUploader type="file" :limit="9" multiple placeholder="上传附件" :def.sync="fileListFile" @data="changeFile"></AiUploader>
</div>
</div>
</div>
<div class="bg-144"></div>
<div class="footer">
<div @click="back">取消</div>
<div class="confirm" @click="confirm">确认发送</div>
</div>
<!-- <u-popup v-model="show" mode="bottom">
<div class="popup">
<div class="item" v-for="(item, index) in popupList" :key="index">
<div class="icon-bg">
<u-icon :name="item.icon" size="56" color="#333"></u-icon>
</div>
<p>{{item.text}}</p>
</div>
</div>
</u-popup> -->
</section>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: "AppMessageNotification",
appName: "群发通知",
data() {
return {
typeList: [
{name: '居民群', type: '2'},
{name: '居民', type: '1'}
],
form: {
content: '',
messageSource: '2', // 1居民 2居民群
},
fileList: [],
fileListImg: [],
fileListVideo: [],
fileListFile: [],
areaIdList: [],
tagIdList: [],
fileDataImg: null,
fileDataVideo: null,
fileDataFile: null,
userList: [],
deptList: [],
deptUserTagList: []
}
},
computed: {
...mapState(['user']),
deptUserList: {
set(v) {
this.userList = v.filter(e => e.kind == 'user')
this.deptList = v.filter(e => e.kind == 'dept')
},
get() {
let {userList, deptList} = this
return [userList, deptList].flat()
}
}
},
methods: {
changeImg(e) {
this.$nextTick(() => {
this.fileListImg.map((item) => {
if(item.id == e.file.id) {
item.mediaId = e.media.mediaId
item.contentType = 'image'
}
})
})
},
changeVideo(e) {
this.$nextTick(() => {
this.fileListVideo.map((item) => {
if(item.id == e.file.id) {
item.mediaId = e.media.mediaId
item.contentType = 'video'
}
})
})
},
changeFile(e) {
this.$nextTick(() => {
this.fileListFile.map((item) => {
if(item.id == e.file.id) {
item.mediaId = e.media.mediaId
item.contentType = 'file'
}
})
})
},
radioGroupChange(e) {
this.form.messageSource = e
uni.setStorageSync('messageSource', e)
this.deptUserTagList = []
this.deptUserList = []
uni.setStorageSync('selectDeptUser', [])
},
toSelect() {
uni.navigateTo({url: `./SelectUser?tagIdList=${this.tagIdList}&areaList=${this.areaIdList}`})
},
timeSelect(e) {
var nowTime = new Date().getTime() * 1
var beginTimes = new Date(e.year + '/' + e.month + '/' + e.day + ' ' + e.hour + ':' + e.minute + ':' + e.second).getTime() * 1
if (nowTime > beginTimes) {
// this.form.sendTime = ''
return this.$u.toast('群发时间应大于当前时间')
} else {
this.form.sendTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`
}
},
confirm() {
this.$loading()
if (!this.deptUserTagList.length) {
return this.$u.toast('请选择部门')
}
if (!this.form.content) {
return this.$u.toast('请输入文本内容')
}
let result = {}, tags = {}
this.deptUserTagList.map(e => {
let {kind, id, tagIdList} = e
result[e.corpId] = [result[e.corpId], {kind, id}].flat()
tagIdList = tagIdList.filter(e => !!e)
if (tagIdList) {
tags[e.corpId] = [...new Set([tags[e.corpId], tagIdList].flat())]
}
})
var deptList = Object.keys(result).map(corpId => {
let res
if (result[corpId]) {
res = {
corpId,
objList: result[corpId].filter(e => !!e)
}
let tagIds = tags[corpId].filter(e => !!e)
if (tagIds?.length > 0) {
res.tagId = tagIds
}
}
return res
}).filter(e => !!e)
this.fileList = []
var contentFile = {
content: this.form.content,
contentType: 'text'
}
this.fileList = [...this.fileListImg, ...this.fileListVideo, ...this.fileListFile]
this.fileList.unshift(contentFile)
var params = {
...this.form,
fileList: this.fileList,
areaId: this.areaIdList.join(','),
deptList: deptList,
}
this.$http.post("/app/pushmessage/addOrUpdate", params).then(res => {
if (res?.code == 0) {
this.$u.toast('发送成功')
uni.setStorageSync('selectDeptUser', [])
setTimeout(() => {
uni.navigateBack()
}, 500)
} else {
this.$u.toast(res.msg)
}
}).catch((err) => {
this.$u.toast(err)
})
},
back() {
uni.navigateBack()
}
},
created() {
this.areaId = this.user.areaId
this.areaName = this.user.areaName
uni.setStorageSync('selectDeptUser', [])
uni.setStorageSync('messageSource', this.form.messageSource)
// uni.$on('selectTag', res => {
// this.tagIdList = res.tagIdList
// this.areaIdList = res.areaIdList
// })
},
onShow() {
this.deptUserTagList = uni.getStorageSync('selectDeptUser')
}
}
</script>
<style lang="scss" scoped>
.AppMessageNotification {
height: 100%;
background-color: #f3f6f9;
.fw500 {
font-weight: 500;
font-size: 32px !important;
}
.color-666 {
color: #666;
font-size: 30px;
}
.tips {
font-size: 34px;
color: #f46 !important;
vertical-align: middle;
}
.mar-b32 {
margin-bottom: 32px;
}
.header {
// background-color: #fff;
padding: 32px 32px 20px 32px;
box-sizing: border-box;
margin-bottom: 16px;
p {
line-height: 44px;
margin-bottom: 8px;
word-break: break-all;
font-size: 28px;
color: #666;
}
}
.select-user {
background-color: #fff;
padding: 34px 32px;
display: flex;
justify-content: space-between;
margin-bottom: 16px;
font-size: 30px;
img {
width: 44px;
height: 44px;
vertical-align: middle;
}
.color-999 {
color: #999;
}
.color-1365DD {
color: #1365DD;
}
.label {
width: 160px;
}
.right {
width: calc(100% - 160px);
text-align: right;
}
}
.pad-lr0 {
padding: 0;
margin-bottom: 32px;
}
.content {
padding: 32px;
box-sizing: border-box;
background-color: #fff;
.title {
line-height: 44px;
margin-bottom: 24px;
}
.mini-title {
line-height: 44px;
margin-bottom: 24px;
}
.textarea {
padding: 16px 32px;
box-sizing: border-box;
border: 1px solid #ddd;
.hint {
padding: 4px 0 8px 0;
text-align: right;
color: #999;
}
}
// .upload{
// padding: 16px 32px;
// line-height: 44px;
// border: 1px solid #ddd;
// border-top: 0;
// .u-icon{
// margin-right: 8px;
// }
// }
.type {
margin-top: 32px;
p {
display: inline-block;
margin-right: 16px;
}
}
.type-content {
margin-top: 32px;
.label {
display: inline-block;
margin-right: 16px;
vertical-align: top;
font-size: 30px;
color: #666;
width: 130px;
}
.value {
width: calc(100% - 130px);
}
.ai-uploader {
display: inline-block;
width: calc(100% - 130px);
}
.flex {
padding: 34px 0;
line-height: 44px;
display: flex;
}
.border-b {
border-bottom: 1px solid #ddd;
}
.flex-label {
.label {
width: 260px;
}
}
}
}
.bg-144 {
height: 144px;
}
.footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 112px;
line-height: 112px;
background: #fff;
display: flex;
font-size: 36px;
font-family: PingFangSC-Regular, PingFang SC;
.confirm {
color: #fff;
background: #1365dd;
}
div {
flex: 1;
text-align: center;
color: #333;
}
}
.popup {
padding-top: 32px;
background-color: #f3f6f9;
.item {
display: inline-block;
width: 25%;
text-align: center;
padding: 32px 0;
.icon-bg {
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
background-color: #fff;
margin: 0 auto;
border-radius: 8px;
margin-bottom: 16px;
.u-icon {
margin-top: 20px;
}
}
p {
line-height: 44px;
font-size: 30px;
font-weight: 500;
color: #333;
}
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,288 @@
<template>
<section class="selectDeptUser">
<div class="header-middle">
<div class="hint">
<span v-for="(item, index) in selectDeptPath" :key="index">
<span v-if="index>0" class="mar-h4">/</span>
<span class="color-3F8DF5" @click="deptNameClick(item, index)">{{ item.name }}</span>
</span>
</div>
<div class="cards" v-for="item in treeList" :key="item.id" @click="itemClick(item)">
<div class="imges">
<div class="imgselect" :class="{checked:item.isChecked}" @click.stop="itemCheck(item, 'dept')"/>
<img src="./components/img/gird--select-icon.png" alt="" class="avatras"/>
</div>
<div class="rightes">
<div class="applicationNames">{{ item.name }}</div>
<img src="./components/img/right-icon.png" alt="" class="imgs"/>
</div>
</div>
<div class="userCards" v-for="e in userList" :key="e.id">
<div class="imges">
<div class="imgselect" :class="{checked:e.isChecked}" @click.stop="itemCheck(e, 'user')"/>
<img src="./components/img/tx@2x.png" alt="" class="avatras"/>
</div>
<div class="rights fill">
<div class="applicationNames" v-text="e.name"/>
<div class="idNumbers">{{ e.phone }}</div>
</div>
</div>
<AiEmpty description="暂无数据" v-if="!hasData"/>
</div>
<div class="subBtn" @click="submit">
<div>确定选择</div>
</div>
</section>
</template>
<script>
export default {
name: "selectDeptUser",
appName: "选择部门/人员",
data() {
return {
selected: [],
allData: null,
treeList: [],
selectDeptPath: [],
userList: [],
}
},
computed: {
hasData() {
return this.treeList?.length > 0 || this.userList?.length > 0
}
},
onLoad() {
// console.log(this.$route.query.selected)
// this.selected = [this.$route.query.selected].flat().filter(e => !!e)?.map(id => ({id, kind: /^\d{1,5}}$/.test(id) ? 'dept' : 'user'})) || []
this.selected = uni.getStorageSync('selectDeptUser') || []
console.log(this.selected)
this.getAllDepts()
},
methods: {
isSelected(id, corpId) {
return !!this.selected.find(e => e.id == id && e.corpId == corpId)
},
getAllDepts() {
this.$http.post('/app/wxcp/wxdepartment/listAllByCorp').then((res) => {
if (res?.data) {
let parents = res.data.map(e => e.parentid)
this.allData = res.data.map(e => ({...e, hasChildren: parents.includes(e.id), isChecked: this.isSelected(e.id, e.corpId)}))
this.deptInit()
}
})
},
deptInit() {
this.treeList = this.allData.filter(e => !e.parentid)
this.selectDeptPath = [{name: "可选范围", id: ''}]
},
itemClick({id, name, corpId}) {
let index = this.selectDeptPath.findIndex(e => e.id == id && e.corpId == corpId)
if (index == -1) {
this.selectDeptPath.push({name, id, corpId})
this.getDeptsAndUsersByParent(id, corpId)
}
},
getDeptsAndUsersByParent(departmentId, corpId) {
this.treeList = this.allData.filter(e => e.parentid == departmentId && e.corpId == corpId)
this.userList = []
this.$http.post(`/app/wxcp/wxuser/listByDeptId`, null, {
params: {departmentId, status: 1, cid: corpId}
}).then(res => {
if (res?.data) {
this.userList = res.data.map(e => ({...e, isChecked: this.isSelected(e.id, e.corpId)}))
}
})
},
deptNameClick(row, index) {
this.userList = []
if (!index) { //第一级别
this.deptInit()
} else {
let length = this.selectDeptPath.length - index
this.selectDeptPath.splice(index + 1, length)
this.getDeptsAndUsersByParent(row.id, row.corpId)
}
},
itemCheck(row, kind) {
row.isChecked = !row.isChecked
if (row.isChecked) {
this.selected.push({...row, kind})
} else {
let index = this.selected.findIndex(e => e.id == row.id)
this.selected.splice(index, 1)
}
this.$forceUpdate()
},
submit() {
console.log([this.selected].flat())
if(![this.selected].flat().length) {
return this.$u.toast('请选择部门或人员')
}
uni.$emit("pagePicker:custom", [this.selected].flat())
uni.setStorageSync('selectDeptUser', [this.selected].flat())
uni.navigateTo({url: `./selectTag`})
},
}
}
</script>
<style lang="scss" scoped>
.selectDeptUser {
height: 100%;
background: #fff;
.header-top {
background: #fff;
padding: 20px 32px;
}
.header-middle {
padding-bottom: 140px;
.hint {
padding: 28px 20px 28px 32px;
line-height: 56px;
box-shadow: 0 1px 0 0 #e4e5e6;
font-size: 30px;
font-weight: 500;
word-break: break-all;
}
.empty-div {
height: 16px;
background: #f5f5f5;
}
.imges {
display: flex;
align-items: center;
.imgselect {
width: 48px;
height: 48px;
vertical-align: middle;
background-image: url("./components/img/xz.png");
background-position: center;
background-size: 100% 100%;
&.checked {
background-image: url("./components/img/xzh.png");
}
}
.avatras {
width: 74px;
height: 74px;
border-radius: 8px;
margin-left: 36px;
}
}
.cards {
display: flex;
align-items: center;
height: 120px;
line-height: 120px;
padding: 0 0 0 32px;
img {
width: 74px;
height: 74px;
border-radius: 8px;
}
.rightes {
width: calc(100% - 160px);
display: flex;
align-items: center;
margin-left: 32px;
border-bottom: 1px solid #e4e5e6;
.applicationNames {
flex: 1;
min-width: 0;
font-size: 36px;
font-weight: 500;
color: #333333;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.imgs {
flex-shrink: 0;
width: 40px;
height: 40px;
margin-right: 20px;
}
}
}
.userCards {
display: flex;
align-items: center;
height: 120px;
line-height: 120px;
padding: 0 0 0 32px;
.rights {
display: flex;
justify-content: space-between;
align-items: center;
margin-left: 32px;
border-bottom: 1px solid #e4e5e6;
padding-right: 40px;
height: inherit;
.applicationNames {
font-size: 36px;
font-weight: 500;
color: #333333;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.idNumbers {
color: #666;
}
}
}
}
.subBtn {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 118px;
background: #f4f8fb;
div {
width: 192px;
height: 80px;
line-height: 80px;
text-align: center;
background: #1365dd;
border-radius: 4px;
font-size: 32px;
color: #fff;
margin: 20px 34px 0 0;
float: right;
}
}
.color-3F8DF5 {
color: #3F8DF5;
}
.mar-h4 {
margin: 0 4px;
}
}
</style>

View File

@@ -0,0 +1,281 @@
<template>
<section class="selectTag">
<p class="title">已选择</p>
<div class="list">
<div class="item" v-for="(item, index) in deptList" :key="index">
<div class="name-flex">
<div class="name">{{ item.name }}</div>
<div class="btn" @click="chooseTag(index)" v-if="!item.parentid">添加标签</div>
</div>
<div class="tag-list" v-if="item.tagList && item.tagList.length">
<span v-for="(items, indexs) in item.tagList" :key="indexs">{{items.name}}</span>
</div>
</div>
<div class="item" v-for="(item, index) in userList" :key="index">
<div class="name-flex">
<div class="name">{{ item.name }}</div>
</div>
</div>
</div>
<div class="bg-144"></div>
<div class="footer">
<div @click="back">上一步</div>
<div class="confirm" @click="confirm">确定</div>
</div>
<u-popup v-model="showTagList" mode="bottom" class="popup">
<div class="popup-title">
<div class="btn" @click="cancelTag">取消</div>
<div class="text">选择标签</div>
<div class="btn confirm" @click="confirmTag">确认</div>
</div>
<div class="popup-tag">
<div class="type-list" v-for="(item, index) in tagList" :key="index">
<p>{{ item.name }}</p>
<div class="tag-list">
<div class="item" :class="items.isCheck ? 'active' : ''" v-for="(items, indexs) in item.tagList" :key="indexs" @click="typeClick(index, indexs)">
{{ items.name }}
</div>
</div>
</div>
<AiEmpty description="暂无数据" v-if="!tagList.length"/>
</div>
</u-popup>
</section>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: "selectTag",
data() {
return {
deptList: [],
userList: [],
showTagList: false,
messageSource: '', //1居民 2居民群
tagList: [],
deptIndex: 0,
noDept: true
}
},
computed: {...mapState(['user'])},
methods: {
chooseTag(index) {
this.deptIndex = index
this.getTagList(this.deptList[index].corpId)
},
typeClick(index, indexs) {
this.tagList[index].tagList[indexs].isCheck = !this.tagList[index].tagList[indexs].isCheck
},
confirmTag() {
this.deptList[this.deptIndex].tagList = []
this.deptList[this.deptIndex].tagIdList = []
this.tagList.map((item) => {
item.tagList.map((items) => {
if(items.isCheck) {
this.deptList[this.deptIndex].tagList.push(items)
this.deptList[this.deptIndex].tagIdList.push(items.id)
}
})
})
this.cancelTag()
},
cancelTag() {
this.tagList.map((item) => {
item.tagList.map((items) => {
items.isCheck = false
})
})
this.showTagList=false
},
getTagList(id) {
var url = this.messageSource == 1 ? `/app/wxcp/wxcorptag/listAllByCorp?dvcpCorpId=${id}&size=1000` : `/app/wxcp/wxgroupchattag/listAllByCorp?dvcpCorpId=${id}&size=1000`
this.$http.post(url).then(res => {
if (res?.code == 0) {
res.data.records.map((item) => {
item.tagList.map((items) => {
items.isCheck = false
})
})
this.tagList = res.data.records
if(this.deptList[this.deptIndex].tagIdList) {
this.tagList.map((item) => {
item.tagList.map((items) => {
if(this.deptList[this.deptIndex].tagIdList.includes(items.id)) {
items.isCheck = true
}
})
})
}
this.showTagList = true
}
})
},
confirm() {
console.log([...this.deptList, ...this.userList])
uni.setStorageSync('selectDeptUser', [...this.deptList, ...this.userList])
uni.navigateBack({ delta: 2 })
},
back() {
uni.navigateBack()
}
},
onShow() {
document.title = '选择部门'
},
onLoad() {
var list = uni.getStorageSync('selectDeptUser')
this.messageSource = uni.getStorageSync('messageSource')
list.map((item) => {
item.tagIdList = []
if(item.kind == 'dept') {
this.deptList.push(item)
}else {
this.userList.push(item)
}
if(!item.parentid && item.kind == 'dept') {
this.noDept = false
}
})
// this.getTagList()
},
}
</script>
<style lang="scss" scoped>
.selectTag {
.title{
padding: 32px;
line-height: 44px;
font-size: 32px;
color: #333;
}
.list{
padding: 0 32px;
.item{
width: 100%;
padding: 32px;
box-sizing: border-box;
background-color: #fff;
margin-bottom: 16px;
border-radius: 8px;
.name-flex{
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 32px;
line-height: 44px;
.name{
width: calc(100% - 138px);
word-break: break-all;
}
.btn{
width: 120px;
text-align: right;
color: #3192F4;
font-size: 30px;
}
}
.tag-list{
span{
display: inline-block;
padding: 12px 32px;
font-size: 28px;
font-family: PingFangSC-Regular, PingFang SC;
color: #333;
line-height: 40px;
background-color: #F3F4F7;
border-radius: 4px;
margin: 0 16px 16px 0;
}
}
}
}
.bg-144{
height: 144px;
}
.footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 112px;
line-height: 112px;
background: #fff;
display: flex;
font-size: 36px;
font-family: PingFangSC-Regular, PingFang SC;
.confirm {
color: #fff;
background: #1365dd;
}
div {
flex: 1;
text-align: center;
color: #333;
}
}
.popup{
.popup-title{
display: flex;
line-height: 44px;
padding: 32px;
position: fixed;
top: 0;
width: 100%;
box-sizing: border-box;
background-color: #fff;
z-index: 9;
.text{
width: calc(100% - 400px);
color: #333;
text-align: center;
}
.btn{
width: 200px;
color: #999;
}
.confirm{
text-align: right;
color: #1365dd;
}
}
.popup-tag{
height: 500px;
padding-top: 120px;
}
.type-list {
padding: 0 32px;
background-color: #fff;
p {
line-height: 44px;
margin-bottom: 24px;
font-size: 30px;
color: #666;
}
.tag-list {
overflow: hidden;
.item {
padding: 12px 32px;
float: left;
font-size: 28px;
font-family: PingFangSC-Regular, PingFang SC;
color: #333;
line-height: 40px;
background-color: #F3F4F7;
border-radius: 4px;
margin: 0 16px 16px 0;
}
.active {
background-color: #3192F4;
color: #fff;
}
}
}
}
}
</style>