|
|
@@ -0,0 +1,420 @@
|
|
|
+<template>
|
|
|
+ <el-drawer
|
|
|
+ v-model="isDrawer"
|
|
|
+ size="480"
|
|
|
+ :title="props.type === 'add' ? '添加监控调阅记录' : '编辑监控调阅记录'"
|
|
|
+ @close="handleCloseDrawer"
|
|
|
+ >
|
|
|
+ <el-form ref="formRef" :model="formParams" :rules="rules" label-placement="left" :label-width="95">
|
|
|
+ <el-form-item label="工号" prop="staffNo" style="margin-bottom: 8px">
|
|
|
+ <el-input placeholder="请输入工号" v-model="formParams.staffNo" v-if="staffNoHtmlType === 'INPUT'" />
|
|
|
+ <el-tree-select
|
|
|
+ v-model="formParams.staffNo"
|
|
|
+ check-strictly
|
|
|
+ placeholder="请输入工号进行搜索"
|
|
|
+ class="protocal-select"
|
|
|
+ filterable
|
|
|
+ remote
|
|
|
+ clearable
|
|
|
+ :loading="loading"
|
|
|
+ :data="staffNoOptions"
|
|
|
+ :render-after-expand="false"
|
|
|
+ :default-expand-all="true"
|
|
|
+ :remote-method="debouncedRemoteMethod"
|
|
|
+ @clear="handleClearStaffNo"
|
|
|
+ @change="handleChangeStaffNo"
|
|
|
+ v-else
|
|
|
+ />
|
|
|
+ <el-text class="text-mode" type="primary" @click="handleChangeStaff">{{
|
|
|
+ `切换为工号${staffNoHtmlType === 'INPUT' ? '选择' : '输入'}方式`
|
|
|
+ }}</el-text>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="姓名" prop="userName">
|
|
|
+ <el-input
|
|
|
+ :placeholder="staffNoHtmlType === 'INPUT' ? '请输入姓名' : '请选择工号,此项自动填充'"
|
|
|
+ v-model="formParams.userName"
|
|
|
+ :disabled="staffNoHtmlType === 'SELECT'"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="所属部门" prop="deptName">
|
|
|
+ <el-tree-select
|
|
|
+ v-model="formParams.deptName"
|
|
|
+ :data="departmentArr"
|
|
|
+ :render-after-expand="false"
|
|
|
+ :default-expand-all="true"
|
|
|
+ check-strictly
|
|
|
+ :placeholder="staffNoHtmlType === 'INPUT' ? '请选择部门' : '请选择工号,此项自动填充'"
|
|
|
+ class="protocal-select"
|
|
|
+ :filter-node-method="filterDept"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ :disabled="staffNoHtmlType === 'SELECT'"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调阅位置" prop="accessLocation">
|
|
|
+ <el-input
|
|
|
+ placeholder="请输入调阅位置"
|
|
|
+ v-model="formParams.accessLocation"
|
|
|
+ maxlength="50"
|
|
|
+ show-word-limit
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调阅时段" prop="accessTimeRange">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="formParams.accessTimeRange"
|
|
|
+ type="datetimerange"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始时间"
|
|
|
+ end-placeholder="结束时间"
|
|
|
+ format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="handleChangeAccessTimeRange"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="是否拷贝" prop="isCopy">
|
|
|
+ <el-radio-group v-model="formParams.isCopy">
|
|
|
+ <el-radio :value="0">否</el-radio>
|
|
|
+ <el-radio :value="1">是</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调阅状态" prop="accessStatus">
|
|
|
+ <el-radio-group v-model="formParams.accessStatus">
|
|
|
+ <el-radio :value="0">待调阅</el-radio>
|
|
|
+ <el-radio :value="1">已调阅</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="审批单上传" prop="approvalFormUrl" :rules="approvalFormRules">
|
|
|
+ <UploadImages
|
|
|
+ ref="uploadImagesRef"
|
|
|
+ :maxCount="1"
|
|
|
+ :image-list="approvalImageList"
|
|
|
+ @upload-success="handleApprovalUploadChange"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="记录人" prop="createdByName">
|
|
|
+ <el-input placeholder="记录人" v-model="formParams.createdByName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-space>
|
|
|
+ <el-button @click="handleCloseDrawer">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmitForm(formRef)">提交</el-button>
|
|
|
+ </el-space>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+ import { ref, reactive, watch, computed, onMounted } from 'vue';
|
|
|
+ import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
|
|
+ import { debounce } from 'lodash-es';
|
|
|
+ import { getAllDepartments } from '@/api/auth/dept';
|
|
|
+ import { queryOrganizationUserTree } from '@/api/system/user';
|
|
|
+ import { OrganizationUserTree } from '@/views/system/user/types';
|
|
|
+ import { calculateTreeData } from '@/utils';
|
|
|
+ import UploadImages from '@/views/disaster/disaster-control/src/components/UploadImages.vue';
|
|
|
+ import { ImageItem } from '@/types/disaster-control';
|
|
|
+ import { UPLOAD_BIZ_TYPE, uploadFileApi } from '@/api/minio';
|
|
|
+ import {
|
|
|
+ findUserByWorkNo,
|
|
|
+ transformTreeData,
|
|
|
+ TransformedTreeNode,
|
|
|
+ findOrgCodeByWorkNo,
|
|
|
+ } from '@/utils/findUserByWorkNo';
|
|
|
+ import {
|
|
|
+ SurveillanceInfoStruct,
|
|
|
+ addSurveillanceInfo,
|
|
|
+ updateSurveillanceInfo,
|
|
|
+ } from '@/api/security-confidentiality-surveillance';
|
|
|
+ import { useUserInfoHook } from '@/hooks/useUserInfoHook';
|
|
|
+
|
|
|
+ const props = defineProps<{
|
|
|
+ type: string; // add or edit
|
|
|
+ initialData: SurveillanceInfoStruct;
|
|
|
+ }>();
|
|
|
+
|
|
|
+ const emits = defineEmits<{
|
|
|
+ (e: 'close'): void;
|
|
|
+ }>();
|
|
|
+
|
|
|
+ const { id, realname, staffNo } = useUserInfoHook();
|
|
|
+
|
|
|
+ const formRef = ref<FormInstance>();
|
|
|
+ const formParams = ref<SurveillanceInfoStruct & { accessTimeRange: [string, string] }>({
|
|
|
+ id: 0,
|
|
|
+ staffNo: '',
|
|
|
+ userName: '',
|
|
|
+ deptId: 0,
|
|
|
+ deptName: '',
|
|
|
+ accessLocation: '',
|
|
|
+ accessStartTime: '',
|
|
|
+ accessEndTime: '',
|
|
|
+ accessTimeRange: ['', ''],
|
|
|
+ isCopy: 0,
|
|
|
+ accessStatus: 0,
|
|
|
+ approvalFormUrl: '',
|
|
|
+ createdById: id,
|
|
|
+ createdByName: realname,
|
|
|
+ createdByStaffNo: staffNo,
|
|
|
+ createdAt: '',
|
|
|
+ updatedAt: '',
|
|
|
+ isDeleted: 0,
|
|
|
+ });
|
|
|
+
|
|
|
+ const rules = reactive<FormRules<SurveillanceInfoStruct & { accessTimeRange: [string, string] }>>({
|
|
|
+ staffNo: { required: true, message: '工号不能为空', trigger: 'blur' },
|
|
|
+ userName: { required: true, message: '姓名不能为空', trigger: 'blur' },
|
|
|
+ deptName: { required: true, message: '所属部门不能为空', trigger: 'blur' },
|
|
|
+ accessLocation: { required: true, message: '调阅位置不能为空', trigger: 'blur' },
|
|
|
+ accessTimeRange: { required: true, message: '调阅时段不能为空', trigger: 'change' },
|
|
|
+ isCopy: { required: true, message: '请选择是否拷贝', trigger: 'change' },
|
|
|
+ accessStatus: { required: true, message: '请选择调阅状态', trigger: 'change' },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 审批单上传验证规则
|
|
|
+ const approvalFormRules = [
|
|
|
+ {
|
|
|
+ validator: (_: unknown, value: string, callback: (err?: Error) => void) => {
|
|
|
+ if (!value || value.trim() === '') {
|
|
|
+ return callback(new Error('请上传审批单'));
|
|
|
+ }
|
|
|
+ callback();
|
|
|
+ },
|
|
|
+ trigger: ['blur', 'change'],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const isDrawer = ref(true);
|
|
|
+ const loading = ref(false);
|
|
|
+ const staffNoOptions = ref<TransformedTreeNode[]>([]); // 工号选择列表
|
|
|
+ const OrganizationSourceData = ref<OrganizationUserTree[]>([]); // 组织结构树原始数据
|
|
|
+ const departmentArr = ref<{ value: string | number; label: string }[]>([]);
|
|
|
+ const sourceDepartArr = ref<{ value: string | number; label: string }[]>([]);
|
|
|
+
|
|
|
+ // 工号输入/选择模式切换
|
|
|
+ type STAFFNO_HTML_TYPE = 'INPUT' | 'SELECT';
|
|
|
+ const staffNoHtmlType = ref<STAFFNO_HTML_TYPE>('SELECT');
|
|
|
+
|
|
|
+ // 审批单上传相关
|
|
|
+ const uploadImagesRef = ref<InstanceType<typeof UploadImages>>();
|
|
|
+ const approvalImages = ref<ImageItem[]>([]);
|
|
|
+
|
|
|
+ const approvalImageList = computed(() => {
|
|
|
+ if (!formParams.value.approvalFormUrl) return [];
|
|
|
+ // 如果是单个URL字符串,转换为数组格式
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(formParams.value.approvalFormUrl);
|
|
|
+ return Array.isArray(parsed) ? parsed : [parsed];
|
|
|
+ } catch {
|
|
|
+ // 如果不是JSON格式,直接作为单个URL处理
|
|
|
+ return [{ url: formParams.value.approvalFormUrl }];
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 通过工号查询组织结构树
|
|
|
+ const remoteMethod = (query: string) => {
|
|
|
+ if (query) {
|
|
|
+ loading.value = true;
|
|
|
+ queryOrganizationUserTree(Number(query)).then((res) => {
|
|
|
+ if (res) {
|
|
|
+ loading.value = false;
|
|
|
+ staffNoOptions.value = transformTreeData(res, true);
|
|
|
+ OrganizationSourceData.value = res;
|
|
|
+ departmentArr.value = transformTreeData(OrganizationSourceData.value, false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ staffNoOptions.value = [];
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 防抖
|
|
|
+ const debouncedRemoteMethod = debounce(remoteMethod, 1000);
|
|
|
+
|
|
|
+ const handleClearStaffNo = () => {
|
|
|
+ formParams.value.staffNo = '';
|
|
|
+ formParams.value.userName = '';
|
|
|
+ formParams.value.deptId = undefined;
|
|
|
+ formParams.value.deptName = '';
|
|
|
+ };
|
|
|
+
|
|
|
+ // 切换工号输入/选择模式
|
|
|
+ const handleChangeStaff = () => {
|
|
|
+ staffNoHtmlType.value = staffNoHtmlType.value === 'INPUT' ? 'SELECT' : 'INPUT';
|
|
|
+ if (staffNoHtmlType.value === 'INPUT') {
|
|
|
+ // 切换到输入模式时,清空工号、姓名和部门,并恢复部门列表
|
|
|
+ formParams.value.staffNo = '';
|
|
|
+ formParams.value.userName = '';
|
|
|
+ formParams.value.deptId = undefined;
|
|
|
+ formParams.value.deptName = '';
|
|
|
+ departmentArr.value = sourceDepartArr.value;
|
|
|
+ } else {
|
|
|
+ // 切换到选择模式时,清空工号、姓名和部门
|
|
|
+ formParams.value.staffNo = '';
|
|
|
+ formParams.value.userName = '';
|
|
|
+ formParams.value.deptId = undefined;
|
|
|
+ formParams.value.deptName = '';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 递归查找树结构中的节点
|
|
|
+ const findNodeInTree = (tree: any[], value: string | number): any => {
|
|
|
+ for (const node of tree) {
|
|
|
+ // 检查当前节点
|
|
|
+ if (node.value === value) {
|
|
|
+ return node;
|
|
|
+ }
|
|
|
+ // 检查子节点(如果有)
|
|
|
+ if (node.children && node.children.length > 0) {
|
|
|
+ const foundNode = findNodeInTree(node.children, value);
|
|
|
+ if (foundNode) {
|
|
|
+ return foundNode;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleChangeStaffNo = (value) => {
|
|
|
+ const findUser = findUserByWorkNo(OrganizationSourceData.value, value);
|
|
|
+ const deptId = Number(findOrgCodeByWorkNo(OrganizationSourceData.value, value));
|
|
|
+ const dept = findNodeInTree(departmentArr.value, deptId);
|
|
|
+ if (findUser) {
|
|
|
+ formParams.value.staffNo = findUser.idtUserWorkNo;
|
|
|
+ formParams.value.userName = findUser.appAccountAccountName;
|
|
|
+ formParams.value.deptId = deptId;
|
|
|
+ formParams.value.deptName = dept ? dept.label : '';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleChangeAccessTimeRange = () => {
|
|
|
+ console.log('accessTimeRange', formParams.value.accessTimeRange);
|
|
|
+ if (formParams.value.accessTimeRange && formParams.value.accessTimeRange.length === 2) {
|
|
|
+ formParams.value.accessStartTime = formParams.value.accessTimeRange[0];
|
|
|
+ formParams.value.accessEndTime = formParams.value.accessTimeRange[1];
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 格式化审批单图片
|
|
|
+ const formatApprovalImage = async (file: File) => {
|
|
|
+ if (!file) return '';
|
|
|
+ const fileName = file.name;
|
|
|
+ const res = await uploadFileApi({ bizType: UPLOAD_BIZ_TYPE.ATTACHMENT, fileName, file });
|
|
|
+ return res.url;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleApprovalUploadChange = async () => {
|
|
|
+ approvalImages.value = uploadImagesRef.value!.getUploadedImages();
|
|
|
+ if (approvalImages.value && approvalImages.value.length > 0) {
|
|
|
+ const image = approvalImages.value[0]; // 只取第一张图片
|
|
|
+ if (!image.file && image.url) {
|
|
|
+ // 如果已有URL,直接使用
|
|
|
+ formParams.value.approvalFormUrl = image.url;
|
|
|
+ } else if (image.file) {
|
|
|
+ // 如果有新文件,上传后使用新URL
|
|
|
+ const url = await formatApprovalImage(image.file);
|
|
|
+ formParams.value.approvalFormUrl = url;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果没有图片,清空URL
|
|
|
+ formParams.value.approvalFormUrl = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动触发审批单字段的校验
|
|
|
+ if (formRef.value) {
|
|
|
+ formRef.value.validateField('approvalFormUrl');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 部门过滤函数
|
|
|
+ const filterDept = (val: string, data: any) => {
|
|
|
+ return data.label.includes(val);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化部门列表
|
|
|
+ const initDepartmentList = () => {
|
|
|
+ const departmentList = ref<{ value: string | number; label: string }[]>([]);
|
|
|
+ getAllDepartments().then((res) => {
|
|
|
+ departmentList.value = calculateTreeData(res, { level: 10, valueKey: 'id', labelKey: 'deptName' }, 1);
|
|
|
+ sourceDepartArr.value = departmentList.value;
|
|
|
+ departmentArr.value = departmentList.value;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleCloseDrawer = () => {
|
|
|
+ emits('close');
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSubmitForm = async (formEl: FormInstance | undefined) => {
|
|
|
+ if (!formEl) return;
|
|
|
+
|
|
|
+ // 处理调阅时段
|
|
|
+ if (formParams.value.accessTimeRange && formParams.value.accessTimeRange.length === 2) {
|
|
|
+ formParams.value.accessStartTime = formParams.value.accessTimeRange[0];
|
|
|
+ formParams.value.accessEndTime = formParams.value.accessTimeRange[1];
|
|
|
+ }
|
|
|
+
|
|
|
+ await formEl.validate((valid) => {
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ if (props.type === 'add') {
|
|
|
+ addSurveillanceInfo(formParams.value).then(() => {
|
|
|
+ ElMessage.success('添加成功');
|
|
|
+ handleCloseDrawer();
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ updateSurveillanceInfo(formParams.value).then(() => {
|
|
|
+ ElMessage.success('编辑成功');
|
|
|
+ handleCloseDrawer();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => props.initialData,
|
|
|
+ (newData) => {
|
|
|
+ if (newData) {
|
|
|
+ formParams.value = { ...newData, accessTimeRange: ['', ''] };
|
|
|
+ // 处理调阅时段
|
|
|
+ if (newData.accessStartTime && newData.accessEndTime) {
|
|
|
+ formParams.value.accessTimeRange = [newData.accessStartTime, newData.accessEndTime];
|
|
|
+ }
|
|
|
+ // 处理审批单图片 - 直接使用URL字符串
|
|
|
+ // approvalFormUrl 已经是字符串格式,不需要额外处理
|
|
|
+ // 如果有工号,尝试查询用户信息
|
|
|
+ if (newData.staffNo) {
|
|
|
+ remoteMethod(newData.staffNo);
|
|
|
+ }
|
|
|
+ // 编辑模式下,记录人字段保持原有数据,不覆盖
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true, deep: true },
|
|
|
+ );
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ initDepartmentList();
|
|
|
+ // 只有在添加模式下才初始化记录人为当前登录用户
|
|
|
+ if (props.type === 'add') {
|
|
|
+ formParams.value.createdByName = realname;
|
|
|
+ }
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+ .protocal-select:deep(.el-select-dropdown__wrap) {
|
|
|
+ max-height: 600px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .text-mode {
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 12px;
|
|
|
+ margin-left: 4px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ text-decoration: underline;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|