Compare commits

..

2 Commits

6 changed files with 631 additions and 0 deletions

View File

@ -190,6 +190,17 @@ const routes = [
parentRoute: 'warningManagement3'
}
},
{
path: '/messageManagement',
name: 'messageManagement',
component: () => import('../views/WarningManagement/law/messageManagement/index.vue'),
meta: {
title: '消息推送设置',
breadcrumb: true,
parentRoute: 'warningManagement3'
}
},
// 项目管理 - 区县
{

View File

@ -372,6 +372,12 @@ export default () => {
path: '/dutyManagement'
});
};
// 跳转到消息推送设置
const gotoMessagePage = () => {
router.push({
path: '/messageManagement'
});
}
onMounted(() => {
getTableData();
@ -396,6 +402,7 @@ export default () => {
columns,
gotoLedgerPage,
gotoDutyPage,
gotoMessagePage,
modelVisible,
model,

View File

@ -17,6 +17,7 @@
<el-button type="primary" @click="script.gotoLedgerPage">线下帮扶台账</el-button>
<el-button type="primary" @click="script.openScheduleDiaog">立即排班</el-button>
<el-button type="primary" @click="script.gotoDutyPage">值班管理</el-button>
<el-button type="primary" @click="script.gotoMessagePage">消息推送设置</el-button>
<el-button type="primary" color="#952DE6" @click="">导出</el-button>
<input type="file" ref="fileInput" style="display: none" @change="handleFileSelect" accept=".*"></input>
<!-- <el-button type="primary" @click="script.gotoLedgerPage">驻地台账</el-button> -->

View File

@ -0,0 +1,267 @@
<template>
<div class="detail-container">
<el-form ref="formRef" :model="form" label-position="right" label-width="auto"
style="max-height: 60vh; overflow-y: hidden; padding-right: 50px" :rules="rules">
<el-form-item label="" prop="schedules">
<div class="user-select-container">
<!-- 左侧用户列表 -->
<div class="user-list-panel">
<div class="panel-title">用户列表</div>
<div class="user-list">
<div v-for="user in currentUsers" :key="user.userId" class="user-item"
:class="{ 'selected': isUserSelected(user) }" @click="toggleUserSelection(user)">
<span class="user-name">{{ user.realName || user.nickName || user.account }}</span>
<span class="user-position">{{ user.positionName }}</span>
</div>
</div>
</div>
<!-- 右侧已选择用户 -->
<div class="selected-panel">
<div class="panel-title">已选择用户 ({{ selectedUsers.length }})</div>
<div class="selected-user-list">
<div v-for="user in selectedUsers" :key="user.userId" class="selected-user-item">
<div class="user-info">
<span class="user-name">{{ user.realName || user.nickName || user.account }}</span>
<span class="user-position">{{ user.positionName }}</span>
</div>
<el-button type="danger" size="small" icon="Delete" circle @click.stop="removeSelectedUser(user)" />
</div>
</div>
</div>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import { request } from "@/utils/request";
import { ElMessage } from 'element-plus';
const formRef = ref(null);
//
const currentUsers = ref([]);
//
const selectedUsers = ref([]);
defineExpose({
formRef,
});
const props = defineProps({
form: {
type: Object,
default: () => ({}),
},
orgId: {
type: String,
default: ''
},
orgName: {
type: String,
default: ''
}
});
const rules = computed(() => {
return {
};
});
//
const isUserSelected = (user) => {
return selectedUsers.value.some(selected => selected.userId === user.userId);
};
//
const toggleUserSelection = (user) => {
if (isUserSelected(user)) {
removeSelectedUser(user);
} else {
// orgId
selectedUsers.value.push({
// ...user,
// orgId: user.orgId, // orgId
// timeRange: null
...user,
orgId: user.orgId,
orgName: props.orgName,
userId: user.userId,
userAccount: user.account,
userPhone: user.phone
});
}
};
//
const removeSelectedUser = (user) => {
const index = selectedUsers.value.findIndex(selected => selected.userId === user.userId);
if (index > -1) {
selectedUsers.value.splice(index, 1);
}
};
// ID
const getUsersByOrgId = async (orgId) => {
// console.log('@@@@@', orgId);
try {
const res = await request({
url: '/snow-ops-platform/user/orgUsers',
method: 'GET',
params: {
orgId
}
})
if (res.code === '00000') {
currentUsers.value = res.data || [];
// console.log('@@@@@', res.data);
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('获取用户失败');
console.error('获取用户失败:', error);
}
}
onMounted(() => {
getUsersByOrgId(props.orgId);
})
watch(selectedUsers.value, (val) => {
props.form.data = val;
}, { deep: true })
</script>
<style scoped>
.form-part {
padding: 20px;
}
.text-center {
text-align: center;
}
.user-select-container {
display: flex;
gap: 20px;
width: 100%;
height: 60vh;
}
.user-list-panel {
flex: 1;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
background-color: #fff;
height: 100%;
}
.selected-panel {
flex: 1;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
background-color: #fff;
display: flex;
flex-direction: column;
height: 100%;
}
.user-list {
flex: 1;
overflow-y: auto;
}
.user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
border: 1px solid transparent;
}
.user-item:hover {
background-color: #f5f7fa;
}
.user-item.selected {
background-color: #409eff;
color: white;
border-color: #409eff;
}
.user-name {
font-size: 14px;
font-weight: 500;
}
.user-position {
font-size: 12px;
opacity: 0.7;
}
.selected-user-list {
flex: 1;
overflow-y: auto;
}
.selected-user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 4px;
background-color: #f0f9ff;
border: 1px solid #e1f5fe;
gap: 12px;
}
.time-picker-container {
flex: 1.2;
min-width: 280px;
}
.time-picker-container :deep(.el-date-editor) {
width: 100%;
}
.user-info {
display: flex;
flex-direction: column;
min-width: 120px;
flex: 0.8;
}
.selected-user-item .user-name {
font-size: 14px;
font-weight: 500;
color: #303133;
}
.selected-user-item .user-position {
font-size: 12px;
color: #606266;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
font-size: 14px;
padding-right: 8px;
}
</style>

View File

@ -0,0 +1,224 @@
import { h, ref, onMounted, reactive, watch, toRaw, nextTick } from "vue";
import { request } from "@/utils/request";
import { useRoute, useRouter } from 'vue-router'
import AddDialog from "./addDialog.vue";
const modelVisible = ref(false); // 弹窗状态
const drawerVisible = ref(false); // 抽屉状态
// 弹窗内容
const model = reactive({
});
const form = reactive({
});
const INIT_FORM = {
};
// 抽屉内容
const drawer = reactive({
title: '',
content: null,
props: {},
onCancel: null,
onConfirm: null,
direction: 'rtl',
size: '50%'
});
const dialogRef = ref(null); // 弹窗实例
const drawerRef = ref(null); // 抽屉实例
// 消息推送组织列表固定六个增加personList存储该组织的人员数组
const messageOrgList = ref([
{ title: '中心领导', orgName: '中心领导', personList: [] },
{ title: '法规处', orgName: '法规处', personList: [] },
{ title: '养护处', orgName: '养护处', personList: [] },
{ title: '农村公路处', orgName: '农村公路处', personList: [] },
{ title: '建设处', orgName: '建设处', personList: [] },
{ title: '市公路应急中心/管理段', orgName: '公路管理段', personList: [] },
])
const messagePushPerson = ref([])
const userOrgsList = ref([])
// 查询所有消息推送人员
const getAllMessagePushPerson = async () => {
try {
const res = await request({
url: '/snow-ops-platform/messagePushPerson/listAll',
method: 'GET',
})
if (res.code === '00000') {
messagePushPerson.value = res.data
// 按orgId分组填充人员数据到对应组织
messageOrgList.value.forEach(org => {
org.personList = messagePushPerson.value.filter(person => person.orgId === org.orgId)
});
// console.log('@@@@@@', messageOrgList.value);
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('获取消息推送人员失败');
console.error('获取消息推送人员失败:', error);
}
}
// 查询所有组织
const getUserOrgs = async () => {
try {
const res = await request({
url: '/snow-ops-platform/user/userOrgs',
method: 'GET',
})
if (res.code === '00000') {
userOrgsList.value = res.data.data
// 遍历后端返回的组织数据与固定组织列表匹配并赋值orgId
messageOrgList.value.forEach(fixedOrg => {
const matchedOrg = userOrgsList.value.find(org =>
org.orgName === fixedOrg.orgName
);
if (matchedOrg) {
fixedOrg.orgId = matchedOrg.orgId;
}
});
// 在获取到orgId后立即根据orgId填充对应的人员数据
messageOrgList.value.forEach(org => {
org.personList = messagePushPerson.value.filter(person => person.orgId === org.orgId)
});
// console.log('@@@@',messageOrgList.value);
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('获取组织失败');
console.error('获取组织失败:', error);
}
}
// 添加消息推送人
const handelAdd = async (data) => {
try {
const loading = ElLoading.service({
lock: true,
text: '操作中',
background: 'rgba(0, 0, 0, 0.7)',
})
const res = await request({
url: '/snow-ops-platform/messagePushPerson/add',
method: 'POST',
data: data
})
loading.close();
if (res.code === '00000') {
ElMessage.success('添加成功');
modelVisible.value = false;
await getAllMessagePushPerson();
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('添加失败');
console.error('添加失败:', error);
}
}
// 打开添加人员弹窗
const openAddDialog = (orgId, orgName) => {
model.title = '添加人员';
Object.assign(form, INIT_FORM);
model.props = {
orgId: orgId,
orgName: orgName,
form: form,
};
model.content = AddDialog;
model.onCancel = () => {
modelVisible.value = false;
};
model.onConfirm = async () => {
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(async () => {
// console.log('@@@@@@form',form);
await handelAdd(form.data)
// await addSchedule(form)
// await publishWarning(form)
})
.catch((err) => {
ElMessage.error('请处理表单中的错误项');
});
};
model.width = "70%"
modelVisible.value = true;
}
// 删除消息推送人
const deletePushPerson = async (person) => {
try {
await ElMessageBox.confirm(
`确定要删除【${person.realName}】吗?`,
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
const loading = ElLoading.service({
lock: true,
text: '操作中',
background: 'rgba(0, 0, 0, 0.7)',
})
const res = await request({
url: '/snow-ops-platform/messagePushPerson/deleteByUserId',
method: 'POST',
data: {
userId: person.userId
}
})
loading.close();
if (res.code === '00000') {
ElMessage.success('删除成功');
await getAllMessagePushPerson();
} else {
throw new Error(res.message)
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败');
console.error('删除失败:', error);
}
}
}
export default () => {
const router = useRouter();
onMounted(async () => {
await getUserOrgs();
await getAllMessagePushPerson();
})
return {
modelVisible,
model,
drawerVisible,
drawer,
dialogRef,
drawerRef,
openAddDialog,
deletePushPerson,
messageOrgList,
}
}

View File

@ -0,0 +1,121 @@
<template>
<div class="root">
<div class="content-box">
<el-card shadow="never" v-for="org in script.messageOrgList.value" class="org-box">
<template #header>
<div class="card-header">
<span>{{ org.title }}</span>
<el-button type="primary" size="small" text
@click="script.openAddDialog(org.orgId, org.orgName)">添加</el-button>
</div>
</template>
<!-- 人员列表 -->
<div class="person-list">
<div v-if="org.personList.length === 0" class="empty-text">暂无人员</div>
<div v-else class="person-item" v-for="person in org.personList" :key="person.userId">
<div class="person-info">
<span class="name">{{ person.realName }}</span>
<!-- <span class="account">({{ person.userAccount }})</span> -->
<span class="phone" v-if="person.userPhone">{{ person.userPhone }}</span>
</div>
<el-button type="danger" size="small" text @click="script.deletePushPerson(person)">删除</el-button>
</div>
</div>
</el-card>
</div>
<div class="model-box">
<MyDialog v-model="script.modelVisible.value" :title="script.model?.title"
:dynamicComponent="script.model?.content" :component-props="script.model?.props"
:onConfirm="script.model?.onConfirm" :onCancel="script.model?.onCancel" ref="dialogRef"
:width="script.model?.width">
</MyDialog>
<MyDrawer v-model="script.drawerVisible.value" :title="script.drawer?.title"
:dynamicComponent="script.drawer?.content" :component-props="script.drawer?.props"
:onConfirm="script.drawer?.onConfirm" :onCancel="script.drawer?.onCancel" ref="drawerRef"
:direction="script.drawer?.direction" :size="script.drawer?.size">
</MyDrawer>
</div>
</div>
</template>
<script setup>
import DynamicTable from "@/component/DynamicTable/index.js";
import { Search, ArrowDown } from "@element-plus/icons-vue";
import MyDialog from "@/component/MyDialog/index.js";
import MyDrawer from "@/component/MyDrawer/index.js";
import scriptFn from "./index.js";
const script = scriptFn();
const { dialogRef, drawerRef } = script;
</script>
<style scoped>
.root {
height: 100%;
padding: 25px;
}
.content-box {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
gap: 20px;
}
.org-box {
flex: 1;
height: 100%;
min-width: fit-content;
}
.card-header {
display: flex;
flex-direction: row;
justify-content: space-between;
white-space: nowrap;
align-items: center;
}
.person-list {
margin-top: 10px;
}
.empty-text {
color: #999;
text-align: center;
padding: 20px 0;
}
.person-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.person-item:last-child {
border-bottom: none;
}
.person-info {
display: flex;
gap: 10px;
align-items: center;
}
.name {
font-weight: bold;
}
.account {
color: #666;
font-size: 0.9em;
}
.phone {
color: #666;
font-size: 0.9em;
}
</style>