初始化

This commit is contained in:
aixianling
2021-12-14 18:36:19 +08:00
parent 9afa4101b6
commit a8dff862d2
327 changed files with 88702 additions and 0 deletions

6
project/sass/.npmrc Normal file
View File

@@ -0,0 +1,6 @@
registry=http://192.168.1.87:4873/
email=aixianling@sinoecare.com
always-auth=true
_auth="YWRtaW46YWRtaW4xMjM="
package-lock=false

View File

@@ -0,0 +1,67 @@
<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: 'AppAddressBook',
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,211 @@
<template>
<ai-detail>
<template slot="title">
<ai-title :title="id ? '编辑成员' : '添加成员'" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<el-form ref="form" :model="form" label-width="110px" label-position="right">
<ai-card title="个人信息">
<template #content>
<div class="ai-form">
<el-form-item label="姓名" prop="name" :rules="[{ required: true, message: '请输入姓名', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入姓名" v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="账号" prop="id" :rules="[{ required: true, message: '请输入账号', trigger: 'blur' }]">
<el-input size="small" :disabled="!!id" placeholder="成员唯一标识,设定以后不支持修改" v-model="form.id"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobile" :rules="[{ required: true, validator: validatorPhone, trigger: 'blur' }]">
<el-input size="small" placeholder="请输入手机号" v-model="form.mobile"></el-input>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio label="1"></el-radio>
<el-radio label="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="座机" prop="telephone">
<el-input size="small" placeholder="请输入座机" v-model="form.telephone"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input size="small" placeholder="请输入邮箱" v-model="form.email"></el-input>
</el-form-item>
<el-form-item label="地址" style="width: 100%;" prop="address">
<el-input size="small" style="width: 100%;" placeholder="请输入地址" v-model="form.address"></el-input>
</el-form-item>
</div>
</template>
</ai-card>
<ai-card title="组织信息">
<template #content>
<el-form-item label="部门" prop="departmentName" style="width: 100%;" :rules="[{ required: true, message: '请选择部门', trigger: 'change' }]">
<el-input size="small" placeholder="请选择..." disabled v-model="form.departmentName">
<ai-user-get slot="append" isStrictly :instance="instance" @change="onChange" v-model="department" isChooseUnit>
<el-button type="info">选择</el-button>
</ai-user-get>
</el-input>
</el-form-item>
<el-form-item label="标签" style="width: 100%;" prop="tags">
<el-select size="small" v-model="form.tagIds" multiple placeholder="请选择标签">
<el-option
v-for="item in tagsList"
:key="item.id"
:label="item.tagname"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="职务" prop="position">
<el-input size="small" placeholder="请输入职务" v-model="form.position"></el-input>
</el-form-item>
</template>
</ai-card>
</el-form>
</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 () {
const validatorPhone = function (rule, value, callback) {
if (value === '') {
callback(new Error('请输入手机号'))
} else if (!/^1\d{10}$/.test(value)) {
callback(new Error('手机号格式错误'))
} else {
callback()
}
}
return {
info: {},
department: [],
validatorPhone: validatorPhone,
form: {
position: '',
name: '',
email: '',
telephone: '',
gender: '',
mobile: '',
departmentName: '',
departmentIds: [],
tagIds: [],
id: ''
},
id: '',
tagsList: [],
areaList: []
}
},
created () {
this.getAreaList()
this.getTags()
if (this.params && this.params.departmentId && !this.params.id) {
this.department = [{
id: String(this.params.departmentId),
name: this.params.departmentName
}]
this.form.departmentIds = [this.params.departmentId]
this.form.departmentName = this.params.departmentName
}
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
}
},
methods: {
getInfo (id) {
this.instance.post(`/app/wxcp/wxuser/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = {
...res.data,
departmentName: res.data.departmentNames,
tagIds: res.data.tags.map(v => v.id),
departmentIds: res.data.departmentIdsStr.split(',')
}
}
})
},
onChange (e) {
if (e.length) {
this.form.departmentIds = e.map(v => v.id)
this.form.departmentName = e.map(v => v.name).join(',')
} else {
this.form.departmentIds = ''
this.form.departmentName = ''
}
},
getTags () {
this.instance.post(`/app/wxcp/wxtag/listAll`).then(res => {
if (res.code == 0) {
this.tagsList = res.data
}
})
},
getAreaList() {
this.instance.post(`/admin/area/queryAreaByParentId?id=341021104000`).then(res => {
if (res.code == 0) {
this.areaList = res.data.map(item => {
item.dictName = item.name
item.dictValue = item.id
return item
})
}
})
},
onClose () {
this.form.explain = ''
},
confirm () {
this.$refs.form.validate((valid) => {
if (valid) {
const api = this.id ? '/app/wxcp/wxuser/update' : '/app/wxcp/wxuser/add'
this.instance.post(api, {
...this.form
}).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,990 @@
<template>
<ai-list class="addressBook">
<template slot="title">
<ai-title title="通讯录管理" isShowBottomBorder></ai-title>
</template>
<template #left>
<div class="addressBook-left">
<div class="addressBook-left__title">
<h2 @click="tabIndex = 0, search.current = 1, getList()" :class="[tabIndex === 0 ? 'tab-active' : '']">
组织架构</h2>
<h2 @click="tabIndex = 1, search.current = 1, getList()" :class="[tabIndex === 1 ? 'tab-active' : '']">标签</h2>
</div>
<div class="addressBook-left__list--title" v-if="tabIndex === 0">
<el-input
size="mini"
placeholder="请输入部门名称"
v-model="unitName"
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<div class="addressBook-left__list--title" v-if="tabIndex === 1">
<el-button size="mini" icon="iconfont iconAdd" @click="isShowTags = true">添加标签</el-button>
<el-input
class="addressBook-left__list--search"
size="mini"
style="width: 154px;"
placeholder="请输入标签名称"
v-model="tagName"
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<div class="addressBook-left__list--wrapper">
<div class="addressBook-left__list" v-show="tabIndex === 0">
<el-tree
:filter-node-method="filterNode"
ref="tree"
:props="defaultProps"
node-key="id"
:data="unitList"
highlight-current
@node-contextmenu="nodeContextmenu"
:current-node-key="search.departmentId"
:default-expanded-keys="defaultExpanded"
:default-checked-keys="defaultChecked"
@current-change="onTreeChange">
<template v-slot="{ node, data }">
<ai-open-data type="departmentName" :openid="data.id"/>
</template>
</el-tree>
<ul
v-if="isShowMenu"
class="el-dropdown-menu el-popper"
:style="{top: menuInfo.y + 'px', left: menuInfo.x + 'px', position: 'fixed', zIndex: 2023}"
x-placement="top-end">
<li class="el-dropdown-menu__item" @click="handleTreeCommand('add', menuInfo.node)">添加子部门</li>
<li class="el-dropdown-menu__item" @click="handleTreeCommand('edit', menuInfo.node)">修改名称</li>
<li class="el-dropdown-menu__item" @click="handleTreeCommand('remove', menuInfo.node)">删除</li>
<li class="el-dropdown-menu__item" :class="[!menuInfo.node.i ? 'is-disabled' : '']"
@click="handleTreeCommand('top', menuInfo.node)">上移
</li>
<li
class="el-dropdown-menu__item"
:class="[(menuInfo.node.i === menuInfo.node.len - 1) || (!menuInfo.node.i && menuInfo.node.i !== 0) ? 'is-disabled' : '']"
@click="handleTreeCommand('bottom', menuInfo.node)">下移
</li>
</ul>
</div>
<div class="addressBook-left__list" v-show="tabIndex === 1">
<div class="addressBook-left__tags">
<div
@click="changeTag(index)"
class="addressBook-left__tags--item"
:class="[currIndex === index ? 'addressBook-left__tags--item-active' : '']"
v-for="(item, index) in tagsList" :key="index">
<span>{{ item.tagname }}</span>
<el-dropdown @command="e => handleCommand(e, item)">
<i class="iconfont iconmore"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="edit">编辑</el-dropdown-item>
<el-dropdown-item command="remove">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</div>
</div>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd" v-if="tabIndex === 0" @click="toAdd('')">添加成员
</el-button>
<ai-import :instance="instance" :dict="dict" v-if="tabIndex === 0" type="wxcp/wxuser" name="通讯录管理"
:importParams="{departmentId:search.departmentId}"/>
<el-button size="small" icon="iconfont iconUpdate_Files" v-if="tabIndex === 0" :loading="btnLoading"
@click="syncMembers">同步数据
</el-button>
<ai-user-get refs="addTags" :instance="instance" v-model="users" @change="onChooseUser"
:disabled="currIndex < 0" v-if="tabIndex === 1" sass>
<el-button size="small" :disabled="currIndex < 0" type="primary" icon="iconfont iconAdd">添加成员</el-button>
</ai-user-get>
</template>
<template slot="right">
<el-input
v-model="search.name"
size="small"
@keyup.enter.native="search.current = 1, getList()"
placeholder="请输入成员姓名、手机号或标签名称"
clearable
@clear="search.current = 1, search.name = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
v-loading="loading"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@handleSelectionChange="handleSelectionChange"
@getList="getList">
<el-table-column slot="avatar" label="" align="right" width="100px">
<template slot-scope="{ row }">
<img class="table-avatar" :src="row.avatar || 'https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png'">
</template>
</el-table-column>
<el-table-column slot="username" label="姓名">
<template slot-scope="{ row }">
<ai-open-data style="height: 20px" type="userName" :openid="row.name"/>
</template>
</el-table-column>
<el-table-column slot="tags" label="标签" align="left">
<template slot-scope="{ row }">
<div class="table-tags" v-if="row.tagNames">
<el-tag type="info" v-for="(item, index) in row.tagNames.split('、')" size="small" :key="index">{{
item
}}
</el-tag>
</div>
</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" @click="toAdd(row.id)" v-show="tabIndex === 0">编辑</el-button>
<el-button type="text" @click="remove(row.id)" v-show="tabIndex === 0">删除</el-button>
<el-button type="text" @click="removeTags(row.id)" v-show="tabIndex === 1">移除</el-button>
</div>
</template>
</el-table-column>
<div slot="paginationBtns" class="addressBook-table__btns">
<span style="margin-right: 8px;" @click="removeAll">{{ tabIndex === 0 ? '批量删除' : '批量移除' }}</span>
<ai-user-get :instance="instance" v-model="department" sass @change="onDepartment"
v-if="tabIndex === 0">
<span>批量导出</span>
</ai-user-get>
</div>
</ai-table>
<ai-dialog
:visible.sync="isShowTags"
width="590px"
:title="tagId ? '编辑标签' : '添加标签'"
@close="onClose"
@onConfirm="onFormConfirm">
<el-form ref="tagForm" :model="tagForm" label-width="110px" label-position="right">
<el-form-item label="标签名称" prop="tagname" :rules="[{ required: true, message: '请输入标签名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入标签名称" v-model="tagForm.tagname"></el-input>
</el-form-item>
</el-form>
</ai-dialog>
<ai-dialog
:visible.sync="isShowDepart"
width="590px"
:title="departId ? '修改名称' : '添加部门'"
@close="onClose"
@onConfirm="onDepartConfirm">
<el-form ref="departForm" :model="departForm" label-width="110px" label-position="right">
<el-form-item label="部门名称" prop="name" :rules="[{ required: true, message: '请输入部门名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入部门名称" v-model="departForm.name"></el-input>
</el-form-item>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data() {
return {
users: [],
isShowMenu: false,
department: [],
btnLoading: false,
menuInfo: {
x: '',
y: '',
node: {}
},
search: {
current: 1,
size: 10,
title: '',
tagname: '',
name: '',
tagIds: '',
departmentId: ''
},
tagForm: {
tagname: ''
},
isShowDepart: false,
departForm: {
name: ''
},
loading: false,
isShowTags: false,
defaultChecked: [],
defaultExpanded: [],
tabIndex: 0,
currIndex: -1,
areaList: [],
total: 0,
colConfigs: [
{type: 'selection', label: ''},
{slot: 'avatar', label: ''},
{slot: 'username'},
{prop: 'position', label: '职务'},
// {prop: 'departmentNames', label: '部门'},
{prop: 'mobile', label: '手机号'},
{slot: 'tags', label: '标签'},
{prop: 'status', label: '账号状态', align: 'center', formart: v => v === 1 ? '已激活' : '未激活'}
],
defaultProps: {
children: 'children',
label: 'name'
},
unitName: '',
rootId: '',
unitList: [],
tagsList: [],
tagName: '',
sourceTagList: [],
tableData: [],
tagId: '',
departmentName: '',
departId: '',
ids: ''
}
},
computed: {
...mapState(['user'])
},
watch: {
unitName(val) {
this.$refs.tree.filter(val)
},
tagName(val) {
if (!val) {
this.tagsList = this.sourceTagList
}
this.tagsList = this.sourceTagList.filter(v => v.tagname.indexOf(val) > -1)
}
},
created() {
this.getTree()
this.getList()
this.getTags()
document.querySelector('html').addEventListener('click', this.bindEvent)
},
methods: {
changeTag(index) {
this.currIndex = index
this.search.current = 1
this.$nextTick(() => {
this.getList()
})
},
bindEvent() {
this.isShowMenu = false
},
nodeContextmenu(e, node) {
this.isShowMenu = true
this.menuInfo = {
x: e.x + 16,
y: e.y + 6,
node
}
},
removeTags(id) {
if (this.currIndex < 0) {
return this.$message.error('请选择标签')
}
this.$confirm('确定移除该成员?').then(() => {
this.instance.post(`/app/wxcp/wxtag/removeTag?userIds=${id}&tagId=${this.tagsList[this.currIndex].id}`).then(res => {
if (res.code == 0) {
this.$message.success('移除成功!')
this.search.current = 1
this.getList()
}
})
})
},
onDepartment(e) {
if (!e.length) {
return this.$message.error('请选择部门')
}
const ids = e.map(v => v.id).join(',')
this.department = []
this.instance.post(`/app/wxcp/wxuser/export?departmentId=${ids}`, null, {
responseType: 'blob'
}).then(res => {
if (res?.type == "application/json") {
let reader = new FileReader()
reader.readAsText(res, "utf-8")
reader.onload = e => {
if (e.target.readyState === 2) {
let ret = JSON.parse(e.target.result)
if (ret?.code == 0) {
this.$message.success(ret.msg)
} else this.$message.error(ret.msg)
}
}
} else {
const link = document.createElement('a')
let blob = new Blob([res], {type: res.type})
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
link.setAttribute('download', `${e[0].name}.xls`)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
this.$message.success('导出成功!')
}
})
},
onChooseUser(e) {
if (!e.length) {
return this.$message.error('请选择成员')
}
this.instance.post(`/app/wxcp/wxtag/markTag`, null, {
params: {
tagId: this.tagsList[this.currIndex].id,
userIds: e.map(v => v.id).join(',')
}
}).then(res => {
if (res.code == 0) {
this.getList()
this.users = []
this.search.current = 1
this.$refs.addTags.reset()
} else {
this.users = []
}
}).catch(() => {
this.users = []
})
},
handleTreeCommand(e, item) {
this.isShowMenu = false
if (e === 'add') {
this.departForm.parentid = item.id
this.departId = ''
this.isShowDepart = true
} else if (e === 'edit') {
this.departForm = {
...item
}
this.departId = item.id
this.isShowDepart = true
} else if (e === 'top') {
if (!item.i) {
return false
}
this.moveDepart(item.id, 0)
} else if (e === 'bottom') {
if ((item.i === item.len - 1) || (!item.i && item.i !== 0)) {
return false
}
this.moveDepart(item.id, 1)
} else if (e === 'remove') {
this.removeDepart(item.id, item.parentid)
}
},
removeDepart(id, parentid) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxdepartment/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.defaultChecked = [parentid]
this.$message.success('删除成功!')
this.getTree()
}
})
})
},
moveDepart(id, type) {
this.instance.post(`/app/wxcp/wxdepartment/move?id=${id}&type=${type}`).then(res => {
if (res.code == 0) {
this.defaultChecked = [id]
this.getTree()
this.$message.success(type === 0 ? '上移成功' : '下移成功')
}
})
},
onDepartConfirm() {
this.$refs.departForm.validate((valid) => {
if (valid) {
this.instance.post(`/app/wxcp/wxdepartment/addOrUpdate`, {
...this.departForm,
departId: this.departId
}).then(res => {
if (res.code == 0) {
this.defaultChecked = [this.departForm.parentid]
this.isShowDepart = false
this.getTree()
this.$message.success(this.departId ? '编辑成功' : '新增成功')
}
})
}
})
},
onFormConfirm() {
this.$refs.tagForm.validate((valid) => {
if (valid) {
this.instance.post(`/app/wxcp/wxtag/addOrUpdate`, {
...this.tagForm
}).then(res => {
if (res.code == 0) {
this.isShowTags = false
this.getTags()
this.$message.success(this.tagId ? '编辑成功' : '新增成功')
}
})
}
})
},
handleCommand(e, item) {
if (e === 'edit') {
this.tagId = item.id
this.tagForm = {
...item
}
this.isShowTags = true
} else {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxtag/delete?id=${item.id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getTags()
}
})
})
}
},
onClose() {
this.tagId = ''
this.tagForm.tagname = ''
this.departForm.name = ''
this.departId = ''
this.departForm.nameEn = ''
this.departForm.parentid = ''
this.departForm.showIndex = ''
},
syncMembers() {
if (!this.search.departmentId) {
return this.$message.error('请选择部门')
}
this.btnLoading = true
this.instance.post(`/app/wxcp/wxdepartment/syncDepart`).then(res => {
if (res.code == 0) {
this.instance.post(`/app/wxcp/wxdepartment/syncUser?departmentId=${this.search.departmentId}`, null, {
timeout: 1000000
}).then(res => {
if (res.code == 0) {
this.$message.success('同步成功')
this.getList()
this.getTree()
}
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
}
}).catch(() => {
this.btnLoading = false
})
},
getTags() {
this.instance.post(`/app/wxcp/wxtag/listAll`).then(res => {
if (res.code == 0) {
this.sourceTagList = res.data.length ? JSON.parse(JSON.stringify(res.data)) : []
this.tagsList = res.data
}
})
},
onSwitchChange(id) {
this.instance.post(`/app/wxcp/wxuser/enable?id=${id}`).then(res => {
if (res.code == 0) {
this.getList()
}
})
},
onTreeChange(e) {
this.departmentName = e.name
this.search.departmentId = e.id || ''
this.search.current = 1
this.isShowMenu = false
this.$nextTick(() => {
this.getList()
})
},
getList() {
this.loading = true
this.instance.post(`/app/wxcp/wxuser/list`, null, {
params: {
...this.search,
departmentId: this.tabIndex === 0 ? this.search.departmentId : '',
tagIds: this.tabIndex === 1 ? (this.currIndex >= 0 ? this.tagsList[this.currIndex].id : '') : '',
listType: this.tabIndex
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
this.$nextTick(() => {
this.loading = false
this.$initWxOpenData()
})
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
removeAll() {
if (!this.ids) return
if (this.tabIndex === 1) {
this.removeTags(this.ids)
} else {
this.remove(this.ids)
}
},
handleSelectionChange(e) {
this.ids = e.map(v => v.id).join(',')
},
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
changeTab(id, index) {
this.currIndex = index
this.search.areaId = id
this.$nextTick(() => {
this.getList()
})
},
getTree() {
let {unitName} = this
this.instance.post(`/app/wxcp/wxdepartment/listAll`, null, {
params: {unitName}
}).then(res => {
if (res?.data) {
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.departmentId = parent.id
this.departmentName = parent.name
this.addChild(parent, res.data)
this.unitList = [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) {
list[i].i = parent.children.length
parent.children.push(list[i])
}
}
if (parent.children.length) {
parent.children.forEach(v => {
v.len = parent.children.length
})
}
if (list.length > 0) {
parent['children'].map(v => this.addChild(v, list))
}
},
formatList(list) {
var arr = []
for (let item of list) {
if (item.childrenUser && item.childrenUser.length) {
delete item.childrenUser
}
if (item.childrenDept && item.childrenDept.length) {
this.formatList(item.childrenDept)
}
arr.push(item)
}
return arr
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxuser/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
id: id || '',
departmentId: this.search.departmentId || '',
departmentName: this.departmentName || ''
}
})
}
}
}
</script>
<style lang="scss" scoped>
.addressBook {
.addressBook-table__btns {
display: flex;
align-items: center;
}
.table-tags {
.el-tag {
margin-right: 8px;
margin-bottom: 8px;
&:last-child {
margin-right: 0;
}
}
}
.import-wrapper {
& > h2 {
margin-bottom: 8px;
font-size: 16px;
color: #222222;
font-weight: Bold;
}
.import-wrapper__tips {
line-height: 1;
margin-bottom: 24px;
div {
display: flex;
margin-bottom: 8px;
color: #222222;
font-size: 14px;
span {
cursor: pointer;
color: #2266FF;
&:hover {
opacity: 0.8;
text-decoration: underline;
}
}
}
}
.import-files {
i {
display: block;
margin-top: 8px;
}
}
i {
color: #999999;
font-size: 12px;
font-style: normal;
}
}
.tree-container {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
.tree-name {
padding-right: 30px;
}
i {
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
padding-right: 8px;
font-weight: normal;
color: #fff;
}
}
.el-tag {
margin-right: 8px;
border: 1px solid #D0D4DC;
background: #F3F4F7;
border-radius: 4px;
font-size: 13px;
color: #222222;
&:last-child {
margin-right: 0;
}
}
.table-avatar {
width: 40px;
height: 40px;
margin-top: 3px;
border-radius: 2px;
border: 1px solid #CCCCCC;
}
.el-button--mini, .el-button--mini.is-round {
height: 28px;
line-height: 28px;
padding: 0;
font-size: 12px;
::v-deep span {
margin-left: 0;
}
}
.addressBook-left__list--title {
display: flex;
align-items: center;
margin: 8px 8px 0;
.addressBook-left__list--search {
flex: 1;
::v-deep input {
width: 100%;
}
}
.el-button {
width: 84px;
flex-shrink: 1;
margin-right: 8px;
}
}
.addressBook-left {
width: 100%;
height: auto;
background: #FAFAFB;
.addressBook-left__title {
display: flex;
align-items: center;
width: 100%;
height: 40px;
background: #ffffff;
h2 {
flex: 1;
height: 100%;
line-height: 40px;
color: #222;
font-size: 14px;
text-align: center;
cursor: pointer;
border-bottom: 2px solid transparent;
&.tab-active {
color: #2266FF;
border-bottom: 2px solid #2266FF;
}
}
}
// ::-webkit-scrollbar {
// width: 1px;
// }
.addressBook-left__list--wrapper {
height: calc(100% - 68px);
padding: 8px;
}
.addressBook-left__list {
width: 100%;
height: 100%;
overflow: auto;
::v-deep .el-tree {
width: fit-content;
min-width: 100%;
}
::v-deep .el-scrollbar__wrap {
margin-bottom: 0 !important;
overflow-x: hidden;
.el-scrollbar__view {
width: fit-content;
min-width: 100%;
}
}
.addressBook-left__tags--item {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 8px 0 16px;
cursor: pointer;
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;
}
}
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;
}
}
}
}
}
::v-deep .ai-list__content--right {
flex: 1;
min-width: 0;
margin-left: 1px;
box-shadow: none;
.ai-list__content--right-wrapper {
width: 100%;
}
}
::v-deep ai-open-data {
}
}
</style>

View File

@@ -0,0 +1,113 @@
<template>
<ai-list v-if="!isShowDetail">
<template slot="title">
<ai-title title="问卷表单" :isShowBottomBorder="false" :instance="instance"></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 :areaId="areaId" :ref="tab.name" v-if="currIndex == i" :is="tab.comp" @change="onChange" lazy :instance="instance" :dict="dict" :permissions="permissions"/>
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<Add v-else-if="componentName === 'Add'" :areaId="areaId" :params="params" :instance="instance" :dict="dict" :permissions="permissions" @change="onChange"></Add>
<Statistics v-else-if="componentName === 'Statistics'" :areaId="areaId" :params="params" :instance="instance" :dict="dict" :permissions="permissions" @change="onChange"></Statistics>
</template>
<script>
import FormList from './components/FormList.vue'
import Template from './components/Template'
import Add from './components/Add'
import Statistics from './components/Statistics'
import { mapState } from 'vuex'
export default {
name: 'AppAskForm',
label: '问卷表单',
components: {
FormList,
Add,
Statistics,
Template
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
tabs () {
const tabList = [
{label: '表单列表', name: 'FormList', comp: FormList, permission: ''},
{label: '共享模板', name: 'Template', comp: Template, permission: ''}
].filter(() => {
return true
})
return tabList
}
},
data () {
return {
activeName: 'JoinEvent',
currIndex: '0',
componentName: '',
params: {},
areaId: '',
isShowDetail: false
}
},
created() {
this.areaId = this.user.info.areaId
},
methods: {
changeArea () {
this.$nextTick(() => {
this.$refs[this.tabs[Number(this.currIndex)].name][0].getList()
})
},
onChange (data) {
if (data.type === 'list') {
this.componentName = 'List'
this.isShowDetail = false
this.params = data.params
if (data.isQuote) {
this.currIndex = 0
}
}
if (data.type === 'detail') {
this.componentName = 'Detail'
this.isShowDetail = true
this.params = data.params
}
if (data.type === 'Statistics') {
this.componentName = 'Statistics'
this.isShowDetail = true
this.params = data.params
}
if (data.type === 'add') {
this.componentName = 'Add'
this.isShowDetail = true
this.params = data.params
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,731 @@
<template>
<ai-detail class="statistics">
<template slot="title">
<ai-title title="数据统计" isShowBack isShowBottomBorder @onBackClick="cancel(false)"></ai-title>
</template>
<template slot="content">
<ai-card title="表单信息">
<template #content>
<ai-wrapper label-width="90px">
<ai-info-item label="表单名称" :value="info.title" isLine></ai-info-item>
<ai-info-item label="发布状态"><span :style="{color: dict.getColor('questionnaireStatus', info.status)}">已发布</span></ai-info-item>
<!-- <ai-info-item label="创建人" openType="userName" :value="info.createUserName"></ai-info-item> -->
<ai-info-item label="创建时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="截止时间" :value="info.periodValidityEndTime ? info.periodValidityEndTime : '永久有效'"></ai-info-item>
<ai-info-item label="提交次数限制" :value="info.commitType === '1' ? '限提交一次' : '不限次数' "></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<div class="statistics-wrapper">
<div class="statistics-wrapper__title">
<span :class="[currIndex === 0 ? 'active' : '']" @click="currIndex = 0">表单统计</span>
<span :class="[currIndex === 1 ? 'active' : '']" @click="currIndex = 1">居民统计</span>
</div>
<div class="statistics-wrapper__body">
<div v-show="currIndex === 0">
<div class="statistics-wrapper__body--info">
<span></span>
<i>{{ subjectList.length }}</i>
<span>其中</span>
<span v-for="(item, index) in fieldTypeCount" :key="index">
{{ mapType(item.field_type) }}<i>{{ item.c }}</i>{{ fieldTypeCount.length - 1 === index ? '' : '' }}
</span>
</div>
<div class="statistics-wrapper__body--list">
<div class="statistics-wrapper__body--item" v-for="(item, index) in subjectList" :key="index">
<div class="statistics-wrapper__body--top" :style="{borderBottom: ['input', 'textarea', 'upload'].indexOf(item.type) > -1 ? 'none' : '1px solid #DDDDDD'}">
<div class="left">
<h2>{{ item.fieldName }}{{ item.fixedLabel }}</h2>
</div>
<div class="right">
<span></span>
<i>{{ fieldDataCount[`field_${index}`] }}</i>
<span>条数据</span>
</div>
</div>
<div class="statistics-wrapper__body--bottom" v-if="['radio', 'checkbox', 'select'].indexOf(item.type) > -1">
<div class="statistics-wrapper__body--select" v-for="(item, i) in item.options" :key="i">
<div class="left">
<h2>{{ item.label }}</h2>
<span>{{ item.c || 0 }}</span>
</div>
<div class="right">
<div class="progress">
<div :style="{width: `${(((item.c || 0) / fieldDataCount[`field_${index}`]) * 100).toFixed(2)}%`}"></div>
</div>
<i>{{ (((item.c || 0) / fieldDataCount[`field_${index}`]) * 100).toFixed(2) }}%</i>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-show="currIndex === 1" style="padding: 16px;">
<!-- <ai-search-bar>
<template #right>
<el-input
v-model="search.name"
size="small"
placeholder="请输入居民名称"
clearable
@keyup.enter.native="search.current = 1, getList()"
@clear="search.current = 1, search.name = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar> -->
<ai-table
class="detail-table__table"
:border="true"
style="margin-top: 4px;"
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:stripe="false"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="userinfo" label="居民" width="260px" align="left">
<template slot-scope="{ row }">
<div class="userinfo">
<img :src="row.avatarUrl || 'https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png'">
<!-- <h3>{{ row.nickName }}</h3> -->
<div class="userinfo-right__top">
<h3><ai-open-data type="userName" :openid="row.nickName"></ai-open-data></h3>
</div>
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="showForm(row.id)">查看表单</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</div>
<ai-dialog
customFooter
:visible.sync="isShowForm"
@onConfirm="isShowForm = false"
width="800px"
title="表单">
<div class="middle-content form">
<div class="middle-content__wrapper">
<div>
<div class="left-item__item left-item__item--banner" key="banner" v-if="info.headPicture">
<img :src="info.headPicture">
</div>
<div class="left-item__item left-item__item--formname" key="title">
<h2>{{ info.title }}</h2>
</div>
<div class="left-item__item left-item__item--text" key="text">
<p>{{ info.tableExplain }}</p>
</div>
</div>
<div
class="left-item__item components-item"
v-for="(item, i) in targetList"
:key="i">
<div class="left-item__item--title">
<i :style="{opacity: item.required ? 1 : 0}">*</i>
<span>{{ i + 1 }}.</span>
<h2>{{ item.label }}</h2>
</div>
<div class="left-item__item--wrapper">
<template v-if="(item.type === 'radio')" >
<div class="radio-item" v-for="(field, index) in item.options" :key="index" >
<input type="radio" disabled :value="field.label" v-model="formInfo[`field_${i}`]" />
<img :src="field.img[0].url" v-if="field.img.length">
<label>{{ field.label }}</label>
</div>
</template>
<template v-if="item.type === 'upload'">
<img style="width: 100%; height: 100%;" :src="formInfo[`field_${i}`]" v-if="formInfo[`field_${i}`]">
<div class="left-item__item--upload" v-else>
<span>图片</span>
</div>
</template>
<template v-if="item.type === 'select'" >
<el-input resize="none" class="preview" type="textarea" style="color: #333" :placeholder="item.placeholder" v-model="formInfo[`field_${i}`]" disabled></el-input>
<!-- <span>{{ formInfo[`field_${i}`] }}</span> -->
<!-- <textarea :placeholder="item.placeholder" v-model="formInfo[`field_${i}`]" disabled></textarea> -->
<!-- <el-select placeholder="请选择" disabled v-model="formInfo[`field_${i}`]" style="width: 100%;">
<el-option
v-for="(item, index) in item.options"
:key="index"
:label="item.label"
:value="item.label">
</el-option>
</el-select> -->
</template>
<template v-if="(item.type === 'checkbox')" >
<div class="radio-item" v-for="(field, index) in item.options" :key="index" >
<input type="checkbox" disabled :value="field.label" v-model="formInfo[`field_${i}`]" />
<img :src="field.img[0].url" v-if="field.img.length">
<label>{{ field.label }}</label>
</div>
</template>
<template v-if="(item.type === 'input')" >
<div class="text-item">
<input :placeholder="item.placeholder" v-model="formInfo[`field_${i}`]" disabled>
</div>
</template>
<template v-if="(item.type === 'textarea')" >
<div class="textarea-item" resize="none">
<textarea :placeholder="item.placeholder" v-model="formInfo[`field_${i}`]" disabled></textarea>
</div>
</template>
</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="isShowForm = false">关闭</el-button>
</template>
</ai-dialog>
</div>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Statistics',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
return {
currIndex: 0,
search: {
name: '',
size: 10,
current: 1
},
info: {},
subjectList: [],
tableData: [],
total: 0,
form: {},
fieldTypeCount: [],
fieldValueDistribution: [],
fieldDataCount: {},
isShowForm: false,
targetList: [],
formInfo: {},
colConfigs: [
{slot: 'userinfo'},
{prop: 'commitTime', label: '提交时间', align: 'center', width: '160px' },
{prop: 'userType', label: '微信类型', align: 'center', width: '100px', formart: v => this.dict.getLabel('wxUserType', v) },
{prop: 'totalScore', label: '分值', align: 'center'}
]
}
},
mounted () {
this.getInfo()
this.getFormInfo()
this.dict.load(['wxUserType']).then(() => {
this.getList()
})
},
methods: {
getList () {
this.instance.post(`/app/appquestionnairetemplate/statisticsResident?id=${this.params.id}`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
this.$initWxOpenData()
}
})
},
showForm (id) {
this.instance.post(`/app/appquestionnairetemplate/queryDataInfoById?id=${this.params.id}&dataId=${id}`).then(res => {
if (res.code == 0) {
this.formInfo = res.data
this.targetList.forEach((item, index) => {
if (item.type === 'checkbox' && this.formInfo[`field_${index}`]) {
this.formInfo[`field_${index}`] = this.formInfo[`field_${index}`].split(',')
}
})
this.isShowForm = true
}
})
},
mapType (type) {
return {
upload: '上传图片',
input: '单行填空',
textarea: '多行填空',
radio: '单选',
checkbox: '多选',
select: '单下拉框'
}[type]
},
getFormInfo () {
this.instance.post(`/app/appquestionnairetemplate/statisticsTable?id=${this.params.id}`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.fieldDataCount = res.data.fieldDataCount
this.fieldTypeCount = res.data.fieldTypeCount
this.fieldValueDistribution = res.data.fieldValueDistribution
this.subjectList = res.data.appQuestionnaireTemplate.fields.map((item, index) => {
const fieldInfo = JSON.parse(item.fieldInfo)
let options = fieldInfo.options
if (['radio', 'checkbox', 'select'].indexOf(item.fieldType) > -1) {
options = fieldInfo.options.map(v => {
res.data.fieldValueDistribution[`field_${index}`].forEach(info => {
if (info.fieldValue === v.label) {
v.c = info.c
}
})
return v
})
}
return {
...item,
...fieldInfo,
options
}
})
}
})
},
getInfo () {
this.instance.post(`/app/appquestionnairetemplate/queryDetailById?id=${this.params.id}`).then(res => {
if (res.code == 0) {
this.info = res.data
this.targetList = res.data.fields.map(item => {
return JSON.parse(item.fieldInfo)
})
this.$initWxOpenData()
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
.statistics {
* {
box-sizing: border-box;
font-weight: normal;
font-style: normal;
}
.preview {
::v-deep .el-textarea.is-disabled, ::v-deep .el-textarea__inner {
color: #666!important;
}
}
.form {
.left-item__item--banner {
img {
width: 100%;
height: 235px;
}
.config-item__banner {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 235px;
line-height: 1;
border: 1px dashed #bbb;
span {
margin-top: 4px;
color: #555555;
font-size: 14px;
}
i {
font-size: 30px;
color: #8899bb;
}
}
}
.left-item__item--formname {
margin: 16px 0 32px;
padding: 0 12px;
color: #333333;
font-size: 15px;
font-weight: normal;
text-align: center;
word-break: break-all;
}
.left-item__item--text {
line-height: 20px;
// margin-bottom: 48px;
padding: 0 12px 20px;
text-align: justify;
color: #666;
font-size: 14px;
word-break: break-all;
}
.components-item {
position: relative;
padding: 16px 16px;
.left-item__item--wrapper {
& > img {
max-width: 300px;
}
}
.left-item__item--upload {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 120px;
height: 120px;
border-radius: 6px;
border: 1px dashed #bbb;
i {
font-size: 24px;
color: #8899bb;
}
span {
margin-top: 4px;
font-size: 12px;
color: #555;
}
}
.text-item {
input {
display: block;
width: 100%;
height: 40px;
border: none;
border-bottom: 1px solid #ddd;
&:focus {
outline: none;
}
&:disabled {
background: #fff;
}
}
}
.textarea-item {
textarea {
width: 100%;
height: 120px;
resize: none;
border: 1px solid #ddd;
padding: 10px;
&:focus {
outline: none;
}
&:disabled {
background: #fff;
}
}
}
.radio-item {
display: flex;
margin-bottom: 10px;
input {
position: relative;
top: 2px;
}
&:last-child {
margin-bottom: 0;
}
label {
margin-left: 10px;
}
img {
width: 60px;
margin-left: 10px;
}
}
}
.left-item__item--title {
display: flex;
margin-bottom: 10px;
i {
position: relative;
top: 3px;
margin-right: 5px;
color: #E22120;
}
span {
position: relative;
top: 3px;
}
h2 {
color: #333333;
font-size: 15px;
}
}
}
.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;
}
}
}
h2, h3 {
margin: 0;
}
.userinfo {
display: flex;
align-items: center;
img {
width: 40px;
height: 40px;
margin-right: 8px;
border-radius: 2px;
}
h3 {
font-size: 14px;
font-weight: normal;
color: #222222;
}
.userinfo-right__top {
display: flex;
align-items: center;
margin-bottom: 10px;
cursor: pointer;
white-space: nowrap;
}
span {
padding-left: 8px;
color: #2EA222;
font-size: 14px;
white-space: nowrap;
}
}
.statistics-wrapper {
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
.statistics-wrapper__title {
display: flex;
align-items: center;
height: 56px;
padding: 0 16px;
border-bottom: 1px solid #EEEEEE;
span {
height: 56px;
line-height: 56px;
color: #888888;
font-size: 16px;
font-weight: 600;
cursor: pointer;
user-select: none;
border-bottom: 3px solid transparent;
&:first-child {
margin-right: 32px;
}
&.active {
color: #222222;
border-color: #2266FF;
}
}
}
.statistics-wrapper__body--list {
padding: 0 40px 20px;
.statistics-wrapper__body--item {
margin-bottom: 20px;
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #DDDDDD;
}
.statistics-wrapper__body--bottom {
padding: 20px;
.statistics-wrapper__body--select {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
& > div {
display: flex;
align-items: center;
}
.left {
h2 {
max-width: 384px;
margin-right: 10px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
font-size: 12px;
color: #222222;
}
span {
color: #888888;
font-size: 12px;
}
}
.right {
i {
width: 50px;
text-align: right;
color: #2266FF;
font-size: 12px;
}
.progress {
position: relative;
width: 500px;
height: 8px;
margin-right: 10px;
background: #EEEEEE;
border-radius: 5px;
div {
position: absolute;
top: 0;
left: 0;
height: 8px;
background: #2266FF;
border-radius: 5px;
}
}
}
}
}
.statistics-wrapper__body--top {
display: flex;
justify-content: space-between;
align-items: center;
height: 70px;
padding: 0 20px;
background: #FFFFFF;
border-radius: 4px 4px 0px 0px;
border-bottom: 1px solid #DDDDDD;
& > div {
display: flex;
align-items: center;
font-size: 14px;
}
.left {
flex: 1;
h2 {
color: #222222;
font-size: 14px;
}
}
.right {
span {
color: #888888;
}
i {
color: #2266FF;
padding: 0 4px;
}
}
}
}
.statistics-wrapper__body--info {
display: flex;
align-items: center;
height: 70px;
line-height: 1;
padding: 0 40px;
color: #555555;
font-size: 14px;
i {
padding: 0 4px;
font-weight: 600;
font-style: normal;
color: #2266FF;
font-size: 14px;
}
}
}
}
</style>

View File

@@ -0,0 +1,283 @@
<template>
<ai-list class="template" isTabs>
<template slot="content">
<ai-search-bar bottomBorder>
<template #left>
<ai-select
v-model="search.type"
@change="search.current = 1, getList()"
placeholder="项目类型"
:selectList="$dict.getDict('questionnaireType')">
</ai-select>
</template>
<template #right>
<el-input
v-model="search.title"
size="small"
placeholder="请输入模板名称"
clearable
@keyup.enter.native="search.current = 1, getList()"
@clear="search.current = 1, search.title = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-search-bar style="margin-top: 12px;">
<template #left>
<el-button type="primary" @click="isShow = true">新建模板</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="type" width="120px" label="项目类型" align="center">
<template slot-scope="{ row }">
<div class="tags-wrapper">
<span class="tags" :class="'type-' + row.type">{{ dict.getLabel('questionnaireType', row.type) }}</span>
</div>
</template>
</el-table-column>
<el-table-column slot="options" width="160px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="toEdit(row.id, row.type)">编辑</el-button>
<el-button type="text" @click="quote(row.id, row.type)">引用</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">
<div class="type-list">
<div class="type-item" @click="currIndex = 0" :class="[currIndex === 0 ? 'active' : '']">
<svg class="icon" aria-hidden="true">
<use xlink:href="#iconwenjuandiaocha"></use>
</svg>
<span>问卷调查</span>
</div>
<div class="type-item" @click="currIndex = 1" :class="[currIndex === 1 ? 'active' : '']">
<svg class="icon" aria-hidden="true">
<use xlink:href="#iconkaoshiceping"></use>
</svg>
<span>考试测评</span>
</div>
<div class="type-item" @click="currIndex = 2" :class="[currIndex === 2 ? 'active' : '']">
<svg class="icon" aria-hidden="true">
<use xlink:href="#iconbaomingdengji"></use>
</svg>
<span>报名登记</span>
</div>
<div class="type-item" @click="currIndex = 3" :class="[currIndex === 3 ? 'active' : '']">
<svg class="icon" aria-hidden="true">
<use xlink:href="#iconmanyidiaocha"></use>
</svg>
<span>满意调查</span>
</div>
<div class="type-item" @click="currIndex = 4" :class="[currIndex === 4 ? 'active' : '']">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icontoupiaopingxuan"></use>
</svg>
<span>投票评选</span>
</div>
</div>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
export default {
name: 'Template',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
status: '',
type: '',
size: 10,
templateType: 1,
title: ''
},
currIndex: 0,
isShow: false,
total: 10,
colConfigs: [
{ prop: 'title', label: '模板名称', align: 'left' },
{ slot: 'type', label: '项目类型', align: 'center' },
{ prop: 'quoteCount', label: '引用次数', align: 'center' },
{ prop: 'createUserName', label: '创建人', align: 'center', openType: 'userName' },
{ prop: 'createUnitName', label: '创建单位', align: 'center', openType: 'departmentName' },
{ prop: 'createTime', label: '创建时间', align: 'center' }
],
tableData: []
}
},
mounted () {
this.getList()
},
methods: {
getList () {
this.instance.post(`/app/appquestionnairetemplate/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
this.$initWxOpenData()
}
})
},
quote (id, type) {
this.$emit('change', {
type: 'add',
params: {
id,
type,
isQuote: true,
templateType: 0
}
})
},
remove (id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appquestionnairetemplate/deleteShareTemplate?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd (id) {
this.$emit('change', {
type: 'Add',
params: {
id
}
})
},
toEdit (id, type) {
this.$emit('change', {
type: 'add',
params: {
id,
type,
templateType: 1
}
})
},
onConfirm () {
this.$emit('change', {
type: 'add',
params: {
id: '',
templateType: 1,
type: this.currIndex
}
})
}
}
}
</script>
<style lang="scss" scoped>
.template {
.tags-wrapper {
display: flex;
justify-content: center;
}
.type-list {
display: flex;
align-items: center;
.type-item {
display: flex;
align-items: center;
justify-content: center;
width: 128px;
height: 64px;
margin-right: 20px;
background: #FFFFFF;
border-radius: 2px;
// box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.08);
border-radius: 2px;
cursor: pointer;
border: 1px solid #E4E8EF;
svg {
width: 24px;
height: 24px;
margin-right: 8px;
}
&.active {
border: 1px solid #2266FF;
}
&:last-child {
margin-right: 0;
}
}
}
.tags {
display: block;
width: 64px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 4px;
font-size: 12px;
}
.type-0 {
color: #2266FF;
background: rgba(34, 102, 255, 0.1);
}
.type-1 {
color: rgba(34, 170, 153, 1);
background: rgba(34, 170, 153, 0.1);
}
.type-2 {
color: rgba(248, 180, 37, 1);
background: rgba(248, 180, 37, 0.1);
}
.type-3 {
color: rgba(102, 119, 187, 1);
background: rgba(102, 119, 187, 0.1);
}
.type-4 {
color: rgba(236, 68, 97, 1);
background: rgba(236, 68, 97, 0.1);
}
}
</style>

View File

@@ -0,0 +1,181 @@
export const components = [
{
type: 'options',
tips: '(可重复添加)',
label: '选项',
children: [
{
type: 'radio',
label: '单选',
fixedLabel: '单选',
value: '',
points: '',
icon: 'iconradio',
isShowPoints: false,
required: true,
hasAnswer: false,
answer: '',
pointType: '0',
pointDict: [
{
dictName: '此题有唯一答案和分值',
dictValue: '0'
},
{
dictName: '每个选项都有对应分值',
dictValue: '1'
}
],
options: [
{
label: '选项1',
value: '',
point: '',
img: []
},
{
label: '选项2',
value: '',
point: '',
img: []
}
],
title: ''
},
{
type: 'checkbox',
label: '多选',
fixedLabel: '多选',
points: '',
icon: 'iconcheck_box',
isShowPoints: false,
required: true,
hasAnswer: false,
answer: [],
value: [],
pointType: '0',
pointDict: [
{
dictName: '此题有唯一答案和分值',
dictValue: '0'
},
{
dictName: '每个选项都有对应分值',
dictValue: '1'
},
{
dictName: '答对几项得几分,答错不得分',
dictValue: '2'
}
],
options: [
{
label: '选项1',
value: '',
point: '',
img: []
},
{
label: '选项2',
point: '',
value: '',
img: []
}
],
title: ''
},
{
type: 'select',
label: '单下拉框',
fixedLabel: '单下拉框',
value: '',
points: '',
icon: 'iconSelect',
isShowPoints: false,
required: true,
hasAnswer: false,
answer: '',
pointType: '0',
pointDict: [
{
dictName: '此题有唯一答案和分值',
dictValue: '0'
},
{
dictName: '每个选项都有对应分值',
dictValue: '1'
}
],
options: [
{
label: '选项1',
value: '',
point: '',
img: []
},
{
label: '选项2',
value: '',
point: '',
img: []
}
],
title: ''
}
]
},
{
type: 'input',
tips: '(可重复添加)',
label: '填空',
children: [
{
type: 'input',
label: '单行填空',
fixedLabel: '单行填空',
value: '',
pointType: '0',
icon: 'icontext_box',
isShowPoints: false,
points: '',
required: true,
hasAnswer: false,
placeholder: '请输入...',
answer: ''
},
{
type: 'textarea',
label: '多行填空',
fixedLabel: '多行填空',
pointType: '0',
icon: 'icontext_area',
points: '',
isShowPoints: false,
required: true,
hasAnswer: false,
answer: '',
placeholder: '请输入...',
value: ''
}
]
},
{
type: 'annex',
tips: '(可重复添加)',
label: '附件',
children: [
{
type: 'upload',
label: '上传图片',
fixedLabel: '上传图片',
value: '',
icon: 'iconpic',
isShowPoints: false,
points: '',
required: true,
hasAnswer: false,
answer: ''
}
]
}
];

View File

@@ -0,0 +1,101 @@
<template>
<section class="conference">
<ai-list v-if="!showDetail">
<template slot="title">
<ai-title title="会议管理"></ai-title>
</template>
<template slot="tabs">
<el-tabs v-model="currIndex" @tab-click="handleClick">
<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"
:permissions="permissions" @goPage="goPage" :listType="listType" />
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<component v-else :is="currentComp" :instance="instance" :dict="dict" :detail="detailRow" :listType="listType" @gotoEdit="gotoAdd" ></component>
</section>
</template>
<script>
import addMeeting from './addMeeting';
import detail from './detail'
import list from './list'
export default {
name: 'AppConference',
label: "会议管理",
components: {addMeeting, detail, list},
provide() {
return {
top: this
}
},
props: {
instance: Function,
dict: Object,
permissions: Function,
},
data() {
return {
//会议状态0、草稿1、未开始2、进行中3、已取消4、已结束
//参会状态0、未确认1、已确认2、缺席
currIndex: "0",
currentComp: "",
showDetail: false,
detailRow: {},
listType: '1',
}
},
computed:{
tabs() {
return [
{label: "我参与的会议", name: "addMeeting", comp: list, detail: detail, permission: ""},
{label: "我发起的会议", name: "addMeeting", comp: list, detail: detail, permission: ""},
]
},
},
methods: {
goPage(params) {
this.detailRow = params.row
this.currentComp = params.comp
if(params.comp == 'detail' || params.comp == 'addMeeting') {
this.showDetail = true
}
},
handleClick() {
if (this.currIndex == 0) {
this.listType = '1'
} else {
this.listType = '0'
}
},
goBack() {
this.showDetail = false;
if (this.currIndex == '0') {
this.listType = '1'
} else {
this.listType = '0'
}
},
gotoAdd(obj) {
this.showDetail = true
this.detailRow = obj
this.currentComp = 'addMeeting'
},
},
}
</script>
<style lang="scss" scoped>
.conference {
height: 100%;
::v-deep .ai-list__content--right-wrapper {
background-color: transparent !important;
box-shadow: none !important;
}
}
</style>

View File

@@ -0,0 +1,244 @@
<template>
<ai-detail class="addMeeting">
<ai-title slot="title" title="发起会议" isShowBack isShowBottomBorder @onBackClick="$parent.goBack"/>
<template #content>
<ai-card title="会议信息">
<template #content>
<el-form :model="saveData" status-icon ref="ruleForm" :rules="rules" label-width="100px"
label-position="right">
<el-form-item label="会议标题:" prop="title">
<el-input v-model="saveData.title" size="small" placeholder="请输入..."
:maxlength="30" show-word-limit></el-input>
</el-form-item>
<el-row type="flex" justify="space-between">
<el-form-item label="开始时间:" prop="startTime">
<el-date-picker
:editable="false"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="saveData.startTime"
:picker-options="pickerOptions"
type="datetime"
size="small"
placeholder="选择日期">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间:" prop="endTime">
<el-date-picker
:editable="false"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="saveData.endTime"
:picker-options="pickerOptions"
type="datetime"
:disabled="!Boolean(saveData.startTime)"
size="small"
placeholder="选择日期">
</el-date-picker>
</el-form-item>
</el-row>
<el-form-item label="会议地点:" prop="address">
<el-input v-model="saveData.address" size="small" placeholder="请输入..."
:maxlength="30" show-word-limit></el-input>
</el-form-item>
<el-form-item label="会议内容:" prop="content">
<el-input v-model="saveData.content" size="small" placeholder="请输入..."
type="textarea" :rows="8" :maxlength="500" show-word-limit></el-input>
</el-form-item>
<el-row type="flex" justify="space-between">
<el-form-item label="参会提醒:" prop="noticeBefore">
<ai-select v-model="saveData.noticeBefore"
placeholder="请选择"
:selectList="dict.getDict('meetingNoticeBefore')"
></ai-select>
</el-form-item>
<el-form-item label="确认提醒:" prop="noticeAfter">
<ai-select v-model="saveData.noticeAfter"
placeholder="请选择"
:selectList="dict.getDict('meetingNoticeAfter')"
></ai-select>
</el-form-item>
</el-row>
<el-form-item label="会议资料:" prop="files">
<ai-uploader :instance="instance" @change="handleChange" isShowTip fileType="file"
v-model="saveData.fileList"
acceptType=".zip,.rar,.doc,.docx,.xls,.ppt,.pptx,.pdf,.txt,.jpg,.png,.xlsx"
:limit="9"></ai-uploader>
</el-form-item>
</el-form>
</template>
</ai-card>
<ai-card title="参会人员信息">
<template #right>
<ai-user-get slot="append" :instance="instance" v-model="saveData.attendees" sass>
<el-button type="text" icon="iconfont iconAdd">选择参会人员</el-button>
</ai-user-get>
</template>
<template #content>
<ai-table
border
:tableData="saveData.attendees"
:colConfigs="colConfigs"
:isShowPagination="false">
<el-table-column label="操作" slot="option" align="center">
<template v-slot="{row}">
<el-button type="text" title="删除" @click="deletePer(row)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="$parent.goBack">取消</el-button>
<el-button type="primary" @click="saveFrom(0)">保存草稿</el-button>
<el-button type="primary" @click="saveFrom(1)">保存并发布</el-button>
</template>
</ai-detail>
</template>
<script>
import {mapState} from "vuex";
import moment from 'dayjs'
export default {
name: "addMeeting",
inject: ['top'],
props: {
instance: Function,
dict: Object,
permissions: Function,
detail: Object
},
data() {
let endTimePass = (rule, value, callback) => {
if (value) {
if (moment(value).unix() - moment(this.saveData.startTime).unix() > 0) {
callback()
} else {
callback(new Error('结束时间要大于开始时间'));
}
} else {
callback(new Error('请选择结束时间'));
}
}
return {
saveData: {
noticeBefore: '4',
noticeAfter: '0',
attendees: [],
fileList: []
},
rules: {
title: [{required: true, message: '请填写会议标题', trigger: 'blur'}],
startTime: [{required: true, message: '请选择开始时间', trigger: 'blur'}],
endTime: [{required: true, validator: endTimePass, trigger: 'change'}],
address: [{required: true, message: '请填写会议地点', trigger: 'blur'}],
// content: [{required: true, message: '请填写会议内容', trigger: 'blur'}],
noticeBefore: [{required: true, message: '请选择参会提醒', trigger: 'blur'}],
noticeAfter: [{required: true, message: '请选择确认提醒', trigger: 'blur'}],
},
pickerOptions: {
disabledDate(time) {
return time.getTime() < Date.now() - 8.64e7;
}
},
showEdit: false,
total: 0,
}
},
methods: {
handleChange(e) {
this.saveData.fileList = e
},
deletePer(scope) {
this.$confirm('确认删除此参会人?')
.then(_ => {
if (this.detail.id) { //编辑
} else { //新增
this.saveData.attendees.map((item, index) => {
if (item.id == scope.id) {
this.saveData.attendees.splice(index, 1)
}
})
}
})
},
saveFrom(status) {
this.$refs["ruleForm"].validate((valid) => {
if (this.saveData.attendees.length == 0) {
return this.$message.error("参会人不能为空!")
}
if (moment(this.saveData.startTime).unix() - moment(Date.now()).unix() < 0) {
return this.$message.error("会议开始时间已过期,请重新选择!");
}
if (valid) {
this.saveData.files = []
this.saveData.fileList.map((item) => {
this.saveData.files.push(item.id)
})
this.instance.post(`/app/appmeetinginfo/add-update`, {
...this.saveData,
status: status,
}).then(res => {
if (res.code == 0) {
if (status != 1) {
this.$message.success("保存草稿成功")
} else {
this.$message.success("发布成功")
}
this.$parent.goBack();
}
});
}
});
},
getDetail() {
this.instance.post(`/app/appmeetinginfo/info-id?id=${this.detail.id}`).then(res => {
if (res?.data) {
this.saveData = {
...res.data,
};
this.saveData.fileList = res.data.files || []
}
});
}
},
created() {
this.dict.load("meetingNoticeAfter", "meetingNoticeBefore").then(
this.$nextTick(() => {
if (JSON.stringify(this.detail) == '{}') {
this.showEdit = false;
} else {
this.showEdit = true;
// this.saveData = {...this.detail};
// this.compereList = this.saveData.hosts;
// this.saveData.attendees = this.saveData.attendees || [];
this.getDetail()
}
})
)
},
computed: {
...mapState(['user']),
headerTitle() {
return this.showEdit ? '修改会议' : '发起会议'
},
colConfigs() {
return [
{prop: 'name', align: 'center', label: '姓名', openType: "userName"},
{prop: 'departName', align: 'center', label: '所属单位', openType: "departmentName"},
{slot: 'option'}
]
},
}
}
</script>
<style lang="scss" scoped>
.addMeeting {
::v-deep .el-button--text {
.iconfont {
color: inherit;
}
}
}
</style>

View File

@@ -0,0 +1,603 @@
<template>
<section class="meetingDetail detail-content">
<ai-detail>
<template #title>
<ai-title title="会议详情" isShowBottomBorder isShowBack @onBackClick="$parent.goBack">
<template #rightBtn>
<p class="conference_top_area" v-if="listType==0">
<!-- <el-button type="primary" v-if="detail.status==0" @click="changeMeeting('是否立即发布会议?')">立即发布</el-button> -->
<el-button type="primary" v-if="detail.status==1" @click="noticeMetting()">参会提醒</el-button>
<el-button type="primary" v-if="detail.status==0" @click="editMeeting()">修改会议</el-button>
<el-button class="del-btn-list" v-if="detail.status==1" @click="changeMeeting('是否取消会议?')">取消会议</el-button>
<el-button class="iconfont iconDelete del-btn-list" v-if="detail.status==0||detail.status==3"
@click="changeMeeting('是否删除会议?')">删除会议
</el-button>
</p>
<!-- v-if="detail.status==0||detail.status==3" -->
<p class="conference_top_area" v-if="listType==1&&info.status==1">
<el-button @click="toDo" style="width:80px;" class="del-btn-list">待定</el-button>
<el-button @click="innerVisible=true" v-if="info.joinStatus!=2" style="width:80px;" class="del-btn-list">
请假
</el-button>
<el-button type="primary" @click="changeMeeting('是否确认参会?')" v-if="info.joinStatus!=1" style="width:80px;">
确认参会
</el-button>
</p>
</template>
</ai-title>
</template>
<template #content>
<ai-dialog
title="确认请假"
:visible.sync="innerVisible"
@onConfirm="queMeeting('writeInfo')"
@onCancel="innerVisible=false;" @close="$refs.writeInfo.resetFields()"
width="520px">
<div class="addother_main" style="width:400px;margin:auto;">
<el-form :model="writeInfo" status-icon ref="writeInfo" label-width="100px" class="demo-ruleForm">
<el-form-item label="请假原因:" prop="reason" autocomplete="off"
:rules="{
required: true, message: '请假原因不能为空', trigger: 'blur'
}"
>
<el-input v-model.trim="writeInfo.reason" autocomplete="off" size="mini" placeholder="请输入..."
type="textarea" :rows="4" :maxlength="100" show-word-limit></el-input>
</el-form-item>
</el-form>
</div>
</ai-dialog>
<ai-card title="会议说明">
<template slot="content">
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<ai-info-item label="会议标题:"><span>{{ info.title }}</span></ai-info-item>
</ai-wrapper>
<!-- <ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="2">
<ai-info-item label="发起单位:" :value="info.unitName" openType="departmentName"/>
<ai-info-item label="发起人:" :value="info.userName" openType="userName"/>
</ai-wrapper> -->
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<!-- <ai-info-item label="会议状态:" v-if="xq.joinStatus==1&&listType==1"><span>{{ xq.joinStatus }}</span>
</ai-info-item> -->
<ai-info-item label="发起时间:"><span>{{ info.createTime }}</span></ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="2">
<ai-info-item label="开始时间:"><span>{{ info.startTime }}</span></ai-info-item>
<ai-info-item label="结束时间:"><span>{{ info.endTime }}</span></ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="2">
<ai-info-item label="参会提醒:"><span>{{
dict.getLabel("meetingNoticeBefore", info.noticeBefore) || "-"
}}</span></ai-info-item>
<ai-info-item label="确认提醒:"><span>{{
dict.getLabel("meetingNoticeAfter", info.noticeAfter) || "-"
}}</span>
</ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<ai-info-item label="会议地点:"><span>{{ info.address }}</span></ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<ai-info-item label="会议内容:"><span v-html="info.content"></span></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="会议资料">
<template slot="content">
<ai-file-list :fileList="info.files" :fileOps="{name: 'name', size: 'fileSizeStr'}"></ai-file-list>
</template>
</ai-card>
<ai-card title="参会名单">
<template slot="content">
<ai-search-bar>
<template #left>
<el-select v-model="search.joinStatus" placeholder="确认状态" size="small" clearable class="vc-input-160"
@change="searchMeetinguser">
<el-option
v-for="(item,k) in confirmStatus"
:key="k"
:label="item.label"
:value="k">
</el-option>
</el-select>
</template>
<!-- <template #right>-->
<!-- <el-input v-model="search.param" placeholder="姓名/手机号" @change="searchMeetinguser()"-->
<!-- size="small" suffix-icon="iconfont iconSearch" clearable/>-->
<!-- </template>-->
</ai-search-bar>
<ai-table
:tableData="info.attendees"
:colConfigs="colConfigs"
:isShowPagination="false"
>
<el-table-column slot="meetingUserName"
label="姓名"
align="center"
show-overflow-tooltip>
<div slot-scope="{row}">
<template v-if="row.meetingUserName">
<ai-open-data type="userName" :openid="row.meetingUserName"/>
</template>
</div>
</el-table-column>
<el-table-column slot="meetingUnitName"
label="所属部门"
align="center"
show-overflow-tooltip>
<div slot-scope="{row}">
<template v-if="row.meetingUnitName">
<ai-open-data type="departmentName" :openid="row.meetingUnitName"/>
</template>
</div>
</el-table-column>
<el-table-column slot="joinStatus"
prop="joinStatus"
label="确认状态"
align="center"
show-overflow-tooltip>
<div slot-scope="{row}">
<p style="color:rgba(255,68,102,1);display:flex;justify-content:center;" v-if="row.joinStatus==2">
请假<i class="el-icon-warning" :title="row.absence" style="cursor: pointer;"></i>
</p>
<span v-else :style="{color:confirmStatus[row.joinStatus].color}"
v-text="confirmStatus[row.joinStatus].label"/>
</div>
</el-table-column>
<el-table-column slot="option"
label="操作"
align="center"
v-if="listType==0"
show-overflow-tooltip>
<div slot-scope="{row}" v-if="row.joinStatus==0">
<span class="el-icon-message-solid" style="cursor: pointer;" title="参会提醒"
@click="noticeMetting(row.meetingUserId)" v-if="detail.status==1"></span>
</div>
</el-table-column>
</ai-table>
</template>
</ai-card>
</template>
</ai-detail>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'meetingDetail',
inject: ['top'],
props: {
instance: Function,
dict: Object,
permissions: Function,
detail: Object,
listType: String
},
data() {
return {
navId: '1',
info: {},
total: 0,
writeInfo: {reason: ''},
meetingUserList: [],
innerVisible: false,
search: {joinStatus: ''}
}
},
created() {
this.dict.load("meetingNoticeAfter", "meetingNoticeBefore").then(() => this.searchDetail(this.detail.id))
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{
slot: 'meetingUserName'
},
// {
// prop: 'meetingUserPhone',
// align: 'center',
// label: '手机号码',
// },
{
slot: 'meetingUnitName'
},
{
slot: 'joinStatus',
},
{
slot: 'option',
}
]
},
confirmStatus() {
return {
0: {label: '未确认', color: "rgba(255,136,34,1)"},
1: {label: '已确认', color: "rgba(34,102,255,1)"},
2: {label: '请假', color: "rgba(255,68,102,1)"},
3: {label: '待定', color: "rgba(255,136,34,1)"},
}
}
},
methods: {
toDo() {
this.$confirm("是否确认待定会议?").then(_ => {
this.instance.post("/app/appmeetinginfo/tobeConfirm", null, {
params: {
meetingId: this.detail.id,
}
}).then(res => {
if (res.code == 0) {
this.$message.success("会议待定");
setTimeout(_ => {
this.$parent.goBack();
}, 800)
}
})
})
},
searchMeetinguser() {
this.instance.post(`/app/appmeetinguser/list`, null, {
params: {...this.search, size: 999, meetingId: this.info.id}
}).then(res => {
if (res?.data) {
this.info.attendees = res.data.records;
this.total = res.data.total;
this.$initWxOpenData()
}
});
},
searchDetail(id) {
this.instance.post(`/app/appmeetinginfo/info-id`, null, {
params: {id}
}).then(res => {
if (res?.data) {
this.info = {
...res.data,
content: this.formatContent(res.data.content || ""),
files: res.data.files || []
};
this.searchMeetinguser()
}
});
},
changeMeeting(title, id) {
this.$confirm(title, {
type: 'warning'
}).then(() => {
if (title == '是否立即发布会议?') {
this.fbMeeting();
} else if (title == '是否删除会议?') {
this.sdMeeting();
} else if (title == '是否取消会议?') {
this.qxMeeting();
} else if (title == '是否确认参会?') {
this.qrMeeting();
} else if (title == '是否删除此参会人员?') {
this.deleteMeetingPer(id)
}
})
},
deleteMeetingPer(id) {
this.instance.post(`/app/appmeetinguser/delete`, null,
{
params: {
id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("删除成功!");
this.searchMeetinguser();
}
});
},
queMeeting(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.instance.post(`/app/appmeetinginfo/absent`, null,
{
params: {
meetingId: this.detail.id,
reason: this.writeInfo.reason
}
}
).then(res => {
if (res && res.code == 0) {
this.innerVisible = false;
this.$parent.goBack();
}
});
} else {
console.log('error submit!!');
return false;
}
});
},
qrMeeting() {
this.instance.post(`/app/appmeetinginfo/confirm`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("确认参会成功!")
this.$parent.goBack();
}
});
},
noticeMetting(meetingId) {
this.instance.post(`/app/appmeetinginfo/notice`, null,
{
params: {
meetingId: this.detail.id,
noticeALL: !meetingId,
meetingUserId: ''
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("提醒成功!")
}
});
},
fbMeeting() {
this.instance.post(`/app/appmeetinginfo/release`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("发布成功!")
this.$parent.goBack();
}
});
},
sdMeeting() {
this.instance.post(`/app/appmeetinginfo/delete`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("删除成功!");
this.$parent.goBack();
}
});
},
qxMeeting() {
this.instance.post(`/app/appmeetinginfo/cancel`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("取消会议成功!");
this.$parent.goBack();
}
});
},
editMeeting() {
this.$parent.goBack();
this.$emit('gotoEdit', this.info)
},
}
}
</script>
<style lang="scss" scoped>
.meetingDetail {
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 1);
.user-search {
width: 100%;
height: 48px;
display: flex;
justify-content: space-between;
align-items: center;
.float-right {
width: calc(100% - 200px);
text-align: right;
}
.input-162 {
display: inline-block;
width: 162px;
}
}
.content {
width: 1000px;
height: calc(100% - 50px);
overflow: hidden;
margin: auto;
display: flex;
justify-content: space-around;
.content-left {
width: 160px;
height: 100%;
.content-left-nav {
width: 158px;
background-color: #ffffff;
border-radius: 4px;
border: solid 1px #eeeeee;
margin-top: 56px;
overflow: hidden;
li {
height: 48px;
line-height: 48px;
padding-left: 24px;
font-size: 14px;
font-weight: normal;
font-stretch: normal;
letter-spacing: 0;
color: #666666;
cursor: pointer;
border-left: 3px solid transparent;
&:hover {
border-left: 3px solid #5088ff;
}
a {
display: block;
}
}
.navActive {
border-left: 3px solid #5088ff;
color: #5088ff;
}
}
}
.content-right {
width: 780px;
height: calc(100% - 80px);
overflow-y: auto;
margin-left: 40px;
box-sizing: border-box;
overflow-x: hidden;
.content-right-title {
width: 780px;
height: 56px;
margin-bottom: 16px;
box-shadow: inset 0px -1px 0px 0px #dad5d5;
display: flex;
justify-content: space-between;
align-items: center;
span {
display: block;
width: 150px;
height: 56px;
line-height: 56px;
color: #333333;
font-weight: bold;
&:nth-of-type(2) {
text-align: right;
width: 200px;
}
}
}
.flie {
width: 100%;
height: 40px;
line-height: 40px;
padding: 0 8px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
color: rgba(51, 51, 51, 1);
background: rgba(255, 255, 255, 1);
border-radius: 4px;
border: 1px solid rgba(208, 212, 220, 1);
margin-bottom: 16px;
cursor: pointer;
p {
display: flex;
justify-content: flex-start;
align-items: center
}
}
.meeting_name {
width: 100%;
padding-bottom: 25px;
font-size: 16px;
box-shadow: inset 0px -1px 0px 0px #dad5d5;
position: relative;
overflow: hidden;
.title {
color: #333333;
height: 28px;
line-height: 28px;
margin-left: 0;
font-weight: bold;
margin-top: 14px;
}
ul {
overflow: hidden;
li {
width: 33.3%;
float: left;
line-height: 28px;
font-size: 14px;
span {
width: 70px;
display: block;
float: left;
color: rgba(153, 153, 153, 1)
}
p {
width: calc(100% - 70px);
float: left;
color: rgba(51, 51, 51, 1);
}
;
}
}
.svg {
width: 88px;
height: 88px;
position: absolute;
right: -20px;
bottom: -20px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,256 @@
<template>
<ai-list isTabs>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" @click="addMetting()">发起会议</el-button>
<ai-select
v-model="search.meetingStatus"
@change="getTableData()"
placeholder="会议状态"
:selectList="dict.getDict($parent.name==0 ? 'meetingStatusSelect' : 'meetingStatus')"
></ai-select>
<ai-select
v-if="$parent.name==0"
v-model="search.confirmStatus"
@change="getTableData()"
placeholder="确认状态"
:selectList="dict.getDict('confirmStatus')"
></ai-select>
</template>
<template #right>
<el-input v-model="search.param" @keyup.enter.native="getTableData()" placeholder="会议标题/地点"
size="small" suffix-icon="iconfont iconSearch" clearable @clear="search.current=1,getTableData()"></el-input>
</template>
</ai-search-bar>
<div class="list_main">
<div class="no-data" style="height:160px;" v-if="tableData.length==0"></div>
<ul v-if="tableData.length>0">
<li v-for="(item,index) in tableData" :key="index" @click="goDetail(item)">
<p>
<span class="conference_title">{{ item.title }}</span>
<span class="time" v-if="item.status==1">{{ item.expirationTime }}</span>
</p>
<p style="width:80%;">
<el-row type="flex" align="middle" class="unit">发起人
<ai-open-data type="userName" :openid="item.userName"/>
</el-row>
<el-row type="flex" align="middle" class="unit">发起单位
<ai-open-data type="departmentName" :openid="item.unitName"/>
</el-row>
</p>
<p style="width:80%;">
<span class="unit">会议时间{{ item.startTime.substring(0, 16) + '至' + item.endTime.substring(0, 16) }}</span>
<el-tooltip :content="item.address" placement="top-start" effect="light">
<span class="unit" v-if="item.address.length>12">会议地点{{
item.address.substring(0, 12) + '....'
}}</span>
<span class="unit" v-else>会议地点{{ item.address }}</span>
</el-tooltip>
</p>
<!-- <p style="width:80%;">
<span
class="unit">会议时间{{
item.startTime.substring(0, 16) + '至' + item.endTime.substring(0, 16)
}}</span>
<el-tooltip :content="item.address" placement="top-start" effect="light">
<span class="unit address"
v-if="item.address.length>12">会议地点{{ item.address.substring(0, 12) + '....' }}</span>
<span class="unit address" v-else>会议地点{{ item.address }}</span>
</el-tooltip>
</p> -->
<h5 :class="{color0:item.status==0,color1:item.status==1,color2:item.status==2,color3:item.status==3,color4:item.status==4}">
<span v-if="item.status==0">草稿箱</span>
<span v-if="item.status==1">未开始</span>
<span v-if="item.status==2">进行中</span>
<span v-if="item.status==3">已取消</span>
<span v-if="item.status==4">已结束</span>
</h5>
<ai-icon class="svg" v-if="item.joinStatus==0" type="svg" icon="iconunidentified"/>
<ai-icon class="svg" v-else-if="item.joinStatus==1" type="svg" icon="iconidentified"/>
<ai-icon class="svg" v-else-if="item.joinStatus==2" type="svg" icon="iconyiqingjia"/>
<ai-icon class="svg" v-else-if="item.joinStatus==3" type="svg" icon="icondaiding"/>
</li>
</ul>
</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="total,prev, pager, next,sizes,jumper"
:total="total">
</el-pagination>
</div>
</template>
</ai-list>
</template>
<script>
export default {
name: "list",
props: {
instance: Function,
dict: Object,
permissions: Function,
listType: String
},
data() {
return {
search: {
meetingStatus: '',
confirmStatus: '',
param: '',
current: 1,
size: 10,
listType: ''
},
tableData: [],
total: 0,
}
},
methods: {
goDetail(item) {
this.$emit('goPage', {
row: item,
comp: 'detail'
});
},
addMetting() {
this.$emit('goPage', {
row: {},
comp: 'addMeeting'
});
},
getTableData() {
this.instance.post(`/app/appmeetinginfo/list`, null, {
params: {
...this.search,
listType: this.listType
}
}).then(res => {
if (res && res.data) {
this.tableData = res.data.records;
this.total = res.data.total;
this.$initWxOpenData()
}
});
},
handleCurrentChange(val) {
this.search.current = val;
this.getTableData();
},
handleSizeChange(val) {
this.search.size = val;
this.getTableData();
},
},
created() {
this.dict.load("confirmStatus", "meetingStatus", "meetingStatusSelect").then(_ => this.getTableData())
}
}
</script>
<style lang="scss" scoped>
.list_main {
width: 100%;
ul {
overflow: hidden;
padding: 0;
margin: 0;
li {
width: 100%;
height: 107px;
background: rgba(255, 255, 255, 1);
border-radius: 4px;
border: 1px solid rgba(216, 224, 232, 1);
box-sizing: border-box;
padding: 16px 16px 16px 50px;
margin-top: 8px;
cursor: pointer;
position: relative;
overflow: hidden;
p {
width: 100%;
height: 25px;
display: flex;
justify-content: space-between;
align-items: center;
.conference_title {
color: rgba(51, 51, 51, 1);
font-size: 16px;
font-weight: bold;
}
.time {
font-size: 14px;
color: #2266FF;
}
.unit {
font-size: 14px;
width: 50%;
}
}
h5 {
width: 100px;
height: 20px;
line-height: 20px;
text-align: center;
position: absolute;
left: -22px;
top: 10px;
box-sizing: border-box;
padding-right: 8px;
font-size: 12px;
transform: rotate(-45deg);
background: #FFF3E8;
color: rgba(255, 136, 34, 1);
box-shadow: -1px 1px 0px 0px rgba(216, 224, 232, 1), 1px -1px 0px 0px rgba(216, 224, 232, 1);
margin: 0;
}
.color0 {
color: #2244FF;
background: #EFF6FF;
}
.color1 {
background: #FFF3E8;
color: rgba(255, 136, 34, 1);
}
.color2 {
background: #EFF6FF;
color: #2266FF;
}
.color3 {
background-color: #D8E0E8;
color: #999999;
}
.color4 {
color: #2EA222;
background-color: #D8E0E8;
}
.svg {
width: 88px;
height: 88px;
position: absolute;
right: -20px;
bottom: -20px;
}
}
}
}
</style>

View File

@@ -0,0 +1,163 @@
<template>
<section class="AppInterview">
<ai-list v-if="!isDetail">
<template #title>
<ai-title title="调查走访" isShowBottomBorder/>
</template>
<template #content>
<ai-search-bar>
<template #left>
<ai-search label="创建日期">
<el-date-picker
size="small"
v-model="search.startTime"
placeholder="开始日期"
@change="page.current = 1, getTableData()"
value-format="yyyy-MM-dd"/>
<el-date-picker
size="small"
v-model="search.endTime"
placeholder="结束日期"
@change="page.current = 1, getTableData()"
value-format="yyyy-MM-dd"/>
</ai-search>
</template>
<template #right>
<el-input
suffix-icon="iconfont iconSearch"
v-model="search.title"
placeholder="标题"
clearable
@keyup.enter.native="page.current = 1, getTableData()"
@clear="page.current = 1, search.title = '', getTableData()"
size="small"/>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:colConfigs="colConfigs"
:total="page.total"
:current.sync="page.current"
:size.sync="page.size"
@getList="getTableData">
<el-table-column label="操作" align="center" slot="options" fixed="right" width="160">
<template slot-scope="{row}">
<div class="table-options">
<el-button type="text" title="详情" @click="handleShow(row.id)">详情</el-button>
<el-button type="text" title="删除" @click="handleDelete(row.id)"
v-if="permissions('app_appinterview_del')">删除
</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<interview-detail v-else/>
</section>
</template>
<script>
import InterviewDetail from "./interviewDetail";
import {mapState} from "vuex";
export default {
name: "AppInterview",
label: "调查走访",
components: {InterviewDetail},
provide() {
return {
interview: this
}
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{label: "标题", prop: "title"},
{label: "时间", prop: "createTime"},
{label: "操作人", prop: "createUserId", openType: 'userName'},
{slot: "options"}
]
},
isDetail() {
return !!this.$route.query.id
}
},
data() {
return {
search: {startTime: null, endTime: null, title: ""},
page: {current: 1, size: 10, total: 0},
tableData: []
}
},
created() {
if (this.isDetail) {
} else this.getTableData()
},
methods: {
getTableData() {
this.instance.post("/app/appinterview/list", null, {
params: {...this.search, ...this.page}
}).then(res => {
if (res?.data) {
this.tableData = res.data.records
this.page.total = res.data.total
this.$initWxOpenData()
}
})
},
handleShow(id) {
if (id) {
this.$router.push({query: {id}})
} else this.$message.error('该条数据异常,无法打开!')
},
handleDelete(ids) {
this.$confirm("是否要删除该调查走访?").then(() => {
this.instance.post("/app/appinterview/delete", null, {params: {ids}}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功!")
this.getTableData()
}
})
}).catch(() => 0)
},
back() {
this.$router.push({query: null})
this.getTableData()
}
}
}
</script>
<style lang="scss" scoped>
.AppInterview {
height: 100%;
::v-deep .dateRange {
.dateLabel {
height: 32px;
border: 1px solid #D0D4DC;
line-height: 32px;
padding: 0 8px;
background: #F5F5F5;
}
.el-input__inner {
border-radius: 0;
transform: translateX(-1px);
}
}
::v-deep .ai-list__content--right-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<section class="interviewDetail">
<ai-detail>
<template #title>
<ai-title title="调查走访详情" isShowBottomBorder isShowBack @onBackClick="interview.back()"/>
</template>
<template #content>
<ai-card title="基本信息">
<template #content>
<ai-wrapper
label-width="56px">
<ai-info-item label="标题" isLine>{{ detail.title }}</ai-info-item>
<ai-info-item label="内容" isLine>{{ detail.content }}</ai-info-item>
<ai-info-item label="图片" isLine>
<div class="images">
<el-image
v-for="(op,i) in detail.fileList"
:key="i"
:src="op.accessUrl"
:preview-src-list="detail.fileList.map(e=>e.accessUrl)">
<i slot="placeholder" class="el-icon-picture-outline"/>
<i slot="error" class="el-icon-picture-outline"/>
</el-image>
</div>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
</template>
<!-- <template #footer>
<el-button @click="interview.back()">取消</el-button>
<el-button type="primary" @click="submitInterview" v-if="interview.permissions('app_appinterview_edit')">保存</el-button>
</template> -->
</ai-detail>
</section>
</template>
<script>
export default {
name: "interviewDetail",
inject: ['interview'],
data() {
return {
detail: {
fileList: []
}
}
},
created() {
this.getDetail()
},
methods: {
getDetail() {
let {id} = this.$route.query
this.interview.instance.post("/app/appinterview/queryDetailById", null, {params: {id}}).then(res => {
if (res?.data) {
this.detail = res.data
}
})
},
submitInterview() {
this.$refs.interviewForm.validate(v => {
if (v) {
this.interview.instance.post("/app/appinterview/update-web", this.detail).then(res => {
if (res?.code == 0) {
this.$message.success("保存成功!")
this.interview.back()
}
})
}
})
},
}
}
</script>
<style lang="scss" scoped>
.interviewDetail {
height: 100%;
::v-deep .images {
display: flex;
gap: 16px;
flex-wrap: wrap;
&:before {
content: none;
}
.el-image__inner {
width: 82px !important;
height: 82px !important;
margin-right: 16px;
}
}
}
</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,195 @@
<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" v-if="foo">
<ai-open-data type="userName" :openid="item.name" :class="persons.length==1 ? 'point' : ''" v-for="(item,index) in persons.slice(0,2)" :key="index"></ai-open-data>
<span v-if="persons.length">{{persons.length}}</span>
</div>
<ai-user-get v-model="form.persons" :instance="instance" @change="onChange" sass>
<el-button type="info">选择</el-button>
</ai-user-get>
</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:[],
foo:true,
}
},
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.foo = false;
this.form.persons = e;
this.persons = e;
setTimeout(() => {
this.foo = true
},500)
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;
this.$initWxOpenData()
}
})
},
},
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:"";
}
}
::v-deep .point{
&:after{
content:"" !important;
}
}
</style>

View File

@@ -0,0 +1,94 @@
<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">发布单位
<ai-open-data type="departmentName" :openid="detailObj.unitName" v-if="detailObj.unitName"></ai-open-data>
</span>
<span style="display:flex">发布人
<ai-open-data type="userName" :openid="detailObj.releaseUserName" v-if="detailObj.releaseUserName"></ai-open-data>
</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;
this.$initWxOpenData()
}
})
}
}
</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,211 @@
<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">发布单位
<template v-if="detailObj.unitName">
<ai-open-data type="departmentName" :openid="detailObj.unitName"></ai-open-data>
</template>
</span>
<span style="display:flex">发布人
<template v-if="detailObj.releaseUserName">
<ai-open-data type="userName" :openid="detailObj.releaseUserName"></ai-open-data>
</template>
</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="changeStatus"
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 }">
<ai-open-data type="userName" :openid="row.readUserName"></ai-open-data>
</template>
</el-table-column>
<el-table-column slot="unitName" label="所属部门" align="center">
<template slot-scope="{ row }">
<ai-open-data type="departmentName" :openid="row.unitName"></ai-open-data>
</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: {
changeStatus(){
this.search.current=1;
this.getList();
},
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;
this.$initWxOpenData()
}
})
},
getList() {
this.tableData = [];
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,265 @@
<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"
@keyup.enter.native="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 }">
<ai-open-data type="userName" :openid="row.releaseUserName"></ai-open-data>
</template>
</el-table-column>
<el-table-column slot="unitName" label="发布部门" align="center">
<template slot-scope="{ row }">
<ai-open-data type="departmentName" :openid="row.unitName"></ai-open-data>
</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="">
<ai-open-data type="userName" :openid="item.name"></ai-open-data>
</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="">
<ai-open-data type="userName" :openid="item.name"></ai-open-data>
</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;
this.$initWxOpenData()
}
})
},
getList() {
this.tableData = [];
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;
this.$initWxOpenData()
}
})
}
},
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>
<ai-open-data type="userName" :openid="item.releaseUserName"></ai-open-data>
</span>
<span style="display:flex;width:33%;">
<b>发布部门</b>
<ai-open-data type="departmentName" :openid="item.unitName"></ai-open-data>
</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;
this.$initWxOpenData()
}
});
},
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,7 @@
{
"AppAccount": "账号管理",
"AppDictionary": "数据字典",
"AppQyWxConfig": "企业微信配置",
"AppUserInfo": "个人中心",
"AppRightsManager": "权限管理"
}

15
project/sass/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "dvcp-sass-apps",
"description": "sass版本应用",
"version": "1.0.0",
"main": "dist/dvcp-sass-apps.common.js",
"files": [
"dist"
],
"publishConfig": {
"registry": "http://192.168.1.87:4873/"
},
"dependencies": {
"dvcp-dv-ui": "^2.0.0"
}
}