|
@@ -148,12 +148,16 @@
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="发布人">
|
|
<el-form-item label="发布人">
|
|
|
- <el-input v-model="formData.publisher" placeholder="请输入发布人" />
|
|
|
|
|
|
|
+ <el-input v-model="formData.publisher" placeholder="自动填充当前用户" disabled />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="处置人">
|
|
<el-form-item label="处置人">
|
|
|
- <el-input v-model="formData.handler" placeholder="请输入处置人" />
|
|
|
|
|
|
|
+ <el-input v-model="formData.handler" placeholder="请选择处置人" readonly @click="openUserPicker">
|
|
|
|
|
+ <template #suffix>
|
|
|
|
|
+ <el-icon style="cursor: pointer;" @click="openUserPicker"><Search /></el-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-input>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="24">
|
|
<el-col :span="24">
|
|
@@ -190,9 +194,12 @@
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="状态">
|
|
<el-form-item label="状态">
|
|
|
<el-select v-model="formData.status" style="width: 100%">
|
|
<el-select v-model="formData.status" style="width: 100%">
|
|
|
- <el-option label="启用" value="ENABLED" />
|
|
|
|
|
- <el-option label="禁用" value="DISABLED" />
|
|
|
|
|
- <el-option label="作废" value="INVALID" />
|
|
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in warningStatusOptions"
|
|
|
|
|
+ :key="item.dictValue"
|
|
|
|
|
+ :label="item.dictLabel"
|
|
|
|
|
+ :value="item.dictValue"
|
|
|
|
|
+ />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
@@ -327,12 +334,61 @@
|
|
|
<el-empty v-else description="暂无附件信息" />
|
|
<el-empty v-else description="暂无附件信息" />
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 处置人选择对话框 -->
|
|
|
|
|
+ <el-dialog v-model="userPickerVisible" title="选择处置人" width="900px" append-to-body @open="loadUserPickerData">
|
|
|
|
|
+ <div class="user-picker-container">
|
|
|
|
|
+ <!-- 左侧部门树 -->
|
|
|
|
|
+ <div class="user-picker-left">
|
|
|
|
|
+ <div class="picker-title">部门列表</div>
|
|
|
|
|
+ <el-input v-model="deptFilterText" placeholder="搜索部门" clearable style="margin-bottom: 12px;" />
|
|
|
|
|
+ <el-tree
|
|
|
|
|
+ ref="deptTreeRef"
|
|
|
|
|
+ :data="deptTree"
|
|
|
|
|
+ :props="{ label: 'label', children: 'children' }"
|
|
|
|
|
+ :filter-node-method="filterDeptNode"
|
|
|
|
|
+ :highlight-current="true"
|
|
|
|
|
+ default-expand-all
|
|
|
|
|
+ @node-click="handleDeptClick"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 右侧用户列表 -->
|
|
|
|
|
+ <div class="user-picker-right">
|
|
|
|
|
+ <div class="picker-title">
|
|
|
|
|
+ 用户列表
|
|
|
|
|
+ <span v-if="currentDeptName" class="current-dept">({{ currentDeptName }})</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-input v-model="userFilterText" placeholder="搜索用户" clearable style="margin-bottom: 12px;" @input="filterUserList" />
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ v-loading="userPickerLoading"
|
|
|
|
|
+ :data="filteredUserList"
|
|
|
|
|
+ highlight-current-row
|
|
|
|
|
+ @current-change="handleUserCurrentChange"
|
|
|
|
|
+ height="400"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column label="用户名称" prop="userName" min-width="120" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="用户昵称" prop="nickName" min-width="120" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="部门" prop="deptName" min-width="140" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="手机号码" prop="phonenumber" width="130" />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button @click="userPickerVisible = false">取 消</el-button>
|
|
|
|
|
+ <el-button type="primary" :disabled="!selectedUser" @click="confirmSelectUser">确 定</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import {computed, onMounted, reactive, ref} from "vue";
|
|
|
|
|
|
|
+import {computed, onMounted, reactive, ref, watch} from "vue";
|
|
|
import {ElMessage, ElMessageBox} from "element-plus";
|
|
import {ElMessage, ElMessageBox} from "element-plus";
|
|
|
|
|
+import { Search } from "@element-plus/icons-vue";
|
|
|
|
|
+import useUserStore from "@/store/modules/user";
|
|
|
|
|
+import { deptTreeSelect, listUser } from "@/api/system/user";
|
|
|
import {
|
|
import {
|
|
|
deleteYjxxData,
|
|
deleteYjxxData,
|
|
|
getAlertTypeDictionaryData,
|
|
getAlertTypeDictionaryData,
|
|
@@ -344,12 +400,15 @@ import {
|
|
|
updateYjxxData
|
|
updateYjxxData
|
|
|
} from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
|
|
} from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
|
|
|
|
|
|
|
|
|
|
+const userStore = useUserStore();
|
|
|
|
|
+
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
const submitLoading = ref(false);
|
|
const submitLoading = ref(false);
|
|
|
const total = ref(0);
|
|
const total = ref(0);
|
|
|
const warningList = ref([]);
|
|
const warningList = ref([]);
|
|
|
const warningTypeOptions = ref([]);
|
|
const warningTypeOptions = ref([]);
|
|
|
const warningLevelOptions = ref([]);
|
|
const warningLevelOptions = ref([]);
|
|
|
|
|
+const warningStatusOptions = ref([]);
|
|
|
const reasonOptions = ref([]);
|
|
const reasonOptions = ref([]);
|
|
|
const uploadRef = ref();
|
|
const uploadRef = ref();
|
|
|
const formAttachments = ref([]);
|
|
const formAttachments = ref([]);
|
|
@@ -357,11 +416,24 @@ const minioBaseUrl = (import.meta.env.VITE_APP_MINIO_BASE_URL || "").trim();
|
|
|
|
|
|
|
|
const editVisible = ref(false);
|
|
const editVisible = ref(false);
|
|
|
const detailVisible = ref(false);
|
|
const detailVisible = ref(false);
|
|
|
|
|
+const userPickerVisible = ref(false);
|
|
|
const dialogTitle = ref("新增预警");
|
|
const dialogTitle = ref("新增预警");
|
|
|
const isEdit = ref(false);
|
|
const isEdit = ref(false);
|
|
|
const formRef = ref();
|
|
const formRef = ref();
|
|
|
const detailData = ref({});
|
|
const detailData = ref({});
|
|
|
|
|
|
|
|
|
|
+// 用户选择器相关
|
|
|
|
|
+const deptTree = ref([]);
|
|
|
|
|
+const userList = ref([]);
|
|
|
|
|
+const filteredUserList = ref([]);
|
|
|
|
|
+const selectedDeptId = ref("");
|
|
|
|
|
+const currentDeptName = ref("");
|
|
|
|
|
+const selectedUser = ref(null);
|
|
|
|
|
+const userPickerLoading = ref(false);
|
|
|
|
|
+const deptFilterText = ref("");
|
|
|
|
|
+const userFilterText = ref("");
|
|
|
|
|
+const deptTreeRef = ref();
|
|
|
|
|
+
|
|
|
const queryParams = reactive({
|
|
const queryParams = reactive({
|
|
|
pageNum: 1,
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
pageSize: 10,
|
|
@@ -395,6 +467,13 @@ const warningLevelLabelMap = computed(() => {
|
|
|
}, {});
|
|
}, {});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+const warningStatusLabelMap = computed(() => {
|
|
|
|
|
+ return warningStatusOptions.value.reduce((acc, item) => {
|
|
|
|
|
+ acc[item.dictValue] = item.dictLabel;
|
|
|
|
|
+ return acc;
|
|
|
|
|
+ }, {});
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
function createEmptyForm() {
|
|
function createEmptyForm() {
|
|
|
return {
|
|
return {
|
|
|
warningId: "",
|
|
warningId: "",
|
|
@@ -410,7 +489,7 @@ function createEmptyForm() {
|
|
|
publisher: "",
|
|
publisher: "",
|
|
|
publishTime: "",
|
|
publishTime: "",
|
|
|
handler: "",
|
|
handler: "",
|
|
|
- status: "ENABLED",
|
|
|
|
|
|
|
+ status: "DRAFT",
|
|
|
warningContent: "",
|
|
warningContent: "",
|
|
|
remark: ""
|
|
remark: ""
|
|
|
};
|
|
};
|
|
@@ -491,33 +570,19 @@ function getLevelTagType(level) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function getStatusText(status) {
|
|
function getStatusText(status) {
|
|
|
- return {
|
|
|
|
|
- ENABLED: "启用",
|
|
|
|
|
- DISABLED: "禁用",
|
|
|
|
|
- INVALID: "作废",
|
|
|
|
|
- DRAFT: "草稿",
|
|
|
|
|
- RELEASED: "已发布",
|
|
|
|
|
- PENDING: "待处理",
|
|
|
|
|
- PROCESSING: "处理中",
|
|
|
|
|
- HANDLED: "已处理",
|
|
|
|
|
- CLOSED: "已解除",
|
|
|
|
|
- RETURNED: "退回"
|
|
|
|
|
- }[status] || status || "-";
|
|
|
|
|
|
|
+ return warningStatusLabelMap.value[status] || status || "-";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function getStatusTagType(status) {
|
|
function getStatusTagType(status) {
|
|
|
|
|
+ // 按照预警状态语义设置 tag 颜色
|
|
|
return {
|
|
return {
|
|
|
- ENABLED: "success",
|
|
|
|
|
- DISABLED: "warning",
|
|
|
|
|
- INVALID: "danger",
|
|
|
|
|
- DRAFT: "info",
|
|
|
|
|
- RELEASED: "success",
|
|
|
|
|
- PENDING: "warning",
|
|
|
|
|
- PROCESSING: "warning",
|
|
|
|
|
- HANDLED: "success",
|
|
|
|
|
- CLOSED: "info",
|
|
|
|
|
- RETURNED: "danger"
|
|
|
|
|
- }[status] || "";
|
|
|
|
|
|
|
+ DRAFT: "info", // 草稿 - 灰色(未发布)
|
|
|
|
|
+ PENDING: "warning", // 待办 - 黄色(等待处置)
|
|
|
|
|
+ PROCESSING: "warning", // 处置中 - 黄色(进行中)
|
|
|
|
|
+ RELEASED: "success", // 已发布 - 绿色
|
|
|
|
|
+ HANDLED: "success", // 已处置 - 绿色(已完成)
|
|
|
|
|
+ CLOSED: "info" // 已解除 - 灰色
|
|
|
|
|
+ }[status] || "info";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function normalizeWarningRow(item) {
|
|
function normalizeWarningRow(item) {
|
|
@@ -563,12 +628,14 @@ function normalizeDisposalRow(item) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function loadDicts() {
|
|
async function loadDicts() {
|
|
|
- const [typeRes, levelRes] = await Promise.all([
|
|
|
|
|
|
|
+ const [typeRes, levelRes, statusRes] = await Promise.all([
|
|
|
getAlertTypeDictionaryData({ dictType: "alert_type" }),
|
|
getAlertTypeDictionaryData({ dictType: "alert_type" }),
|
|
|
- getAlertTypeDictionaryData({ dictType: "alert_level" })
|
|
|
|
|
|
|
+ getAlertTypeDictionaryData({ dictType: "alert_level" }),
|
|
|
|
|
+ getAlertTypeDictionaryData({ dictType: "pre_alarm_status" })
|
|
|
]);
|
|
]);
|
|
|
warningTypeOptions.value = typeRes.rows || [];
|
|
warningTypeOptions.value = typeRes.rows || [];
|
|
|
warningLevelOptions.value = levelRes.rows || [];
|
|
warningLevelOptions.value = levelRes.rows || [];
|
|
|
|
|
+ warningStatusOptions.value = statusRes.rows || [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function loadReasonOptions() {
|
|
async function loadReasonOptions() {
|
|
@@ -629,10 +696,97 @@ function resetQuery() {
|
|
|
loadList();
|
|
loadList();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 部门树过滤
|
|
|
|
|
+watch(deptFilterText, (val) => {
|
|
|
|
|
+ deptTreeRef.value?.filter(val);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+function filterDeptNode(value, data) {
|
|
|
|
|
+ if (!value) return true;
|
|
|
|
|
+ return data.label.indexOf(value) !== -1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 用户选择器相关函数
|
|
|
|
|
+async function loadUserPickerData() {
|
|
|
|
|
+ userPickerLoading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ await loadDeptTree();
|
|
|
|
|
+ // 默认加载第一个部门的用户
|
|
|
|
|
+ if (deptTree.value.length > 0) {
|
|
|
|
|
+ const firstDept = deptTree.value[0];
|
|
|
|
|
+ selectedDeptId.value = firstDept.id;
|
|
|
|
|
+ currentDeptName.value = firstDept.label;
|
|
|
|
|
+ await loadUserList(firstDept.id);
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ userPickerLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadDeptTree() {
|
|
|
|
|
+ const res = await deptTreeSelect();
|
|
|
|
|
+ deptTree.value = res.data || [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadUserList(deptId) {
|
|
|
|
|
+ userPickerLoading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await listUser({ deptId, pageNum: 1, pageSize: 100 });
|
|
|
|
|
+ userList.value = res.rows || [];
|
|
|
|
|
+ filteredUserList.value = userList.value;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ userPickerLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleDeptClick(data) {
|
|
|
|
|
+ selectedDeptId.value = data.id;
|
|
|
|
|
+ currentDeptName.value = data.label;
|
|
|
|
|
+ loadUserList(data.id);
|
|
|
|
|
+ selectedUser.value = null;
|
|
|
|
|
+ userFilterText.value = "";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function filterUserList() {
|
|
|
|
|
+ const keyword = userFilterText.value.toLowerCase();
|
|
|
|
|
+ if (!keyword) {
|
|
|
|
|
+ filteredUserList.value = userList.value;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ filteredUserList.value = userList.value.filter((user) => {
|
|
|
|
|
+ const userName = (user.userName || "").toLowerCase();
|
|
|
|
|
+ const nickName = (user.nickName || "").toLowerCase();
|
|
|
|
|
+ const deptName = (user.deptName || "").toLowerCase();
|
|
|
|
|
+ return userName.includes(keyword) || nickName.includes(keyword) || deptName.includes(keyword);
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleUserCurrentChange(row) {
|
|
|
|
|
+ selectedUser.value = row;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openUserPicker() {
|
|
|
|
|
+ selectedUser.value = null;
|
|
|
|
|
+ userFilterText.value = "";
|
|
|
|
|
+ deptFilterText.value = "";
|
|
|
|
|
+ userPickerVisible.value = true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function confirmSelectUser() {
|
|
|
|
|
+ if (!selectedUser.value) {
|
|
|
|
|
+ ElMessage.warning("请选择用户");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ formData.handler = selectedUser.value.userName;
|
|
|
|
|
+ userPickerVisible.value = false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function handleAdd() {
|
|
function handleAdd() {
|
|
|
isEdit.value = false;
|
|
isEdit.value = false;
|
|
|
dialogTitle.value = "新增预警";
|
|
dialogTitle.value = "新增预警";
|
|
|
resetForm();
|
|
resetForm();
|
|
|
|
|
+ // 自动填充发布人为当前用户
|
|
|
|
|
+ formData.publisher = userStore.name;
|
|
|
editVisible.value = true;
|
|
editVisible.value = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -667,7 +821,7 @@ async function submitForm() {
|
|
|
publisher: formData.publisher,
|
|
publisher: formData.publisher,
|
|
|
publishTime: formData.publishTime,
|
|
publishTime: formData.publishTime,
|
|
|
handler: formData.handler,
|
|
handler: formData.handler,
|
|
|
- status: formData.status || "ENABLED",
|
|
|
|
|
|
|
+ status: formData.status || "DRAFT",
|
|
|
warningContent: formData.warningContent,
|
|
warningContent: formData.warningContent,
|
|
|
remark: formData.remark
|
|
remark: formData.remark
|
|
|
},
|
|
},
|
|
@@ -825,4 +979,40 @@ onMounted(async () => {
|
|
|
align-items: flex-start;
|
|
align-items: flex-start;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/* 用户选择器样式 */
|
|
|
|
|
+.user-picker-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ height: 460px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.user-picker-left {
|
|
|
|
|
+ width: 280px;
|
|
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.user-picker-right {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.picker-title {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #374151;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.picker-title .current-dept {
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|